Comparar commits
47 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 03aa8387d5 | |||
| cba54cbb19 | |||
| 0dfaf84aab | |||
| ff427043dc | |||
| f60c424351 | |||
| ca7429a91d | |||
| ed3dffe5d8 | |||
| 24e2628423 | |||
| 15d7b70440 | |||
| fdb2698d00 | |||
| 2df0b154ef | |||
| c8ef454ad2 | |||
| 1ab0f00d59 | |||
| 0e14275ec3 | |||
| d919adb856 | |||
| d63b712736 | |||
| 36fe0ed46d | |||
| e064235776 | |||
| 17a02e879a | |||
| 3ca7bcc3df | |||
| 29810d5b8a | |||
| fd15177b40 | |||
| 4b1fe5793a | |||
| 510f1931e9 | |||
| a35e7481b7 | |||
| b49614214e | |||
| b170fd33d8 | |||
| 967a501336 | |||
| f78e11a679 | |||
| 7c7533f2c1 | |||
| 2c8f36df04 | |||
| 0bdc3a4f0f | |||
| 606e15b89d | |||
| 388b32d097 | |||
| 8f0ee1554b | |||
| 2eb2784baf | |||
| f3c10d4eaa | |||
| 2ffca3b255 | |||
| 17a684b067 | |||
| 98e30c8c57 | |||
| f7a206abcb | |||
| 7acbb28638 | |||
| 89d9765caa | |||
| eb22322f49 | |||
| 1a75dd99ea | |||
| 89859f9fd0 | |||
| 61f920b590 |
@@ -0,0 +1,30 @@
|
||||
language: node_js
|
||||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
dist: trusty
|
||||
node_js: stable
|
||||
sudo: required
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- google-chrome-stable
|
||||
- os: osx
|
||||
node_js: stable
|
||||
osx_image: xcode8.3
|
||||
|
||||
before_install:
|
||||
- npm install google-closure-library
|
||||
- npm install webdriverio
|
||||
# Symlink closure library
|
||||
- ln -s $(npm root)/google-closure-library ../closure-library
|
||||
|
||||
before_script:
|
||||
- export DISPLAY=:99.0
|
||||
- if [ "${TRAVIS_OS_NAME}" == "linux" ]; then ( scripts/setup_linux_env.sh ) fi
|
||||
- if [ "${TRAVIS_OS_NAME}" == "osx" ]; then ( scripts/setup_osx_env.sh ) fi
|
||||
- sleep 2
|
||||
|
||||
script:
|
||||
- set -x
|
||||
- npm test
|
||||
+37
-2
@@ -1,5 +1,40 @@
|
||||
# Contributing to Blockly
|
||||
|
||||
Please make pull requests against develop, not master. If your patch needs to go into master immediately, include a note in your PR.
|
||||
Want to contribute? Great!
|
||||
- First, read this page (including the small print at the end).
|
||||
- Second, please make pull requests against develop, not master. If your patch
|
||||
needs to go into master immediately, include a note in your PR.
|
||||
|
||||
For more information, head over to the [Blockly Developers site](https://developers.google.com/blockly/guides/modify/contributing).
|
||||
For more information on style guide and other details, head over to the [Blockly Developers site](https://developers.google.com/blockly/guides/modify/contributing).
|
||||
|
||||
### Before you contribute
|
||||
Before we can use your code, you must sign the
|
||||
[Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual)
|
||||
(CLA), which you can do online. The CLA is necessary mainly because you own the
|
||||
copyright to your changes, even after your contribution becomes part of our
|
||||
codebase, so we need your permission to use and distribute your code. We also
|
||||
need to be sure of various other things—for instance that you'll tell us if you
|
||||
know that your code infringes on other people's patents. You don't have to sign
|
||||
the CLA until after you've submitted your code for review and a member has
|
||||
approved it, but you must do it before we can put your code into our codebase.
|
||||
|
||||
### Larger changes
|
||||
Before you start working on a larger contribution, you should get in touch with
|
||||
us first through the issue tracker with your idea so that we can help out and
|
||||
possibly guide you. Coordinating up front makes it much easier to avoid
|
||||
frustration later on.
|
||||
|
||||
### Code reviews
|
||||
All submissions, including submissions by project members, require review. We
|
||||
use Github pull requests for this purpose.
|
||||
|
||||
### Browser compatibility
|
||||
We care strongly about making Blockly work on all browsers. As of 2017 we
|
||||
support IE 10 and 11, Edge, Chrome, Safari, and Firefox. We will not accept
|
||||
changes that only work on a subset of those browsers. You can check [caniuse.com](https://caniuse.com/)
|
||||
for compatibility information.
|
||||
|
||||
### The small print
|
||||
Contributions made by corporations are covered by a different agreement than
|
||||
the one above, the
|
||||
[Software Grant and Corporate Contributor License Agreement](https://cla.developers.google.com/about/google-corporate).
|
||||
|
||||
+8
-1
@@ -1,4 +1,5 @@
|
||||
# Blockly
|
||||
# Blockly [](https://travis-ci.org/google/blockly)
|
||||
|
||||
|
||||
Google's Blockly is a web-based, visual programming editor. Users can drag
|
||||
blocks together to build programs. All code is free and open source.
|
||||
@@ -8,3 +9,9 @@ blocks together to build programs. All code is free and open source.
|
||||

|
||||
|
||||
Blockly has an active [developer forum](https://groups.google.com/forum/#!forum/blockly). Please drop by and say hello. Show us your prototypes early; collectively we have a lot of experience and can offer hints which will save you time.
|
||||
|
||||
Help us focus our development efforts by telling us [what you are doing with
|
||||
Blockly](https://developers.google.com/blockly/registration). The questionnaire only takes
|
||||
a few minutes and will help us better support the Blockly community.
|
||||
|
||||
Want to contribute? Great! First, read [our guidelines for contributors](https://developers.google.com/blockly/guides/modify/contributing).
|
||||
|
||||
@@ -22,6 +22,29 @@
|
||||
* @author madeeha@google.com (Madeeha Ghori)
|
||||
*/
|
||||
|
||||
goog.provide('blocklyApp.AppComponent');
|
||||
|
||||
goog.require('Blockly');
|
||||
|
||||
goog.require('blocklyApp.AudioService');
|
||||
goog.require('blocklyApp.BlockConnectionService');
|
||||
goog.require('blocklyApp.BlockOptionsModalComponent');
|
||||
goog.require('blocklyApp.BlockOptionsModalService');
|
||||
goog.require('blocklyApp.KeyboardInputService');
|
||||
goog.require('blocklyApp.NotificationsService');
|
||||
goog.require('blocklyApp.SidebarComponent');
|
||||
goog.require('blocklyApp.ToolboxModalComponent');
|
||||
goog.require('blocklyApp.ToolboxModalService');
|
||||
goog.require('blocklyApp.TranslatePipe');
|
||||
goog.require('blocklyApp.TreeService');
|
||||
goog.require('blocklyApp.UtilsService');
|
||||
goog.require('blocklyApp.VariableAddModalComponent');
|
||||
goog.require('blocklyApp.VariableModalService');
|
||||
goog.require('blocklyApp.VariableRenameModalComponent');
|
||||
goog.require('blocklyApp.VariableRemoveModalComponent');
|
||||
goog.require('blocklyApp.WorkspaceComponent');
|
||||
|
||||
|
||||
blocklyApp.workspace = new Blockly.Workspace();
|
||||
|
||||
blocklyApp.AppComponent = ng.core.Component({
|
||||
@@ -36,9 +59,11 @@ blocklyApp.AppComponent = ng.core.Component({
|
||||
<span aria-live="polite" role="status">{{getAriaLiveReadout()}}</span>
|
||||
</div>
|
||||
|
||||
<blockly-block-options-modal></blockly-block-options-modal>
|
||||
<blockly-add-variable-modal></blockly-add-variable-modal>
|
||||
<blockly-rename-variable-modal></blockly-rename-variable-modal>
|
||||
<blockly-remove-variable-modal></blockly-remove-variable-modal>
|
||||
<blockly-toolbox-modal></blockly-toolbox-modal>
|
||||
<blockly-variable-modal></blockly-variable-modal>
|
||||
<blockly-block-options-modal></blockly-block-options-modal>
|
||||
|
||||
<label id="blockly-translate-button" aria-hidden="true" hidden>
|
||||
{{'BUTTON'|translate}}
|
||||
@@ -51,7 +76,9 @@ blocklyApp.AppComponent = ng.core.Component({
|
||||
blocklyApp.BlockOptionsModalComponent,
|
||||
blocklyApp.SidebarComponent,
|
||||
blocklyApp.ToolboxModalComponent,
|
||||
blocklyApp.VariableModalComponent,
|
||||
blocklyApp.VariableAddModalComponent,
|
||||
blocklyApp.VariableRenameModalComponent,
|
||||
blocklyApp.VariableRemoveModalComponent,
|
||||
blocklyApp.WorkspaceComponent
|
||||
],
|
||||
pipes: [blocklyApp.TranslatePipe],
|
||||
|
||||
@@ -22,6 +22,11 @@
|
||||
* @author sll@google.com (Sean Lip)
|
||||
*/
|
||||
|
||||
goog.provide('blocklyApp.AudioService');
|
||||
|
||||
goog.require('blocklyApp.NotificationsService');
|
||||
|
||||
|
||||
blocklyApp.AudioService = ng.core.Class({
|
||||
constructor: [
|
||||
blocklyApp.NotificationsService, function(notificationsService) {
|
||||
|
||||
@@ -23,6 +23,12 @@
|
||||
* @author sll@google.com (Sean Lip)
|
||||
*/
|
||||
|
||||
goog.provide('blocklyApp.BlockConnectionService');
|
||||
|
||||
goog.require('blocklyApp.AudioService');
|
||||
goog.require('blocklyApp.NotificationsService');
|
||||
|
||||
|
||||
blocklyApp.BlockConnectionService = ng.core.Class({
|
||||
constructor: [
|
||||
blocklyApp.NotificationsService, blocklyApp.AudioService,
|
||||
|
||||
@@ -23,6 +23,16 @@
|
||||
* @author sll@google.com (Sean Lip)
|
||||
*/
|
||||
|
||||
goog.provide('blocklyApp.BlockOptionsModalComponent');
|
||||
|
||||
goog.require('blocklyApp.AudioService');
|
||||
goog.require('blocklyApp.BlockOptionsModalService');
|
||||
goog.require('blocklyApp.KeyboardInputService');
|
||||
goog.require('blocklyApp.TranslatePipe');
|
||||
|
||||
goog.require('Blockly.CommonModal');
|
||||
|
||||
|
||||
blocklyApp.BlockOptionsModalComponent = ng.core.Component({
|
||||
selector: 'blockly-block-options-modal',
|
||||
template: `
|
||||
@@ -39,7 +49,7 @@ blocklyApp.BlockOptionsModalComponent = ng.core.Component({
|
||||
*ngFor="#buttonInfo of actionButtonsInfo; #buttonIndex=index">
|
||||
<button [id]="getOptionId(buttonIndex)"
|
||||
(click)="buttonInfo.action(); hideModal();"
|
||||
[ngClass]="{activeButton: activeActionButtonIndex == buttonIndex}">
|
||||
[ngClass]="{activeButton: activeButtonIndex == buttonIndex}">
|
||||
{{buttonInfo.translationIdForText|translate}}
|
||||
</button>
|
||||
</div>
|
||||
@@ -48,7 +58,7 @@ blocklyApp.BlockOptionsModalComponent = ng.core.Component({
|
||||
<div class="blocklyModalButtonContainer">
|
||||
<button [id]="getCancelOptionId()"
|
||||
(click)="dismissModal()"
|
||||
[ngClass]="{activeButton: activeActionButtonIndex == actionButtonsInfo.length}">
|
||||
[ngClass]="{activeButton: activeButtonIndex == actionButtonsInfo.length}">
|
||||
{{'CANCEL'|translate}}
|
||||
</button>
|
||||
</div>
|
||||
@@ -68,7 +78,7 @@ blocklyApp.BlockOptionsModalComponent = ng.core.Component({
|
||||
|
||||
this.modalIsVisible = false;
|
||||
this.actionButtonsInfo = [];
|
||||
this.activeActionButtonIndex = -1;
|
||||
this.activeButtonIndex = -1;
|
||||
this.onDismissCallback = null;
|
||||
|
||||
var that = this;
|
||||
@@ -79,67 +89,26 @@ blocklyApp.BlockOptionsModalComponent = ng.core.Component({
|
||||
that.activeActionButtonIndex = -1;
|
||||
that.onDismissCallback = onDismissCallback;
|
||||
|
||||
that.keyboardInputService.setOverride({
|
||||
// Tab key: navigates to the previous or next item in the list.
|
||||
'9': function(evt) {
|
||||
Blockly.CommonModal.setupKeyboardOverrides(that);
|
||||
that.keyboardInputService.addOverride('13', function(evt) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
|
||||
if (evt.shiftKey) {
|
||||
// Move to the previous item in the list.
|
||||
if (that.activeActionButtonIndex <= 0) {
|
||||
that.activeActionButtonIndex = 0;
|
||||
that.audioService.playOopsSound();
|
||||
} else {
|
||||
that.activeActionButtonIndex--;
|
||||
}
|
||||
} else {
|
||||
// Move to the next item in the list.
|
||||
if (that.activeActionButtonIndex ==
|
||||
that.actionButtonsInfo.length) {
|
||||
that.audioService.playOopsSound();
|
||||
} else {
|
||||
that.activeActionButtonIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
that.focusOnOption(that.activeActionButtonIndex);
|
||||
},
|
||||
// Enter key: selects an action, performs it, and closes the modal.
|
||||
'13': function(evt) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
|
||||
if (that.activeActionButtonIndex == -1) {
|
||||
if (that.activeButtonIndex == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
var button = document.getElementById(
|
||||
that.getOptionId(that.activeActionButtonIndex));
|
||||
if (that.activeActionButtonIndex <
|
||||
that.getOptionId(that.activeButtonIndex));
|
||||
if (that.activeButtonIndex <
|
||||
that.actionButtonsInfo.length) {
|
||||
that.actionButtonsInfo[that.activeActionButtonIndex].action();
|
||||
that.actionButtonsInfo[that.activeButtonIndex].action();
|
||||
} else {
|
||||
that.dismissModal();
|
||||
}
|
||||
|
||||
that.hideModal();
|
||||
},
|
||||
// Escape key: closes the modal.
|
||||
'27': function() {
|
||||
that.dismissModal();
|
||||
},
|
||||
// Up key: no-op.
|
||||
'38': function(evt) {
|
||||
// Prevent the page from scrolling.
|
||||
evt.preventDefault();
|
||||
},
|
||||
// Down key: no-op.
|
||||
'40': function(evt) {
|
||||
// Prevent the page from scrolling.
|
||||
evt.preventDefault();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
setTimeout(function() {
|
||||
document.getElementById('blockOptionsModal').focus();
|
||||
@@ -152,6 +121,10 @@ blocklyApp.BlockOptionsModalComponent = ng.core.Component({
|
||||
var button = document.getElementById(this.getOptionId(index));
|
||||
button.focus();
|
||||
},
|
||||
// Counts the number of interactive elements for the modal.
|
||||
numInteractiveElements: function() {
|
||||
return this.actionButtonsInfo.length + 1;
|
||||
},
|
||||
// Returns the ID for the corresponding option button.
|
||||
getOptionId: function(index) {
|
||||
return 'block-options-modal-option-' + index;
|
||||
|
||||
@@ -23,6 +23,9 @@
|
||||
* @author sll@google.com (Sean Lip)
|
||||
*/
|
||||
|
||||
goog.provide('blocklyApp.BlockOptionsModalService');
|
||||
|
||||
|
||||
blocklyApp.BlockOptionsModalService = ng.core.Class({
|
||||
constructor: [function() {
|
||||
this.actionButtonsInfo = [];
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
goog.provide('Blockly.CommonModal');
|
||||
|
||||
|
||||
Blockly.CommonModal = function() {};
|
||||
|
||||
Blockly.CommonModal.setupKeyboardOverrides = function(component) {
|
||||
component.keyboardInputService.setOverride({
|
||||
// Tab key: navigates to the previous or next item in the list.
|
||||
'9': function(evt) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
|
||||
if (evt.shiftKey) {
|
||||
// Move to the previous item in the list.
|
||||
if (component.activeButtonIndex <= 0) {
|
||||
component.activeActionButtonIndex = 0;
|
||||
component.audioService.playOopsSound();
|
||||
} else {
|
||||
component.activeButtonIndex--;
|
||||
}
|
||||
} else {
|
||||
// Move to the next item in the list.
|
||||
if (component.activeButtonIndex == component.numInteractiveElements(component) - 1) {
|
||||
component.audioService.playOopsSound();
|
||||
} else {
|
||||
component.activeButtonIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
component.focusOnOption(component.activeButtonIndex, component);
|
||||
},
|
||||
// Escape key: closes the modal.
|
||||
'27': function() {
|
||||
component.dismissModal();
|
||||
},
|
||||
// Up key: no-op.
|
||||
'38': function(evt) {
|
||||
evt.preventDefault();
|
||||
},
|
||||
// Down key: no-op.
|
||||
'40': function(evt) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Blockly.CommonModal.getInteractiveElements = function(component) {
|
||||
return Array.prototype.filter.call(
|
||||
component.getInteractiveContainer().elements, function(element) {
|
||||
if (element.type === 'hidden') {
|
||||
return false;
|
||||
}
|
||||
if (element.disabled) {
|
||||
return false;
|
||||
}
|
||||
if (element.tabIndex < 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
Blockly.CommonModal.numInteractiveElements = function(component) {
|
||||
var elements = this.getInteractiveElements(component);
|
||||
return elements.length;
|
||||
};
|
||||
|
||||
Blockly.CommonModal.focusOnOption = function(index, component) {
|
||||
var elements = this.getInteractiveElements(component);
|
||||
var button = elements[index];
|
||||
button.focus();
|
||||
};
|
||||
|
||||
Blockly.CommonModal.hideModal = function() {
|
||||
this.modalIsVisible = false;
|
||||
this.keyboardInputService.clearOverride();
|
||||
};
|
||||
@@ -24,6 +24,13 @@
|
||||
* @author madeeha@google.com (Madeeha Ghori)
|
||||
*/
|
||||
|
||||
goog.provide('blocklyApp.FieldSegmentComponent');
|
||||
|
||||
goog.require('blocklyApp.NotificationsService');
|
||||
goog.require('blocklyApp.TranslatePipe');
|
||||
goog.require('blocklyApp.VariableModalService');
|
||||
|
||||
|
||||
blocklyApp.FieldSegmentComponent = ng.core.Component({
|
||||
selector: 'blockly-field-segment',
|
||||
template: `
|
||||
@@ -51,11 +58,10 @@ blocklyApp.FieldSegmentComponent = ng.core.Component({
|
||||
<template [ngIf]="isDropdown()">
|
||||
{{getPrefixText()}}
|
||||
<select [id]="mainFieldId" [name]="mainFieldId"
|
||||
[ngModel]="mainField.getValue()" (ngModelChange)="setDropdownValue($event)"
|
||||
[ngModel]="selectedOption" (ngModelChange)="setDropdownValue($event)"
|
||||
(keydown.enter)="selectOption()"
|
||||
tabindex="-1">
|
||||
<option *ngFor="#option of dropdownOptions" value="{{option.value}}"
|
||||
[selected]="mainField.getValue() == option.value">
|
||||
<option *ngFor="#option of dropdownOptions" value="{{option.value}}">
|
||||
{{option.text}}
|
||||
</option>
|
||||
</select>
|
||||
@@ -75,10 +81,17 @@ blocklyApp.FieldSegmentComponent = ng.core.Component({
|
||||
this.dropdownOptions = [];
|
||||
this.rawOptions = [];
|
||||
}],
|
||||
// Angular2 hook - called after initialization.
|
||||
ngAfterContentInit: function() {
|
||||
if (this.mainField) {
|
||||
this.mainField.initModel();
|
||||
}
|
||||
},
|
||||
// Angular2 hook - called to check if the cached component needs an update.
|
||||
ngDoCheck: function() {
|
||||
if (this.isDropdown() && this.shouldBreakCache()) {
|
||||
this.optionValue = this.mainField.getValue();
|
||||
this.fieldValue = this.mainField.getValue();
|
||||
this.rawOptions = this.mainField.getOptions();
|
||||
this.dropdownOptions = this.rawOptions.map(function(valueAndText) {
|
||||
return {
|
||||
@@ -86,6 +99,13 @@ blocklyApp.FieldSegmentComponent = ng.core.Component({
|
||||
value: valueAndText[1]
|
||||
};
|
||||
});
|
||||
|
||||
// Set the currently selected value to the variable on the field.
|
||||
for (var i = 0; i < this.dropdownOptions.length; i++) {
|
||||
if (this.dropdownOptions[i].text === this.fieldValue) {
|
||||
this.selectedOption = this.dropdownOptions[i].value;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// Returns whether the mutable, cached information needs to be refreshed.
|
||||
@@ -97,11 +117,15 @@ blocklyApp.FieldSegmentComponent = ng.core.Component({
|
||||
|
||||
for (var i = 0; i < this.rawOptions.length; i++) {
|
||||
// Compare the value of the cached options with the values in the field.
|
||||
if (newOptions[i][1] != this.rawOptions[i][1]) {
|
||||
if (newOptions[i][0] != this.rawOptions[i][0]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.fieldValue != this.mainField.getValue()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
// Gets the prefix text, to be printed before a field.
|
||||
@@ -143,8 +167,17 @@ blocklyApp.FieldSegmentComponent = ng.core.Component({
|
||||
},
|
||||
// Confirm a selection for dropdown fields.
|
||||
selectOption: function() {
|
||||
if (this.optionValue == Blockly.Msg.RENAME_VARIABLE) {
|
||||
this.variableModalService.showModal_(this.mainField.getValue());
|
||||
if (this.optionValue != Blockly.RENAME_VARIABLE_ID && this.optionValue !=
|
||||
Blockly.DELETE_VARIABLE_ID) {
|
||||
this.mainField.setValue(this.optionValue);
|
||||
}
|
||||
|
||||
if (this.optionValue == Blockly.RENAME_VARIABLE_ID) {
|
||||
this.variableModalService.showRenameModal_(this.mainField.getValue());
|
||||
}
|
||||
|
||||
if (this.optionValue == Blockly.DELETE_VARIABLE_ID) {
|
||||
this.variableModalService.showRemoveModal_(this.mainField.getValue());
|
||||
}
|
||||
},
|
||||
// Sets the value on a dropdown input.
|
||||
@@ -154,10 +187,6 @@ blocklyApp.FieldSegmentComponent = ng.core.Component({
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.optionValue != Blockly.Msg.RENAME_VARIABLE) {
|
||||
this.mainField.setValue(this.optionValue);
|
||||
}
|
||||
|
||||
var optionText = undefined;
|
||||
for (var i = 0; i < this.dropdownOptions.length; i++) {
|
||||
if (this.dropdownOptions[i].value == optionValue) {
|
||||
|
||||
@@ -23,6 +23,9 @@
|
||||
* @author sll@google.com (Sean Lip)
|
||||
*/
|
||||
|
||||
goog.provide('blocklyApp.KeyboardInputService');
|
||||
|
||||
|
||||
blocklyApp.KeyboardInputService = ng.core.Class({
|
||||
constructor: [function() {
|
||||
// Default custom actions for global keystrokes. The keys of this object
|
||||
@@ -46,6 +49,9 @@ blocklyApp.KeyboardInputService = ng.core.Class({
|
||||
setOverride: function(newKeysToActions) {
|
||||
this.keysToActionsOverride = newKeysToActions;
|
||||
},
|
||||
addOverride: function(keyCode, action) {
|
||||
this.keysToActionsOverride[keyCode] = action;
|
||||
},
|
||||
clearOverride: function() {
|
||||
this.keysToActionsOverride = null;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,9 @@
|
||||
* @author sll@google.com (Sean Lip)
|
||||
*/
|
||||
|
||||
goog.provide('blocklyApp.NotificationsService');
|
||||
|
||||
|
||||
blocklyApp.NotificationsService = ng.core.Class({
|
||||
constructor: [function() {
|
||||
this.currentMessage = '';
|
||||
|
||||
@@ -24,6 +24,17 @@
|
||||
* @author sll@google.com (Sean Lip)
|
||||
*/
|
||||
|
||||
goog.provide('blocklyApp.SidebarComponent');
|
||||
|
||||
goog.require('blocklyApp.UtilsService');
|
||||
|
||||
goog.require('blocklyApp.BlockConnectionService');
|
||||
goog.require('blocklyApp.ToolboxModalService');
|
||||
goog.require('blocklyApp.TranslatePipe');
|
||||
goog.require('blocklyApp.TreeService');
|
||||
goog.require('blocklyApp.VariableModalService');
|
||||
|
||||
|
||||
blocklyApp.SidebarComponent = ng.core.Component({
|
||||
selector: 'blockly-sidebar',
|
||||
template: `
|
||||
@@ -52,6 +63,11 @@ blocklyApp.SidebarComponent = ng.core.Component({
|
||||
class="blocklySidebarButton">
|
||||
{{'ERASE_WORKSPACE'|translate}}
|
||||
</button>
|
||||
<button *ngIf="hasVariableCategory()" id="add-variable"
|
||||
(click)="showAddVariableModal()"
|
||||
class="blocklySidebarButton">
|
||||
Add Variable
|
||||
</button>
|
||||
</div>
|
||||
`,
|
||||
pipes: [blocklyApp.TranslatePipe]
|
||||
@@ -62,9 +78,10 @@ blocklyApp.SidebarComponent = ng.core.Component({
|
||||
blocklyApp.ToolboxModalService,
|
||||
blocklyApp.TreeService,
|
||||
blocklyApp.UtilsService,
|
||||
blocklyApp.VariableModalService,
|
||||
function(
|
||||
blockConnectionService, toolboxModalService, treeService,
|
||||
utilsService) {
|
||||
utilsService, variableService) {
|
||||
// ACCESSIBLE_GLOBALS is a global variable defined by the containing
|
||||
// page. It should contain a key, customSidebarButtons, describing
|
||||
// additional buttons that should be displayed after the default ones.
|
||||
@@ -77,6 +94,7 @@ blocklyApp.SidebarComponent = ng.core.Component({
|
||||
this.toolboxModalService = toolboxModalService;
|
||||
this.treeService = treeService;
|
||||
this.utilsService = utilsService;
|
||||
this.variableModalService = variableService;
|
||||
|
||||
this.ID_FOR_ATTACH_TO_LINK_BUTTON = 'blocklyAttachToLinkBtn';
|
||||
this.ID_FOR_CREATE_NEW_GROUP_BUTTON = 'blocklyCreateNewGroupBtn';
|
||||
@@ -88,6 +106,9 @@ blocklyApp.SidebarComponent = ng.core.Component({
|
||||
isWorkspaceEmpty: function() {
|
||||
return this.utilsService.isWorkspaceEmpty();
|
||||
},
|
||||
hasVariableCategory: function() {
|
||||
return this.toolboxModalService.toolboxHasVariableCategory();
|
||||
},
|
||||
clearWorkspace: function() {
|
||||
blocklyApp.workspace.clear();
|
||||
this.treeService.clearAllActiveDescs();
|
||||
@@ -104,5 +125,8 @@ blocklyApp.SidebarComponent = ng.core.Component({
|
||||
showToolboxModalForCreateNewGroup: function() {
|
||||
this.toolboxModalService.showToolboxModalForCreateNewGroup(
|
||||
this.ID_FOR_CREATE_NEW_GROUP_BUTTON);
|
||||
},
|
||||
showAddVariableModal: function() {
|
||||
this.variableModalService.showAddModal_("item");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -23,6 +23,17 @@
|
||||
* @author sll@google.com (Sean Lip)
|
||||
*/
|
||||
|
||||
goog.provide('blocklyApp.ToolboxModalComponent');
|
||||
|
||||
goog.require('Blockly.CommonModal');
|
||||
goog.require('blocklyApp.AudioService');
|
||||
goog.require('blocklyApp.KeyboardInputService');
|
||||
goog.require('blocklyApp.ToolboxModalService');
|
||||
goog.require('blocklyApp.TranslatePipe');
|
||||
goog.require('blocklyApp.TreeService');
|
||||
goog.require('blocklyApp.UtilsService');
|
||||
|
||||
|
||||
blocklyApp.ToolboxModalComponent = ng.core.Component({
|
||||
selector: 'blockly-toolbox-modal',
|
||||
template: `
|
||||
@@ -103,34 +114,8 @@ blocklyApp.ToolboxModalComponent = ng.core.Component({
|
||||
that.firstBlockIndexes.push(cumulativeIndex);
|
||||
that.totalNumBlocks = cumulativeIndex;
|
||||
|
||||
that.keyboardInputService.setOverride({
|
||||
// Tab key: navigates to the previous or next item in the list.
|
||||
'9': function(evt) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
|
||||
if (evt.shiftKey) {
|
||||
// Move to the previous item in the list.
|
||||
if (that.activeButtonIndex <= 0) {
|
||||
that.activeActionButtonIndex = 0;
|
||||
that.audioService.playOopsSound();
|
||||
} else {
|
||||
that.activeButtonIndex--;
|
||||
}
|
||||
} else {
|
||||
// Move to the next item in the list.
|
||||
if (that.activeButtonIndex == that.totalNumBlocks) {
|
||||
that.audioService.playOopsSound();
|
||||
} else {
|
||||
that.activeButtonIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
that.focusOnOption(that.activeButtonIndex);
|
||||
},
|
||||
// Enter key: selects a block (or the 'Cancel' button), and closes
|
||||
// the modal.
|
||||
'13': function(evt) {
|
||||
Blockly.CommonModal.setupKeyboardOverrides(that);
|
||||
that.keyboardInputService.addOverride('13', function(evt) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
|
||||
@@ -154,20 +139,7 @@ blocklyApp.ToolboxModalComponent = ng.core.Component({
|
||||
|
||||
// The 'Cancel' button has been pressed.
|
||||
that.dismissModal();
|
||||
},
|
||||
// Escape key: closes the modal.
|
||||
'27': function() {
|
||||
that.dismissModal();
|
||||
},
|
||||
// Up key: no-op.
|
||||
'38': function(evt) {
|
||||
evt.preventDefault();
|
||||
},
|
||||
// Down key: no-op.
|
||||
'40': function(evt) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
setTimeout(function() {
|
||||
document.getElementById('toolboxModal').focus();
|
||||
@@ -177,10 +149,15 @@ blocklyApp.ToolboxModalComponent = ng.core.Component({
|
||||
}
|
||||
],
|
||||
// Closes the modal (on both success and failure).
|
||||
hideModal_: function() {
|
||||
this.modalIsVisible = false;
|
||||
this.keyboardInputService.clearOverride();
|
||||
this.toolboxModalService.hideModal();
|
||||
hideModal_: Blockly.CommonModal.hideModal,
|
||||
// Focuses on the button represented by the given index.
|
||||
focusOnOption: function(index) {
|
||||
var button = document.getElementById(this.getOptionId(index));
|
||||
button.focus();
|
||||
},
|
||||
// Counts the number of interactive elements for the modal.
|
||||
numInteractiveElements: function() {
|
||||
return this.totalNumBlocks + 1;
|
||||
},
|
||||
getOverallIndex: function(categoryIndex, blockIndex) {
|
||||
return this.firstBlockIndexes[categoryIndex] + blockIndex;
|
||||
@@ -191,11 +168,6 @@ blocklyApp.ToolboxModalComponent = ng.core.Component({
|
||||
getBlockDescription: function(block) {
|
||||
return this.utilsService.getBlockDescription(block);
|
||||
},
|
||||
// Focuses on the button represented by the given index.
|
||||
focusOnOption: function(index) {
|
||||
var button = document.getElementById(this.getOptionId(index));
|
||||
button.focus();
|
||||
},
|
||||
// Returns the ID for the corresponding option button.
|
||||
getOptionId: function(index) {
|
||||
return 'toolbox-modal-option-' + index;
|
||||
|
||||
@@ -23,6 +23,15 @@
|
||||
* @author sll@google.com (Sean Lip)
|
||||
*/
|
||||
|
||||
goog.provide('blocklyApp.ToolboxModalService');
|
||||
|
||||
goog.require('blocklyApp.UtilsService');
|
||||
|
||||
goog.require('blocklyApp.BlockConnectionService');
|
||||
goog.require('blocklyApp.NotificationsService');
|
||||
goog.require('blocklyApp.TreeService');
|
||||
|
||||
|
||||
blocklyApp.ToolboxModalService = ng.core.Class({
|
||||
constructor: [
|
||||
blocklyApp.BlockConnectionService,
|
||||
@@ -42,6 +51,7 @@ blocklyApp.ToolboxModalService = ng.core.Class({
|
||||
this.selectedToolboxCategories = null;
|
||||
this.onSelectBlockCallback = null;
|
||||
this.onDismissCallback = null;
|
||||
this.hasVariableCategory = null;
|
||||
// The aim of the pre-show hook is to populate the modal component with
|
||||
// the information it needs to display the modal (e.g., which categories
|
||||
// and blocks to display).
|
||||
@@ -50,45 +60,56 @@ blocklyApp.ToolboxModalService = ng.core.Class({
|
||||
'A pre-show hook must be defined for the toolbox modal before it ' +
|
||||
'can be shown.');
|
||||
};
|
||||
|
||||
// Populate the toolbox categories.
|
||||
this.allToolboxCategories = [];
|
||||
var toolboxXmlElt = document.getElementById('blockly-toolbox-xml');
|
||||
var toolboxCategoryElts = toolboxXmlElt.getElementsByTagName('category');
|
||||
if (toolboxCategoryElts.length) {
|
||||
this.allToolboxCategories = Array.from(toolboxCategoryElts).map(
|
||||
function(categoryElt) {
|
||||
var tmpWorkspace = new Blockly.Workspace();
|
||||
Blockly.Xml.domToWorkspace(categoryElt, tmpWorkspace);
|
||||
return {
|
||||
categoryName: categoryElt.attributes.name.value,
|
||||
blocks: tmpWorkspace.topBlocks_
|
||||
};
|
||||
}
|
||||
);
|
||||
this.computeCategoriesForCreateNewGroupModal_();
|
||||
} else {
|
||||
// A timeout seems to be needed in order for the .children accessor to
|
||||
// work correctly.
|
||||
var that = this;
|
||||
setTimeout(function() {
|
||||
// If there are no top-level categories, we create a single category
|
||||
// containing all the top-level blocks.
|
||||
var tmpWorkspace = new Blockly.Workspace();
|
||||
Array.from(toolboxXmlElt.children).forEach(function(topLevelNode) {
|
||||
Blockly.Xml.domToBlock(tmpWorkspace, topLevelNode);
|
||||
});
|
||||
|
||||
that.allToolboxCategories = [{
|
||||
categoryName: '',
|
||||
blocks: tmpWorkspace.topBlocks_
|
||||
}];
|
||||
|
||||
that.computeCategoriesForCreateNewGroupModal_();
|
||||
});
|
||||
}
|
||||
}
|
||||
],
|
||||
populateToolbox_: function() {
|
||||
// Populate the toolbox categories.
|
||||
this.allToolboxCategories = [];
|
||||
var toolboxXmlElt = document.getElementById('blockly-toolbox-xml');
|
||||
var toolboxCategoryElts = toolboxXmlElt.getElementsByTagName('category');
|
||||
if (toolboxCategoryElts.length) {
|
||||
this.allToolboxCategories = Array.from(toolboxCategoryElts).map(
|
||||
function(categoryElt) {
|
||||
var tmpWorkspace = new Blockly.Workspace();
|
||||
var custom = categoryElt.attributes.custom
|
||||
// TODO (corydiers): Implement custom flyouts once #1153 is solved.
|
||||
if (custom && custom.value == Blockly.VARIABLE_CATEGORY_NAME) {
|
||||
var varBlocks =
|
||||
Blockly.Variables.flyoutCategoryBlocks(blocklyApp.workspace);
|
||||
varBlocks.forEach(function(block) {
|
||||
Blockly.Xml.domToBlock(block, tmpWorkspace);
|
||||
});
|
||||
} else {
|
||||
Blockly.Xml.domToWorkspace(categoryElt, tmpWorkspace);
|
||||
}
|
||||
return {
|
||||
categoryName: categoryElt.attributes.name.value,
|
||||
blocks: tmpWorkspace.topBlocks_
|
||||
};
|
||||
}
|
||||
);
|
||||
this.computeCategoriesForCreateNewGroupModal_();
|
||||
} else {
|
||||
// A timeout seems to be needed in order for the .children accessor to
|
||||
// work correctly.
|
||||
var that = this;
|
||||
setTimeout(function() {
|
||||
// If there are no top-level categories, we create a single category
|
||||
// containing all the top-level blocks.
|
||||
var tmpWorkspace = new Blockly.Workspace();
|
||||
Array.from(toolboxXmlElt.children).forEach(function(topLevelNode) {
|
||||
Blockly.Xml.domToBlock(tmpWorkspace, topLevelNode);
|
||||
});
|
||||
|
||||
that.allToolboxCategories = [{
|
||||
categoryName: '',
|
||||
blocks: tmpWorkspace.topBlocks_
|
||||
}];
|
||||
|
||||
that.computeCategoriesForCreateNewGroupModal_();
|
||||
});
|
||||
}
|
||||
},
|
||||
computeCategoriesForCreateNewGroupModal_: function() {
|
||||
// Precompute toolbox categories for blocks that have no output
|
||||
// connection (and that can therefore be used as the base block of a
|
||||
@@ -119,6 +140,26 @@ blocklyApp.ToolboxModalService = ng.core.Class({
|
||||
isModalShown: function() {
|
||||
return this.modalIsShown;
|
||||
},
|
||||
toolboxHasVariableCategory: function() {
|
||||
if (this.hasVariableCategory === null) {
|
||||
var toolboxXmlElt = document.getElementById('blockly-toolbox-xml');
|
||||
var toolboxCategoryElts = toolboxXmlElt.getElementsByTagName('category');
|
||||
var that = this;
|
||||
Array.from(toolboxCategoryElts).forEach(
|
||||
function(categoryElt) {
|
||||
var custom = categoryElt.attributes.custom;
|
||||
if (custom && custom.value == Blockly.VARIABLE_CATEGORY_NAME) {
|
||||
that.hasVariableCategory = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (this.hasVariableCategory === null) {
|
||||
this.hasVariableCategory = false;
|
||||
}
|
||||
}
|
||||
|
||||
return this.hasVariableCategory;
|
||||
},
|
||||
showModal_: function(
|
||||
selectedToolboxCategories, onSelectBlockCallback, onDismissCallback) {
|
||||
this.selectedToolboxCategories = selectedToolboxCategories;
|
||||
@@ -135,6 +176,7 @@ blocklyApp.ToolboxModalService = ng.core.Class({
|
||||
var that = this;
|
||||
|
||||
var selectedToolboxCategories = [];
|
||||
this.populateToolbox_();
|
||||
this.allToolboxCategories.forEach(function(toolboxCategory) {
|
||||
var selectedBlocks = toolboxCategory.blocks.filter(function(block) {
|
||||
return that.blockConnectionService.canBeAttachedToMarkedConnection(
|
||||
@@ -172,6 +214,7 @@ blocklyApp.ToolboxModalService = ng.core.Class({
|
||||
},
|
||||
showToolboxModalForCreateNewGroup: function(sourceButtonId) {
|
||||
var that = this;
|
||||
this.populateToolbox_();
|
||||
this.showModal_(this.toolboxCategoriesForNewGroup, function(block) {
|
||||
var blockDescription = that.utilsService.getBlockDescription(block);
|
||||
var xml = Blockly.Xml.blockToDom(block);
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
* @author sll@google.com (Sean Lip)
|
||||
*/
|
||||
|
||||
goog.provide('blocklyApp.TranslatePipe');
|
||||
|
||||
|
||||
blocklyApp.TranslatePipe = ng.core.Pipe({
|
||||
name: 'translate'
|
||||
})
|
||||
|
||||
@@ -25,6 +25,17 @@
|
||||
* @author madeeha@google.com (Madeeha Ghori)
|
||||
*/
|
||||
|
||||
goog.provide('blocklyApp.TreeService');
|
||||
|
||||
goog.require('blocklyApp.UtilsService');
|
||||
|
||||
goog.require('blocklyApp.AudioService');
|
||||
goog.require('blocklyApp.BlockConnectionService');
|
||||
goog.require('blocklyApp.BlockOptionsModalService');
|
||||
goog.require('blocklyApp.NotificationsService');
|
||||
goog.require('blocklyApp.VariableModalService');
|
||||
|
||||
|
||||
blocklyApp.TreeService = ng.core.Class({
|
||||
constructor: [
|
||||
blocklyApp.AudioService,
|
||||
@@ -32,14 +43,16 @@ blocklyApp.TreeService = ng.core.Class({
|
||||
blocklyApp.BlockOptionsModalService,
|
||||
blocklyApp.NotificationsService,
|
||||
blocklyApp.UtilsService,
|
||||
blocklyApp.VariableModalService,
|
||||
function(
|
||||
audioService, blockConnectionService, blockOptionsModalService,
|
||||
notificationsService, utilsService) {
|
||||
notificationsService, utilsService, variableModalService) {
|
||||
this.audioService = audioService;
|
||||
this.blockConnectionService = blockConnectionService;
|
||||
this.blockOptionsModalService = blockOptionsModalService;
|
||||
this.notificationsService = notificationsService;
|
||||
this.utilsService = utilsService;
|
||||
this.variableModalService = variableModalService;
|
||||
|
||||
// The suffix used for all IDs of block root elements.
|
||||
this.BLOCK_ROOT_ID_SUFFIX_ = blocklyApp.BLOCK_ROOT_ID_SUFFIX;
|
||||
@@ -207,11 +220,10 @@ blocklyApp.TreeService = ng.core.Class({
|
||||
var activeDesc = document.getElementById(this.getActiveDescId(treeId));
|
||||
if (activeDesc) {
|
||||
activeDesc.classList.remove('blocklyActiveDescendant');
|
||||
}
|
||||
|
||||
if (this.activeDescendantIds_[treeId]) {
|
||||
delete this.activeDescendantIds_[treeId];
|
||||
} else {
|
||||
throw Error(
|
||||
'The active desc element for the tree with ID ' + treeId +
|
||||
' is invalid.');
|
||||
}
|
||||
},
|
||||
clearAllActiveDescs: function() {
|
||||
@@ -460,14 +472,16 @@ blocklyApp.TreeService = ng.core.Class({
|
||||
onKeypress: function(e, tree) {
|
||||
// TODO(sll): Instead of this, have a common ActiveContextService which
|
||||
// returns true if at least one modal is shown, and false otherwise.
|
||||
if (this.blockOptionsModalService.isModalShown()) {
|
||||
if (this.blockOptionsModalService.isModalShown() ||
|
||||
this.variableModalService.isModalShown()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var treeId = tree.id;
|
||||
var activeDesc = document.getElementById(this.getActiveDescId(treeId));
|
||||
if (!activeDesc) {
|
||||
console.error('ERROR: no active descendant for current tree.');
|
||||
// The underlying Blockly instance may have decided blocks needed to
|
||||
// be deleted. This is not necessarily an error, but needs to be repaired.
|
||||
this.initActiveDesc(treeId);
|
||||
activeDesc = document.getElementById(this.getActiveDescId(treeId));
|
||||
}
|
||||
|
||||
@@ -25,7 +25,9 @@
|
||||
* @author madeeha@google.com (Madeeha Ghori)
|
||||
*/
|
||||
|
||||
var blocklyApp = {};
|
||||
goog.provide('blocklyApp.UtilsService');
|
||||
|
||||
|
||||
blocklyApp.ID_FOR_EMPTY_WORKSPACE_BTN = 'blocklyEmptyWorkspaceBtn';
|
||||
blocklyApp.BLOCK_ROOT_ID_SUFFIX = '-blockRoot';
|
||||
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* AccessibleBlockly
|
||||
*
|
||||
* Copyright 2017 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the 'License');
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an 'AS IS' BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Component representing the variable rename modal.
|
||||
*
|
||||
* @author corydiers@google.com (Cory Diers)
|
||||
*/
|
||||
|
||||
goog.provide('blocklyApp.VariableAddModalComponent');
|
||||
|
||||
goog.require('blocklyApp.AudioService');
|
||||
goog.require('blocklyApp.KeyboardInputService');
|
||||
goog.require('blocklyApp.TranslatePipe');
|
||||
goog.require('blocklyApp.VariableModalService');
|
||||
|
||||
goog.require('Blockly.CommonModal');
|
||||
|
||||
|
||||
blocklyApp.VariableAddModalComponent = ng.core.Component({
|
||||
selector: 'blockly-add-variable-modal',
|
||||
template: `
|
||||
<div *ngIf="modalIsVisible"class="blocklyModalCurtain"
|
||||
(click)="dismissModal()">
|
||||
<!-- $event.stopPropagation() prevents the modal from closing when its
|
||||
interior is clicked. -->
|
||||
<div id="varModal" class="blocklyModal" role="alertdialog"
|
||||
(click)="$event.stopPropagation()" tabindex="0"
|
||||
aria-labelledby="variableModalHeading">
|
||||
<h3 id="variableModalHeading">Add a variable...</h3>
|
||||
|
||||
<form id="varForm">
|
||||
<p id="inputLabel">New Variable Name:
|
||||
<input id="mainFieldId" type="text" [ngModel]="VALUE"
|
||||
(ngModelChange)="setTextValue($event)" tabindex="0"
|
||||
aria-labelledby="inputLabel" />
|
||||
</p>
|
||||
<hr>
|
||||
<button type="button" id="submitButton" (click)="submit()">
|
||||
SUBMIT
|
||||
</button>
|
||||
<button type="button" id="cancelButton" (click)="dismissModal()">
|
||||
CANCEL
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
pipes: [blocklyApp.TranslatePipe]
|
||||
})
|
||||
.Class({
|
||||
constructor: [
|
||||
blocklyApp.AudioService, blocklyApp.KeyboardInputService, blocklyApp.VariableModalService,
|
||||
function(audioService, keyboardService, variableService) {
|
||||
this.workspace = blocklyApp.workspace;
|
||||
this.variableModalService = variableService;
|
||||
this.audioService = audioService;
|
||||
this.keyboardInputService = keyboardService
|
||||
this.modalIsVisible = false;
|
||||
this.activeButtonIndex = -1;
|
||||
|
||||
var that = this;
|
||||
this.variableModalService.registerPreAddShowHook(
|
||||
function() {
|
||||
that.modalIsVisible = true;
|
||||
|
||||
Blockly.CommonModal.setupKeyboardOverrides(that);
|
||||
|
||||
setTimeout(function() {
|
||||
document.getElementById('varModal').focus();
|
||||
}, 150);
|
||||
}
|
||||
);
|
||||
}
|
||||
],
|
||||
// Caches the current text variable as the user types.
|
||||
setTextValue: function(newValue) {
|
||||
this.variableName = newValue;
|
||||
},
|
||||
// Closes the modal (on both success and failure).
|
||||
hideModal_: Blockly.CommonModal.hideModal,
|
||||
// Focuses on the button represented by the given index.
|
||||
focusOnOption: Blockly.CommonModal.focusOnOption,
|
||||
// Counts the number of interactive elements for the modal.
|
||||
numInteractiveElements: Blockly.CommonModal.numInteractiveElements,
|
||||
// Gets all the interactive elements for the modal.
|
||||
getInteractiveElements: Blockly.CommonModal.getInteractiveElements,
|
||||
// Gets the container with interactive elements.
|
||||
getInteractiveContainer: function() {
|
||||
return document.getElementById("varForm");
|
||||
},
|
||||
// Submits the name change for the variable.
|
||||
submit: function() {
|
||||
this.workspace.createVariable(this.variableName);
|
||||
this.dismissModal();
|
||||
},
|
||||
// Dismisses and closes the modal.
|
||||
dismissModal: function() {
|
||||
this.variableModalService.hideModal();
|
||||
this.hideModal_();
|
||||
}
|
||||
})
|
||||
@@ -23,27 +23,67 @@
|
||||
* @author corydiers@google.com (Cory Diers)
|
||||
*/
|
||||
|
||||
goog.provide('blocklyApp.VariableModalService');
|
||||
|
||||
blocklyApp.VariableModalService = ng.core.Class({
|
||||
constructor: [
|
||||
function() {
|
||||
this.modalIsShown = false;
|
||||
}
|
||||
],
|
||||
// Registers a hook to be called before the modal is shown.
|
||||
registerPreShowHook: function(preShowHook) {
|
||||
this.preShowHook = function(oldName) {
|
||||
// Registers a hook to be called before the add modal is shown.
|
||||
registerPreAddShowHook: function(preShowHook) {
|
||||
this.preAddShowHook = function() {
|
||||
preShowHook();
|
||||
};
|
||||
},
|
||||
// Registers a hook to be called before the rename modal is shown.
|
||||
registerPreRenameShowHook: function(preShowHook) {
|
||||
this.preRenameShowHook = function(oldName) {
|
||||
preShowHook(oldName);
|
||||
};
|
||||
},
|
||||
// Registers a hook to be called before the remove modal is shown.
|
||||
registerPreRemoveShowHook: function(preShowHook) {
|
||||
this.preRemoveShowHook = function(oldName, count) {
|
||||
preShowHook(oldName, count);
|
||||
};
|
||||
},
|
||||
// Returns true if the variable modal is shown.
|
||||
isModalShown: function() {
|
||||
return this.modalIsShown;
|
||||
},
|
||||
// Show the variable modal.
|
||||
showModal_: function(oldName) {
|
||||
this.preShowHook(oldName);
|
||||
// Show the add variable modal.
|
||||
showAddModal_: function() {
|
||||
this.preAddShowHook();
|
||||
this.modalIsShown = true;
|
||||
},
|
||||
// Show the rename variable modal.
|
||||
showRenameModal_: function(oldName) {
|
||||
this.preRenameShowHook(oldName);
|
||||
this.modalIsShown = true;
|
||||
},
|
||||
// Show the remove variable modal.
|
||||
showRemoveModal_: function(oldName) {
|
||||
var count = this.getNumVariables(oldName);
|
||||
this.modalIsShown = true;
|
||||
if (count > 1) {
|
||||
this.preRemoveShowHook(oldName, count);
|
||||
} else {
|
||||
var variable = blocklyApp.workspace.getVariable(oldName);
|
||||
blocklyApp.workspace.deleteVariableInternal_(variable);
|
||||
// Allow the execution loop to finish before "closing" the modal. While
|
||||
// the modal never opens, its being "open" should prevent other keypresses
|
||||
// anyway.
|
||||
var that = this;
|
||||
setTimeout(function() {
|
||||
that.modalIsShown = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
getNumVariables: function(oldName) {
|
||||
return blocklyApp.workspace.getVariableUses(oldName).length;
|
||||
},
|
||||
// Hide the variable modal.
|
||||
hideModal: function() {
|
||||
this.modalIsShown = false;
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* AccessibleBlockly
|
||||
*
|
||||
* Copyright 2017 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the 'License');
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an 'AS IS' BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Component representing the variable remove modal.
|
||||
*
|
||||
* @author corydiers@google.com (Cory Diers)
|
||||
*/
|
||||
|
||||
goog.provide('blocklyApp.VariableRemoveModalComponent');
|
||||
|
||||
goog.require('blocklyApp.AudioService');
|
||||
goog.require('blocklyApp.KeyboardInputService');
|
||||
goog.require('blocklyApp.TranslatePipe');
|
||||
goog.require('blocklyApp.TreeService');
|
||||
goog.require('blocklyApp.VariableModalService');
|
||||
|
||||
goog.require('Blockly.CommonModal');
|
||||
|
||||
|
||||
blocklyApp.VariableRemoveModalComponent = ng.core.Component({
|
||||
selector: 'blockly-remove-variable-modal',
|
||||
template: `
|
||||
<div *ngIf="modalIsVisible"class="blocklyModalCurtain"
|
||||
(click)="dismissModal()">
|
||||
<!-- $event.stopPropagation() prevents the modal from closing when its
|
||||
interior is clicked. -->
|
||||
<div id="varModal" class="blocklyModal" role="alertdialog"
|
||||
(click)="$event.stopPropagation()" tabindex="0"
|
||||
aria-labelledby="variableModalHeading">
|
||||
<h3 id="variableModalHeading">
|
||||
Delete {{getNumVariables()}} uses of the "{{currentVariableName}}"
|
||||
variable?
|
||||
</h3>
|
||||
|
||||
<form id="varForm">
|
||||
<hr>
|
||||
<button type="button" id="yesButton" (click)="submit()">
|
||||
YES
|
||||
</button>
|
||||
<button type="button" id="noButton" (click)="dismissModal()">
|
||||
NO
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
pipes: [blocklyApp.TranslatePipe]
|
||||
})
|
||||
.Class({
|
||||
constructor: [
|
||||
blocklyApp.AudioService,
|
||||
blocklyApp.KeyboardInputService,
|
||||
blocklyApp.TreeService,
|
||||
blocklyApp.VariableModalService,
|
||||
function(audioService, keyboardService, treeService, variableService) {
|
||||
this.workspace = blocklyApp.workspace;
|
||||
this.treeService = treeService;
|
||||
this.variableModalService = variableService;
|
||||
this.audioService = audioService;
|
||||
this.keyboardInputService = keyboardService
|
||||
this.modalIsVisible = false;
|
||||
this.activeButtonIndex = -1;
|
||||
this.currentVariableName = "";
|
||||
this.count = 0;
|
||||
|
||||
var that = this;
|
||||
this.variableModalService.registerPreRemoveShowHook(
|
||||
function(name, count) {
|
||||
that.currentVariableName = name;
|
||||
that.count = count
|
||||
that.modalIsVisible = true;
|
||||
|
||||
Blockly.CommonModal.setupKeyboardOverrides(that);
|
||||
|
||||
setTimeout(function() {
|
||||
document.getElementById('varModal').focus();
|
||||
}, 150);
|
||||
}
|
||||
);
|
||||
}
|
||||
],
|
||||
// Closes the modal (on both success and failure).
|
||||
hideModal_: Blockly.CommonModal.hideModal,
|
||||
// Focuses on the button represented by the given index.
|
||||
focusOnOption: Blockly.CommonModal.focusOnOption,
|
||||
// Counts the number of interactive elements for the modal.
|
||||
numInteractiveElements: Blockly.CommonModal.numInteractiveElements,
|
||||
// Gets all the interactive elements for the modal.
|
||||
getInteractiveElements: Blockly.CommonModal.getInteractiveElements,
|
||||
// Gets the container with interactive elements.
|
||||
getInteractiveContainer: function() {
|
||||
return document.getElementById("varForm");
|
||||
},
|
||||
getNumVariables: function() {
|
||||
return this.variableModalService.getNumVariables(this.currentVariableName);
|
||||
},
|
||||
// Submits the name change for the variable.
|
||||
submit: function() {
|
||||
var variable = blocklyApp.workspace.getVariable(this.currentVariableName);
|
||||
blocklyApp.workspace.deleteVariableInternal_(variable);
|
||||
this.dismissModal();
|
||||
},
|
||||
// Dismisses and closes the modal.
|
||||
dismissModal: function() {
|
||||
this.variableModalService.hideModal();
|
||||
this.hideModal_();
|
||||
}
|
||||
})
|
||||
+30
-73
@@ -18,13 +18,22 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Component representing the variable modal.
|
||||
* @fileoverview Component representing the variable rename modal.
|
||||
*
|
||||
* @author corydiers@google.com (Cory Diers)
|
||||
*/
|
||||
|
||||
blocklyApp.VariableModalComponent = ng.core.Component({
|
||||
selector: 'blockly-variable-modal',
|
||||
goog.provide('blocklyApp.VariableRenameModalComponent');
|
||||
|
||||
goog.require('Blockly.CommonModal');
|
||||
goog.require('blocklyApp.AudioService');
|
||||
goog.require('blocklyApp.KeyboardInputService');
|
||||
goog.require('blocklyApp.TranslatePipe');
|
||||
goog.require('blocklyApp.VariableModalService');
|
||||
|
||||
|
||||
blocklyApp.VariableRenameModalComponent = ng.core.Component({
|
||||
selector: 'blockly-rename-variable-modal',
|
||||
template: `
|
||||
<div *ngIf="modalIsVisible"class="blocklyModalCurtain"
|
||||
(click)="dismissModal()">
|
||||
@@ -33,6 +42,10 @@ blocklyApp.VariableModalComponent = ng.core.Component({
|
||||
<div id="varModal" class="blocklyModal" role="alertdialog"
|
||||
(click)="$event.stopPropagation()" tabindex="0"
|
||||
aria-labelledby="variableModalHeading">
|
||||
<h3 id="variableModalHeading">
|
||||
Rename the "{{currentVariableName}}" variable...
|
||||
</h3>
|
||||
|
||||
<form id="varForm">
|
||||
<p id="inputLabel">New Variable Name:
|
||||
<input id="mainFieldId" type="text" [ngModel]="VALUE"
|
||||
@@ -40,10 +53,10 @@ blocklyApp.VariableModalComponent = ng.core.Component({
|
||||
aria-labelledby="inputLabel" />
|
||||
</p>
|
||||
<hr>
|
||||
<button id="submitButton" (click)="submit()">
|
||||
<button type="button" id="submitButton" (click)="submit()">
|
||||
SUBMIT
|
||||
</button>
|
||||
<button id="cancelButton" (click)="dismissModal()">
|
||||
<button type="button" id="cancelButton" (click)="dismissModal()">
|
||||
CANCEL
|
||||
</button>
|
||||
</form>
|
||||
@@ -65,52 +78,15 @@ blocklyApp.VariableModalComponent = ng.core.Component({
|
||||
this.currentVariableName = "";
|
||||
|
||||
var that = this;
|
||||
this.variableModalService.registerPreShowHook(
|
||||
this.variableModalService.registerPreRenameShowHook(
|
||||
function(oldName) {
|
||||
that.currentVariableName = oldName;
|
||||
that.modalIsVisible = true;
|
||||
|
||||
that.keyboardInputService.setOverride({
|
||||
// Tab key: navigates to the previous or next item in the list.
|
||||
'9': function(evt) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
|
||||
if (evt.shiftKey) {
|
||||
// Move to the previous item in the list.
|
||||
if (that.activeButtonIndex <= 0) {
|
||||
that.activeActionButtonIndex = 0;
|
||||
that.audioService.playOopsSound();
|
||||
} else {
|
||||
that.activeButtonIndex--;
|
||||
}
|
||||
} else {
|
||||
// Move to the next item in the list.
|
||||
if (that.activeButtonIndex == that.numInteractiveElements() - 1) {
|
||||
that.audioService.playOopsSound();
|
||||
} else {
|
||||
that.activeButtonIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
that.focusOnOption(that.activeButtonIndex);
|
||||
},
|
||||
// Escape key: closes the modal.
|
||||
'27': function() {
|
||||
that.dismissModal();
|
||||
},
|
||||
// Up key: no-op.
|
||||
'38': function(evt) {
|
||||
evt.preventDefault();
|
||||
},
|
||||
// Down key: no-op.
|
||||
'40': function(evt) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
});
|
||||
Blockly.CommonModal.setupKeyboardOverrides(that);
|
||||
|
||||
setTimeout(function() {
|
||||
document.getElementById('mainFieldId').focus();
|
||||
document.getElementById('varModal').focus();
|
||||
}, 150);
|
||||
}
|
||||
);
|
||||
@@ -121,44 +97,25 @@ blocklyApp.VariableModalComponent = ng.core.Component({
|
||||
this.variableName = newValue;
|
||||
},
|
||||
// Closes the modal (on both success and failure).
|
||||
hideModal_: function() {
|
||||
this.modalIsVisible = false;
|
||||
this.keyboardInputService.clearOverride();
|
||||
},
|
||||
hideModal_: Blockly.CommonModal.hideModal,
|
||||
// Focuses on the button represented by the given index.
|
||||
focusOnOption: function(index) {
|
||||
var elements = this.getInteractiveElements();
|
||||
var button = elements[index];
|
||||
button.focus();
|
||||
},
|
||||
focusOnOption: Blockly.CommonModal.focusOnOption,
|
||||
// Counts the number of interactive elements for the modal.
|
||||
numInteractiveElements: function() {
|
||||
var elements = this.getInteractiveElements();
|
||||
return elements.length;
|
||||
},
|
||||
numInteractiveElements: Blockly.CommonModal.numInteractiveElements,
|
||||
// Gets all the interactive elements for the modal.
|
||||
getInteractiveElements: function() {
|
||||
return Array.prototype.filter.call(
|
||||
document.getElementById("varForm").elements, function(element) {
|
||||
if (element.type === 'hidden') {
|
||||
return false;
|
||||
}
|
||||
if (element.disabled) {
|
||||
return false;
|
||||
}
|
||||
if (element.tabIndex < 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
getInteractiveElements: Blockly.CommonModal.getInteractiveElements,
|
||||
// Gets the container with interactive elements.
|
||||
getInteractiveContainer: function() {
|
||||
return document.getElementById("varForm");
|
||||
},
|
||||
// Submits the name change for the variable.
|
||||
submit: function() {
|
||||
this.workspace.renameVariable(this.currentVariableName, this.variableName);
|
||||
this.hideModal_();
|
||||
this.dismissModal();
|
||||
},
|
||||
// Dismisses and closes the modal.
|
||||
dismissModal: function() {
|
||||
this.variableModalService.hideModal();
|
||||
this.hideModal_();
|
||||
}
|
||||
})
|
||||
@@ -23,6 +23,17 @@
|
||||
* @author madeeha@google.com (Madeeha Ghori)
|
||||
*/
|
||||
|
||||
goog.provide('blocklyApp.WorkspaceBlockComponent');
|
||||
|
||||
goog.require('blocklyApp.UtilsService');
|
||||
|
||||
goog.require('blocklyApp.AudioService');
|
||||
goog.require('blocklyApp.BlockConnectionService');
|
||||
goog.require('blocklyApp.FieldSegmentComponent');
|
||||
goog.require('blocklyApp.TranslatePipe');
|
||||
goog.require('blocklyApp.TreeService');
|
||||
|
||||
|
||||
blocklyApp.WorkspaceBlockComponent = ng.core.Component({
|
||||
selector: 'blockly-workspace-block',
|
||||
template: `
|
||||
|
||||
@@ -24,6 +24,16 @@
|
||||
* @author madeeha@google.com (Madeeha Ghori)
|
||||
*/
|
||||
|
||||
goog.provide('blocklyApp.WorkspaceComponent');
|
||||
|
||||
goog.require('blocklyApp.NotificationsService');
|
||||
goog.require('blocklyApp.ToolboxModalService');
|
||||
goog.require('blocklyApp.TranslatePipe');
|
||||
goog.require('blocklyApp.TreeService');
|
||||
|
||||
goog.require('blocklyApp.WorkspaceBlockComponent');
|
||||
|
||||
|
||||
blocklyApp.WorkspaceComponent = ng.core.Component({
|
||||
selector: 'blockly-workspace',
|
||||
template: `
|
||||
|
||||
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
+757
-605
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+320
-297
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
@@ -687,7 +687,7 @@ Blockly.Blocks['procedures_callnoreturn'] = {
|
||||
// Block is deleted or is in a flyout.
|
||||
return;
|
||||
}
|
||||
if (event.type == Blockly.Events.CREATE &&
|
||||
if (event.type == Blockly.Events.BLOCK_CREATE &&
|
||||
event.ids.indexOf(this.id) != -1) {
|
||||
// Look for the case where a procedure call was created (usually through
|
||||
// paste) and there is no matching definition. In this case, create
|
||||
@@ -730,7 +730,7 @@ Blockly.Blocks['procedures_callnoreturn'] = {
|
||||
Blockly.Xml.domToWorkspace(xml, this.workspace);
|
||||
Blockly.Events.setGroup(false);
|
||||
}
|
||||
} else if (event.type == Blockly.Events.DELETE) {
|
||||
} else if (event.type == Blockly.Events.BLOCK_DELETE) {
|
||||
// Look for the case where a procedure definition has been deleted,
|
||||
// leaving this block (a procedure call) orphaned. In this case, delete
|
||||
// the orphan.
|
||||
|
||||
+410
-369
@@ -57,377 +57,148 @@ Blockly.defineBlocksWithJsonArray([ // BEGIN JSON EXTRACT
|
||||
"text_quotes",
|
||||
"parent_tooltip_when_inline"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "text_join",
|
||||
"message0": "",
|
||||
"output": "String",
|
||||
"colour": "%{BKY_TEXTS_HUE}",
|
||||
"helpUrl": "%{BKY_TEXT_JOIN_HELPURL}",
|
||||
"tooltip": "%{BKY_TEXT_JOIN_TOOLTIP}",
|
||||
"mutator": "text_join_mutator"
|
||||
|
||||
},
|
||||
{
|
||||
"type": "text_create_join_container",
|
||||
"message0": "%{BKY_TEXT_CREATE_JOIN_TITLE_JOIN} %1 %2",
|
||||
"args0": [{
|
||||
"type": "input_dummy"
|
||||
},
|
||||
{
|
||||
"type": "input_statement",
|
||||
"name": "STACK"
|
||||
}],
|
||||
"colour": "%{BKY_TEXTS_HUE}",
|
||||
"tooltip": "%{BKY_TEXT_CREATE_JOIN_TOOLTIP}",
|
||||
"enableContextMenu": false
|
||||
},
|
||||
{
|
||||
"type": "text_create_join_item",
|
||||
"message0": "%{BKY_TEXT_CREATE_JOIN_ITEM_TITLE_ITEM}",
|
||||
"previousStatement": null,
|
||||
"nextStatement": null,
|
||||
"colour": "%{BKY_TEXTS_HUE}",
|
||||
"tooltip": "{%BKY_TEXT_CREATE_JOIN_ITEM_TOOLTIP}",
|
||||
"enableContextMenu": false
|
||||
},
|
||||
{
|
||||
"type": "text_append",
|
||||
"message0": "%{BKY_TEXT_APPEND_TITLE}",
|
||||
"args0": [{
|
||||
"type": "field_variable",
|
||||
"name": "VAR",
|
||||
"variable": "%{BKY_TEXT_APPEND_VARIABLE}"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "TEXT"
|
||||
}],
|
||||
"previousStatement": null,
|
||||
"nextStatement": null,
|
||||
"colour": "%{BKY_TEXTS_HUE}",
|
||||
"extensions": [
|
||||
"text_append_tooltip"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "text_length",
|
||||
"message0": "%{BKY_TEXT_LENGTH_TITLE}",
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "VALUE",
|
||||
"check": ['String', 'Array']
|
||||
}
|
||||
],
|
||||
"output": 'Number',
|
||||
"colour": "%{BKY_TEXTS_HUE}",
|
||||
"tooltip": "%{BKY_TEXT_LENGTH_TOOLTIP}",
|
||||
"helpUrl": "%{BKY_TEXT_LENGTH_HELPURL}"
|
||||
},
|
||||
{
|
||||
"type": "text_isEmpty",
|
||||
"message0": "%{BKY_TEXT_ISEMPTY_TITLE}",
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "VALUE",
|
||||
"check": ['String', 'Array']
|
||||
}
|
||||
],
|
||||
"output": 'Boolean',
|
||||
"colour": "%{BKY_TEXTS_HUE}",
|
||||
"tooltip": "%{BKY_TEXT_ISEMPTY_TOOLTIP}",
|
||||
"helpUrl": "%{BKY_TEXT_ISEMPTY_HELPURL}"
|
||||
},
|
||||
{
|
||||
"type": "text_indexOf",
|
||||
"message0": "%{BKY_TEXT_INDEXOF_TITLE}",
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "VALUE",
|
||||
"check": "String"
|
||||
},
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "END",
|
||||
"options": [
|
||||
[
|
||||
"%{BKY_TEXT_INDEXOF_OPERATOR_FIRST}",
|
||||
"FIRST"
|
||||
],
|
||||
[
|
||||
"%{BKY_TEXT_INDEXOF_OPERATOR_LAST}",
|
||||
"LAST"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "FIND",
|
||||
"check": "String"
|
||||
}
|
||||
],
|
||||
"output": "Number",
|
||||
"colour": "%{BKY_TEXTS_HUE}",
|
||||
"helpUrl": "%{BKY_TEXT_INDEXOF_HELPURL}",
|
||||
"inputsInline": true,
|
||||
"extensions": [
|
||||
"text_indexOf_tooltip"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "text_charAt",
|
||||
"message0": "%{BKY_TEXT_CHARAT_TITLE}", // "in text %1 %2"
|
||||
"args0": [
|
||||
{
|
||||
"type":"input_value",
|
||||
"name": "VALUE",
|
||||
"check": "String"
|
||||
},
|
||||
{
|
||||
"type": "input_dummy",
|
||||
"name": "AT"
|
||||
}
|
||||
],
|
||||
"output": "String",
|
||||
"colour": "%{BKY_TEXTS_HUE}",
|
||||
"helpUrl": "%{BKY_TEXT_CHARAT_HELPURL}",
|
||||
"inputsInline": true,
|
||||
"mutator": "text_charAt_mutator"
|
||||
}
|
||||
]); // END JSON EXTRACT (Do not delete this comment.)
|
||||
|
||||
/** Wraps TEXT field with images of double quote characters. */
|
||||
Blockly.Constants.Text.textQuotesExtension = function() {
|
||||
this.mixin(Blockly.Constants.Text.QUOTE_IMAGE_MIXIN);
|
||||
this.quoteField_('TEXT');
|
||||
};
|
||||
|
||||
Blockly.Extensions.register('text_quotes',
|
||||
Blockly.Constants.Text.textQuotesExtension);
|
||||
|
||||
Blockly.Blocks['text_join'] = {
|
||||
/**
|
||||
* Block for creating a string made up of any number of elements of any type.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.setHelpUrl(Blockly.Msg.TEXT_JOIN_HELPURL);
|
||||
this.setColour(Blockly.Blocks.texts.HUE);
|
||||
this.itemCount_ = 2;
|
||||
this.mixin(Blockly.Constants.Text.QUOTE_IMAGE_MIXIN);
|
||||
this.updateShape_();
|
||||
this.setOutput(true, 'String');
|
||||
this.setMutator(new Blockly.Mutator(['text_create_join_item']));
|
||||
this.setTooltip(Blockly.Msg.TEXT_JOIN_TOOLTIP);
|
||||
},
|
||||
/**
|
||||
* Create XML to represent number of text inputs.
|
||||
* @return {!Element} XML storage element.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
mutationToDom: function() {
|
||||
var container = document.createElement('mutation');
|
||||
container.setAttribute('items', this.itemCount_);
|
||||
return container;
|
||||
},
|
||||
/**
|
||||
* Parse XML to restore the text inputs.
|
||||
* @param {!Element} xmlElement XML storage element.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
domToMutation: function(xmlElement) {
|
||||
this.itemCount_ = parseInt(xmlElement.getAttribute('items'), 10);
|
||||
this.updateShape_();
|
||||
},
|
||||
/**
|
||||
* Populate the mutator's dialog with this block's components.
|
||||
* @param {!Blockly.Workspace} workspace Mutator's workspace.
|
||||
* @return {!Blockly.Block} Root block in mutator.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
decompose: function(workspace) {
|
||||
var containerBlock = workspace.newBlock('text_create_join_container');
|
||||
containerBlock.initSvg();
|
||||
var connection = containerBlock.getInput('STACK').connection;
|
||||
for (var i = 0; i < this.itemCount_; i++) {
|
||||
var itemBlock = workspace.newBlock('text_create_join_item');
|
||||
itemBlock.initSvg();
|
||||
connection.connect(itemBlock.previousConnection);
|
||||
connection = itemBlock.nextConnection;
|
||||
}
|
||||
return containerBlock;
|
||||
},
|
||||
/**
|
||||
* Reconfigure this block based on the mutator dialog's components.
|
||||
* @param {!Blockly.Block} containerBlock Root block in mutator.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
compose: function(containerBlock) {
|
||||
var itemBlock = containerBlock.getInputTargetBlock('STACK');
|
||||
// Count number of inputs.
|
||||
var connections = [];
|
||||
while (itemBlock) {
|
||||
connections.push(itemBlock.valueConnection_);
|
||||
itemBlock = itemBlock.nextConnection &&
|
||||
itemBlock.nextConnection.targetBlock();
|
||||
}
|
||||
// Disconnect any children that don't belong.
|
||||
for (var i = 0; i < this.itemCount_; i++) {
|
||||
var connection = this.getInput('ADD' + i).connection.targetConnection;
|
||||
if (connection && connections.indexOf(connection) == -1) {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
this.itemCount_ = connections.length;
|
||||
this.updateShape_();
|
||||
// Reconnect any child blocks.
|
||||
for (var i = 0; i < this.itemCount_; i++) {
|
||||
Blockly.Mutator.reconnect(connections[i], this, 'ADD' + i);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Store pointers to any connected child blocks.
|
||||
* @param {!Blockly.Block} containerBlock Root block in mutator.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
saveConnections: function(containerBlock) {
|
||||
var itemBlock = containerBlock.getInputTargetBlock('STACK');
|
||||
var i = 0;
|
||||
while (itemBlock) {
|
||||
var input = this.getInput('ADD' + i);
|
||||
itemBlock.valueConnection_ = input && input.connection.targetConnection;
|
||||
i++;
|
||||
itemBlock = itemBlock.nextConnection &&
|
||||
itemBlock.nextConnection.targetBlock();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Modify this block to have the correct number of inputs.
|
||||
* @private
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
updateShape_: function() {
|
||||
if (this.itemCount_ && this.getInput('EMPTY')) {
|
||||
this.removeInput('EMPTY');
|
||||
} else if (!this.itemCount_ && !this.getInput('EMPTY')) {
|
||||
this.appendDummyInput('EMPTY')
|
||||
.appendField(this.newQuote_(true))
|
||||
.appendField(this.newQuote_(false));
|
||||
}
|
||||
// Add new inputs.
|
||||
for (var i = 0; i < this.itemCount_; i++) {
|
||||
if (!this.getInput('ADD' + i)) {
|
||||
var input = this.appendValueInput('ADD' + i);
|
||||
if (i == 0) {
|
||||
input.appendField(Blockly.Msg.TEXT_JOIN_TITLE_CREATEWITH);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Remove deleted inputs.
|
||||
while (this.getInput('ADD' + i)) {
|
||||
this.removeInput('ADD' + i);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['text_create_join_container'] = {
|
||||
/**
|
||||
* Mutator block for container.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.setColour(Blockly.Blocks.texts.HUE);
|
||||
this.appendDummyInput()
|
||||
.appendField(Blockly.Msg.TEXT_CREATE_JOIN_TITLE_JOIN);
|
||||
this.appendStatementInput('STACK');
|
||||
this.setTooltip(Blockly.Msg.TEXT_CREATE_JOIN_TOOLTIP);
|
||||
this.contextMenu = false;
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['text_create_join_item'] = {
|
||||
/**
|
||||
* Mutator block for add items.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.setColour(Blockly.Blocks.texts.HUE);
|
||||
this.appendDummyInput()
|
||||
.appendField(Blockly.Msg.TEXT_CREATE_JOIN_ITEM_TITLE_ITEM);
|
||||
this.setPreviousStatement(true);
|
||||
this.setNextStatement(true);
|
||||
this.setTooltip(Blockly.Msg.TEXT_CREATE_JOIN_ITEM_TOOLTIP);
|
||||
this.contextMenu = false;
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['text_append'] = {
|
||||
/**
|
||||
* Block for appending to a variable in place.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.setHelpUrl(Blockly.Msg.TEXT_APPEND_HELPURL);
|
||||
this.setColour(Blockly.Blocks.texts.HUE);
|
||||
this.appendValueInput('TEXT')
|
||||
.appendField(Blockly.Msg.TEXT_APPEND_TO)
|
||||
.appendField(new Blockly.FieldVariable(
|
||||
Blockly.Msg.TEXT_APPEND_VARIABLE), 'VAR')
|
||||
.appendField(Blockly.Msg.TEXT_APPEND_APPENDTEXT);
|
||||
this.setPreviousStatement(true);
|
||||
this.setNextStatement(true);
|
||||
// Assign 'this' to a variable for use in the tooltip closure below.
|
||||
var thisBlock = this;
|
||||
this.setTooltip(function() {
|
||||
return Blockly.Msg.TEXT_APPEND_TOOLTIP.replace('%1',
|
||||
thisBlock.getFieldValue('VAR'));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['text_length'] = {
|
||||
/**
|
||||
* Block for string length.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.TEXT_LENGTH_TITLE,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "VALUE",
|
||||
"check": ['String', 'Array']
|
||||
}
|
||||
],
|
||||
"output": 'Number',
|
||||
"colour": Blockly.Blocks.texts.HUE,
|
||||
"tooltip": Blockly.Msg.TEXT_LENGTH_TOOLTIP,
|
||||
"helpUrl": Blockly.Msg.TEXT_LENGTH_HELPURL
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['text_isEmpty'] = {
|
||||
/**
|
||||
* Block for is the string null?
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.TEXT_ISEMPTY_TITLE,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "VALUE",
|
||||
"check": ['String', 'Array']
|
||||
}
|
||||
],
|
||||
"output": 'Boolean',
|
||||
"colour": Blockly.Blocks.texts.HUE,
|
||||
"tooltip": Blockly.Msg.TEXT_ISEMPTY_TOOLTIP,
|
||||
"helpUrl": Blockly.Msg.TEXT_ISEMPTY_HELPURL
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['text_indexOf'] = {
|
||||
/**
|
||||
* Block for finding a substring in the text.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
var OPERATORS = [
|
||||
[Blockly.Msg.TEXT_INDEXOF_OPERATOR_FIRST, 'FIRST'],
|
||||
[Blockly.Msg.TEXT_INDEXOF_OPERATOR_LAST, 'LAST']
|
||||
];
|
||||
this.setHelpUrl(Blockly.Msg.TEXT_INDEXOF_HELPURL);
|
||||
this.setColour(Blockly.Blocks.texts.HUE);
|
||||
this.setOutput(true, 'Number');
|
||||
this.appendValueInput('VALUE')
|
||||
.setCheck('String')
|
||||
.appendField(Blockly.Msg.TEXT_INDEXOF_INPUT_INTEXT);
|
||||
this.appendValueInput('FIND')
|
||||
.setCheck('String')
|
||||
.appendField(new Blockly.FieldDropdown(OPERATORS), 'END');
|
||||
if (Blockly.Msg.TEXT_INDEXOF_TAIL) {
|
||||
this.appendDummyInput().appendField(Blockly.Msg.TEXT_INDEXOF_TAIL);
|
||||
}
|
||||
this.setInputsInline(true);
|
||||
// Assign 'this' to a variable for use in the tooltip closure below.
|
||||
var thisBlock = this;
|
||||
this.setTooltip(function() {
|
||||
return Blockly.Msg.TEXT_INDEXOF_TOOLTIP.replace('%1',
|
||||
thisBlock.workspace.options.oneBasedIndex ? '0' : '-1');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['text_charAt'] = {
|
||||
/**
|
||||
* Block for getting a character from the string.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.WHERE_OPTIONS = [
|
||||
[Blockly.Msg.TEXT_CHARAT_FROM_START, 'FROM_START'],
|
||||
[Blockly.Msg.TEXT_CHARAT_FROM_END, 'FROM_END'],
|
||||
[Blockly.Msg.TEXT_CHARAT_FIRST, 'FIRST'],
|
||||
[Blockly.Msg.TEXT_CHARAT_LAST, 'LAST'],
|
||||
[Blockly.Msg.TEXT_CHARAT_RANDOM, 'RANDOM']
|
||||
];
|
||||
this.setHelpUrl(Blockly.Msg.TEXT_CHARAT_HELPURL);
|
||||
this.setColour(Blockly.Blocks.texts.HUE);
|
||||
this.setOutput(true, 'String');
|
||||
this.appendValueInput('VALUE')
|
||||
.setCheck('String')
|
||||
.appendField(Blockly.Msg.TEXT_CHARAT_INPUT_INTEXT);
|
||||
this.appendDummyInput('AT');
|
||||
this.setInputsInline(true);
|
||||
this.updateAt_(true);
|
||||
// Assign 'this' to a variable for use in the tooltip closure below.
|
||||
var thisBlock = this;
|
||||
this.setTooltip(function() {
|
||||
var where = thisBlock.getFieldValue('WHERE');
|
||||
var tooltip = Blockly.Msg.TEXT_CHARAT_TOOLTIP;
|
||||
if (where == 'FROM_START' || where == 'FROM_END') {
|
||||
var msg = (where == 'FROM_START') ?
|
||||
Blockly.Msg.LISTS_INDEX_FROM_START_TOOLTIP :
|
||||
Blockly.Msg.LISTS_INDEX_FROM_END_TOOLTIP;
|
||||
tooltip += ' ' + msg.replace('%1',
|
||||
thisBlock.workspace.options.oneBasedIndex ? '#1' : '#0');
|
||||
}
|
||||
return tooltip;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Create XML to represent whether there is an 'AT' input.
|
||||
* @return {!Element} XML storage element.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
mutationToDom: function() {
|
||||
var container = document.createElement('mutation');
|
||||
var isAt = this.getInput('AT').type == Blockly.INPUT_VALUE;
|
||||
container.setAttribute('at', isAt);
|
||||
return container;
|
||||
},
|
||||
/**
|
||||
* Parse XML to restore the 'AT' input.
|
||||
* @param {!Element} xmlElement XML storage element.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
domToMutation: function(xmlElement) {
|
||||
// Note: Until January 2013 this block did not have mutations,
|
||||
// so 'at' defaults to true.
|
||||
var isAt = (xmlElement.getAttribute('at') != 'false');
|
||||
this.updateAt_(isAt);
|
||||
},
|
||||
/**
|
||||
* Create or delete an input for the numeric index.
|
||||
* @param {boolean} isAt True if the input should exist.
|
||||
* @private
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
updateAt_: function(isAt) {
|
||||
// Destroy old 'AT' and 'ORDINAL' inputs.
|
||||
this.removeInput('AT');
|
||||
this.removeInput('ORDINAL', true);
|
||||
// Create either a value 'AT' input or a dummy input.
|
||||
if (isAt) {
|
||||
this.appendValueInput('AT').setCheck('Number');
|
||||
if (Blockly.Msg.ORDINAL_NUMBER_SUFFIX) {
|
||||
this.appendDummyInput('ORDINAL')
|
||||
.appendField(Blockly.Msg.ORDINAL_NUMBER_SUFFIX);
|
||||
}
|
||||
} else {
|
||||
this.appendDummyInput('AT');
|
||||
}
|
||||
if (Blockly.Msg.TEXT_CHARAT_TAIL) {
|
||||
this.removeInput('TAIL', true);
|
||||
this.appendDummyInput('TAIL')
|
||||
.appendField(Blockly.Msg.TEXT_CHARAT_TAIL);
|
||||
}
|
||||
var menu = new Blockly.FieldDropdown(this.WHERE_OPTIONS, function(value) {
|
||||
var newAt = (value == 'FROM_START') || (value == 'FROM_END');
|
||||
// The 'isAt' variable is available due to this function being a closure.
|
||||
if (newAt != isAt) {
|
||||
var block = this.sourceBlock_;
|
||||
block.updateAt_(newAt);
|
||||
// This menu has been destroyed and replaced. Update the replacement.
|
||||
block.setFieldValue(value, 'WHERE');
|
||||
return null;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
this.getInput('AT').appendField(menu, 'WHERE');
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['text_getSubstring'] = {
|
||||
/**
|
||||
* Block for getting substring.
|
||||
@@ -717,7 +488,7 @@ Blockly.Blocks['text_count'] = {
|
||||
],
|
||||
"output": "Number",
|
||||
"inputsInline": true,
|
||||
"colour": Blockly.Blocks.math.HUE,
|
||||
"colour": Blockly.Blocks.texts.HUE,
|
||||
"tooltip": Blockly.Msg.TEXT_COUNT_TOOLTIP,
|
||||
"helpUrl": Blockly.Msg.TEXT_COUNT_HELPURL
|
||||
});
|
||||
@@ -849,3 +620,273 @@ Blockly.Constants.Text.QUOTE_IMAGE_MIXIN = {
|
||||
}
|
||||
};
|
||||
|
||||
/** Wraps TEXT field with images of double quote characters. */
|
||||
Blockly.Constants.Text.TEXT_QUOTES_EXTENSION = function() {
|
||||
this.mixin(Blockly.Constants.Text.QUOTE_IMAGE_MIXIN);
|
||||
this.quoteField_('TEXT');
|
||||
};
|
||||
|
||||
/**
|
||||
* Mixin for mutator functions in the 'text_join_mutator' extension.
|
||||
* @mixin
|
||||
* @augments Blockly.Block
|
||||
* @package
|
||||
*/
|
||||
Blockly.Constants.Text.TEXT_JOIN_MUTATOR_MIXIN = {
|
||||
/**
|
||||
* Create XML to represent number of text inputs.
|
||||
* @return {!Element} XML storage element.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
mutationToDom: function() {
|
||||
var container = document.createElement('mutation');
|
||||
container.setAttribute('items', this.itemCount_);
|
||||
return container;
|
||||
},
|
||||
/**
|
||||
* Parse XML to restore the text inputs.
|
||||
* @param {!Element} xmlElement XML storage element.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
domToMutation: function(xmlElement) {
|
||||
this.itemCount_ = parseInt(xmlElement.getAttribute('items'), 10);
|
||||
this.updateShape_();
|
||||
},
|
||||
/**
|
||||
* Populate the mutator's dialog with this block's components.
|
||||
* @param {!Blockly.Workspace} workspace Mutator's workspace.
|
||||
* @return {!Blockly.Block} Root block in mutator.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
decompose: function(workspace) {
|
||||
var containerBlock = workspace.newBlock('text_create_join_container');
|
||||
containerBlock.initSvg();
|
||||
var connection = containerBlock.getInput('STACK').connection;
|
||||
for (var i = 0; i < this.itemCount_; i++) {
|
||||
var itemBlock = workspace.newBlock('text_create_join_item');
|
||||
itemBlock.initSvg();
|
||||
connection.connect(itemBlock.previousConnection);
|
||||
connection = itemBlock.nextConnection;
|
||||
}
|
||||
return containerBlock;
|
||||
},
|
||||
/**
|
||||
* Reconfigure this block based on the mutator dialog's components.
|
||||
* @param {!Blockly.Block} containerBlock Root block in mutator.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
compose: function(containerBlock) {
|
||||
var itemBlock = containerBlock.getInputTargetBlock('STACK');
|
||||
// Count number of inputs.
|
||||
var connections = [];
|
||||
while (itemBlock) {
|
||||
connections.push(itemBlock.valueConnection_);
|
||||
itemBlock = itemBlock.nextConnection &&
|
||||
itemBlock.nextConnection.targetBlock();
|
||||
}
|
||||
// Disconnect any children that don't belong.
|
||||
for (var i = 0; i < this.itemCount_; i++) {
|
||||
var connection = this.getInput('ADD' + i).connection.targetConnection;
|
||||
if (connection && connections.indexOf(connection) == -1) {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
this.itemCount_ = connections.length;
|
||||
this.updateShape_();
|
||||
// Reconnect any child blocks.
|
||||
for (var i = 0; i < this.itemCount_; i++) {
|
||||
Blockly.Mutator.reconnect(connections[i], this, 'ADD' + i);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Store pointers to any connected child blocks.
|
||||
* @param {!Blockly.Block} containerBlock Root block in mutator.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
saveConnections: function(containerBlock) {
|
||||
var itemBlock = containerBlock.getInputTargetBlock('STACK');
|
||||
var i = 0;
|
||||
while (itemBlock) {
|
||||
var input = this.getInput('ADD' + i);
|
||||
itemBlock.valueConnection_ = input && input.connection.targetConnection;
|
||||
i++;
|
||||
itemBlock = itemBlock.nextConnection &&
|
||||
itemBlock.nextConnection.targetBlock();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Modify this block to have the correct number of inputs.
|
||||
* @private
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
updateShape_: function() {
|
||||
if (this.itemCount_ && this.getInput('EMPTY')) {
|
||||
this.removeInput('EMPTY');
|
||||
} else if (!this.itemCount_ && !this.getInput('EMPTY')) {
|
||||
this.appendDummyInput('EMPTY')
|
||||
.appendField(this.newQuote_(true))
|
||||
.appendField(this.newQuote_(false));
|
||||
}
|
||||
// Add new inputs.
|
||||
for (var i = 0; i < this.itemCount_; i++) {
|
||||
if (!this.getInput('ADD' + i)) {
|
||||
var input = this.appendValueInput('ADD' + i);
|
||||
if (i == 0) {
|
||||
input.appendField(Blockly.Msg.TEXT_JOIN_TITLE_CREATEWITH);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Remove deleted inputs.
|
||||
while (this.getInput('ADD' + i)) {
|
||||
this.removeInput('ADD' + i);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Performs final setup of a text_join block.
|
||||
Blockly.Constants.Text.TEXT_JOIN_EXTENSION = function() {
|
||||
// Add the quote mixin for the itemCount_ = 0 case.
|
||||
this.mixin(Blockly.Constants.Text.QUOTE_IMAGE_MIXIN);
|
||||
// initialize the mutator values
|
||||
this.itemCount_ = 2;
|
||||
this.updateShape_();
|
||||
// Configure the mutator ui
|
||||
this.setMutator(new Blockly.Mutator(['text_create_join_item']));
|
||||
};
|
||||
|
||||
Blockly.Constants.Text.TEXT_APPEND_TOOLTIP_EXTENSION = function() {
|
||||
// Assign 'this' to a variable for use in the tooltip closure below.
|
||||
var thisBlock = this;
|
||||
this.setTooltip(function() {
|
||||
if (Blockly.Msg.TEXT_APPEND_TOOLTIP) {
|
||||
return Blockly.Msg.TEXT_APPEND_TOOLTIP.replace('%1',
|
||||
thisBlock.getFieldValue('VAR'));
|
||||
}
|
||||
return '';
|
||||
});
|
||||
};
|
||||
|
||||
Blockly.Constants.Text.TEXT_INDEXOF_TOOLTIP_EXTENSION = function() {
|
||||
// Assign 'this' to a variable for use in the tooltip closure below.
|
||||
var thisBlock = this;
|
||||
this.setTooltip(function() {
|
||||
return Blockly.Msg.TEXT_INDEXOF_TOOLTIP.replace('%1',
|
||||
thisBlock.workspace.options.oneBasedIndex ? '0' : '-1');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Mixin for mutator functions in the 'text_charAt_mutator' extension.
|
||||
* @mixin
|
||||
* @augments Blockly.Block
|
||||
* @package
|
||||
*/
|
||||
Blockly.Constants.Text.TEXT_CHARAT_MUTATOR_MIXIN = {
|
||||
/**
|
||||
* Create XML to represent whether there is an 'AT' input.
|
||||
* @return {!Element} XML storage element.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
mutationToDom: function() {
|
||||
var container = document.createElement('mutation');
|
||||
var isAt = this.getInput('AT').type == Blockly.INPUT_VALUE;
|
||||
container.setAttribute('at', isAt);
|
||||
return container;
|
||||
},
|
||||
/**
|
||||
* Parse XML to restore the 'AT' input.
|
||||
* @param {!Element} xmlElement XML storage element.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
domToMutation: function(xmlElement) {
|
||||
// Note: Until January 2013 this block did not have mutations,
|
||||
// so 'at' defaults to true.
|
||||
var isAt = (xmlElement.getAttribute('at') != 'false');
|
||||
this.updateAt_(isAt);
|
||||
},
|
||||
/**
|
||||
* Create or delete an input for the numeric index.
|
||||
* @param {boolean} isAt True if the input should exist.
|
||||
* @private
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
updateAt_: function(isAt) {
|
||||
// Destroy old 'AT' and 'ORDINAL' inputs.
|
||||
this.removeInput('AT');
|
||||
this.removeInput('ORDINAL', true);
|
||||
// Create either a value 'AT' input or a dummy input.
|
||||
if (isAt) {
|
||||
this.appendValueInput('AT').setCheck('Number');
|
||||
if (Blockly.Msg.ORDINAL_NUMBER_SUFFIX) {
|
||||
this.appendDummyInput('ORDINAL')
|
||||
.appendField(Blockly.Msg.ORDINAL_NUMBER_SUFFIX);
|
||||
}
|
||||
} else {
|
||||
this.appendDummyInput('AT');
|
||||
}
|
||||
if (Blockly.Msg.TEXT_CHARAT_TAIL) {
|
||||
this.removeInput('TAIL', true);
|
||||
this.appendDummyInput('TAIL')
|
||||
.appendField(Blockly.Msg.TEXT_CHARAT_TAIL);
|
||||
}
|
||||
var menu = new Blockly.FieldDropdown(this.WHERE_OPTIONS, function(value) {
|
||||
var newAt = (value == 'FROM_START') || (value == 'FROM_END');
|
||||
// The 'isAt' variable is available due to this function being a closure.
|
||||
if (newAt != isAt) {
|
||||
var block = this.sourceBlock_;
|
||||
block.updateAt_(newAt);
|
||||
// This menu has been destroyed and replaced. Update the replacement.
|
||||
block.setFieldValue(value, 'WHERE');
|
||||
return null;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
this.getInput('AT').appendField(menu, 'WHERE');
|
||||
}
|
||||
};
|
||||
|
||||
// Does the initial mutator update of text_charAt and adds the tooltip
|
||||
Blockly.Constants.Text.TEXT_CHARAT_EXTENSION = function() {
|
||||
this.WHERE_OPTIONS = [
|
||||
[Blockly.Msg.TEXT_CHARAT_FROM_START, 'FROM_START'],
|
||||
[Blockly.Msg.TEXT_CHARAT_FROM_END, 'FROM_END'],
|
||||
[Blockly.Msg.TEXT_CHARAT_FIRST, 'FIRST'],
|
||||
[Blockly.Msg.TEXT_CHARAT_LAST, 'LAST'],
|
||||
[Blockly.Msg.TEXT_CHARAT_RANDOM, 'RANDOM']
|
||||
];
|
||||
this.updateAt_(true);
|
||||
// Assign 'this' to a variable for use in the tooltip closure below.
|
||||
var thisBlock = this;
|
||||
this.setTooltip(function() {
|
||||
var where = thisBlock.getFieldValue('WHERE');
|
||||
var tooltip = Blockly.Msg.TEXT_CHARAT_TOOLTIP;
|
||||
if (where == 'FROM_START' || where == 'FROM_END') {
|
||||
var msg = (where == 'FROM_START') ?
|
||||
Blockly.Msg.LISTS_INDEX_FROM_START_TOOLTIP :
|
||||
Blockly.Msg.LISTS_INDEX_FROM_END_TOOLTIP;
|
||||
if (msg) {
|
||||
tooltip += ' ' + msg.replace('%1',
|
||||
thisBlock.workspace.options.oneBasedIndex ? '#1' : '#0');
|
||||
}
|
||||
}
|
||||
return tooltip;
|
||||
});
|
||||
};
|
||||
|
||||
Blockly.Extensions.register('text_indexOf_tooltip',
|
||||
Blockly.Constants.Text.TEXT_INDEXOF_TOOLTIP_EXTENSION);
|
||||
|
||||
Blockly.Extensions.register('text_quotes',
|
||||
Blockly.Constants.Text.TEXT_QUOTES_EXTENSION);
|
||||
|
||||
Blockly.Extensions.register('text_append_tooltip',
|
||||
Blockly.Constants.Text.TEXT_APPEND_TOOLTIP_EXTENSION);
|
||||
|
||||
Blockly.Extensions.registerMutator('text_join_mutator',
|
||||
Blockly.Constants.Text.TEXT_JOIN_MUTATOR_MIXIN,
|
||||
Blockly.Constants.Text.TEXT_JOIN_EXTENSION);
|
||||
|
||||
Blockly.Extensions.registerMutator('text_charAt_mutator',
|
||||
Blockly.Constants.Text.TEXT_CHARAT_MUTATOR_MIXIN,
|
||||
Blockly.Constants.Text.TEXT_CHARAT_EXTENSION);
|
||||
+46
-29
@@ -1,8 +1,25 @@
|
||||
// Do not edit this file; automatically generated by build.py.
|
||||
'use strict';
|
||||
|
||||
/*
|
||||
|
||||
// Copyright 2012 Google Inc. Apache License 2.0
|
||||
Visual Blocks Editor
|
||||
|
||||
Copyright 2012 Google Inc.
|
||||
https://developers.google.com/blockly/
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
Blockly.Blocks.colour={};Blockly.Constants={};Blockly.Constants.Colour={};Blockly.Constants.Colour.HUE=20;Blockly.Blocks.colour.HUE=Blockly.Constants.Colour.HUE;
|
||||
Blockly.defineBlocksWithJsonArray([{type:"colour_picker",message0:"%1",args0:[{type:"field_colour",name:"COLOUR",colour:"#ff0000"}],output:"Colour",colour:"%{BKY_COLOUR_HUE}",helpUrl:"%{BKY_COLOUR_PICKER_HELPURL}",tooltip:"%{BKY_COLOUR_PICKER_TOOLTIP}",extensions:["parent_tooltip_when_inline"]},{type:"colour_random",message0:"%{BKY_COLOUR_RANDOM_TITLE}",output:"Colour",colour:"%{BKY_COLOUR_HUE}",helpUrl:"%{BKY_COLOUR_RANDOM_HELPURL}",tooltip:"%{BKY_COLOUR_RANDOM_TOOLTIP}"},{type:"colour_rgb",message0:"%{BKY_COLOUR_RGB_TITLE} %{BKY_COLOUR_RGB_RED} %1 %{BKY_COLOUR_RGB_GREEN} %2 %{BKY_COLOUR_RGB_BLUE} %3",
|
||||
args0:[{type:"input_value",name:"RED",check:"Number",align:"RIGHT"},{type:"input_value",name:"GREEN",check:"Number",align:"RIGHT"},{type:"input_value",name:"BLUE",check:"Number",align:"RIGHT"}],output:"Colour",colour:"%{BKY_COLOUR_HUE}",helpUrl:"%{BKY_COLOUR_RGB_HELPURL}",tooltip:"%{BKY_COLOUR_RGB_TOOLTIP}"},{type:"colour_blend",message0:"%{BKY_COLOUR_BLEND_TITLE} %{BKY_COLOUR_BLEND_COLOUR1} %1 %{BKY_COLOUR_BLEND_COLOUR2} %2 %{BKY_COLOUR_BLEND_RATIO} %3",args0:[{type:"input_value",name:"COLOUR1",
|
||||
@@ -34,7 +51,7 @@ this.removeInput("ORDINAL",!0);a?(this.appendValueInput("AT").setCheck("Number")
|
||||
Blockly.Blocks.lists_getSublist={init:function(){this.WHERE_OPTIONS_1=[[Blockly.Msg.LISTS_GET_SUBLIST_START_FROM_START,"FROM_START"],[Blockly.Msg.LISTS_GET_SUBLIST_START_FROM_END,"FROM_END"],[Blockly.Msg.LISTS_GET_SUBLIST_START_FIRST,"FIRST"]];this.WHERE_OPTIONS_2=[[Blockly.Msg.LISTS_GET_SUBLIST_END_FROM_START,"FROM_START"],[Blockly.Msg.LISTS_GET_SUBLIST_END_FROM_END,"FROM_END"],[Blockly.Msg.LISTS_GET_SUBLIST_END_LAST,"LAST"]];this.setHelpUrl(Blockly.Msg.LISTS_GET_SUBLIST_HELPURL);this.setColour(Blockly.Blocks.lists.HUE);
|
||||
this.appendValueInput("LIST").setCheck("Array").appendField(Blockly.Msg.LISTS_GET_SUBLIST_INPUT_IN_LIST);this.appendDummyInput("AT1");this.appendDummyInput("AT2");Blockly.Msg.LISTS_GET_SUBLIST_TAIL&&this.appendDummyInput("TAIL").appendField(Blockly.Msg.LISTS_GET_SUBLIST_TAIL);this.setInputsInline(!0);this.setOutput(!0,"Array");this.updateAt_(1,!0);this.updateAt_(2,!0);this.setTooltip(Blockly.Msg.LISTS_GET_SUBLIST_TOOLTIP)},mutationToDom:function(){var a=document.createElement("mutation"),b=this.getInput("AT1").type==
|
||||
Blockly.INPUT_VALUE;a.setAttribute("at1",b);b=this.getInput("AT2").type==Blockly.INPUT_VALUE;a.setAttribute("at2",b);return a},domToMutation:function(a){var b="true"==a.getAttribute("at1");a="true"==a.getAttribute("at2");this.updateAt_(1,b);this.updateAt_(2,a)},updateAt_:function(a,b){this.removeInput("AT"+a);this.removeInput("ORDINAL"+a,!0);b?(this.appendValueInput("AT"+a).setCheck("Number"),Blockly.Msg.ORDINAL_NUMBER_SUFFIX&&this.appendDummyInput("ORDINAL"+a).appendField(Blockly.Msg.ORDINAL_NUMBER_SUFFIX)):
|
||||
this.appendDummyInput("AT"+a);var c=new Blockly.FieldDropdown(this["WHERE_OPTIONS_"+a],function(c){var e="FROM_START"==c||"FROM_END"==c;if(e!=b){var d=this.sourceBlock_;d.updateAt_(a,e);d.setFieldValue(c,"WHERE"+a);return null}});this.getInput("AT"+a).appendField(c,"WHERE"+a);1==a&&(this.moveInputBefore("AT1","AT2"),this.getInput("ORDINAL1")&&this.moveInputBefore("ORDINAL1","AT2"));Blockly.Msg.LISTS_GET_SUBLIST_TAIL&&this.moveInputBefore("TAIL",null)}};
|
||||
this.appendDummyInput("AT"+a);var c=new Blockly.FieldDropdown(this["WHERE_OPTIONS_"+a],function(c){var d="FROM_START"==c||"FROM_END"==c;if(d!=b){var f=this.sourceBlock_;f.updateAt_(a,d);f.setFieldValue(c,"WHERE"+a);return null}});this.getInput("AT"+a).appendField(c,"WHERE"+a);1==a&&(this.moveInputBefore("AT1","AT2"),this.getInput("ORDINAL1")&&this.moveInputBefore("ORDINAL1","AT2"));Blockly.Msg.LISTS_GET_SUBLIST_TAIL&&this.moveInputBefore("TAIL",null)}};
|
||||
Blockly.Blocks.lists_sort={init:function(){this.jsonInit({message0:Blockly.Msg.LISTS_SORT_TITLE,args0:[{type:"field_dropdown",name:"TYPE",options:[[Blockly.Msg.LISTS_SORT_TYPE_NUMERIC,"NUMERIC"],[Blockly.Msg.LISTS_SORT_TYPE_TEXT,"TEXT"],[Blockly.Msg.LISTS_SORT_TYPE_IGNORECASE,"IGNORE_CASE"]]},{type:"field_dropdown",name:"DIRECTION",options:[[Blockly.Msg.LISTS_SORT_ORDER_ASCENDING,"1"],[Blockly.Msg.LISTS_SORT_ORDER_DESCENDING,"-1"]]},{type:"input_value",name:"LIST",check:"Array"}],output:"Array",colour:Blockly.Blocks.lists.HUE,
|
||||
tooltip:Blockly.Msg.LISTS_SORT_TOOLTIP,helpUrl:Blockly.Msg.LISTS_SORT_HELPURL})}};
|
||||
Blockly.Blocks.lists_split={init:function(){var a=this,b=new Blockly.FieldDropdown([[Blockly.Msg.LISTS_SPLIT_LIST_FROM_TEXT,"SPLIT"],[Blockly.Msg.LISTS_SPLIT_TEXT_FROM_LIST,"JOIN"]],function(b){a.updateType_(b)});this.setHelpUrl(Blockly.Msg.LISTS_SPLIT_HELPURL);this.setColour(Blockly.Blocks.lists.HUE);this.appendValueInput("INPUT").setCheck("String").appendField(b,"MODE");this.appendValueInput("DELIM").setCheck("String").appendField(Blockly.Msg.LISTS_SPLIT_WITH_DELIMITER);this.setInputsInline(!0);
|
||||
@@ -95,7 +112,7 @@ mutationToDom:function(a){var b=document.createElement("mutation");a&&b.setAttri
|
||||
this.arguments_.push(c.getAttribute("name"));this.updateParams_();Blockly.Procedures.mutateCallers(this);this.setStatements_("false"!==a.getAttribute("statements"))},decompose:function(a){var b=a.newBlock("procedures_mutatorcontainer");b.initSvg();this.getInput("RETURN")?b.setFieldValue(this.hasStatements_?"TRUE":"FALSE","STATEMENTS"):b.getInput("STATEMENT_INPUT").setVisible(!1);for(var c=b.getInput("STACK").connection,d=0;d<this.arguments_.length;d++){var e=a.newBlock("procedures_mutatorarg");e.initSvg();
|
||||
e.setFieldValue(this.arguments_[d],"NAME");e.oldLocation=d;c.connect(e.previousConnection);c=e.nextConnection}Blockly.Procedures.mutateCallers(this);return b},compose:function(a){this.arguments_=[];this.paramIds_=[];for(var b=a.getInputTargetBlock("STACK");b;)this.arguments_.push(b.getFieldValue("NAME")),this.paramIds_.push(b.id),b=b.nextConnection&&b.nextConnection.targetBlock();this.updateParams_();Blockly.Procedures.mutateCallers(this);a=a.getFieldValue("STATEMENTS");if(null!==a&&(a="TRUE"==a,
|
||||
this.hasStatements_!=a))if(a)this.setStatements_(!0),Blockly.Mutator.reconnect(this.statementConnection_,this,"STACK"),this.statementConnection_=null;else{a=this.getInput("STACK").connection;if(this.statementConnection_=a.targetConnection)a=a.targetBlock(),a.unplug(),a.bumpNeighbours_();this.setStatements_(!1)}},getProcedureDef:function(){return[this.getFieldValue("NAME"),this.arguments_,!1]},getVars:function(){return this.arguments_},renameVar:function(a,b){for(var c=!1,d=0;d<this.arguments_.length;d++)Blockly.Names.equals(a,
|
||||
this.arguments_[d])&&(this.arguments_[d]=b,c=!0);if(c&&(this.updateParams_(),this.mutator.isVisible()))for(var c=this.mutator.workspace_.getAllBlocks(),d=0,e;e=c[d];d++)"procedures_mutatorarg"==e.type&&Blockly.Names.equals(a,e.getFieldValue("NAME"))&&e.setFieldValue(b,"NAME")},customContextMenu:function(a){var b={enabled:!0},c=this.getFieldValue("NAME");b.text=Blockly.Msg.PROCEDURES_CREATE_DO.replace("%1",c);var d=goog.dom.createDom("mutation");d.setAttribute("name",c);for(var e=0;e<this.arguments_.length;e++)c=
|
||||
this.arguments_[d])&&(this.arguments_[d]=b,c=!0);if(c&&(this.updateParams_(),this.mutator.isVisible()))for(var c=this.mutator.workspace_.getAllBlocks(),d=0,e;e=c[d];d++)"procedures_mutatorarg"==e.type&&Blockly.Names.equals(a,e.getFieldValue("NAME"))&&e.setFieldValue(b,"NAME")},customContextMenu:function(a){var b={enabled:!0};var c=this.getFieldValue("NAME");b.text=Blockly.Msg.PROCEDURES_CREATE_DO.replace("%1",c);var d=goog.dom.createDom("mutation");d.setAttribute("name",c);for(var e=0;e<this.arguments_.length;e++)c=
|
||||
goog.dom.createDom("arg"),c.setAttribute("name",this.arguments_[e]),d.appendChild(c);d=goog.dom.createDom("block",null,d);d.setAttribute("type",this.callType_);b.callback=Blockly.ContextMenu.callbackFactory(this,d);a.push(b);if(!this.isCollapsed())for(e=0;e<this.arguments_.length;e++)b={enabled:!0},c=this.arguments_[e],b.text=Blockly.Msg.VARIABLES_SET_CREATE_GET.replace("%1",c),d=goog.dom.createDom("field",null,c),d.setAttribute("name","VAR"),d=goog.dom.createDom("block",null,d),d.setAttribute("type",
|
||||
"variables_get"),b.callback=Blockly.ContextMenu.callbackFactory(this,d),a.push(b)},callType_:"procedures_callnoreturn"};
|
||||
Blockly.Blocks.procedures_defreturn={init:function(){var a=new Blockly.FieldTextInput("",Blockly.Procedures.rename);a.setSpellcheck(!1);this.appendDummyInput().appendField(Blockly.Msg.PROCEDURES_DEFRETURN_TITLE).appendField(a,"NAME").appendField("","PARAMS");this.appendValueInput("RETURN").setAlign(Blockly.ALIGN_RIGHT).appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);this.setMutator(new Blockly.Mutator(["procedures_mutatorarg"]));(this.workspace.options.comments||this.workspace.options.parentWorkspace&&
|
||||
@@ -105,40 +122,29 @@ callType_:"procedures_callreturn"};Blockly.Blocks.procedures_mutatorcontainer={i
|
||||
Blockly.Blocks.procedures_mutatorarg={init:function(){var a=new Blockly.FieldTextInput("x",this.validator_);this.appendDummyInput().appendField(Blockly.Msg.PROCEDURES_MUTATORARG_TITLE).appendField(a,"NAME");this.setPreviousStatement(!0);this.setNextStatement(!0);this.setColour(Blockly.Blocks.procedures.HUE);this.setTooltip(Blockly.Msg.PROCEDURES_MUTATORARG_TOOLTIP);this.contextMenu=!1;a.onFinishEditing_=this.createNewVar_;a.onFinishEditing_("x")},validator_:function(a){return(a=a.replace(/[\s\xa0]+/g,
|
||||
" ").replace(/^ | $/g,""))||null},createNewVar_:function(a){var b=this.sourceBlock_;b&&b.workspace&&b.workspace.options&&b.workspace.options.parentWorkspace&&b.workspace.options.parentWorkspace.createVariable(a)}};
|
||||
Blockly.Blocks.procedures_callnoreturn={init:function(){this.appendDummyInput("TOPROW").appendField(this.id,"NAME");this.setPreviousStatement(!0);this.setNextStatement(!0);this.setColour(Blockly.Blocks.procedures.HUE);this.setHelpUrl(Blockly.Msg.PROCEDURES_CALLNORETURN_HELPURL);this.arguments_=[];this.quarkConnections_={};this.quarkIds_=null},getProcedureCall:function(){return this.getFieldValue("NAME")},renameProcedure:function(a,b){Blockly.Names.equals(a,this.getProcedureCall())&&(this.setFieldValue(b,
|
||||
"NAME"),this.setTooltip((this.outputConnection?Blockly.Msg.PROCEDURES_CALLRETURN_TOOLTIP:Blockly.Msg.PROCEDURES_CALLNORETURN_TOOLTIP).replace("%1",b)))},setProcedureParameters_:function(a,b){var c=Blockly.Procedures.getDefinition(this.getProcedureCall(),this.workspace),d=c&&c.mutator&&c.mutator.isVisible();d||(this.quarkConnections_={},this.quarkIds_=null);if(b)if(goog.array.equals(this.arguments_,a))this.quarkIds_=b;else{if(b.length!=a.length)throw"Error: paramNames and paramIds must be the same length.";
|
||||
this.setCollapsed(!1);this.quarkIds_||(this.quarkConnections_={},a.join("\n")==this.arguments_.join("\n")?this.quarkIds_=b:this.quarkIds_=[]);c=this.rendered;this.rendered=!1;for(var e=0;e<this.arguments_.length;e++){var f=this.getInput("ARG"+e);f&&(f=f.connection.targetConnection,this.quarkConnections_[this.quarkIds_[e]]=f,d&&f&&-1==b.indexOf(this.quarkIds_[e])&&(f.disconnect(),f.getSourceBlock().bumpNeighbours_()))}this.arguments_=[].concat(a);this.updateShape_();if(this.quarkIds_=b)for(e=0;e<this.arguments_.length;e++)d=
|
||||
this.quarkIds_[e],d in this.quarkConnections_&&(f=this.quarkConnections_[d],Blockly.Mutator.reconnect(f,this,"ARG"+e)||delete this.quarkConnections_[d]);(this.rendered=c)&&this.render()}},updateShape_:function(){for(var a=0;a<this.arguments_.length;a++){var b=this.getField("ARGNAME"+a);if(b){Blockly.Events.disable();try{b.setValue(this.arguments_[a])}finally{Blockly.Events.enable()}}else b=new Blockly.FieldLabel(this.arguments_[a]),this.appendValueInput("ARG"+a).setAlign(Blockly.ALIGN_RIGHT).appendField(b,
|
||||
"NAME"),this.setTooltip((this.outputConnection?Blockly.Msg.PROCEDURES_CALLRETURN_TOOLTIP:Blockly.Msg.PROCEDURES_CALLNORETURN_TOOLTIP).replace("%1",b)))},setProcedureParameters_:function(a,b){var c,d=Blockly.Procedures.getDefinition(this.getProcedureCall(),this.workspace),e=d&&d.mutator&&d.mutator.isVisible();e||(this.quarkConnections_={},this.quarkIds_=null);if(b)if(goog.array.equals(this.arguments_,a))this.quarkIds_=b;else{if(b.length!=a.length)throw"Error: paramNames and paramIds must be the same length.";
|
||||
this.setCollapsed(!1);this.quarkIds_||(this.quarkConnections_={},a.join("\n")==this.arguments_.join("\n")?this.quarkIds_=b:this.quarkIds_=[]);d=this.rendered;this.rendered=!1;for(var f=0;f<this.arguments_.length;f++)if(c=this.getInput("ARG"+f))c=c.connection.targetConnection,this.quarkConnections_[this.quarkIds_[f]]=c,e&&c&&-1==b.indexOf(this.quarkIds_[f])&&(c.disconnect(),c.getSourceBlock().bumpNeighbours_());this.arguments_=[].concat(a);this.updateShape_();if(this.quarkIds_=b)for(f=0;f<this.arguments_.length;f++)e=
|
||||
this.quarkIds_[f],e in this.quarkConnections_&&(c=this.quarkConnections_[e],Blockly.Mutator.reconnect(c,this,"ARG"+f)||delete this.quarkConnections_[e]);(this.rendered=d)&&this.render()}},updateShape_:function(){for(var a=0;a<this.arguments_.length;a++){var b=this.getField("ARGNAME"+a);if(b){Blockly.Events.disable();try{b.setValue(this.arguments_[a])}finally{Blockly.Events.enable()}}else b=new Blockly.FieldLabel(this.arguments_[a]),this.appendValueInput("ARG"+a).setAlign(Blockly.ALIGN_RIGHT).appendField(b,
|
||||
"ARGNAME"+a).init()}for(;this.getInput("ARG"+a);)this.removeInput("ARG"+a),a++;if(a=this.getInput("TOPROW"))this.arguments_.length?this.getField("WITH")||(a.appendField(Blockly.Msg.PROCEDURES_CALL_BEFORE_PARAMS,"WITH"),a.init()):this.getField("WITH")&&a.removeField("WITH")},mutationToDom:function(){var a=document.createElement("mutation");a.setAttribute("name",this.getProcedureCall());for(var b=0;b<this.arguments_.length;b++){var c=document.createElement("arg");c.setAttribute("name",this.arguments_[b]);
|
||||
a.appendChild(c)}return a},domToMutation:function(a){var b=a.getAttribute("name");this.renameProcedure(this.getProcedureCall(),b);for(var b=[],c=[],d=0,e;e=a.childNodes[d];d++)"arg"==e.nodeName.toLowerCase()&&(b.push(e.getAttribute("name")),c.push(e.getAttribute("paramId")));this.setProcedureParameters_(b,c)},renameVar:function(a,b){for(var c=0;c<this.arguments_.length;c++)Blockly.Names.equals(a,this.arguments_[c])&&(this.arguments_[c]=b,this.getField("ARGNAME"+c).setValue(b))},onchange:function(a){if(this.workspace&&
|
||||
!this.workspace.isFlyout)if(a.type==Blockly.Events.CREATE&&-1!=a.ids.indexOf(this.id)){var b=this.getProcedureCall(),b=Blockly.Procedures.getDefinition(b,this.workspace);!b||b.type==this.defType_&&JSON.stringify(b.arguments_)==JSON.stringify(this.arguments_)||(b=null);if(!b){Blockly.Events.setGroup(a.group);a=goog.dom.createDom("xml");b=goog.dom.createDom("block");b.setAttribute("type",this.defType_);var c=this.getRelativeToSurfaceXY(),d=c.y+2*Blockly.SNAP_RADIUS;b.setAttribute("x",c.x+Blockly.SNAP_RADIUS*
|
||||
(this.RTL?-1:1));b.setAttribute("y",d);c=this.mutationToDom();b.appendChild(c);c=goog.dom.createDom("field");c.setAttribute("name","NAME");c.appendChild(document.createTextNode(this.getProcedureCall()));b.appendChild(c);a.appendChild(b);Blockly.Xml.domToWorkspace(a,this.workspace);Blockly.Events.setGroup(!1)}}else a.type==Blockly.Events.DELETE&&(b=this.getProcedureCall(),b=Blockly.Procedures.getDefinition(b,this.workspace),b||(Blockly.Events.setGroup(a.group),this.dispose(!0,!1),Blockly.Events.setGroup(!1)))},
|
||||
!this.workspace.isFlyout)if(a.type==Blockly.Events.BLOCK_CREATE&&-1!=a.ids.indexOf(this.id)){var b=this.getProcedureCall();b=Blockly.Procedures.getDefinition(b,this.workspace);!b||b.type==this.defType_&&JSON.stringify(b.arguments_)==JSON.stringify(this.arguments_)||(b=null);if(!b){Blockly.Events.setGroup(a.group);a=goog.dom.createDom("xml");b=goog.dom.createDom("block");b.setAttribute("type",this.defType_);var c=this.getRelativeToSurfaceXY(),d=c.y+2*Blockly.SNAP_RADIUS;b.setAttribute("x",c.x+Blockly.SNAP_RADIUS*
|
||||
(this.RTL?-1:1));b.setAttribute("y",d);c=this.mutationToDom();b.appendChild(c);c=goog.dom.createDom("field");c.setAttribute("name","NAME");c.appendChild(document.createTextNode(this.getProcedureCall()));b.appendChild(c);a.appendChild(b);Blockly.Xml.domToWorkspace(a,this.workspace);Blockly.Events.setGroup(!1)}}else a.type==Blockly.Events.BLOCK_DELETE&&(b=this.getProcedureCall(),b=Blockly.Procedures.getDefinition(b,this.workspace),b||(Blockly.Events.setGroup(a.group),this.dispose(!0,!1),Blockly.Events.setGroup(!1)))},
|
||||
customContextMenu:function(a){var b={enabled:!0};b.text=Blockly.Msg.PROCEDURES_HIGHLIGHT_DEF;var c=this.getProcedureCall(),d=this.workspace;b.callback=function(){var a=Blockly.Procedures.getDefinition(c,d);a&&a.select()};a.push(b)},defType_:"procedures_defnoreturn"};
|
||||
Blockly.Blocks.procedures_callreturn={init:function(){this.appendDummyInput("TOPROW").appendField("","NAME");this.setOutput(!0);this.setColour(Blockly.Blocks.procedures.HUE);this.setHelpUrl(Blockly.Msg.PROCEDURES_CALLRETURN_HELPURL);this.arguments_=[];this.quarkConnections_={};this.quarkIds_=null},getProcedureCall:Blockly.Blocks.procedures_callnoreturn.getProcedureCall,renameProcedure:Blockly.Blocks.procedures_callnoreturn.renameProcedure,setProcedureParameters_:Blockly.Blocks.procedures_callnoreturn.setProcedureParameters_,
|
||||
updateShape_:Blockly.Blocks.procedures_callnoreturn.updateShape_,mutationToDom:Blockly.Blocks.procedures_callnoreturn.mutationToDom,domToMutation:Blockly.Blocks.procedures_callnoreturn.domToMutation,renameVar:Blockly.Blocks.procedures_callnoreturn.renameVar,onchange:Blockly.Blocks.procedures_callnoreturn.onchange,customContextMenu:Blockly.Blocks.procedures_callnoreturn.customContextMenu,defType_:"procedures_defreturn"};
|
||||
Blockly.Blocks.procedures_ifreturn={init:function(){this.appendValueInput("CONDITION").setCheck("Boolean").appendField(Blockly.Msg.CONTROLS_IF_MSG_IF);this.appendValueInput("VALUE").appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);this.setInputsInline(!0);this.setPreviousStatement(!0);this.setNextStatement(!0);this.setColour(Blockly.Blocks.procedures.HUE);this.setTooltip(Blockly.Msg.PROCEDURES_IFRETURN_TOOLTIP);this.setHelpUrl(Blockly.Msg.PROCEDURES_IFRETURN_HELPURL);this.hasReturnValue_=!0},
|
||||
mutationToDom:function(){var a=document.createElement("mutation");a.setAttribute("value",Number(this.hasReturnValue_));return a},domToMutation:function(a){this.hasReturnValue_=1==a.getAttribute("value");this.hasReturnValue_||(this.removeInput("VALUE"),this.appendDummyInput("VALUE").appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN))},onchange:function(){if(this.workspace.isDragging&&!this.workspace.isDragging()){var a=!1,b=this;do{if(-1!=this.FUNCTION_TYPES.indexOf(b.type)){a=!0;break}b=b.getSurroundParent()}while(b);
|
||||
a?("procedures_defnoreturn"==b.type&&this.hasReturnValue_?(this.removeInput("VALUE"),this.appendDummyInput("VALUE").appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN),this.hasReturnValue_=!1):"procedures_defreturn"!=b.type||this.hasReturnValue_||(this.removeInput("VALUE"),this.appendValueInput("VALUE").appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN),this.hasReturnValue_=!0),this.setWarningText(null),this.isInFlyout||this.setDisabled(!1)):(this.setWarningText(Blockly.Msg.PROCEDURES_IFRETURN_WARNING),
|
||||
this.isInFlyout||this.getInheritedDisabled()||this.setDisabled(!0))}},FUNCTION_TYPES:["procedures_defnoreturn","procedures_defreturn"]};Blockly.Blocks.texts={};Blockly.Constants.Text={};Blockly.Constants.Text.HUE=160;Blockly.Blocks.texts.HUE=Blockly.Constants.Text.HUE;Blockly.defineBlocksWithJsonArray([{type:"text",message0:"%1",args0:[{type:"field_input",name:"TEXT",text:""}],output:"String",colour:"%{BKY_TEXTS_HUE}",helpUrl:"%{BKY_TEXT_TEXT_HELPURL}",tooltip:"%{BKY_TEXT_TEXT_TOOLTIP}",extensions:["text_quotes","parent_tooltip_when_inline"]}]);
|
||||
Blockly.Constants.Text.textQuotesExtension=function(){this.mixin(Blockly.Constants.Text.QUOTE_IMAGE_MIXIN);this.quoteField_("TEXT")};Blockly.Extensions.register("text_quotes",Blockly.Constants.Text.textQuotesExtension);
|
||||
Blockly.Blocks.text_join={init:function(){this.setHelpUrl(Blockly.Msg.TEXT_JOIN_HELPURL);this.setColour(Blockly.Blocks.texts.HUE);this.itemCount_=2;this.mixin(Blockly.Constants.Text.QUOTE_IMAGE_MIXIN);this.updateShape_();this.setOutput(!0,"String");this.setMutator(new Blockly.Mutator(["text_create_join_item"]));this.setTooltip(Blockly.Msg.TEXT_JOIN_TOOLTIP)},mutationToDom:function(){var a=document.createElement("mutation");a.setAttribute("items",this.itemCount_);return a},domToMutation:function(a){this.itemCount_=
|
||||
parseInt(a.getAttribute("items"),10);this.updateShape_()},decompose:function(a){var b=a.newBlock("text_create_join_container");b.initSvg();for(var c=b.getInput("STACK").connection,d=0;d<this.itemCount_;d++){var e=a.newBlock("text_create_join_item");e.initSvg();c.connect(e.previousConnection);c=e.nextConnection}return b},compose:function(a){var b=a.getInputTargetBlock("STACK");for(a=[];b;)a.push(b.valueConnection_),b=b.nextConnection&&b.nextConnection.targetBlock();for(b=0;b<this.itemCount_;b++){var c=
|
||||
this.getInput("ADD"+b).connection.targetConnection;c&&-1==a.indexOf(c)&&c.disconnect()}this.itemCount_=a.length;this.updateShape_();for(b=0;b<this.itemCount_;b++)Blockly.Mutator.reconnect(a[b],this,"ADD"+b)},saveConnections:function(a){a=a.getInputTargetBlock("STACK");for(var b=0;a;){var c=this.getInput("ADD"+b);a.valueConnection_=c&&c.connection.targetConnection;b++;a=a.nextConnection&&a.nextConnection.targetBlock()}},updateShape_:function(){this.itemCount_&&this.getInput("EMPTY")?this.removeInput("EMPTY"):
|
||||
this.itemCount_||this.getInput("EMPTY")||this.appendDummyInput("EMPTY").appendField(this.newQuote_(!0)).appendField(this.newQuote_(!1));for(var a=0;a<this.itemCount_;a++)if(!this.getInput("ADD"+a)){var b=this.appendValueInput("ADD"+a);0==a&&b.appendField(Blockly.Msg.TEXT_JOIN_TITLE_CREATEWITH)}for(;this.getInput("ADD"+a);)this.removeInput("ADD"+a),a++}};
|
||||
Blockly.Blocks.text_create_join_container={init:function(){this.setColour(Blockly.Blocks.texts.HUE);this.appendDummyInput().appendField(Blockly.Msg.TEXT_CREATE_JOIN_TITLE_JOIN);this.appendStatementInput("STACK");this.setTooltip(Blockly.Msg.TEXT_CREATE_JOIN_TOOLTIP);this.contextMenu=!1}};
|
||||
Blockly.Blocks.text_create_join_item={init:function(){this.setColour(Blockly.Blocks.texts.HUE);this.appendDummyInput().appendField(Blockly.Msg.TEXT_CREATE_JOIN_ITEM_TITLE_ITEM);this.setPreviousStatement(!0);this.setNextStatement(!0);this.setTooltip(Blockly.Msg.TEXT_CREATE_JOIN_ITEM_TOOLTIP);this.contextMenu=!1}};
|
||||
Blockly.Blocks.text_append={init:function(){this.setHelpUrl(Blockly.Msg.TEXT_APPEND_HELPURL);this.setColour(Blockly.Blocks.texts.HUE);this.appendValueInput("TEXT").appendField(Blockly.Msg.TEXT_APPEND_TO).appendField(new Blockly.FieldVariable(Blockly.Msg.TEXT_APPEND_VARIABLE),"VAR").appendField(Blockly.Msg.TEXT_APPEND_APPENDTEXT);this.setPreviousStatement(!0);this.setNextStatement(!0);var a=this;this.setTooltip(function(){return Blockly.Msg.TEXT_APPEND_TOOLTIP.replace("%1",a.getFieldValue("VAR"))})}};
|
||||
Blockly.Blocks.text_length={init:function(){this.jsonInit({message0:Blockly.Msg.TEXT_LENGTH_TITLE,args0:[{type:"input_value",name:"VALUE",check:["String","Array"]}],output:"Number",colour:Blockly.Blocks.texts.HUE,tooltip:Blockly.Msg.TEXT_LENGTH_TOOLTIP,helpUrl:Blockly.Msg.TEXT_LENGTH_HELPURL})}};
|
||||
Blockly.Blocks.text_isEmpty={init:function(){this.jsonInit({message0:Blockly.Msg.TEXT_ISEMPTY_TITLE,args0:[{type:"input_value",name:"VALUE",check:["String","Array"]}],output:"Boolean",colour:Blockly.Blocks.texts.HUE,tooltip:Blockly.Msg.TEXT_ISEMPTY_TOOLTIP,helpUrl:Blockly.Msg.TEXT_ISEMPTY_HELPURL})}};
|
||||
Blockly.Blocks.text_indexOf={init:function(){var a=[[Blockly.Msg.TEXT_INDEXOF_OPERATOR_FIRST,"FIRST"],[Blockly.Msg.TEXT_INDEXOF_OPERATOR_LAST,"LAST"]];this.setHelpUrl(Blockly.Msg.TEXT_INDEXOF_HELPURL);this.setColour(Blockly.Blocks.texts.HUE);this.setOutput(!0,"Number");this.appendValueInput("VALUE").setCheck("String").appendField(Blockly.Msg.TEXT_INDEXOF_INPUT_INTEXT);this.appendValueInput("FIND").setCheck("String").appendField(new Blockly.FieldDropdown(a),"END");Blockly.Msg.TEXT_INDEXOF_TAIL&&this.appendDummyInput().appendField(Blockly.Msg.TEXT_INDEXOF_TAIL);
|
||||
this.setInputsInline(!0);var b=this;this.setTooltip(function(){return Blockly.Msg.TEXT_INDEXOF_TOOLTIP.replace("%1",b.workspace.options.oneBasedIndex?"0":"-1")})}};
|
||||
Blockly.Blocks.text_charAt={init:function(){this.WHERE_OPTIONS=[[Blockly.Msg.TEXT_CHARAT_FROM_START,"FROM_START"],[Blockly.Msg.TEXT_CHARAT_FROM_END,"FROM_END"],[Blockly.Msg.TEXT_CHARAT_FIRST,"FIRST"],[Blockly.Msg.TEXT_CHARAT_LAST,"LAST"],[Blockly.Msg.TEXT_CHARAT_RANDOM,"RANDOM"]];this.setHelpUrl(Blockly.Msg.TEXT_CHARAT_HELPURL);this.setColour(Blockly.Blocks.texts.HUE);this.setOutput(!0,"String");this.appendValueInput("VALUE").setCheck("String").appendField(Blockly.Msg.TEXT_CHARAT_INPUT_INTEXT);this.appendDummyInput("AT");
|
||||
this.setInputsInline(!0);this.updateAt_(!0);var a=this;this.setTooltip(function(){var b=a.getFieldValue("WHERE"),c=Blockly.Msg.TEXT_CHARAT_TOOLTIP;if("FROM_START"==b||"FROM_END"==b)c+=" "+("FROM_START"==b?Blockly.Msg.LISTS_INDEX_FROM_START_TOOLTIP:Blockly.Msg.LISTS_INDEX_FROM_END_TOOLTIP).replace("%1",a.workspace.options.oneBasedIndex?"#1":"#0");return c})},mutationToDom:function(){var a=document.createElement("mutation"),b=this.getInput("AT").type==Blockly.INPUT_VALUE;a.setAttribute("at",b);return a},
|
||||
domToMutation:function(a){a="false"!=a.getAttribute("at");this.updateAt_(a)},updateAt_:function(a){this.removeInput("AT");this.removeInput("ORDINAL",!0);a?(this.appendValueInput("AT").setCheck("Number"),Blockly.Msg.ORDINAL_NUMBER_SUFFIX&&this.appendDummyInput("ORDINAL").appendField(Blockly.Msg.ORDINAL_NUMBER_SUFFIX)):this.appendDummyInput("AT");Blockly.Msg.TEXT_CHARAT_TAIL&&(this.removeInput("TAIL",!0),this.appendDummyInput("TAIL").appendField(Blockly.Msg.TEXT_CHARAT_TAIL));var b=new Blockly.FieldDropdown(this.WHERE_OPTIONS,
|
||||
function(b){var c="FROM_START"==b||"FROM_END"==b;if(c!=a){var e=this.sourceBlock_;e.updateAt_(c);e.setFieldValue(b,"WHERE");return null}});this.getInput("AT").appendField(b,"WHERE")}};
|
||||
this.isInFlyout||this.getInheritedDisabled()||this.setDisabled(!0))}},FUNCTION_TYPES:["procedures_defnoreturn","procedures_defreturn"]};Blockly.Blocks.texts={};Blockly.Constants.Text={};Blockly.Constants.Text.HUE=160;Blockly.Blocks.texts.HUE=Blockly.Constants.Text.HUE;
|
||||
Blockly.defineBlocksWithJsonArray([{type:"text",message0:"%1",args0:[{type:"field_input",name:"TEXT",text:""}],output:"String",colour:"%{BKY_TEXTS_HUE}",helpUrl:"%{BKY_TEXT_TEXT_HELPURL}",tooltip:"%{BKY_TEXT_TEXT_TOOLTIP}",extensions:["text_quotes","parent_tooltip_when_inline"]},{type:"text_join",message0:"",output:"String",colour:"%{BKY_TEXTS_HUE}",helpUrl:"%{BKY_TEXT_JOIN_HELPURL}",tooltip:"%{BKY_TEXT_JOIN_TOOLTIP}",mutator:"text_join_mutator"},{type:"text_create_join_container",message0:"%{BKY_TEXT_CREATE_JOIN_TITLE_JOIN} %1 %2",
|
||||
args0:[{type:"input_dummy"},{type:"input_statement",name:"STACK"}],colour:"%{BKY_TEXTS_HUE}",tooltip:"%{BKY_TEXT_CREATE_JOIN_TOOLTIP}",enableContextMenu:!1},{type:"text_create_join_item",message0:"%{BKY_TEXT_CREATE_JOIN_ITEM_TITLE_ITEM}",previousStatement:null,nextStatement:null,colour:"%{BKY_TEXTS_HUE}",tooltip:"{%BKY_TEXT_CREATE_JOIN_ITEM_TOOLTIP}",enableContextMenu:!1},{type:"text_append",message0:"%{BKY_TEXT_APPEND_TITLE}",args0:[{type:"field_variable",name:"VAR",variable:"%{BKY_TEXT_APPEND_VARIABLE}"},
|
||||
{type:"input_value",name:"TEXT"}],previousStatement:null,nextStatement:null,colour:"%{BKY_TEXTS_HUE}",extensions:["text_append_tooltip"]},{type:"text_length",message0:"%{BKY_TEXT_LENGTH_TITLE}",args0:[{type:"input_value",name:"VALUE",check:["String","Array"]}],output:"Number",colour:"%{BKY_TEXTS_HUE}",tooltip:"%{BKY_TEXT_LENGTH_TOOLTIP}",helpUrl:"%{BKY_TEXT_LENGTH_HELPURL}"},{type:"text_isEmpty",message0:"%{BKY_TEXT_ISEMPTY_TITLE}",args0:[{type:"input_value",name:"VALUE",check:["String","Array"]}],
|
||||
output:"Boolean",colour:"%{BKY_TEXTS_HUE}",tooltip:"%{BKY_TEXT_ISEMPTY_TOOLTIP}",helpUrl:"%{BKY_TEXT_ISEMPTY_HELPURL}"},{type:"text_indexOf",message0:"%{BKY_TEXT_INDEXOF_TITLE}",args0:[{type:"input_value",name:"VALUE",check:"String"},{type:"field_dropdown",name:"END",options:[["%{BKY_TEXT_INDEXOF_OPERATOR_FIRST}","FIRST"],["%{BKY_TEXT_INDEXOF_OPERATOR_LAST}","LAST"]]},{type:"input_value",name:"FIND",check:"String"}],output:"Number",colour:"%{BKY_TEXTS_HUE}",helpUrl:"%{BKY_TEXT_INDEXOF_HELPURL}",inputsInline:!0,
|
||||
extensions:["text_indexOf_tooltip"]},{type:"text_charAt",message0:"%{BKY_TEXT_CHARAT_TITLE}",args0:[{type:"input_value",name:"VALUE",check:"String"},{type:"input_dummy",name:"AT"}],output:"String",colour:"%{BKY_TEXTS_HUE}",helpUrl:"%{BKY_TEXT_CHARAT_HELPURL}",inputsInline:!0,mutator:"text_charAt_mutator"}]);
|
||||
Blockly.Blocks.text_getSubstring={init:function(){this.WHERE_OPTIONS_1=[[Blockly.Msg.TEXT_GET_SUBSTRING_START_FROM_START,"FROM_START"],[Blockly.Msg.TEXT_GET_SUBSTRING_START_FROM_END,"FROM_END"],[Blockly.Msg.TEXT_GET_SUBSTRING_START_FIRST,"FIRST"]];this.WHERE_OPTIONS_2=[[Blockly.Msg.TEXT_GET_SUBSTRING_END_FROM_START,"FROM_START"],[Blockly.Msg.TEXT_GET_SUBSTRING_END_FROM_END,"FROM_END"],[Blockly.Msg.TEXT_GET_SUBSTRING_END_LAST,"LAST"]];this.setHelpUrl(Blockly.Msg.TEXT_GET_SUBSTRING_HELPURL);this.setColour(Blockly.Blocks.texts.HUE);
|
||||
this.appendValueInput("STRING").setCheck("String").appendField(Blockly.Msg.TEXT_GET_SUBSTRING_INPUT_IN_TEXT);this.appendDummyInput("AT1");this.appendDummyInput("AT2");Blockly.Msg.TEXT_GET_SUBSTRING_TAIL&&this.appendDummyInput("TAIL").appendField(Blockly.Msg.TEXT_GET_SUBSTRING_TAIL);this.setInputsInline(!0);this.setOutput(!0,"String");this.updateAt_(1,!0);this.updateAt_(2,!0);this.setTooltip(Blockly.Msg.TEXT_GET_SUBSTRING_TOOLTIP)},mutationToDom:function(){var a=document.createElement("mutation"),
|
||||
b=this.getInput("AT1").type==Blockly.INPUT_VALUE;a.setAttribute("at1",b);b=this.getInput("AT2").type==Blockly.INPUT_VALUE;a.setAttribute("at2",b);return a},domToMutation:function(a){var b="true"==a.getAttribute("at1");a="true"==a.getAttribute("at2");this.updateAt_(1,b);this.updateAt_(2,a)},updateAt_:function(a,b){this.removeInput("AT"+a);this.removeInput("ORDINAL"+a,!0);b?(this.appendValueInput("AT"+a).setCheck("Number"),Blockly.Msg.ORDINAL_NUMBER_SUFFIX&&this.appendDummyInput("ORDINAL"+a).appendField(Blockly.Msg.ORDINAL_NUMBER_SUFFIX)):
|
||||
this.appendDummyInput("AT"+a);2==a&&Blockly.Msg.TEXT_GET_SUBSTRING_TAIL&&(this.removeInput("TAIL",!0),this.appendDummyInput("TAIL").appendField(Blockly.Msg.TEXT_GET_SUBSTRING_TAIL));var c=new Blockly.FieldDropdown(this["WHERE_OPTIONS_"+a],function(c){var e="FROM_START"==c||"FROM_END"==c;if(e!=b){var d=this.sourceBlock_;d.updateAt_(a,e);d.setFieldValue(c,"WHERE"+a);return null}});this.getInput("AT"+a).appendField(c,"WHERE"+a);1==a&&this.moveInputBefore("AT1","AT2")}};
|
||||
this.appendDummyInput("AT"+a);2==a&&Blockly.Msg.TEXT_GET_SUBSTRING_TAIL&&(this.removeInput("TAIL",!0),this.appendDummyInput("TAIL").appendField(Blockly.Msg.TEXT_GET_SUBSTRING_TAIL));var c=new Blockly.FieldDropdown(this["WHERE_OPTIONS_"+a],function(c){var d="FROM_START"==c||"FROM_END"==c;if(d!=b){var f=this.sourceBlock_;f.updateAt_(a,d);f.setFieldValue(c,"WHERE"+a);return null}});this.getInput("AT"+a).appendField(c,"WHERE"+a);1==a&&this.moveInputBefore("AT1","AT2")}};
|
||||
Blockly.Blocks.text_changeCase={init:function(){var a=[[Blockly.Msg.TEXT_CHANGECASE_OPERATOR_UPPERCASE,"UPPERCASE"],[Blockly.Msg.TEXT_CHANGECASE_OPERATOR_LOWERCASE,"LOWERCASE"],[Blockly.Msg.TEXT_CHANGECASE_OPERATOR_TITLECASE,"TITLECASE"]];this.setHelpUrl(Blockly.Msg.TEXT_CHANGECASE_HELPURL);this.setColour(Blockly.Blocks.texts.HUE);this.appendValueInput("TEXT").setCheck("String").appendField(new Blockly.FieldDropdown(a),"CASE");this.setOutput(!0,"String");this.setTooltip(Blockly.Msg.TEXT_CHANGECASE_TOOLTIP)}};
|
||||
Blockly.Blocks.text_trim={init:function(){var a=[[Blockly.Msg.TEXT_TRIM_OPERATOR_BOTH,"BOTH"],[Blockly.Msg.TEXT_TRIM_OPERATOR_LEFT,"LEFT"],[Blockly.Msg.TEXT_TRIM_OPERATOR_RIGHT,"RIGHT"]];this.setHelpUrl(Blockly.Msg.TEXT_TRIM_HELPURL);this.setColour(Blockly.Blocks.texts.HUE);this.appendValueInput("TEXT").setCheck("String").appendField(new Blockly.FieldDropdown(a),"MODE");this.setOutput(!0,"String");this.setTooltip(Blockly.Msg.TEXT_TRIM_TOOLTIP)}};
|
||||
Blockly.Blocks.text_print={init:function(){this.jsonInit({message0:Blockly.Msg.TEXT_PRINT_TITLE,args0:[{type:"input_value",name:"TEXT"}],previousStatement:null,nextStatement:null,colour:Blockly.Blocks.texts.HUE,tooltip:Blockly.Msg.TEXT_PRINT_TOOLTIP,helpUrl:Blockly.Msg.TEXT_PRINT_HELPURL})}};
|
||||
@@ -146,12 +152,23 @@ Blockly.Blocks.text_prompt_ext={init:function(){var a=[[Blockly.Msg.TEXT_PROMPT_
|
||||
updateType_:function(a){this.outputConnection.setCheck("NUMBER"==a?"Number":"String")},mutationToDom:function(){var a=document.createElement("mutation");a.setAttribute("type",this.getFieldValue("TYPE"));return a},domToMutation:function(a){this.updateType_(a.getAttribute("type"))}};
|
||||
Blockly.Blocks.text_prompt={init:function(){this.mixin(Blockly.Constants.Text.QUOTE_IMAGE_MIXIN);var a=[[Blockly.Msg.TEXT_PROMPT_TYPE_TEXT,"TEXT"],[Blockly.Msg.TEXT_PROMPT_TYPE_NUMBER,"NUMBER"]],b=this;this.setHelpUrl(Blockly.Msg.TEXT_PROMPT_HELPURL);this.setColour(Blockly.Blocks.texts.HUE);a=new Blockly.FieldDropdown(a,function(a){b.updateType_(a)});this.appendDummyInput().appendField(a,"TYPE").appendField(this.newQuote_(!0)).appendField(new Blockly.FieldTextInput(""),"TEXT").appendField(this.newQuote_(!1));
|
||||
this.setOutput(!0,"String");this.setTooltip(function(){return"TEXT"==b.getFieldValue("TYPE")?Blockly.Msg.TEXT_PROMPT_TOOLTIP_TEXT:Blockly.Msg.TEXT_PROMPT_TOOLTIP_NUMBER})},updateType_:Blockly.Blocks.text_prompt_ext.updateType_,mutationToDom:Blockly.Blocks.text_prompt_ext.mutationToDom,domToMutation:Blockly.Blocks.text_prompt_ext.domToMutation};
|
||||
Blockly.Blocks.text_count={init:function(){this.jsonInit({message0:Blockly.Msg.TEXT_COUNT_MESSAGE0,args0:[{type:"input_value",name:"SUB",check:"String"},{type:"input_value",name:"TEXT",check:"String"}],output:"Number",inputsInline:!0,colour:Blockly.Blocks.math.HUE,tooltip:Blockly.Msg.TEXT_COUNT_TOOLTIP,helpUrl:Blockly.Msg.TEXT_COUNT_HELPURL})}};
|
||||
Blockly.Blocks.text_count={init:function(){this.jsonInit({message0:Blockly.Msg.TEXT_COUNT_MESSAGE0,args0:[{type:"input_value",name:"SUB",check:"String"},{type:"input_value",name:"TEXT",check:"String"}],output:"Number",inputsInline:!0,colour:Blockly.Blocks.texts.HUE,tooltip:Blockly.Msg.TEXT_COUNT_TOOLTIP,helpUrl:Blockly.Msg.TEXT_COUNT_HELPURL})}};
|
||||
Blockly.Blocks.text_replace={init:function(){this.jsonInit({message0:Blockly.Msg.TEXT_REPLACE_MESSAGE0,args0:[{type:"input_value",name:"FROM",check:"String"},{type:"input_value",name:"TO",check:"String"},{type:"input_value",name:"TEXT",check:"String"}],output:"String",inputsInline:!0,colour:Blockly.Blocks.texts.HUE,tooltip:Blockly.Msg.TEXT_REPLACE_TOOLTIP,helpUrl:Blockly.Msg.TEXT_REPLACE_HELPURL})}};
|
||||
Blockly.Blocks.text_reverse={init:function(){this.jsonInit({message0:Blockly.Msg.TEXT_REVERSE_MESSAGE0,args0:[{type:"input_value",name:"TEXT",check:"String"}],output:"String",inputsInline:!0,colour:Blockly.Blocks.texts.HUE,tooltip:Blockly.Msg.TEXT_REVERSE_TOOLTIP,helpUrl:Blockly.Msg.TEXT_REVERSE_HELPURL})}};
|
||||
Blockly.Constants.Text.QUOTE_IMAGE_MIXIN={QUOTE_IMAGE_LEFT_DATAURI:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAAn0lEQVQI1z3OMa5BURSF4f/cQhAKjUQhuQmFNwGJEUi0RKN5rU7FHKhpjEH3TEMtkdBSCY1EIv8r7nFX9e29V7EBAOvu7RPjwmWGH/VuF8CyN9/OAdvqIXYLvtRaNjx9mMTDyo+NjAN1HNcl9ZQ5oQMM3dgDUqDo1l8DzvwmtZN7mnD+PkmLa+4mhrxVA9fRowBWmVBhFy5gYEjKMfz9AylsaRRgGzvZAAAAAElFTkSuQmCC",QUOTE_IMAGE_RIGHT_DATAURI:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAAqUlEQVQI1z3KvUpCcRiA8ef9E4JNHhI0aFEacm1o0BsI0Slx8wa8gLauoDnoBhq7DcfWhggONDmJJgqCPA7neJ7p934EOOKOnM8Q7PDElo/4x4lFb2DmuUjcUzS3URnGib9qaPNbuXvBO3sGPHJDRG6fGVdMSeWDP2q99FQdFrz26Gu5Tq7dFMzUvbXy8KXeAj57cOklgA+u1B5AoslLtGIHQMaCVnwDnADZIFIrXsoXrgAAAABJRU5ErkJggg==",
|
||||
QUOTE_IMAGE_WIDTH:12,QUOTE_IMAGE_HEIGHT:12,quoteField_:function(a){for(var b=0,c;c=this.inputList[b];b++)for(var d=0,e;e=c.fieldRow[d];d++)if(a==e.name){c.insertFieldAt(d,this.newQuote_(!0));c.insertFieldAt(d+2,this.newQuote_(!1));return}console.warn('field named "'+a+'" not found in '+this.toDevString())},newQuote_:function(a){a=this.RTL?!a:a;return new Blockly.FieldImage(a?this.QUOTE_IMAGE_LEFT_DATAURI:this.QUOTE_IMAGE_RIGHT_DATAURI,this.QUOTE_IMAGE_WIDTH,this.QUOTE_IMAGE_HEIGHT,a?"\u201c":"\u201d")}};Blockly.Blocks.variables={};Blockly.Constants.Variables={};Blockly.Constants.Variables.HUE=330;Blockly.Blocks.variables.HUE=Blockly.Constants.Variables.HUE;
|
||||
QUOTE_IMAGE_WIDTH:12,QUOTE_IMAGE_HEIGHT:12,quoteField_:function(a){for(var b=0,c;c=this.inputList[b];b++)for(var d=0,e;e=c.fieldRow[d];d++)if(a==e.name){c.insertFieldAt(d,this.newQuote_(!0));c.insertFieldAt(d+2,this.newQuote_(!1));return}console.warn('field named "'+a+'" not found in '+this.toDevString())},newQuote_:function(a){a=this.RTL?!a:a;return new Blockly.FieldImage(a?this.QUOTE_IMAGE_LEFT_DATAURI:this.QUOTE_IMAGE_RIGHT_DATAURI,this.QUOTE_IMAGE_WIDTH,this.QUOTE_IMAGE_HEIGHT,a?"\u201c":"\u201d")}};
|
||||
Blockly.Constants.Text.TEXT_QUOTES_EXTENSION=function(){this.mixin(Blockly.Constants.Text.QUOTE_IMAGE_MIXIN);this.quoteField_("TEXT")};
|
||||
Blockly.Constants.Text.TEXT_JOIN_MUTATOR_MIXIN={mutationToDom:function(){var a=document.createElement("mutation");a.setAttribute("items",this.itemCount_);return a},domToMutation:function(a){this.itemCount_=parseInt(a.getAttribute("items"),10);this.updateShape_()},decompose:function(a){var b=a.newBlock("text_create_join_container");b.initSvg();for(var c=b.getInput("STACK").connection,d=0;d<this.itemCount_;d++){var e=a.newBlock("text_create_join_item");e.initSvg();c.connect(e.previousConnection);c=
|
||||
e.nextConnection}return b},compose:function(a){var b=a.getInputTargetBlock("STACK");for(a=[];b;)a.push(b.valueConnection_),b=b.nextConnection&&b.nextConnection.targetBlock();for(b=0;b<this.itemCount_;b++){var c=this.getInput("ADD"+b).connection.targetConnection;c&&-1==a.indexOf(c)&&c.disconnect()}this.itemCount_=a.length;this.updateShape_();for(b=0;b<this.itemCount_;b++)Blockly.Mutator.reconnect(a[b],this,"ADD"+b)},saveConnections:function(a){a=a.getInputTargetBlock("STACK");for(var b=0;a;){var c=
|
||||
this.getInput("ADD"+b);a.valueConnection_=c&&c.connection.targetConnection;b++;a=a.nextConnection&&a.nextConnection.targetBlock()}},updateShape_:function(){this.itemCount_&&this.getInput("EMPTY")?this.removeInput("EMPTY"):this.itemCount_||this.getInput("EMPTY")||this.appendDummyInput("EMPTY").appendField(this.newQuote_(!0)).appendField(this.newQuote_(!1));for(var a=0;a<this.itemCount_;a++)if(!this.getInput("ADD"+a)){var b=this.appendValueInput("ADD"+a);0==a&&b.appendField(Blockly.Msg.TEXT_JOIN_TITLE_CREATEWITH)}for(;this.getInput("ADD"+
|
||||
a);)this.removeInput("ADD"+a),a++}};Blockly.Constants.Text.TEXT_JOIN_EXTENSION=function(){this.mixin(Blockly.Constants.Text.QUOTE_IMAGE_MIXIN);this.itemCount_=2;this.updateShape_();this.setMutator(new Blockly.Mutator(["text_create_join_item"]))};Blockly.Constants.Text.TEXT_APPEND_TOOLTIP_EXTENSION=function(){var a=this;this.setTooltip(function(){return Blockly.Msg.TEXT_APPEND_TOOLTIP?Blockly.Msg.TEXT_APPEND_TOOLTIP.replace("%1",a.getFieldValue("VAR")):""})};
|
||||
Blockly.Constants.Text.TEXT_INDEXOF_TOOLTIP_EXTENSION=function(){var a=this;this.setTooltip(function(){return Blockly.Msg.TEXT_INDEXOF_TOOLTIP.replace("%1",a.workspace.options.oneBasedIndex?"0":"-1")})};
|
||||
Blockly.Constants.Text.TEXT_CHARAT_MUTATOR_MIXIN={mutationToDom:function(){var a=document.createElement("mutation"),b=this.getInput("AT").type==Blockly.INPUT_VALUE;a.setAttribute("at",b);return a},domToMutation:function(a){a="false"!=a.getAttribute("at");this.updateAt_(a)},updateAt_:function(a){this.removeInput("AT");this.removeInput("ORDINAL",!0);a?(this.appendValueInput("AT").setCheck("Number"),Blockly.Msg.ORDINAL_NUMBER_SUFFIX&&this.appendDummyInput("ORDINAL").appendField(Blockly.Msg.ORDINAL_NUMBER_SUFFIX)):
|
||||
this.appendDummyInput("AT");Blockly.Msg.TEXT_CHARAT_TAIL&&(this.removeInput("TAIL",!0),this.appendDummyInput("TAIL").appendField(Blockly.Msg.TEXT_CHARAT_TAIL));var b=new Blockly.FieldDropdown(this.WHERE_OPTIONS,function(b){var c="FROM_START"==b||"FROM_END"==b;if(c!=a){var e=this.sourceBlock_;e.updateAt_(c);e.setFieldValue(b,"WHERE");return null}});this.getInput("AT").appendField(b,"WHERE")}};
|
||||
Blockly.Constants.Text.TEXT_CHARAT_EXTENSION=function(){this.WHERE_OPTIONS=[[Blockly.Msg.TEXT_CHARAT_FROM_START,"FROM_START"],[Blockly.Msg.TEXT_CHARAT_FROM_END,"FROM_END"],[Blockly.Msg.TEXT_CHARAT_FIRST,"FIRST"],[Blockly.Msg.TEXT_CHARAT_LAST,"LAST"],[Blockly.Msg.TEXT_CHARAT_RANDOM,"RANDOM"]];this.updateAt_(!0);var a=this;this.setTooltip(function(){var b=a.getFieldValue("WHERE"),c=Blockly.Msg.TEXT_CHARAT_TOOLTIP;("FROM_START"==b||"FROM_END"==b)&&(b="FROM_START"==b?Blockly.Msg.LISTS_INDEX_FROM_START_TOOLTIP:
|
||||
Blockly.Msg.LISTS_INDEX_FROM_END_TOOLTIP)&&(c+=" "+b.replace("%1",a.workspace.options.oneBasedIndex?"#1":"#0"));return c})};Blockly.Extensions.register("text_indexOf_tooltip",Blockly.Constants.Text.TEXT_INDEXOF_TOOLTIP_EXTENSION);Blockly.Extensions.register("text_quotes",Blockly.Constants.Text.TEXT_QUOTES_EXTENSION);Blockly.Extensions.register("text_append_tooltip",Blockly.Constants.Text.TEXT_APPEND_TOOLTIP_EXTENSION);
|
||||
Blockly.Extensions.registerMutator("text_join_mutator",Blockly.Constants.Text.TEXT_JOIN_MUTATOR_MIXIN,Blockly.Constants.Text.TEXT_JOIN_EXTENSION);Blockly.Extensions.registerMutator("text_charAt_mutator",Blockly.Constants.Text.TEXT_CHARAT_MUTATOR_MIXIN,Blockly.Constants.Text.TEXT_CHARAT_EXTENSION);Blockly.Blocks.variables={};Blockly.Constants.Variables={};Blockly.Constants.Variables.HUE=330;Blockly.Blocks.variables.HUE=Blockly.Constants.Variables.HUE;
|
||||
Blockly.defineBlocksWithJsonArray([{type:"variables_get",message0:"%1",args0:[{type:"field_variable",name:"VAR",variable:"%{BKY_VARIABLES_DEFAULT_NAME}"}],output:null,colour:"%{BKY_VARIABLES_HUE}",helpUrl:"%{BKY_VARIABLES_GET_HELPURL}",tooltip:"%{BKY_VARIABLES_GET_TOOLTIP}",extensions:["contextMenu_variableSetterGetter"]},{type:"variables_set",message0:"%{BKY_VARIABLES_SET}",args0:[{type:"field_variable",name:"VAR",variable:"%{BKY_VARIABLES_DEFAULT_NAME}"},{type:"input_value",name:"VALUE"}],previousStatement:null,
|
||||
nextStatement:null,colour:"%{BKY_VARIABLES_HUE}",tooltip:"%{BKY_VARIABLES_SET_TOOLTIP}",helpUrl:"%{BKY_VARIABLES_SET_HELPURL}",extensions:["contextMenu_variableSetterGetter"]}]);
|
||||
Blockly.Constants.Variables.CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN={customContextMenu:function(a){if("variables_get"==this.type)var b="variables_set",c=Blockly.Msg.VARIABLES_GET_CREATE_SET;else b="variables_get",c=Blockly.Msg.VARIABLES_SET_CREATE_GET;var d={enabled:0<this.workspace.remainingCapacity()},e=this.getFieldValue("VAR");d.text=c.replace("%1",e);c=goog.dom.createDom("field",null,e);c.setAttribute("name","VAR");c=goog.dom.createDom("block",null,c);c.setAttribute("type",b);d.callback=
|
||||
Blockly.Constants.Variables.CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN={customContextMenu:function(a){if("variables_get"==this.type){var b="variables_set";var c=Blockly.Msg.VARIABLES_GET_CREATE_SET}else b="variables_get",c=Blockly.Msg.VARIABLES_SET_CREATE_GET;var d={enabled:0<this.workspace.remainingCapacity()},e=this.getFieldValue("VAR");d.text=c.replace("%1",e);c=goog.dom.createDom("field",null,e);c.setAttribute("name","VAR");c=goog.dom.createDom("block",null,c);c.setAttribute("type",b);d.callback=
|
||||
Blockly.ContextMenu.callbackFactory(this,c);a.push(d)}};Blockly.Extensions.registerMixin("contextMenu_variableSetterGetter",Blockly.Constants.Variables.CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN);
|
||||
+136
-51
@@ -16,7 +16,16 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# This script generates two versions of Blockly's core files:
|
||||
# Usage: build.py <0 or more of accessible, core, generators, langfiles>
|
||||
# build.py with no parameters builds all files.
|
||||
# core builds blockly_compressed, blockly_uncompressed, and blocks_compressed.
|
||||
# accessible builds blockly_accessible_compressed,
|
||||
# blockly_accessible_uncompressed, and blocks_compressed.
|
||||
# generators builds every <language>_compressed.js.
|
||||
# langfiles builds every msg/js/<LANG>.js file.
|
||||
|
||||
# This script generates four versions of Blockly's core files. The first pair
|
||||
# are:
|
||||
# blockly_compressed.js
|
||||
# blockly_uncompressed.js
|
||||
# The compressed file is a concatenation of all of Blockly's core files which
|
||||
@@ -28,6 +37,12 @@
|
||||
# been renamed. The uncompressed file also allows for a faster developement
|
||||
# cycle since there is no need to rebuild or recompile, just reload.
|
||||
#
|
||||
# The second pair are:
|
||||
# blockly_accessible_compressed.js
|
||||
# blockly_accessible_uncompressed.js
|
||||
# These files are analogous to blockly_compressed and blockly_uncompressed,
|
||||
# but also include the visually-impaired module for Blockly.
|
||||
#
|
||||
# This script also generates:
|
||||
# blocks_compressed.js: The compressed Blockly language blocks.
|
||||
# javascript_compressed.js: The compressed Javascript generator.
|
||||
@@ -41,6 +56,14 @@ if sys.version_info[0] != 2:
|
||||
raise Exception("Blockly build only compatible with Python 2.x.\n"
|
||||
"You are using: " + sys.version)
|
||||
|
||||
for arg in sys.argv[1:len(sys.argv)]:
|
||||
if (arg != 'core' and
|
||||
arg != 'accessible' and
|
||||
arg != 'generators' and
|
||||
arg != 'langfiles'):
|
||||
raise Exception("Invalid argument: \"" + arg + "\". Usage: build.py <0 or more of accessible," +
|
||||
" core, generators, langfiles>")
|
||||
|
||||
import errno, glob, httplib, json, os, re, subprocess, threading, urllib
|
||||
|
||||
|
||||
@@ -71,13 +94,13 @@ class Gen_uncompressed(threading.Thread):
|
||||
"""Generate a JavaScript file that loads Blockly's raw files.
|
||||
Runs in a separate thread.
|
||||
"""
|
||||
def __init__(self, search_paths):
|
||||
def __init__(self, search_paths, target_filename):
|
||||
threading.Thread.__init__(self)
|
||||
self.search_paths = search_paths
|
||||
self.target_filename = target_filename
|
||||
|
||||
def run(self):
|
||||
target_filename = 'blockly_uncompressed.js'
|
||||
f = open(target_filename, 'w')
|
||||
f = open(self.target_filename, 'w')
|
||||
f.write(HEADER)
|
||||
f.write("""
|
||||
var isNodeJS = !!(typeof module !== 'undefined' && module.exports &&
|
||||
@@ -92,7 +115,7 @@ window.BLOCKLY_DIR = (function() {
|
||||
if (!isNodeJS) {
|
||||
// Find name of current directory.
|
||||
var scripts = document.getElementsByTagName('script');
|
||||
var re = new RegExp('(.+)[\/]blockly_uncompressed\.js$');
|
||||
var re = new RegExp('(.+)[\/]blockly_(.*)uncompressed\.js$');
|
||||
for (var i = 0, script; script = scripts[i]; i++) {
|
||||
var match = re.exec(script.src);
|
||||
if (match) {
|
||||
@@ -122,6 +145,7 @@ window.BLOCKLY_BOOT = function() {
|
||||
base_path = calcdeps.FindClosureBasePath(self.search_paths)
|
||||
for dep in calcdeps.BuildDependenciesFromFiles(self.search_paths):
|
||||
add_dependency.append(calcdeps.GetDepsLine(dep, base_path))
|
||||
add_dependency.sort() # Deterministic build.
|
||||
add_dependency = '\n'.join(add_dependency)
|
||||
# Find the Blockly directory name and replace it with a JS variable.
|
||||
# This allows blockly_uncompressed.js to be compiled on one computer and be
|
||||
@@ -135,7 +159,7 @@ window.BLOCKLY_BOOT = function() {
|
||||
for dep in calcdeps.BuildDependenciesFromFiles(self.search_paths):
|
||||
if not dep.filename.startswith(os.pardir + os.sep): # '../'
|
||||
provides.extend(dep.provides)
|
||||
provides.sort()
|
||||
provides.sort() # Deterministic build.
|
||||
f.write('\n')
|
||||
f.write('// Load Blockly.\n')
|
||||
for provide in provides:
|
||||
@@ -159,7 +183,7 @@ if (isNodeJS) {
|
||||
}
|
||||
""")
|
||||
f.close()
|
||||
print("SUCCESS: " + target_filename)
|
||||
print("SUCCESS: " + self.target_filename)
|
||||
|
||||
|
||||
class Gen_compressed(threading.Thread):
|
||||
@@ -168,18 +192,27 @@ class Gen_compressed(threading.Thread):
|
||||
Uses the Closure Compiler's online API.
|
||||
Runs in a separate thread.
|
||||
"""
|
||||
def __init__(self, search_paths):
|
||||
def __init__(self, search_paths, bundles):
|
||||
threading.Thread.__init__(self)
|
||||
self.search_paths = search_paths
|
||||
self.bundles = bundles
|
||||
|
||||
def run(self):
|
||||
self.gen_core()
|
||||
self.gen_blocks()
|
||||
self.gen_generator("javascript")
|
||||
self.gen_generator("python")
|
||||
self.gen_generator("php")
|
||||
self.gen_generator("dart")
|
||||
self.gen_generator("lua")
|
||||
if ('core' in self.bundles):
|
||||
self.gen_core()
|
||||
|
||||
if ('accessible' in self.bundles):
|
||||
self.gen_accessible()
|
||||
|
||||
if ('core' in self.bundles or 'accessible' in self.bundles):
|
||||
self.gen_blocks()
|
||||
|
||||
if ('generators' in self.bundles):
|
||||
self.gen_generator("javascript")
|
||||
self.gen_generator("python")
|
||||
self.gen_generator("php")
|
||||
self.gen_generator("dart")
|
||||
self.gen_generator("lua")
|
||||
|
||||
def gen_core(self):
|
||||
target_filename = "blockly_compressed.js"
|
||||
@@ -197,6 +230,62 @@ class Gen_compressed(threading.Thread):
|
||||
# Read in all the source files.
|
||||
filenames = calcdeps.CalculateDependencies(self.search_paths,
|
||||
[os.path.join("core", "blockly.js")])
|
||||
filenames.sort() # Deterministic build.
|
||||
for filename in filenames:
|
||||
# Filter out the Closure files (the compiler will add them).
|
||||
if filename.startswith(os.pardir + os.sep): # '../'
|
||||
continue
|
||||
f = open(filename)
|
||||
params.append(("js_code", "".join(f.readlines())))
|
||||
f.close()
|
||||
|
||||
self.do_compile(params, target_filename, filenames, "")
|
||||
|
||||
def gen_accessible(self):
|
||||
target_filename = "blockly_accessible_compressed.js"
|
||||
# Define the parameters for the POST request.
|
||||
params = [
|
||||
("compilation_level", "SIMPLE_OPTIMIZATIONS"),
|
||||
("use_closure_library", "true"),
|
||||
("language_out", "ES5"),
|
||||
("output_format", "json"),
|
||||
("output_info", "compiled_code"),
|
||||
("output_info", "warnings"),
|
||||
("output_info", "errors"),
|
||||
("output_info", "statistics"),
|
||||
]
|
||||
|
||||
# Read in all the source files.
|
||||
filenames = calcdeps.CalculateDependencies(self.search_paths,
|
||||
[os.path.join("accessible", "app.component.js")])
|
||||
filenames.sort() # Deterministic build.
|
||||
for filename in filenames:
|
||||
# Filter out the Closure files (the compiler will add them).
|
||||
if filename.startswith(os.pardir + os.sep): # '../'
|
||||
continue
|
||||
f = open(filename)
|
||||
params.append(("js_code", "".join(f.readlines())))
|
||||
f.close()
|
||||
|
||||
self.do_compile(params, target_filename, filenames, "")
|
||||
|
||||
def gen_accessible(self):
|
||||
target_filename = "blockly_accessible_compressed.js"
|
||||
# Define the parameters for the POST request.
|
||||
params = [
|
||||
("compilation_level", "SIMPLE_OPTIMIZATIONS"),
|
||||
("use_closure_library", "true"),
|
||||
("language_out", "ES5"),
|
||||
("output_format", "json"),
|
||||
("output_info", "compiled_code"),
|
||||
("output_info", "warnings"),
|
||||
("output_info", "errors"),
|
||||
("output_info", "statistics"),
|
||||
]
|
||||
|
||||
# Read in all the source files.
|
||||
filenames = calcdeps.CalculateDependencies(self.search_paths,
|
||||
[os.path.join("accessible", "app.component.js")])
|
||||
for filename in filenames:
|
||||
# Filter out the Closure files (the compiler will add them).
|
||||
if filename.startswith(os.pardir + os.sep): # '../'
|
||||
@@ -223,6 +312,7 @@ class Gen_compressed(threading.Thread):
|
||||
# Add Blockly.Blocks to be compatible with the compiler.
|
||||
params.append(("js_code", "goog.provide('Blockly.Blocks');"))
|
||||
filenames = glob.glob(os.path.join("blocks", "*.js"))
|
||||
filenames.sort() # Deterministic build.
|
||||
for filename in filenames:
|
||||
f = open(filename)
|
||||
params.append(("js_code", "".join(f.readlines())))
|
||||
@@ -249,6 +339,7 @@ class Gen_compressed(threading.Thread):
|
||||
params.append(("js_code", "goog.provide('Blockly.Generator');"))
|
||||
filenames = glob.glob(
|
||||
os.path.join("generators", language, "*.js"))
|
||||
filenames.sort() # Deterministic build.
|
||||
filenames.insert(0, os.path.join("generators", language + ".js"))
|
||||
for filename in filenames:
|
||||
f = open(filename)
|
||||
@@ -313,31 +404,6 @@ class Gen_compressed(threading.Thread):
|
||||
|
||||
code = HEADER + "\n" + json_data["compiledCode"]
|
||||
code = code.replace(remove, "")
|
||||
|
||||
# Trim down Google's Apache licences.
|
||||
# The Closure Compiler used to preserve these until August 2015.
|
||||
# Delete this in a few months if the licences don't return.
|
||||
LICENSE = re.compile("""/\\*
|
||||
|
||||
[\w ]+
|
||||
|
||||
(Copyright \\d+ Google Inc.)
|
||||
https://developers.google.com/blockly/
|
||||
|
||||
Licensed under the Apache License, Version 2.0 \(the "License"\);
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
\\*/""")
|
||||
code = re.sub(LICENSE, r"\n// \1 Apache License 2.0", code)
|
||||
|
||||
stats = json_data["statistics"]
|
||||
original_b = stats["originalSize"]
|
||||
compressed_b = stats["compressedSize"]
|
||||
@@ -362,8 +428,9 @@ class Gen_langfiles(threading.Thread):
|
||||
Runs in a separate thread.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, force_gen):
|
||||
threading.Thread.__init__(self)
|
||||
self.force_gen = force_gen
|
||||
|
||||
def _rebuild(self, srcs, dests):
|
||||
# Determine whether any of the files in srcs is newer than any in dests.
|
||||
@@ -385,9 +452,10 @@ class Gen_langfiles(threading.Thread):
|
||||
|
||||
def run(self):
|
||||
# The files msg/json/{en,qqq,synonyms}.json depend on msg/messages.js.
|
||||
if self._rebuild([os.path.join("msg", "messages.js")],
|
||||
[os.path.join("msg", "json", f) for f in
|
||||
["en.json", "qqq.json", "synonyms.json"]]):
|
||||
if (self.force_gen or
|
||||
self._rebuild([os.path.join("msg", "messages.js")],
|
||||
[os.path.join("msg", "json", f) for f in
|
||||
["en.json", "qqq.json", "synonyms.json"]])):
|
||||
try:
|
||||
subprocess.check_call([
|
||||
"python",
|
||||
@@ -456,14 +524,31 @@ if __name__ == "__main__":
|
||||
developers.google.com/blockly/guides/modify/web/closure""")
|
||||
sys.exit(1)
|
||||
|
||||
search_paths = calcdeps.ExpandDirectories(
|
||||
core_search_paths = calcdeps.ExpandDirectories(
|
||||
["core", os.path.join(os.path.pardir, "closure-library")])
|
||||
core_search_paths.sort() # Deterministic build.
|
||||
full_search_paths = calcdeps.ExpandDirectories(
|
||||
["accessible", "core", os.path.join(os.path.pardir, "closure-library")])
|
||||
full_search_paths.sort() # Deterministic build.
|
||||
|
||||
# Run both tasks in parallel threads.
|
||||
if (len(sys.argv) == 1):
|
||||
args = ['core', 'accessible', 'generators', 'defaultlangfiles']
|
||||
else:
|
||||
args = sys.argv
|
||||
|
||||
# Uncompressed and compressed are run in parallel threads.
|
||||
# Uncompressed is limited by processor speed.
|
||||
# Compressed is limited by network and server speed.
|
||||
Gen_uncompressed(search_paths).start()
|
||||
Gen_compressed(search_paths).start()
|
||||
if ('core' in args):
|
||||
Gen_uncompressed(core_search_paths, 'blockly_uncompressed.js').start()
|
||||
|
||||
# This is run locally in a separate thread.
|
||||
Gen_langfiles().start()
|
||||
if ('accessible' in args):
|
||||
Gen_uncompressed(full_search_paths, 'blockly_accessible_uncompressed.js').start()
|
||||
|
||||
# Compressed is limited by network and server speed.
|
||||
Gen_compressed(full_search_paths, args).start()
|
||||
|
||||
# This is run locally in a separate thread
|
||||
# defaultlangfiles checks for changes in the msg files, while manually asking
|
||||
# to build langfiles will force the messages to be rebuilt.
|
||||
if ('langfiles' in args or 'defaultlangfiles' in args):
|
||||
Gen_langfiles('langfiles' in args).start()
|
||||
|
||||
+29
-54
@@ -119,6 +119,8 @@ Blockly.Block = function(workspace, prototypeName, opt_id) {
|
||||
this.comment = null;
|
||||
|
||||
/**
|
||||
* The block's position in workspace units. (0, 0) is at the workspace's
|
||||
* origin; scale does not change this value.
|
||||
* @type {!goog.math.Coordinate}
|
||||
* @private
|
||||
*/
|
||||
@@ -154,7 +156,7 @@ Blockly.Block = function(workspace, prototypeName, opt_id) {
|
||||
/** @type {boolean|undefined} */
|
||||
this.inputsInlineDefault = this.inputsInline;
|
||||
if (Blockly.Events.isEnabled()) {
|
||||
Blockly.Events.fire(new Blockly.Events.Create(this));
|
||||
Blockly.Events.fire(new Blockly.Events.BlockCreate(this));
|
||||
}
|
||||
// Bind an onchange function, if it exists.
|
||||
if (goog.isFunction(this.onchange)) {
|
||||
@@ -207,7 +209,7 @@ Blockly.Block.prototype.dispose = function(healStack) {
|
||||
}
|
||||
this.unplug(healStack);
|
||||
if (Blockly.Events.isEnabled()) {
|
||||
Blockly.Events.fire(new Blockly.Events.Delete(this));
|
||||
Blockly.Events.fire(new Blockly.Events.BlockDelete(this));
|
||||
}
|
||||
Blockly.Events.disable();
|
||||
|
||||
@@ -309,7 +311,7 @@ Blockly.Block.prototype.getConnections_ = function() {
|
||||
/**
|
||||
* Walks down a stack of blocks and finds the last next connection on the stack.
|
||||
* @return {Blockly.Connection} The last next connection on the stack, or null.
|
||||
* @private
|
||||
* @package
|
||||
*/
|
||||
Blockly.Block.prototype.lastConnectionInStack_ = function() {
|
||||
var nextConnection = this.nextConnection;
|
||||
@@ -330,43 +332,9 @@ Blockly.Block.prototype.lastConnectionInStack_ = function() {
|
||||
* connected should not coincidentally line up on screen.
|
||||
* @private
|
||||
*/
|
||||
// TODO: Refactor to return early in headless mode.
|
||||
Blockly.Block.prototype.bumpNeighbours_ = function() {
|
||||
if (!this.workspace) {
|
||||
return; // Deleted block.
|
||||
}
|
||||
if (Blockly.dragMode_ != Blockly.DRAG_NONE) {
|
||||
return; // Don't bump blocks during a drag.
|
||||
}
|
||||
var rootBlock = this.getRootBlock();
|
||||
if (rootBlock.isInFlyout) {
|
||||
return; // Don't move blocks around in a flyout.
|
||||
}
|
||||
// Loop through every connection on this block.
|
||||
var myConnections = this.getConnections_(false);
|
||||
for (var i = 0, connection; connection = myConnections[i]; i++) {
|
||||
// Spider down from this block bumping all sub-blocks.
|
||||
if (connection.isConnected() && connection.isSuperior()) {
|
||||
connection.targetBlock().bumpNeighbours_();
|
||||
}
|
||||
|
||||
var neighbours = connection.neighbours_(Blockly.SNAP_RADIUS);
|
||||
for (var j = 0, otherConnection; otherConnection = neighbours[j]; j++) {
|
||||
// If both connections are connected, that's probably fine. But if
|
||||
// either one of them is unconnected, then there could be confusion.
|
||||
if (!connection.isConnected() || !otherConnection.isConnected()) {
|
||||
// Only bump blocks if they are from different tree structures.
|
||||
if (otherConnection.getSourceBlock().getRootBlock() != rootBlock) {
|
||||
// Always bump the inferior block.
|
||||
if (connection.isSuperior()) {
|
||||
otherConnection.bumpAwayFrom_(connection);
|
||||
} else {
|
||||
connection.bumpAwayFrom_(otherConnection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
console.warn('Not expected to reach this bumpNeighbours_ function. The ' +
|
||||
'BlockSvg function for bumpNeighbours_ was expected to be called instead.');
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -825,7 +793,7 @@ Blockly.Block.prototype.setOutput = function(newBoolean, opt_check) {
|
||||
*/
|
||||
Blockly.Block.prototype.setInputsInline = function(newBoolean) {
|
||||
if (this.inputsInline != newBoolean) {
|
||||
Blockly.Events.fire(new Blockly.Events.Change(
|
||||
Blockly.Events.fire(new Blockly.Events.BlockChange(
|
||||
this, 'inline', null, this.inputsInline, newBoolean));
|
||||
this.inputsInline = newBoolean;
|
||||
}
|
||||
@@ -864,7 +832,7 @@ Blockly.Block.prototype.getInputsInline = function() {
|
||||
*/
|
||||
Blockly.Block.prototype.setDisabled = function(disabled) {
|
||||
if (this.disabled != disabled) {
|
||||
Blockly.Events.fire(new Blockly.Events.Change(
|
||||
Blockly.Events.fire(new Blockly.Events.BlockChange(
|
||||
this, 'disabled', null, this.disabled, disabled));
|
||||
this.disabled = disabled;
|
||||
}
|
||||
@@ -901,7 +869,7 @@ Blockly.Block.prototype.isCollapsed = function() {
|
||||
*/
|
||||
Blockly.Block.prototype.setCollapsed = function(collapsed) {
|
||||
if (this.collapsed_ != collapsed) {
|
||||
Blockly.Events.fire(new Blockly.Events.Change(
|
||||
Blockly.Events.fire(new Blockly.Events.BlockChange(
|
||||
this, 'collapsed', null, this.collapsed_, collapsed));
|
||||
this.collapsed_ = collapsed;
|
||||
}
|
||||
@@ -1099,10 +1067,14 @@ Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) {
|
||||
for (var i = 0; i < tokens.length; i++) {
|
||||
var token = tokens[i];
|
||||
if (typeof token == 'number') {
|
||||
goog.asserts.assert(token > 0 && token <= args.length,
|
||||
'Message index %%s out of range.', token);
|
||||
goog.asserts.assert(!indexDup[token],
|
||||
'Message index %%s duplicated.', token);
|
||||
if (token <= 0 || token > args.length) {
|
||||
throw new Error('Block \"' + this.type + '\": ' +
|
||||
'Message index %' + token + ' out of range.');
|
||||
}
|
||||
if (indexDup[token]) {
|
||||
throw new Error('Block \"' + this.type + '\": ' +
|
||||
'Message index %' + token + ' duplicated.');
|
||||
}
|
||||
indexDup[token] = true;
|
||||
indexCount++;
|
||||
elements.push(args[token - 1]);
|
||||
@@ -1113,8 +1085,10 @@ Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) {
|
||||
}
|
||||
}
|
||||
}
|
||||
goog.asserts.assert(indexCount == args.length,
|
||||
'block "%s": Message does not reference all %s arg(s).', this.type, args.length);
|
||||
if(indexCount != args.length) {
|
||||
throw new Error('Block \"' + this.type + '\": ' +
|
||||
'Message does not reference all ' + args.length + ' arg(s).');
|
||||
}
|
||||
// Add last dummy input if needed.
|
||||
if (elements.length && (typeof elements[elements.length - 1] == 'string' ||
|
||||
goog.string.startsWith(elements[elements.length - 1]['type'],
|
||||
@@ -1271,7 +1245,8 @@ Blockly.Block.newFieldTextInputFromJson_ = function(options) {
|
||||
*/
|
||||
Blockly.Block.newFieldVariableFromJson_ = function(options) {
|
||||
var varname = Blockly.utils.replaceMessageReferences(options['variable']);
|
||||
return new Blockly.FieldVariable(varname);
|
||||
var variableTypes = options['variableTypes'];
|
||||
return new Blockly.FieldVariable(varname, null, variableTypes);
|
||||
};
|
||||
|
||||
|
||||
@@ -1421,7 +1396,7 @@ Blockly.Block.prototype.getCommentText = function() {
|
||||
*/
|
||||
Blockly.Block.prototype.setCommentText = function(text) {
|
||||
if (this.comment != text) {
|
||||
Blockly.Events.fire(new Blockly.Events.Change(
|
||||
Blockly.Events.fire(new Blockly.Events.BlockChange(
|
||||
this, 'comment', null, this.comment, text || ''));
|
||||
this.comment = text;
|
||||
}
|
||||
@@ -1445,7 +1420,7 @@ Blockly.Block.prototype.setMutator = function(/* mutator */) {
|
||||
|
||||
/**
|
||||
* Return the coordinates of the top-left corner of this block relative to the
|
||||
* drawing surface's origin (0,0).
|
||||
* drawing surface's origin (0,0), in workspace units.
|
||||
* @return {!goog.math.Coordinate} Object with .x and .y properties.
|
||||
*/
|
||||
Blockly.Block.prototype.getRelativeToSurfaceXY = function() {
|
||||
@@ -1454,12 +1429,12 @@ Blockly.Block.prototype.getRelativeToSurfaceXY = function() {
|
||||
|
||||
/**
|
||||
* Move a block by a relative offset.
|
||||
* @param {number} dx Horizontal offset.
|
||||
* @param {number} dy Vertical offset.
|
||||
* @param {number} dx Horizontal offset, in workspace units.
|
||||
* @param {number} dy Vertical offset, in workspace units.
|
||||
*/
|
||||
Blockly.Block.prototype.moveBy = function(dx, dy) {
|
||||
goog.asserts.assert(!this.parentBlock_, 'Block has parent.');
|
||||
var event = new Blockly.Events.Move(this);
|
||||
var event = new Blockly.Events.BlockMove(this);
|
||||
this.xy_.translate(dx, dy);
|
||||
event.recordNew();
|
||||
Blockly.Events.fire(event);
|
||||
|
||||
@@ -80,6 +80,15 @@ Blockly.BlockDragSurfaceSvg.prototype.container_ = null;
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.scale_ = 1;
|
||||
|
||||
/**
|
||||
* Cached value for the translation of the drag surface.
|
||||
* This translation is in pixel units, because the scale is applied to the
|
||||
* drag group rather than the top-level SVG.
|
||||
* @type {goog.math.Coordinate}
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.surfaceXY_ = null;
|
||||
|
||||
/**
|
||||
* Create the drag surface and inject it into the container.
|
||||
*/
|
||||
@@ -109,13 +118,14 @@ Blockly.BlockDragSurfaceSvg.prototype.setBlocksAndShow = function(blocks) {
|
||||
// appendChild removes the blocks from the previous parent
|
||||
this.dragGroup_.appendChild(blocks);
|
||||
this.SVG_.style.display = 'block';
|
||||
this.surfaceXY_ = new goog.math.Coordinate(0, 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Translate and scale the entire drag surface group to keep in sync with the
|
||||
* workspace.
|
||||
* @param {number} x X translation
|
||||
* @param {number} y Y translation
|
||||
* Translate and scale the entire drag surface group to the given position, to
|
||||
* keep in sync with the workspace.
|
||||
* @param {number} x X translation in workspace coordinates.
|
||||
* @param {number} y Y translation in workspace coordinates.
|
||||
* @param {number} scale Scale of the group.
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.translateAndScaleGroup = function(x, y, scale) {
|
||||
@@ -128,6 +138,23 @@ Blockly.BlockDragSurfaceSvg.prototype.translateAndScaleGroup = function(x, y, sc
|
||||
' scale(' + scale + ')');
|
||||
};
|
||||
|
||||
/**
|
||||
* Translate the drag surface's SVG based on its internal state.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.translateSurfaceInternal_ = function() {
|
||||
var x = this.surfaceXY_.x;
|
||||
var y = this.surfaceXY_.y;
|
||||
// This is a work-around to prevent a the blocks from rendering
|
||||
// fuzzy while they are being dragged on the drag surface.
|
||||
x = x.toFixed(0);
|
||||
y = y.toFixed(0);
|
||||
this.SVG_.style.display = 'block';
|
||||
|
||||
Blockly.utils.setCssTransform(this.SVG_,
|
||||
'translate3d(' + x + 'px, ' + y + 'px, 0px)');
|
||||
};
|
||||
|
||||
/**
|
||||
* Translate the entire drag surface during a drag.
|
||||
* We translate the drag surface instead of the blocks inside the surface
|
||||
@@ -137,15 +164,8 @@ Blockly.BlockDragSurfaceSvg.prototype.translateAndScaleGroup = function(x, y, sc
|
||||
* @param {number} y Y translation for the entire surface.
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.translateSurface = function(x, y) {
|
||||
x *= this.scale_;
|
||||
y *= this.scale_;
|
||||
// This is a work-around to prevent a the blocks from rendering
|
||||
// fuzzy while they are being dragged on the drag surface.
|
||||
x = x.toFixed(0);
|
||||
y = y.toFixed(0);
|
||||
this.SVG_.style.display = 'block';
|
||||
Blockly.utils.setCssTransform(this.SVG_,
|
||||
'translate3d(' + x + 'px, ' + y + 'px, 0px)');
|
||||
this.surfaceXY_ = new goog.math.Coordinate(x * this.scale_, y * this.scale_);
|
||||
this.translateSurfaceInternal_();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -180,12 +200,21 @@ Blockly.BlockDragSurfaceSvg.prototype.getCurrentBlock = function() {
|
||||
/**
|
||||
* Clear the group and hide the surface; move the blocks off onto the provided
|
||||
* element.
|
||||
* @param {!Element} newSurface Surface the dragging blocks should be moved to.
|
||||
* If the block is being deleted it doesn't need to go back to the original
|
||||
* surface, since it would be removed immediately during dispose.
|
||||
* @param {Element} opt_newSurface Surface the dragging blocks should be moved
|
||||
* to, or null if the blocks should be removed from this surface without
|
||||
* being moved to a different surface.
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.clearAndHide = function(newSurface) {
|
||||
// appendChild removes the node from this.dragGroup_
|
||||
newSurface.appendChild(this.getCurrentBlock());
|
||||
Blockly.BlockDragSurfaceSvg.prototype.clearAndHide = function(opt_newSurface) {
|
||||
if (opt_newSurface) {
|
||||
// appendChild removes the node from this.dragGroup_
|
||||
opt_newSurface.appendChild(this.getCurrentBlock());
|
||||
} else {
|
||||
this.dragGroup_.removeChild(this.getCurrentBlock());
|
||||
}
|
||||
this.SVG_.style.display = 'none';
|
||||
goog.asserts.assert(this.dragGroup_.childNodes.length == 0,
|
||||
'Drag group was not cleared.');
|
||||
this.surfaceXY_ = null;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,324 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2017 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Methods for dragging a block visually.
|
||||
* @author fenichel@google.com (Rachel Fenichel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.BlockDragger');
|
||||
|
||||
goog.require('Blockly.DraggedConnectionManager');
|
||||
|
||||
goog.require('goog.math.Coordinate');
|
||||
goog.require('goog.asserts');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a block dragger. It moves blocks around the workspace when they
|
||||
* are being dragged by a mouse or touch.
|
||||
* @param {!Blockly.Block} block The block to drag.
|
||||
* @param {!Blockly.WorkspaceSvg} workspace The workspace to drag on.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.BlockDragger = function(block, workspace) {
|
||||
/**
|
||||
* The top block in the stack that is being dragged.
|
||||
* @type {!Blockly.BlockSvg}
|
||||
* @private
|
||||
*/
|
||||
this.draggingBlock_ = block;
|
||||
|
||||
/**
|
||||
* The workspace on which the block is being dragged.
|
||||
* @type {!Blockly.WorkspaceSvg}
|
||||
* @private
|
||||
*/
|
||||
this.workspace_ = workspace;
|
||||
|
||||
/**
|
||||
* Object that keeps track of connections on dragged blocks.
|
||||
* @type {!Blockly.DraggedConnectionManager}
|
||||
* @private
|
||||
*/
|
||||
this.draggedConnectionManager_ = new Blockly.DraggedConnectionManager(
|
||||
this.draggingBlock_);
|
||||
|
||||
/**
|
||||
* Which delete area the mouse pointer is over, if any.
|
||||
* One of {@link Blockly.DELETE_AREA_TRASH},
|
||||
* {@link Blockly.DELETE_AREA_TOOLBOX}, or {@link Blockly.DELETE_AREA_NONE}.
|
||||
* @type {?number}
|
||||
* @private
|
||||
*/
|
||||
this.deleteArea_ = null;
|
||||
|
||||
/**
|
||||
* Whether the block would be deleted if dropped immediately.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.wouldDeleteBlock_ = false;
|
||||
|
||||
/**
|
||||
* The location of the top left corner of the dragging block at the beginning
|
||||
* of the drag in workspace coordinates.
|
||||
* @type {!goog.math.Coordinate}
|
||||
* @private
|
||||
*/
|
||||
this.startXY_ = this.draggingBlock_.getRelativeToSurfaceXY();
|
||||
|
||||
/**
|
||||
* A list of all of the icons (comment, warning, and mutator) that are
|
||||
* on this block and its descendants. Moving an icon moves the bubble that
|
||||
* extends from it if that bubble is open.
|
||||
* @type {Array.<!Object>}
|
||||
* @private
|
||||
*/
|
||||
this.dragIconData_ = Blockly.BlockDragger.initIconData_(block);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sever all links from this object.
|
||||
* @package
|
||||
*/
|
||||
Blockly.BlockDragger.prototype.dispose = function() {
|
||||
this.draggingBlock_ = null;
|
||||
this.workspace_ = null;
|
||||
this.startWorkspace_ = null;
|
||||
this.dragIconData_.length = 0;
|
||||
|
||||
if (this.draggedConnectionManager_) {
|
||||
this.draggedConnectionManager_.dispose();
|
||||
this.draggedConnectionManager_ = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Make a list of all of the icons (comment, warning, and mutator) that are
|
||||
* on this block and its descendants. Moving an icon moves the bubble that
|
||||
* extends from it if that bubble is open.
|
||||
* @param {!Blockly.BlockSvg} block The root block that is being dragged.
|
||||
* @return {!Array.<!Object>} The list of all icons and their locations.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragger.initIconData_ = function(block) {
|
||||
// Build a list of icons that need to be moved and where they started.
|
||||
var dragIconData = [];
|
||||
var descendants = block.getDescendants();
|
||||
for (var i = 0, descendant; descendant = descendants[i]; i++) {
|
||||
var icons = descendant.getIcons();
|
||||
for (var j = 0; j < icons.length; j++) {
|
||||
var data = {
|
||||
// goog.math.Coordinate with x and y properties (workspace coordinates).
|
||||
location: icons[j].getIconLocation(),
|
||||
// Blockly.Icon
|
||||
icon: icons[j]
|
||||
};
|
||||
dragIconData.push(data);
|
||||
}
|
||||
}
|
||||
return dragIconData;
|
||||
};
|
||||
|
||||
/**
|
||||
* Start dragging a block. This includes moving it to the drag surface.
|
||||
* @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has
|
||||
* moved from the position at mouse down, in pixel units.
|
||||
* @package
|
||||
*/
|
||||
Blockly.BlockDragger.prototype.startBlockDrag = function(currentDragDeltaXY) {
|
||||
if (!Blockly.Events.getGroup()) {
|
||||
Blockly.Events.setGroup(true);
|
||||
}
|
||||
|
||||
this.workspace_.setResizesEnabled(false);
|
||||
Blockly.BlockSvg.disconnectUiStop_();
|
||||
|
||||
if (this.draggingBlock_.getParent()) {
|
||||
this.draggingBlock_.unplug();
|
||||
var delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
|
||||
var newLoc = goog.math.Coordinate.sum(this.startXY_, delta);
|
||||
|
||||
this.draggingBlock_.translate(newLoc.x, newLoc.y);
|
||||
this.draggingBlock_.disconnectUiEffect();
|
||||
}
|
||||
this.draggingBlock_.setDragging(true);
|
||||
// For future consideration: we may be able to put moveToDragSurface inside
|
||||
// the block dragger, which would also let the block not track the block drag
|
||||
// surface.
|
||||
this.draggingBlock_.moveToDragSurface_();
|
||||
|
||||
if (this.workspace_.toolbox_) {
|
||||
this.workspace_.toolbox_.addDeleteStyle();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute a step of block dragging, based on the given event. Update the
|
||||
* display accordingly.
|
||||
* @param {!Event} e The most recent move event.
|
||||
* @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has
|
||||
* moved from the position at the start of the drag, in pixel units.
|
||||
* @package
|
||||
*/
|
||||
Blockly.BlockDragger.prototype.dragBlock = function(e, currentDragDeltaXY) {
|
||||
var delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
|
||||
var newLoc = goog.math.Coordinate.sum(this.startXY_, delta);
|
||||
|
||||
this.draggingBlock_.moveDuringDrag(newLoc);
|
||||
this.dragIcons_(delta);
|
||||
|
||||
this.deleteArea_ = this.workspace_.isDeleteArea(e);
|
||||
this.draggedConnectionManager_.update(delta, this.deleteArea_);
|
||||
|
||||
this.updateCursorDuringBlockDrag_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Finish a block drag and put the block back on the workspace.
|
||||
* @param {!Event} e The mouseup/touchend event.
|
||||
* @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has
|
||||
* moved from the position at the start of the drag, in pixel units.
|
||||
* @package
|
||||
*/
|
||||
Blockly.BlockDragger.prototype.endBlockDrag = function(e, currentDragDeltaXY) {
|
||||
// Make sure internal state is fresh.
|
||||
this.dragBlock(e, currentDragDeltaXY);
|
||||
this.dragIconData_ = [];
|
||||
|
||||
Blockly.BlockSvg.disconnectUiStop_();
|
||||
|
||||
var delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
|
||||
var newLoc = goog.math.Coordinate.sum(this.startXY_, delta);
|
||||
this.draggingBlock_.moveOffDragSurface_(newLoc);
|
||||
|
||||
var deleted = this.maybeDeleteBlock_();
|
||||
if (!deleted) {
|
||||
// These are expensive and don't need to be done if we're deleting.
|
||||
this.draggingBlock_.moveConnections_(delta.x, delta.y);
|
||||
this.draggingBlock_.setDragging(false);
|
||||
this.draggedConnectionManager_.applyConnections();
|
||||
this.draggingBlock_.render();
|
||||
this.fireMoveEvent_();
|
||||
this.draggingBlock_.scheduleSnapAndBump();
|
||||
}
|
||||
this.workspace_.setResizesEnabled(true);
|
||||
|
||||
if (this.workspace_.toolbox_) {
|
||||
this.workspace_.toolbox_.removeDeleteStyle();
|
||||
}
|
||||
Blockly.Events.setGroup(false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Fire a move event at the end of a block drag.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragger.prototype.fireMoveEvent_ = function() {
|
||||
var event = new Blockly.Events.BlockMove(this.draggingBlock_);
|
||||
event.oldCoordinate = this.startXY_;
|
||||
event.recordNew();
|
||||
Blockly.Events.fire(event);
|
||||
};
|
||||
|
||||
/**
|
||||
* Shut the trash can and, if necessary, delete the dragging block.
|
||||
* Should be called at the end of a block drag.
|
||||
* @return {boolean} whether the block was deleted.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragger.prototype.maybeDeleteBlock_ = function() {
|
||||
var trashcan = this.workspace_.trashcan;
|
||||
|
||||
if (this.wouldDeleteBlock_) {
|
||||
if (trashcan) {
|
||||
goog.Timer.callOnce(trashcan.close, 100, trashcan);
|
||||
}
|
||||
// Fire a move event, so we know where to go back to for an undo.
|
||||
this.fireMoveEvent_();
|
||||
this.draggingBlock_.dispose(false, true);
|
||||
} else if (trashcan) {
|
||||
// Make sure the trash can is closed.
|
||||
trashcan.close();
|
||||
}
|
||||
return this.wouldDeleteBlock_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the cursor (and possibly the trash can lid) to reflect whether the
|
||||
* dragging block would be deleted if released immediately.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragger.prototype.updateCursorDuringBlockDrag_ = function() {
|
||||
this.wouldDeleteBlock_ = this.draggedConnectionManager_.wouldDeleteBlock();
|
||||
var trashcan = this.workspace_.trashcan;
|
||||
if (this.wouldDeleteBlock_) {
|
||||
this.draggingBlock_.setDeleteStyle(true);
|
||||
if (this.deleteArea_ == Blockly.DELETE_AREA_TRASH && trashcan) {
|
||||
trashcan.setOpen_(true);
|
||||
}
|
||||
} else {
|
||||
this.draggingBlock_.setDeleteStyle(false);
|
||||
if (trashcan) {
|
||||
trashcan.setOpen_(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a coordinate object from pixels to workspace units, including a
|
||||
* correction for mutator workspaces.
|
||||
* This function does not consider differing origins. It simply scales the
|
||||
* input's x and y values.
|
||||
* @param {!goog.math.Coordinate} pixelCoord A coordinate with x and y values
|
||||
* in css pixel units.
|
||||
* @return {!goog.math.Coordinate} The input coordinate divided by the workspace
|
||||
* scale.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragger.prototype.pixelsToWorkspaceUnits_ = function(pixelCoord) {
|
||||
var result = new goog.math.Coordinate(pixelCoord.x / this.workspace_.scale,
|
||||
pixelCoord.y / this.workspace_.scale);
|
||||
if (this.workspace_.isMutator) {
|
||||
// If we're in a mutator, its scale is always 1, purely because of some
|
||||
// oddities in our rendering optimizations. The actual scale is the same as
|
||||
// the scale on the parent workspace.
|
||||
// Fix that for dragging.
|
||||
var mainScale = this.workspace_.options.parentWorkspace.scale;
|
||||
result = result.scale(1 / mainScale);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Move all of the icons connected to this drag.
|
||||
* @param {!goog.math.Coordinate} dxy How far to move the icons from their
|
||||
* original positions, in workspace units.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragger.prototype.dragIcons_ = function(dxy) {
|
||||
// Moving icons moves their associated bubbles.
|
||||
for (var i = 0; i < this.dragIconData_.length; i++) {
|
||||
var data = this.dragIconData_[i];
|
||||
data.icon.setIconLocation(goog.math.Coordinate.sum(data.location, dxy));
|
||||
}
|
||||
};
|
||||
@@ -255,6 +255,28 @@ Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_LTR =
|
||||
Blockly.BlockSvg.DISTANCE_45_OUTSIDE) + ',' +
|
||||
(Blockly.BlockSvg.DISTANCE_45_OUTSIDE + 0.5);
|
||||
|
||||
/**
|
||||
* Returns a bounding box describing the dimensions of this block
|
||||
* and any blocks stacked below it.
|
||||
* @return {!{height: number, width: number}} Object with height and width
|
||||
* properties in workspace units.
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.getHeightWidth = function() {
|
||||
var height = this.height;
|
||||
var width = this.width;
|
||||
// Recursively add size of subsequent blocks.
|
||||
var nextBlock = this.getNextBlock();
|
||||
if (nextBlock) {
|
||||
var nextHeightWidth = nextBlock.getHeightWidth();
|
||||
height += nextHeightWidth.height - 4; // Height of tab.
|
||||
width = Math.max(width, nextHeightWidth.width);
|
||||
} else if (!this.nextConnection && !this.outputConnection) {
|
||||
// Add a bit of margin under blocks with no bottom tab.
|
||||
height += 2;
|
||||
}
|
||||
return {height: height, width: width};
|
||||
};
|
||||
|
||||
/**
|
||||
* Render the block.
|
||||
* Lays out and reflows a block based on its contents and settings.
|
||||
|
||||
+169
-398
@@ -28,6 +28,7 @@ goog.provide('Blockly.BlockSvg');
|
||||
|
||||
goog.require('Blockly.Block');
|
||||
goog.require('Blockly.ContextMenu');
|
||||
goog.require('Blockly.Grid');
|
||||
goog.require('Blockly.RenderedConnection');
|
||||
goog.require('Blockly.Touch');
|
||||
goog.require('Blockly.utils');
|
||||
@@ -56,6 +57,7 @@ Blockly.BlockSvg = function(workspace, prototypeName, opt_id) {
|
||||
* @private
|
||||
*/
|
||||
this.svgGroup_ = Blockly.utils.createSvgElement('g', {}, null);
|
||||
this.svgGroup_.translate_ = '';
|
||||
|
||||
/**
|
||||
* @type {SVGElement}
|
||||
@@ -89,7 +91,7 @@ Blockly.BlockSvg = function(workspace, prototypeName, opt_id) {
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.useDragSurface_ = Blockly.utils.is3dSupported() && workspace.blockDragSurface_;
|
||||
this.useDragSurface_ = Blockly.utils.is3dSupported() && !!workspace.blockDragSurface_;
|
||||
|
||||
Blockly.Tooltip.bindMouseEvents(this.svgPath_);
|
||||
Blockly.BlockSvg.superClass_.constructor.call(this,
|
||||
@@ -99,10 +101,12 @@ goog.inherits(Blockly.BlockSvg, Blockly.Block);
|
||||
|
||||
/**
|
||||
* Height of this block, not including any statement blocks above or below.
|
||||
* Height is in workspace units.
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.height = 0;
|
||||
/**
|
||||
* Width of this block, including any connected value blocks.
|
||||
* Width is in workspace units.
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.width = 0;
|
||||
|
||||
@@ -138,9 +142,6 @@ Blockly.BlockSvg.prototype.initSvg = function() {
|
||||
if (!this.workspace.options.readOnly && !this.eventsInit_) {
|
||||
Blockly.bindEventWithChecks_(this.getSvgRoot(), 'mousedown', this,
|
||||
this.onMouseDown_);
|
||||
var thisBlock = this;
|
||||
Blockly.bindEvent_(this.getSvgRoot(), 'touchstart', null,
|
||||
function(e) {Blockly.longStart_(e, thisBlock);});
|
||||
}
|
||||
this.eventsInit_ = true;
|
||||
|
||||
@@ -229,70 +230,6 @@ Blockly.BlockSvg.prototype.getIcons = function() {
|
||||
return icons;
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrapper function called when a mouseUp occurs during a drag operation.
|
||||
* @type {Array.<!Array>}
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.onMouseUpWrapper_ = null;
|
||||
|
||||
/**
|
||||
* Wrapper function called when a mouseMove occurs during a drag operation.
|
||||
* @type {Array.<!Array>}
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.onMouseMoveWrapper_ = null;
|
||||
|
||||
/**
|
||||
* Stop binding to the global mouseup and mousemove events.
|
||||
* @package
|
||||
*/
|
||||
Blockly.BlockSvg.terminateDrag = function() {
|
||||
Blockly.BlockSvg.disconnectUiStop_();
|
||||
if (Blockly.BlockSvg.onMouseUpWrapper_) {
|
||||
Blockly.unbindEvent_(Blockly.BlockSvg.onMouseUpWrapper_);
|
||||
Blockly.BlockSvg.onMouseUpWrapper_ = null;
|
||||
}
|
||||
if (Blockly.BlockSvg.onMouseMoveWrapper_) {
|
||||
Blockly.unbindEvent_(Blockly.BlockSvg.onMouseMoveWrapper_);
|
||||
Blockly.BlockSvg.onMouseMoveWrapper_ = null;
|
||||
}
|
||||
var selected = Blockly.selected;
|
||||
if (Blockly.dragMode_ == Blockly.DRAG_FREE) {
|
||||
// Terminate a drag operation.
|
||||
if (selected) {
|
||||
// Update the connection locations.
|
||||
var xy = selected.getRelativeToSurfaceXY();
|
||||
var dxy = goog.math.Coordinate.difference(xy, selected.dragStartXY_);
|
||||
var event = new Blockly.Events.Move(selected);
|
||||
event.oldCoordinate = selected.dragStartXY_;
|
||||
event.recordNew();
|
||||
Blockly.Events.fire(event);
|
||||
|
||||
selected.moveConnections_(dxy.x, dxy.y);
|
||||
delete selected.draggedBubbles_;
|
||||
selected.setDragging_(false);
|
||||
selected.moveOffDragSurface_();
|
||||
selected.render();
|
||||
selected.workspace.setResizesEnabled(true);
|
||||
// Ensure that any snap and bump are part of this move's event group.
|
||||
var group = Blockly.Events.getGroup();
|
||||
setTimeout(function() {
|
||||
Blockly.Events.setGroup(group);
|
||||
selected.snapToGrid();
|
||||
Blockly.Events.setGroup(false);
|
||||
}, Blockly.BUMP_DELAY / 2);
|
||||
setTimeout(function() {
|
||||
Blockly.Events.setGroup(group);
|
||||
selected.bumpNeighbours_();
|
||||
Blockly.Events.setGroup(false);
|
||||
}, Blockly.BUMP_DELAY);
|
||||
}
|
||||
}
|
||||
Blockly.dragMode_ = Blockly.DRAG_NONE;
|
||||
Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set parent of this block to be a new block or null.
|
||||
* @param {Blockly.BlockSvg} newParent New parent block.
|
||||
@@ -324,8 +261,12 @@ Blockly.BlockSvg.prototype.setParent = function(newParent) {
|
||||
|
||||
/**
|
||||
* Return the coordinates of the top-left corner of this block relative to the
|
||||
* drawing surface's origin (0,0).
|
||||
* @return {!goog.math.Coordinate} Object with .x and .y properties.
|
||||
* drawing surface's origin (0,0), in workspace units.
|
||||
* If the block is on the workspace, (0, 0) is the origin of the workspace
|
||||
* coordinate system.
|
||||
* This does not change with workspace scale.
|
||||
* @return {!goog.math.Coordinate} Object with .x and .y properties in
|
||||
* workspace coordinates.
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.getRelativeToSurfaceXY = function() {
|
||||
var x = 0;
|
||||
@@ -358,12 +299,12 @@ Blockly.BlockSvg.prototype.getRelativeToSurfaceXY = function() {
|
||||
|
||||
/**
|
||||
* Move a block by a relative offset.
|
||||
* @param {number} dx Horizontal offset.
|
||||
* @param {number} dy Vertical offset.
|
||||
* @param {number} dx Horizontal offset in workspace units.
|
||||
* @param {number} dy Vertical offset in workspace units.
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.moveBy = function(dx, dy) {
|
||||
goog.asserts.assert(!this.parentBlock_, 'Block has parent.');
|
||||
var event = new Blockly.Events.Move(this);
|
||||
var event = new Blockly.Events.BlockMove(this);
|
||||
var xy = this.getRelativeToSurfaceXY();
|
||||
this.translate(xy.x + dx, xy.y + dy);
|
||||
this.moveConnections_(dx, dy);
|
||||
@@ -375,8 +316,8 @@ Blockly.BlockSvg.prototype.moveBy = function(dx, dy) {
|
||||
/**
|
||||
* Transforms a block by setting the translation on the transform attribute
|
||||
* of the block's SVG.
|
||||
* @param {number} x The x coordinate of the translation.
|
||||
* @param {number} y The y coordinate of the translation.
|
||||
* @param {number} x The x coordinate of the translation in workspace units.
|
||||
* @param {number} y The y coordinate of the translation in workspace units.
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.translate = function(x, y) {
|
||||
this.getSvgRoot().setAttribute('transform',
|
||||
@@ -396,6 +337,7 @@ Blockly.BlockSvg.prototype.moveToDragSurface_ = function() {
|
||||
// The translation for drag surface blocks,
|
||||
// is equal to the current relative-to-surface position,
|
||||
// to keep the position in sync as it move on/off the surface.
|
||||
// This is in workspace coordinates.
|
||||
var xy = this.getRelativeToSurfaceXY();
|
||||
this.clearTransformAttributes_();
|
||||
this.workspace.blockDragSurface_.translateSurface(xy.x, xy.y);
|
||||
@@ -407,19 +349,37 @@ Blockly.BlockSvg.prototype.moveToDragSurface_ = function() {
|
||||
* Move this block back to the workspace block canvas.
|
||||
* Generally should be called at the same time as setDragging_(false).
|
||||
* Does nothing if useDragSurface_ is false.
|
||||
* @param {!goog.math.Coordinate} newXY The position the block should take on
|
||||
* on the workspace canvas, in workspace coordinates.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.moveOffDragSurface_ = function() {
|
||||
Blockly.BlockSvg.prototype.moveOffDragSurface_ = function(newXY) {
|
||||
if (!this.useDragSurface_) {
|
||||
return;
|
||||
}
|
||||
// Translate to current position, turning off 3d.
|
||||
var xy = this.getRelativeToSurfaceXY();
|
||||
this.clearTransformAttributes_();
|
||||
this.translate(xy.x, xy.y);
|
||||
this.translate(newXY.x, newXY.y);
|
||||
this.workspace.blockDragSurface_.clearAndHide(this.workspace.getCanvas());
|
||||
};
|
||||
|
||||
/**
|
||||
* Move this block during a drag, taking into account whether we are using a
|
||||
* drag surface to translate blocks.
|
||||
* This block must be a top-level block.
|
||||
* @param {!goog.math.Coordinate} newLoc The location to translate to, in
|
||||
* workspace coordinates.
|
||||
* @package
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.moveDuringDrag = function(newLoc) {
|
||||
if (this.useDragSurface_) {
|
||||
this.workspace.blockDragSurface_.translateSurface(newLoc.x, newLoc.y);
|
||||
} else {
|
||||
this.svgGroup_.translate_ = 'translate(' + newLoc.x + ',' + newLoc.y + ')';
|
||||
this.svgGroup_.setAttribute('transform',
|
||||
this.svgGroup_.translate_ + this.svgGroup_.skew_);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear the block of transform="..." attributes.
|
||||
* Used when the block is switching from 3d to 2d transform or vice versa.
|
||||
@@ -436,7 +396,7 @@ Blockly.BlockSvg.prototype.snapToGrid = function() {
|
||||
if (!this.workspace) {
|
||||
return; // Deleted block.
|
||||
}
|
||||
if (Blockly.dragMode_ != Blockly.DRAG_NONE) {
|
||||
if (this.workspace.isDragging()) {
|
||||
return; // Don't bump blocks during a drag.
|
||||
}
|
||||
if (this.getParent()) {
|
||||
@@ -445,11 +405,11 @@ Blockly.BlockSvg.prototype.snapToGrid = function() {
|
||||
if (this.isInFlyout) {
|
||||
return; // Don't move blocks around in a flyout.
|
||||
}
|
||||
if (!this.workspace.options.gridOptions ||
|
||||
!this.workspace.options.gridOptions['snap']) {
|
||||
var grid = this.workspace.getGrid();
|
||||
if (!grid || !grid.shouldSnap()) {
|
||||
return; // Config says no snapping.
|
||||
}
|
||||
var spacing = this.workspace.options.gridOptions['spacing'];
|
||||
var spacing = grid.getSpacing();
|
||||
var half = spacing / 2;
|
||||
var xy = this.getRelativeToSurfaceXY();
|
||||
var dx = Math.round((xy.x - half) / spacing) * spacing + half - xy.x;
|
||||
@@ -461,31 +421,10 @@ Blockly.BlockSvg.prototype.snapToGrid = function() {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a bounding box describing the dimensions of this block
|
||||
* and any blocks stacked below it.
|
||||
* @return {!{height: number, width: number}} Object with height and width
|
||||
* properties.
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.getHeightWidth = function() {
|
||||
var height = this.height;
|
||||
var width = this.width;
|
||||
// Recursively add size of subsequent blocks.
|
||||
var nextBlock = this.getNextBlock();
|
||||
if (nextBlock) {
|
||||
var nextHeightWidth = nextBlock.getHeightWidth();
|
||||
height += nextHeightWidth.height - 4; // Height of tab.
|
||||
width = Math.max(width, nextHeightWidth.width);
|
||||
} else if (!this.nextConnection && !this.outputConnection) {
|
||||
// Add a bit of margin under blocks with no bottom tab.
|
||||
height += 2;
|
||||
}
|
||||
return {height: height, width: width};
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the coordinates of a bounding box describing the dimensions of this
|
||||
* block and any blocks stacked below it.
|
||||
* Coordinate system: workspace coordinates.
|
||||
* @return {!{topLeft: goog.math.Coordinate, bottomRight: goog.math.Coordinate}}
|
||||
* Object with top left and bottom right coordinates of the bounding box.
|
||||
*/
|
||||
@@ -606,126 +545,9 @@ Blockly.BlockSvg.prototype.tab = function(start, forward) {
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.onMouseDown_ = function(e) {
|
||||
if (this.workspace.options.readOnly) {
|
||||
return;
|
||||
}
|
||||
if (this.isInFlyout) {
|
||||
// longStart's simulation of right-clicks for longpresses on touch devices
|
||||
// calls the onMouseDown_ function defined on the prototype of the object
|
||||
// the was longpressed (in this case, a Blockly.BlockSvg). In this case
|
||||
// that behaviour is wrong, because Blockly.Flyout.prototype.blockMouseDown
|
||||
// should be called for a mousedown on a block in the flyout, which blocks
|
||||
// execution of the block's onMouseDown_ function.
|
||||
if (e.type == 'touchstart' && Blockly.utils.isRightButton(e)) {
|
||||
Blockly.Flyout.blockRightClick_(e, this);
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (this.isInMutator) {
|
||||
// Mutator's coordinate system could be out of date because the bubble was
|
||||
// dragged, the block was moved, the parent workspace zoomed, etc.
|
||||
this.workspace.resize();
|
||||
}
|
||||
|
||||
this.workspace.updateScreenCalculationsIfScrolled();
|
||||
this.workspace.markFocused();
|
||||
Blockly.terminateDrag_();
|
||||
this.select();
|
||||
Blockly.hideChaff();
|
||||
if (Blockly.utils.isRightButton(e)) {
|
||||
// Right-click.
|
||||
this.showContextMenu_(e);
|
||||
// Click, not drag, so stop waiting for other touches from this identifier.
|
||||
Blockly.Touch.clearTouchIdentifier();
|
||||
} else if (!this.isMovable()) {
|
||||
// Allow immovable blocks to be selected and context menued, but not
|
||||
// dragged. Let this event bubble up to document, so the workspace may be
|
||||
// dragged instead.
|
||||
return;
|
||||
} else {
|
||||
if (!Blockly.Events.getGroup()) {
|
||||
Blockly.Events.setGroup(true);
|
||||
}
|
||||
// Left-click (or middle click)
|
||||
Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED);
|
||||
|
||||
this.dragStartXY_ = this.getRelativeToSurfaceXY();
|
||||
this.workspace.startDrag(e, this.dragStartXY_);
|
||||
|
||||
Blockly.dragMode_ = Blockly.DRAG_STICKY;
|
||||
Blockly.BlockSvg.onMouseUpWrapper_ = Blockly.bindEventWithChecks_(document,
|
||||
'mouseup', this, this.onMouseUp_);
|
||||
Blockly.BlockSvg.onMouseMoveWrapper_ = Blockly.bindEventWithChecks_(
|
||||
document, 'mousemove', this, this.onMouseMove_);
|
||||
// Build a list of bubbles that need to be moved and where they started.
|
||||
this.draggedBubbles_ = [];
|
||||
var descendants = this.getDescendants();
|
||||
for (var i = 0, descendant; descendant = descendants[i]; i++) {
|
||||
var icons = descendant.getIcons();
|
||||
for (var j = 0; j < icons.length; j++) {
|
||||
var data = icons[j].getIconLocation();
|
||||
data.bubble = icons[j];
|
||||
this.draggedBubbles_.push(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
// This event has been handled. No need to bubble up to the document.
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a mouse-up anywhere in the SVG pane. Is only registered when a
|
||||
* block is clicked. We can't use mouseUp on the block since a fast-moving
|
||||
* cursor can briefly escape the block before it catches up.
|
||||
* @param {!Event} e Mouse up event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.onMouseUp_ = function(e) {
|
||||
Blockly.Touch.clearTouchIdentifier();
|
||||
if (Blockly.dragMode_ != Blockly.DRAG_FREE &&
|
||||
!Blockly.WidgetDiv.isVisible()) {
|
||||
Blockly.Events.fire(
|
||||
new Blockly.Events.Ui(this, 'click', undefined, undefined));
|
||||
}
|
||||
Blockly.terminateDrag_();
|
||||
|
||||
var deleteArea = this.workspace.isDeleteArea(e);
|
||||
|
||||
// Connect to a nearby block, but not if it's over the toolbox.
|
||||
if (Blockly.selected && Blockly.highlightedConnection_ &&
|
||||
deleteArea != Blockly.DELETE_AREA_TOOLBOX) {
|
||||
// Connect two blocks together.
|
||||
Blockly.localConnection_.connect(Blockly.highlightedConnection_);
|
||||
if (this.rendered) {
|
||||
// Trigger a connection animation.
|
||||
// Determine which connection is inferior (lower in the source stack).
|
||||
var inferiorConnection = Blockly.localConnection_.isSuperior() ?
|
||||
Blockly.highlightedConnection_ : Blockly.localConnection_;
|
||||
inferiorConnection.getSourceBlock().connectionUiEffect();
|
||||
}
|
||||
if (this.workspace.trashcan) {
|
||||
// Don't throw an object in the trash can if it just got connected.
|
||||
this.workspace.trashcan.close();
|
||||
}
|
||||
} else if (deleteArea && !this.getParent() && Blockly.selected.isDeletable()) {
|
||||
// We didn't connect the block, and it was over the trash can or the
|
||||
// toolbox. Delete it.
|
||||
var trashcan = this.workspace.trashcan;
|
||||
if (trashcan) {
|
||||
goog.Timer.callOnce(trashcan.close, 100, trashcan);
|
||||
}
|
||||
Blockly.selected.dispose(false, true);
|
||||
}
|
||||
if (Blockly.highlightedConnection_) {
|
||||
Blockly.highlightedConnection_.unhighlight();
|
||||
Blockly.highlightedConnection_ = null;
|
||||
}
|
||||
Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN);
|
||||
if (!Blockly.WidgetDiv.isVisible()) {
|
||||
Blockly.Events.setGroup(false);
|
||||
var gesture = this.workspace.getGesture(e);
|
||||
if (gesture) {
|
||||
gesture.handleBlockStart(e, this);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -868,7 +690,7 @@ Blockly.BlockSvg.prototype.showContextMenu_ = function(e) {
|
||||
menuOptions.push(helpOption);
|
||||
|
||||
// Allow the block to add or modify menuOptions.
|
||||
if (this.customContextMenu && !block.isInFlyout) {
|
||||
if (this.customContextMenu) {
|
||||
this.customContextMenu(menuOptions);
|
||||
}
|
||||
|
||||
@@ -879,8 +701,10 @@ Blockly.BlockSvg.prototype.showContextMenu_ = function(e) {
|
||||
/**
|
||||
* Move the connections for this block and all blocks attached under it.
|
||||
* Also update any attached bubbles.
|
||||
* @param {number} dx Horizontal offset from current location.
|
||||
* @param {number} dy Vertical offset from current location.
|
||||
* @param {number} dx Horizontal offset from current location, in workspace
|
||||
* units.
|
||||
* @param {number} dy Vertical offset from current location, in workspace
|
||||
* units.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.moveConnections_ = function(dx, dy) {
|
||||
@@ -907,9 +731,9 @@ Blockly.BlockSvg.prototype.moveConnections_ = function(dx, dy) {
|
||||
/**
|
||||
* Recursively adds or removes the dragging class to this node and its children.
|
||||
* @param {boolean} adding True if adding, false if removing.
|
||||
* @private
|
||||
* @package
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.setDragging_ = function(adding) {
|
||||
Blockly.BlockSvg.prototype.setDragging = function(adding) {
|
||||
if (adding) {
|
||||
var group = this.getSvgRoot();
|
||||
group.translate_ = '';
|
||||
@@ -925,149 +749,7 @@ Blockly.BlockSvg.prototype.setDragging_ = function(adding) {
|
||||
}
|
||||
// Recurse through all blocks attached under this one.
|
||||
for (var i = 0; i < this.childBlocks_.length; i++) {
|
||||
this.childBlocks_[i].setDragging_(adding);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Drag this block to follow the mouse.
|
||||
* @param {!Event} e Mouse move event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.onMouseMove_ = function(e) {
|
||||
if (e.type == 'mousemove' && e.clientX <= 1 && e.clientY == 0 &&
|
||||
e.button == 0) {
|
||||
/* HACK:
|
||||
Safari Mobile 6.0 and Chrome for Android 18.0 fire rogue mousemove
|
||||
events on certain touch actions. Ignore events with these signatures.
|
||||
This may result in a one-pixel blind spot in other browsers,
|
||||
but this shouldn't be noticeable. */
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
var oldXY = this.getRelativeToSurfaceXY();
|
||||
var newXY = this.workspace.moveDrag(e);
|
||||
|
||||
if (Blockly.dragMode_ == Blockly.DRAG_STICKY) {
|
||||
// Still dragging within the sticky DRAG_RADIUS.
|
||||
var dr = goog.math.Coordinate.distance(oldXY, newXY) * this.workspace.scale;
|
||||
if (dr > Blockly.DRAG_RADIUS) {
|
||||
// Switch to unrestricted dragging.
|
||||
Blockly.dragMode_ = Blockly.DRAG_FREE;
|
||||
Blockly.longStop_();
|
||||
this.workspace.setResizesEnabled(false);
|
||||
|
||||
var disconnectEffect = !!this.parentBlock_;
|
||||
// If in a stack, either split the stack, or pull out single block.
|
||||
var healStack = !Blockly.DRAG_STACK;
|
||||
if (e.altKey || e.ctrlKey || e.metaKey) {
|
||||
healStack = !healStack;
|
||||
}
|
||||
// Push this block to the very top of the stack.
|
||||
this.unplug(healStack);
|
||||
if (disconnectEffect) {
|
||||
var group = this.getSvgRoot();
|
||||
group.translate_ = 'translate(' + newXY.x + ',' + newXY.y + ')';
|
||||
this.disconnectUiEffect();
|
||||
}
|
||||
this.setDragging_(true);
|
||||
this.moveToDragSurface_();
|
||||
}
|
||||
}
|
||||
if (Blockly.dragMode_ == Blockly.DRAG_FREE) {
|
||||
// Unrestricted dragging.
|
||||
var dxy = goog.math.Coordinate.difference(oldXY, this.dragStartXY_);
|
||||
var group = this.getSvgRoot();
|
||||
if (this.useDragSurface_) {
|
||||
this.workspace.blockDragSurface_.translateSurface(newXY.x, newXY.y);
|
||||
} else {
|
||||
group.translate_ = 'translate(' + newXY.x + ',' + newXY.y + ')';
|
||||
group.setAttribute('transform', group.translate_ + group.skew_);
|
||||
}
|
||||
// Drag all the nested bubbles.
|
||||
for (var i = 0; i < this.draggedBubbles_.length; i++) {
|
||||
var commentData = this.draggedBubbles_[i];
|
||||
commentData.bubble.setIconLocation(
|
||||
goog.math.Coordinate.sum(commentData, dxy));
|
||||
}
|
||||
|
||||
// Check to see if any of this block's connections are within range of
|
||||
// another block's connection.
|
||||
var myConnections = this.getConnections_(false);
|
||||
// Also check the last connection on this stack
|
||||
var lastOnStack = this.lastConnectionInStack_();
|
||||
if (lastOnStack && lastOnStack != this.nextConnection) {
|
||||
myConnections.push(lastOnStack);
|
||||
}
|
||||
var closestConnection = null;
|
||||
var localConnection = null;
|
||||
var radiusConnection = Blockly.SNAP_RADIUS;
|
||||
for (var i = 0; i < myConnections.length; i++) {
|
||||
var myConnection = myConnections[i];
|
||||
var neighbour = myConnection.closest(radiusConnection, dxy);
|
||||
if (neighbour.connection) {
|
||||
closestConnection = neighbour.connection;
|
||||
localConnection = myConnection;
|
||||
radiusConnection = neighbour.radius;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove connection highlighting if needed.
|
||||
if (Blockly.highlightedConnection_ &&
|
||||
Blockly.highlightedConnection_ != closestConnection) {
|
||||
Blockly.highlightedConnection_.unhighlight();
|
||||
Blockly.highlightedConnection_ = null;
|
||||
Blockly.localConnection_ = null;
|
||||
}
|
||||
|
||||
var wouldDeleteBlock = this.updateCursor_(e, closestConnection);
|
||||
|
||||
// Add connection highlighting if needed.
|
||||
if (!wouldDeleteBlock && closestConnection &&
|
||||
closestConnection != Blockly.highlightedConnection_) {
|
||||
closestConnection.highlight();
|
||||
Blockly.highlightedConnection_ = closestConnection;
|
||||
Blockly.localConnection_ = localConnection;
|
||||
}
|
||||
}
|
||||
// This event has been handled. No need to bubble up to the document.
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
/**
|
||||
* Provide visual indication of whether the block will be deleted if
|
||||
* dropped here.
|
||||
* Prefer connecting over dropping into the trash can, but prefer dragging to
|
||||
* the toolbox over connecting to other blocks.
|
||||
* @param {!Event} e Mouse move event.
|
||||
* @param {Blockly.Connection} closestConnection The connection this block would
|
||||
* potentially connect to if dropped here, or null.
|
||||
* @return {boolean} True if the block would be deleted if dropped here,
|
||||
* otherwise false.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.updateCursor_ = function(e, closestConnection) {
|
||||
var deleteArea = this.workspace.isDeleteArea(e);
|
||||
var wouldConnect = Blockly.selected && closestConnection &&
|
||||
deleteArea != Blockly.DELETE_AREA_TOOLBOX;
|
||||
var wouldDelete = deleteArea && !this.getParent() &&
|
||||
Blockly.selected.isDeletable();
|
||||
var showDeleteCursor = wouldDelete && !wouldConnect;
|
||||
|
||||
if (showDeleteCursor) {
|
||||
Blockly.Css.setCursor(Blockly.Css.Cursor.DELETE);
|
||||
if (deleteArea == Blockly.DELETE_AREA_TRASH && this.workspace.trashcan) {
|
||||
this.workspace.trashcan.setOpen_(true);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED);
|
||||
if (this.workspace.trashcan) {
|
||||
this.workspace.trashcan.setOpen_(false);
|
||||
}
|
||||
return false;
|
||||
this.childBlocks_[i].setDragging(adding);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1142,7 +824,7 @@ Blockly.BlockSvg.prototype.dispose = function(healStack, animate) {
|
||||
// If this block is being dragged, unlink the mouse events.
|
||||
if (Blockly.selected == this) {
|
||||
this.unselect();
|
||||
Blockly.terminateDrag_();
|
||||
this.workspace.cancelCurrentGesture();
|
||||
}
|
||||
// If this block has a context menu open, close it.
|
||||
if (Blockly.ContextMenu.currentBlock == this) {
|
||||
@@ -1181,7 +863,7 @@ Blockly.BlockSvg.prototype.dispose = function(healStack, animate) {
|
||||
* Play some UI effects (sound, animation) when disposing of a block.
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.disposeUiEffect = function() {
|
||||
this.workspace.playAudio('delete');
|
||||
this.workspace.getAudioManager().play('delete');
|
||||
|
||||
var xy = this.workspace.getSvgXY(/** @type {!Element} */ (this.svgGroup_));
|
||||
// Deeply clone the current block.
|
||||
@@ -1219,10 +901,8 @@ Blockly.BlockSvg.disposeUiStep_ = function(clone, rtl, start, workspaceScale) {
|
||||
var scale = (1 - percent) * workspaceScale;
|
||||
clone.setAttribute('transform', 'translate(' + x + ',' + y + ')' +
|
||||
' scale(' + scale + ')');
|
||||
var closure = function() {
|
||||
Blockly.BlockSvg.disposeUiStep_(clone, rtl, start, workspaceScale);
|
||||
};
|
||||
setTimeout(closure, 10);
|
||||
setTimeout(Blockly.BlockSvg.disposeUiStep_, 10, clone, rtl, start,
|
||||
workspaceScale);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1230,7 +910,7 @@ Blockly.BlockSvg.disposeUiStep_ = function(clone, rtl, start, workspaceScale) {
|
||||
* Play some UI effects (sound, ripple) after a connection has been established.
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.connectionUiEffect = function() {
|
||||
this.workspace.playAudio('click');
|
||||
this.workspace.getAudioManager().play('click');
|
||||
if (this.workspace.scale < 1) {
|
||||
return; // Too small to care about visual effects.
|
||||
}
|
||||
@@ -1267,10 +947,8 @@ Blockly.BlockSvg.connectionUiStep_ = function(ripple, start, workspaceScale) {
|
||||
} else {
|
||||
ripple.setAttribute('r', percent * 25 * workspaceScale);
|
||||
ripple.style.opacity = 1 - percent;
|
||||
var closure = function() {
|
||||
Blockly.BlockSvg.connectionUiStep_(ripple, start, workspaceScale);
|
||||
};
|
||||
Blockly.BlockSvg.disconnectUiStop_.pid_ = setTimeout(closure, 10);
|
||||
Blockly.BlockSvg.disconnectUiStop_.pid_ = setTimeout(
|
||||
Blockly.BlockSvg.connectionUiStep_, 10, ripple, start, workspaceScale);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1278,7 +956,7 @@ Blockly.BlockSvg.connectionUiStep_ = function(ripple, start, workspaceScale) {
|
||||
* Play some UI effects (sound, animation) when disconnecting a block.
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.disconnectUiEffect = function() {
|
||||
this.workspace.playAudio('disconnect');
|
||||
this.workspace.getAudioManager().play('disconnect');
|
||||
if (this.workspace.scale < 1) {
|
||||
return; // Too small to care about visual effects.
|
||||
}
|
||||
@@ -1314,11 +992,10 @@ Blockly.BlockSvg.disconnectUiStep_ = function(group, magnitude, start) {
|
||||
var skew = Math.round(Math.sin(percent * Math.PI * WIGGLES) *
|
||||
(1 - percent) * magnitude);
|
||||
group.skew_ = 'skewX(' + skew + ')';
|
||||
var closure = function() {
|
||||
Blockly.BlockSvg.disconnectUiStep_(group, magnitude, start);
|
||||
};
|
||||
Blockly.BlockSvg.disconnectUiStop_.group = group;
|
||||
Blockly.BlockSvg.disconnectUiStop_.pid = setTimeout(closure, 10);
|
||||
Blockly.BlockSvg.disconnectUiStop_.pid =
|
||||
setTimeout(Blockly.BlockSvg.disconnectUiStep_, 10, group, magnitude,
|
||||
start);
|
||||
}
|
||||
group.setAttribute('transform', group.translate_ + group.skew_);
|
||||
};
|
||||
@@ -1470,7 +1147,7 @@ Blockly.BlockSvg.prototype.setWarningText = function(text, opt_id) {
|
||||
clearTimeout(this.setWarningText.pid_[id]);
|
||||
delete this.setWarningText.pid_[id];
|
||||
}
|
||||
if (Blockly.dragMode_ == Blockly.DRAG_FREE) {
|
||||
if (this.workspace.isDragging()) {
|
||||
// Don't change the warning text during a drag.
|
||||
// Wait until the drag finishes.
|
||||
var thisBlock = this;
|
||||
@@ -1581,13 +1258,6 @@ Blockly.BlockSvg.prototype.setHighlighted = function(highlighted) {
|
||||
Blockly.BlockSvg.prototype.addSelect = function() {
|
||||
Blockly.utils.addClass(/** @type {!Element} */ (this.svgGroup_),
|
||||
'blocklySelected');
|
||||
// Move the selected block to the top of the stack.
|
||||
var block = this;
|
||||
do {
|
||||
var root = block.getSvgRoot();
|
||||
root.parentNode.appendChild(root);
|
||||
block = block.getParent();
|
||||
} while (block);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1598,6 +1268,22 @@ Blockly.BlockSvg.prototype.removeSelect = function() {
|
||||
'blocklySelected');
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the cursor over this block by adding or removing a class.
|
||||
* @param {boolean} enable True if the delete cursor should be shown, false
|
||||
* otherwise.
|
||||
* @package
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.setDeleteStyle = function(enable) {
|
||||
if (enable) {
|
||||
Blockly.utils.addClass(/** @type {!Element} */ (this.svgGroup_),
|
||||
'blocklyDraggingDelete');
|
||||
} else {
|
||||
Blockly.utils.removeClass(/** @type {!Element} */ (this.svgGroup_),
|
||||
'blocklyDraggingDelete');
|
||||
}
|
||||
};
|
||||
|
||||
// Overrides of functions on Blockly.Block that take into account whether the
|
||||
// block has been rendered.
|
||||
|
||||
@@ -1613,6 +1299,22 @@ Blockly.BlockSvg.prototype.setColour = function(colour) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Move this block to the front of the visible workspace.
|
||||
* <g> tags do not respect z-index so svg renders them in the
|
||||
* order that they are in the dom. By placing this block first within the
|
||||
* block group's <g>, it will render on top of any other blocks.
|
||||
* @package
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.bringToFront = function() {
|
||||
var block = this;
|
||||
do {
|
||||
var root = block.getSvgRoot();
|
||||
root.parentNode.appendChild(root);
|
||||
block = block.getParent();
|
||||
} while (block);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set whether this block can chain onto the bottom of another block.
|
||||
* @param {boolean} newBoolean True if there can be a previous statement.
|
||||
@@ -1736,7 +1438,7 @@ Blockly.BlockSvg.prototype.appendInput_ = function(type, name) {
|
||||
* Otherwise, for a non-rendered block return an empty list, and for a
|
||||
* collapsed block don't return inputs connections.
|
||||
* @return {!Array.<!Blockly.Connection>} Array of connections.
|
||||
* @private
|
||||
* @package
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.getConnections_ = function(all) {
|
||||
var myConnections = [];
|
||||
@@ -1770,3 +1472,72 @@ Blockly.BlockSvg.prototype.getConnections_ = function(all) {
|
||||
Blockly.BlockSvg.prototype.makeConnection_ = function(type) {
|
||||
return new Blockly.RenderedConnection(this, type);
|
||||
};
|
||||
|
||||
/**
|
||||
* Bump unconnected blocks out of alignment. Two blocks which aren't actually
|
||||
* connected should not coincidentally line up on screen.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.bumpNeighbours_ = function() {
|
||||
if (!this.workspace) {
|
||||
return; // Deleted block.
|
||||
}
|
||||
if (Blockly.dragMode_ != Blockly.DRAG_NONE) {
|
||||
return; // Don't bump blocks during a drag.
|
||||
}
|
||||
var rootBlock = this.getRootBlock();
|
||||
if (rootBlock.isInFlyout) {
|
||||
return; // Don't move blocks around in a flyout.
|
||||
}
|
||||
// Loop through every connection on this block.
|
||||
var myConnections = this.getConnections_(false);
|
||||
for (var i = 0, connection; connection = myConnections[i]; i++) {
|
||||
|
||||
// Spider down from this block bumping all sub-blocks.
|
||||
if (connection.isConnected() && connection.isSuperior()) {
|
||||
connection.targetBlock().bumpNeighbours_();
|
||||
}
|
||||
|
||||
var neighbours = connection.neighbours_(Blockly.SNAP_RADIUS);
|
||||
for (var j = 0, otherConnection; otherConnection = neighbours[j]; j++) {
|
||||
|
||||
// If both connections are connected, that's probably fine. But if
|
||||
// either one of them is unconnected, then there could be confusion.
|
||||
if (!connection.isConnected() || !otherConnection.isConnected()) {
|
||||
// Only bump blocks if they are from different tree structures.
|
||||
if (otherConnection.getSourceBlock().getRootBlock() != rootBlock) {
|
||||
|
||||
// Always bump the inferior block.
|
||||
if (connection.isSuperior()) {
|
||||
otherConnection.bumpAwayFrom_(connection);
|
||||
} else {
|
||||
connection.bumpAwayFrom_(otherConnection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Schedule snapping to grid and bumping neighbours to occur after a brief
|
||||
* delay.
|
||||
* @package
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.scheduleSnapAndBump = function() {
|
||||
var block = this;
|
||||
// Ensure that any snap and bump are part of this move's event group.
|
||||
var group = Blockly.Events.getGroup();
|
||||
|
||||
setTimeout(function() {
|
||||
Blockly.Events.setGroup(group);
|
||||
block.snapToGrid();
|
||||
Blockly.Events.setGroup(false);
|
||||
}, Blockly.BUMP_DELAY / 2);
|
||||
|
||||
setTimeout(function() {
|
||||
Blockly.Events.setGroup(group);
|
||||
block.bumpNeighbours_();
|
||||
Blockly.Events.setGroup(false);
|
||||
}, Blockly.BUMP_DELAY);
|
||||
};
|
||||
|
||||
+11
-41
@@ -73,20 +73,6 @@ Blockly.mainWorkspace = null;
|
||||
*/
|
||||
Blockly.selected = null;
|
||||
|
||||
/**
|
||||
* Currently highlighted connection (during a drag).
|
||||
* @type {Blockly.Connection}
|
||||
* @private
|
||||
*/
|
||||
Blockly.highlightedConnection_ = null;
|
||||
|
||||
/**
|
||||
* Connection on dragged block that matches the highlighted connection.
|
||||
* @type {Blockly.Connection}
|
||||
* @private
|
||||
*/
|
||||
Blockly.localConnection_ = null;
|
||||
|
||||
/**
|
||||
* All of the connections on blocks that are currently being dragged.
|
||||
* @type {!Array.<!Blockly.Connection>}
|
||||
@@ -108,15 +94,6 @@ Blockly.clipboardXml_ = null;
|
||||
*/
|
||||
Blockly.clipboardSource_ = null;
|
||||
|
||||
/**
|
||||
* Is the mouse dragging a block?
|
||||
* DRAG_NONE - No drag operation.
|
||||
* DRAG_STICKY - Still inside the sticky DRAG_RADIUS.
|
||||
* DRAG_FREE - Freely draggable.
|
||||
* @private
|
||||
*/
|
||||
Blockly.dragMode_ = Blockly.DRAG_NONE;
|
||||
|
||||
/**
|
||||
* Cached value for whether 3D is supported.
|
||||
* @type {!boolean}
|
||||
@@ -206,10 +183,18 @@ Blockly.onKeyDown_ = function(e) {
|
||||
// Do this first to prevent an error in the delete code from resulting in
|
||||
// data loss.
|
||||
e.preventDefault();
|
||||
// Don't delete while dragging. Jeez.
|
||||
if (Blockly.mainWorkspace.isDragging()) {
|
||||
return;
|
||||
}
|
||||
if (Blockly.selected && Blockly.selected.isDeletable()) {
|
||||
deleteBlock = true;
|
||||
}
|
||||
} else if (e.altKey || e.ctrlKey || e.metaKey) {
|
||||
// Don't use meta keys during drags.
|
||||
if (Blockly.mainWorkspace.isDragging()) {
|
||||
return;
|
||||
}
|
||||
if (Blockly.selected &&
|
||||
Blockly.selected.isDeletable() && Blockly.selected.isMovable()) {
|
||||
if (e.keyCode == 67) {
|
||||
@@ -239,25 +224,11 @@ Blockly.onKeyDown_ = function(e) {
|
||||
// Common code for delete and cut.
|
||||
Blockly.Events.setGroup(true);
|
||||
Blockly.hideChaff();
|
||||
var heal = Blockly.dragMode_ != Blockly.DRAG_FREE;
|
||||
Blockly.selected.dispose(heal, true);
|
||||
if (Blockly.highlightedConnection_) {
|
||||
Blockly.highlightedConnection_.unhighlight();
|
||||
Blockly.highlightedConnection_ = null;
|
||||
}
|
||||
Blockly.selected.dispose(/* heal */ true, true);
|
||||
Blockly.Events.setGroup(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Stop binding to the global mouseup and mousemove events.
|
||||
* @private
|
||||
*/
|
||||
Blockly.terminateDrag_ = function() {
|
||||
Blockly.BlockSvg.terminateDrag();
|
||||
Blockly.Flyout.terminateDrag_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Copy a block onto the local clipboard.
|
||||
* @param {!Blockly.Block} block Block to be copied.
|
||||
@@ -265,9 +236,8 @@ Blockly.terminateDrag_ = function() {
|
||||
*/
|
||||
Blockly.copy_ = function(block) {
|
||||
var xmlBlock = Blockly.Xml.blockToDom(block);
|
||||
if (Blockly.dragMode_ != Blockly.DRAG_FREE) {
|
||||
Blockly.Xml.deleteNext(xmlBlock);
|
||||
}
|
||||
// Copy only the selected block and internal blocks.
|
||||
Blockly.Xml.deleteNext(xmlBlock);
|
||||
// Encode start position in XML.
|
||||
var xy = block.getRelativeToSurfaceXY();
|
||||
xmlBlock.setAttribute('x', block.RTL ? -xy.x : xy.x);
|
||||
|
||||
@@ -152,7 +152,6 @@ Blockly.Bubble.unbindDragEvents_ = function() {
|
||||
*/
|
||||
Blockly.Bubble.bubbleMouseUp_ = function(/*e*/) {
|
||||
Blockly.Touch.clearTouchIdentifier();
|
||||
Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN);
|
||||
Blockly.Bubble.unbindDragEvents_();
|
||||
};
|
||||
|
||||
@@ -280,8 +279,6 @@ Blockly.Bubble.prototype.bubbleMouseDown_ = function(e) {
|
||||
return;
|
||||
}
|
||||
// Left-click (or middle click)
|
||||
Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED);
|
||||
|
||||
this.workspace_.startDrag(e, new goog.math.Coordinate(
|
||||
this.workspace_.RTL ? -this.relativeLeft_ : this.relativeLeft_,
|
||||
this.relativeTop_));
|
||||
@@ -323,8 +320,6 @@ Blockly.Bubble.prototype.resizeMouseDown_ = function(e) {
|
||||
return;
|
||||
}
|
||||
// Left-click (or middle click)
|
||||
Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED);
|
||||
|
||||
this.workspace_.startDrag(e, new goog.math.Coordinate(
|
||||
this.workspace_.RTL ? -this.width_ : this.width_, this.height_));
|
||||
|
||||
|
||||
+2
-2
@@ -119,7 +119,7 @@ Blockly.Comment.prototype.createEditor_ = function() {
|
||||
});
|
||||
Blockly.bindEventWithChecks_(textarea, 'change', this, function(e) {
|
||||
if (this.text_ != textarea.value) {
|
||||
Blockly.Events.fire(new Blockly.Events.Change(
|
||||
Blockly.Events.fire(new Blockly.Events.BlockChange(
|
||||
this.block_, 'comment', null, this.text_, textarea.value));
|
||||
this.text_ = textarea.value;
|
||||
}
|
||||
@@ -257,7 +257,7 @@ Blockly.Comment.prototype.getText = function() {
|
||||
*/
|
||||
Blockly.Comment.prototype.setText = function(text) {
|
||||
if (this.text_ != text) {
|
||||
Blockly.Events.fire(new Blockly.Events.Change(
|
||||
Blockly.Events.fire(new Blockly.Events.BlockChange(
|
||||
this.block_, 'comment', null, this.text_, text));
|
||||
this.text_ = text;
|
||||
}
|
||||
|
||||
@@ -219,7 +219,7 @@ Blockly.Connection.prototype.connect_ = function(childConnection) {
|
||||
|
||||
var event;
|
||||
if (Blockly.Events.isEnabled()) {
|
||||
event = new Blockly.Events.Move(childBlock);
|
||||
event = new Blockly.Events.BlockMove(childBlock);
|
||||
}
|
||||
// Establish the connections.
|
||||
Blockly.Connection.connectReciprocally_(parentConnection, childConnection);
|
||||
@@ -241,12 +241,6 @@ Blockly.Connection.prototype.dispose = function() {
|
||||
if (this.inDB_) {
|
||||
this.db_.removeConnection_(this);
|
||||
}
|
||||
if (Blockly.highlightedConnection_ == this) {
|
||||
Blockly.highlightedConnection_ = null;
|
||||
}
|
||||
if (Blockly.localConnection_ == this) {
|
||||
Blockly.localConnection_ = null;
|
||||
}
|
||||
this.db_ = null;
|
||||
this.dbOpposite_ = null;
|
||||
};
|
||||
@@ -508,7 +502,7 @@ Blockly.Connection.prototype.disconnectInternal_ = function(parentBlock,
|
||||
childBlock) {
|
||||
var event;
|
||||
if (Blockly.Events.isEnabled()) {
|
||||
event = new Blockly.Events.Move(childBlock);
|
||||
event = new Blockly.Events.BlockMove(childBlock);
|
||||
}
|
||||
var otherConnection = this.targetConnection;
|
||||
otherConnection.targetConnection = null;
|
||||
|
||||
@@ -32,6 +32,13 @@ goog.provide('Blockly.constants');
|
||||
*/
|
||||
Blockly.DRAG_RADIUS = 5;
|
||||
|
||||
/**
|
||||
* Number of pixels the mouse must move before a drag/scroll starts from the
|
||||
* flyout. Because the drag-intention is determined when this is reached, it is
|
||||
* larger than Blockly.DRAG_RADIUS so that the drag-direction is clearer.
|
||||
*/
|
||||
Blockly.FLYOUT_DRAG_RADIUS = 10;
|
||||
|
||||
/**
|
||||
* Maximum misalignment between connections for them to snap together.
|
||||
*/
|
||||
@@ -207,6 +214,12 @@ Blockly.TOOLBOX_AT_LEFT = 2;
|
||||
*/
|
||||
Blockly.TOOLBOX_AT_RIGHT = 3;
|
||||
|
||||
/**
|
||||
* ENUM representing that an event is not in any delete areas.
|
||||
* Null for backwards compatibility reasons.
|
||||
* @const
|
||||
*/
|
||||
Blockly.DELETE_AREA_NONE = null;
|
||||
|
||||
/**
|
||||
* ENUM representing that an event is in the delete area of the trash can.
|
||||
@@ -236,3 +249,19 @@ Blockly.VARIABLE_CATEGORY_NAME = 'VARIABLE';
|
||||
* @const {string}
|
||||
*/
|
||||
Blockly.PROCEDURE_CATEGORY_NAME = 'PROCEDURE';
|
||||
|
||||
/**
|
||||
* String for use in the dropdown created in field_variable.
|
||||
* This string indicates that this option in the dropdown is 'Rename
|
||||
* variable...' and if selected, should trigger the prompt to rename a variable.
|
||||
* @const {string}
|
||||
*/
|
||||
Blockly.RENAME_VARIABLE_ID = 'RENAME_VARIABLE_ID';
|
||||
|
||||
/**
|
||||
* String for use in the dropdown created in field_variable.
|
||||
* This string indicates that this option in the dropdown is 'Delete the "%1"
|
||||
* variable' and if selected, should trigger the prompt to delete a variable.
|
||||
* @const {string}
|
||||
*/
|
||||
Blockly.DELETE_VARIABLE_ID = 'DELETE_VARIABLE_ID';
|
||||
|
||||
+45
-11
@@ -43,6 +43,12 @@ goog.require('goog.ui.MenuItem');
|
||||
*/
|
||||
Blockly.ContextMenu.currentBlock = null;
|
||||
|
||||
/**
|
||||
* @type {Array.<!Array>} Opaque data that can be passed to unbindEvent_.
|
||||
* @private
|
||||
*/
|
||||
Blockly.ContextMenu.eventWrapper_ = null;
|
||||
|
||||
/**
|
||||
* Construct the menu based on the list of options and show the menu.
|
||||
* @param {!Event} e Mouse event.
|
||||
@@ -55,12 +61,33 @@ Blockly.ContextMenu.show = function(e, options, rtl) {
|
||||
Blockly.ContextMenu.hide();
|
||||
return;
|
||||
}
|
||||
var menu = Blockly.ContextMenu.populate_(options, rtl);
|
||||
|
||||
goog.events.listen(menu, goog.ui.Component.EventType.ACTION,
|
||||
Blockly.ContextMenu.hide);
|
||||
|
||||
Blockly.ContextMenu.position_(menu, e, rtl);
|
||||
// 1ms delay is required for focusing on context menus because some other
|
||||
// mouse event is still waiting in the queue and clears focus.
|
||||
setTimeout(function() {menu.getElement().focus();}, 1);
|
||||
Blockly.ContextMenu.currentBlock = null; // May be set by Blockly.Block.
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the context menu object and populate it with the given options.
|
||||
* @param {!Array.<!Object>} options Array of menu options.
|
||||
* @param {boolean} rtl True if RTL, false if LTR.
|
||||
* @return {!goog.ui.Menu} The menu that will be shown on right click.
|
||||
* @private
|
||||
*/
|
||||
Blockly.ContextMenu.populate_ = function(options, rtl) {
|
||||
/* Here's what one option object looks like:
|
||||
{text: 'Make It So',
|
||||
enabled: true,
|
||||
callback: Blockly.MakeItSo}
|
||||
*/
|
||||
var menu = new goog.ui.Menu();
|
||||
menu.setAllowAutoFocus(true);
|
||||
menu.setRightToLeft(rtl);
|
||||
for (var i = 0, option; option = options[i]; i++) {
|
||||
var menuItem = new goog.ui.MenuItem(option.text);
|
||||
@@ -70,15 +97,25 @@ Blockly.ContextMenu.show = function(e, options, rtl) {
|
||||
if (option.enabled) {
|
||||
goog.events.listen(menuItem, goog.ui.Component.EventType.ACTION,
|
||||
option.callback);
|
||||
menuItem.handleContextMenu = function(e) {
|
||||
menuItem.handleContextMenu = function(/* e */) {
|
||||
// Right-clicking on menu option should count as a click.
|
||||
goog.events.dispatchEvent(this, goog.ui.Component.EventType.ACTION);
|
||||
};
|
||||
}
|
||||
}
|
||||
goog.events.listen(menu, goog.ui.Component.EventType.ACTION,
|
||||
Blockly.ContextMenu.hide);
|
||||
// Record windowSize and scrollOffset before adding menu.
|
||||
return menu;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add the menu to the page and position it correctly.
|
||||
* @param {!goog.ui.Menu} menu The menu to add and position.
|
||||
* @param {!Event} e Mouse event for the right click that is making the context
|
||||
* menu appear.
|
||||
* @param {boolean} rtl True if RTL, false if LTR.
|
||||
* @private
|
||||
*/
|
||||
Blockly.ContextMenu.position_ = function(menu, e, rtl) {
|
||||
// Record windowSize and scrollOffset before adding menu.
|
||||
var windowSize = goog.dom.getViewportSize();
|
||||
var scrollOffset = goog.style.getViewportPageOffset(document);
|
||||
var div = Blockly.WidgetDiv.DIV;
|
||||
@@ -109,12 +146,6 @@ Blockly.ContextMenu.show = function(e, options, rtl) {
|
||||
}
|
||||
}
|
||||
Blockly.WidgetDiv.position(x, y, windowSize, scrollOffset, rtl);
|
||||
|
||||
menu.setAllowAutoFocus(true);
|
||||
// 1ms delay is required for focusing on context menus because some other
|
||||
// mouse event is still waiting in the queue and clears focus.
|
||||
setTimeout(function() {menuDom.focus();}, 1);
|
||||
Blockly.ContextMenu.currentBlock = null; // May be set by Blockly.Block.
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -123,6 +154,9 @@ Blockly.ContextMenu.show = function(e, options, rtl) {
|
||||
Blockly.ContextMenu.hide = function() {
|
||||
Blockly.WidgetDiv.hideIfOwner(Blockly.ContextMenu);
|
||||
Blockly.ContextMenu.currentBlock = null;
|
||||
if (Blockly.ContextMenu.eventWrapper_) {
|
||||
Blockly.unbindEvent_(Blockly.ContextMenu.eventWrapper_);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -150,7 +184,7 @@ Blockly.ContextMenu.callbackFactory = function(block, xml) {
|
||||
Blockly.Events.enable();
|
||||
}
|
||||
if (Blockly.Events.isEnabled() && !newBlock.isShadow()) {
|
||||
Blockly.Events.fire(new Blockly.Events.Create(newBlock));
|
||||
Blockly.Events.fire(new Blockly.Events.BlockCreate(newBlock));
|
||||
}
|
||||
newBlock.select();
|
||||
};
|
||||
|
||||
+56
-28
@@ -95,41 +95,17 @@ Blockly.Css.inject = function(hasCss, pathToMedia) {
|
||||
var cssTextNode = document.createTextNode(text);
|
||||
cssNode.appendChild(cssTextNode);
|
||||
Blockly.Css.styleSheet_ = cssNode.sheet;
|
||||
Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the cursor to be displayed when over something draggable.
|
||||
* See See https://github.com/google/blockly/issues/981 for context.
|
||||
* @param {Blockly.Css.Cursor} cursor Enum.
|
||||
* @deprecated April 2017.
|
||||
*/
|
||||
Blockly.Css.setCursor = function(cursor) {
|
||||
if (Blockly.Css.currentCursor_ == cursor) {
|
||||
return;
|
||||
}
|
||||
Blockly.Css.currentCursor_ = cursor;
|
||||
var url = 'url(' + Blockly.Css.mediaPath_ + '/' + cursor + '.cur), auto';
|
||||
// There are potentially hundreds of draggable objects. Changing their style
|
||||
// properties individually is too slow, so change the CSS rule instead.
|
||||
var rule = '.blocklyDraggable {\n cursor: ' + url + ';\n}\n';
|
||||
Blockly.Css.styleSheet_.deleteRule(0);
|
||||
Blockly.Css.styleSheet_.insertRule(rule, 0);
|
||||
// There is probably only one toolbox, so just change its style property.
|
||||
var toolboxen = document.getElementsByClassName('blocklyToolboxDiv');
|
||||
for (var i = 0, toolbox; toolbox = toolboxen[i]; i++) {
|
||||
if (cursor == Blockly.Css.Cursor.DELETE) {
|
||||
toolbox.style.cursor = url;
|
||||
} else {
|
||||
toolbox.style.cursor = '';
|
||||
}
|
||||
}
|
||||
// Set cursor on the whole document, so that rapid movements
|
||||
// don't result in cursor changing to an arrow momentarily.
|
||||
var html = document.body.parentNode;
|
||||
if (cursor == Blockly.Css.Cursor.OPEN) {
|
||||
html.style.cursor = '';
|
||||
} else {
|
||||
html.style.cursor = url;
|
||||
}
|
||||
console.warn('Deprecated call to Blockly.Css.setCursor.' +
|
||||
'See https://github.com/google/blockly/issues/981 for context');
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -154,6 +130,7 @@ Blockly.Css.CONTENT = [
|
||||
'height: 100%;',
|
||||
'position: relative;',
|
||||
'overflow: hidden;', /* So blocks in drag surface disappear at edges */
|
||||
'touch-action: none',
|
||||
'}',
|
||||
|
||||
'.blocklyNonSelectable {',
|
||||
@@ -232,6 +209,49 @@ Blockly.Css.CONTENT = [
|
||||
'display: none;',
|
||||
'}',
|
||||
|
||||
'.blocklyDraggable {',
|
||||
/* backup for browsers (e.g. IE11) that don't support grab */
|
||||
'cursor: url("<<<PATH>>>/handopen.cur"), auto;',
|
||||
'cursor: grab;',
|
||||
'cursor: -webkit-grab;',
|
||||
'cursor: -moz-grab;',
|
||||
'}',
|
||||
|
||||
'.blocklyDragging {',
|
||||
/* backup for browsers (e.g. IE11) that don't support grabbing */
|
||||
'cursor: url("<<<PATH>>>/handclosed.cur"), auto;',
|
||||
'cursor: grabbing;',
|
||||
'cursor: -webkit-grabbing;',
|
||||
'cursor: -moz-grabbing;',
|
||||
'}',
|
||||
/* Changes cursor on mouse down. Not effective in Firefox because of
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=771241 */
|
||||
'.blocklyDraggable:active {',
|
||||
/* backup for browsers (e.g. IE11) that don't support grabbing */
|
||||
'cursor: url("<<<PATH>>>/handclosed.cur"), auto;',
|
||||
'cursor: grabbing;',
|
||||
'cursor: -webkit-grabbing;',
|
||||
'cursor: -moz-grabbing;',
|
||||
'}',
|
||||
/* Change the cursor on the whole drag surface in case the mouse gets
|
||||
ahead of block during a drag. This way the cursor is still a closed hand.
|
||||
*/
|
||||
'.blocklyBlockDragSurface .blocklyDraggable {',
|
||||
/* backup for browsers (e.g. IE11) that don't support grabbing */
|
||||
'cursor: url("<<<PATH>>>/handclosed.cur"), auto;',
|
||||
'cursor: grabbing;',
|
||||
'cursor: -webkit-grabbing;',
|
||||
'cursor: -moz-grabbing;',
|
||||
'}',
|
||||
|
||||
'.blocklyDragging.blocklyDraggingDelete {',
|
||||
'cursor: url("<<<PATH>>>/handdelete.cur"), auto;',
|
||||
'}',
|
||||
|
||||
'.blocklyToolboxDelete {',
|
||||
'cursor: url("<<<PATH>>>/handdelete.cur"), auto;',
|
||||
'}',
|
||||
|
||||
'.blocklyDragging>.blocklyPath,',
|
||||
'.blocklyDragging>.blocklyPathLight {',
|
||||
'fill-opacity: .8;',
|
||||
@@ -390,6 +410,10 @@ Blockly.Css.CONTENT = [
|
||||
'fill-opacity: .8;',
|
||||
'}',
|
||||
|
||||
'.blocklyTransparentBackground {',
|
||||
'opacity: 0;',
|
||||
'}',
|
||||
|
||||
'.blocklyMainWorkspaceScrollbar {',
|
||||
'z-index: 20;',
|
||||
'}',
|
||||
@@ -582,6 +606,10 @@ Blockly.Css.CONTENT = [
|
||||
'vertical-align: middle;',
|
||||
'}',
|
||||
|
||||
'.blocklyToolboxDelete .blocklyTreeLabel {',
|
||||
'cursor: url("<<<PATH>>>/handdelete.cur"), auto;',
|
||||
'}',
|
||||
|
||||
'.blocklyTreeSelected .blocklyTreeLabel {',
|
||||
'color: #fff;',
|
||||
'}',
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2017 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Class that controls updates to connections during drags.
|
||||
* @author fenichel@google.com (Rachel Fenichel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.DraggedConnectionManager');
|
||||
|
||||
goog.require('Blockly.RenderedConnection');
|
||||
|
||||
goog.require('goog.math.Coordinate');
|
||||
|
||||
|
||||
/**
|
||||
* Class that controls updates to connections during drags. It is primarily
|
||||
* responsible for finding the closest eligible connection and highlighting or
|
||||
* unhiglighting it as needed during a drag.
|
||||
* @param {!Blockly.BlockSvg} block The top block in the stack being dragged.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.DraggedConnectionManager = function(block) {
|
||||
Blockly.selected = block;
|
||||
|
||||
/**
|
||||
* The top block in the stack being dragged.
|
||||
* Does not change during a drag.
|
||||
* @type {!Blockly.Block}
|
||||
* @private
|
||||
*/
|
||||
this.topBlock_ = block;
|
||||
|
||||
/**
|
||||
* The workspace on which these connections are being dragged.
|
||||
* Does not change during a drag.
|
||||
* @type {!Blockly.WorkspaceSvg}
|
||||
* @private
|
||||
*/
|
||||
this.workspace_ = block.workspace;
|
||||
|
||||
/**
|
||||
* The connections on the dragging blocks that are available to connect to
|
||||
* other blocks. This includes all open connections on the top block, as well
|
||||
* as the last connection on the block stack.
|
||||
* Does not change during a drag.
|
||||
* @type {!Array.<!Blockly.RenderedConnection>}
|
||||
* @private
|
||||
*/
|
||||
this.availableConnections_ = this.initAvailableConnections_();
|
||||
|
||||
/**
|
||||
* The connection that this block would connect to if released immediately.
|
||||
* Updated on every mouse move.
|
||||
* @type {Blockly.RenderedConnection}
|
||||
* @private
|
||||
*/
|
||||
this.closestConnection_ = null;
|
||||
|
||||
/**
|
||||
* The connection that would connect to this.closestConnection_ if this block
|
||||
* were released immediately.
|
||||
* Updated on every mouse move.
|
||||
* @type {Blockly.RenderedConnection}
|
||||
* @private
|
||||
*/
|
||||
this.localConnection_ = null;
|
||||
|
||||
/**
|
||||
* The distance between this.closestConnection_ and this.localConnection_,
|
||||
* in workspace units.
|
||||
* Updated on every mouse move.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.radiusConnection_ = 0;
|
||||
|
||||
/**
|
||||
* Whether the block would be deleted if it were dropped immediately.
|
||||
* Updated on every mouse move.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.wouldDeleteBlock_ = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sever all links from this object.
|
||||
* @package
|
||||
*/
|
||||
Blockly.DraggedConnectionManager.prototype.dispose = function() {
|
||||
this.topBlock_ = null;
|
||||
this.workspace_ = null;
|
||||
this.availableConnections_.length = 0;
|
||||
this.closestConnection_ = null;
|
||||
this.localConnection_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return whether the block would be deleted if dropped immediately, based on
|
||||
* information from the most recent move event.
|
||||
* @return {boolean} true if the block would be deleted if dropped immediately.
|
||||
* @package
|
||||
*/
|
||||
Blockly.DraggedConnectionManager.prototype.wouldDeleteBlock = function() {
|
||||
return this.wouldDeleteBlock_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Connect to the closest connection and render the results.
|
||||
* This should be called at the end of a drag.
|
||||
* @package
|
||||
*/
|
||||
Blockly.DraggedConnectionManager.prototype.applyConnections = function() {
|
||||
if (this.closestConnection_) {
|
||||
// Connect two blocks together.
|
||||
this.localConnection_.connect(this.closestConnection_);
|
||||
if (this.topBlock_.rendered) {
|
||||
// Trigger a connection animation.
|
||||
// Determine which connection is inferior (lower in the source stack).
|
||||
var inferiorConnection = this.localConnection_.isSuperior() ?
|
||||
this.closestConnection_ : this.localConnection_;
|
||||
inferiorConnection.getSourceBlock().connectionUiEffect();
|
||||
}
|
||||
this.removeHighlighting_();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update highlighted connections based on the most recent move location.
|
||||
* @param {!goog.math.Coordinate} dxy Position relative to drag start,
|
||||
* in workspace units.
|
||||
* @param {?number} deleteArea One of {@link Blockly.DELETE_AREA_TRASH},
|
||||
* {@link Blockly.DELETE_AREA_TOOLBOX}, or {@link Blockly.DELETE_AREA_NONE}.
|
||||
* @package
|
||||
*/
|
||||
Blockly.DraggedConnectionManager.prototype.update = function(dxy, deleteArea) {
|
||||
var oldClosestConnection = this.closestConnection_;
|
||||
var closestConnectionChanged = this.updateClosest_(dxy);
|
||||
|
||||
if (closestConnectionChanged && oldClosestConnection) {
|
||||
oldClosestConnection.unhighlight();
|
||||
}
|
||||
|
||||
// Prefer connecting over dropping into the trash can, but prefer dragging to
|
||||
// the toolbox over connecting to other blocks.
|
||||
var wouldConnect = !!this.closestConnection_ &&
|
||||
deleteArea != Blockly.DELETE_AREA_TOOLBOX;
|
||||
var wouldDelete = !!deleteArea && !this.topBlock_.getParent() &&
|
||||
this.topBlock_.isDeletable();
|
||||
this.wouldDeleteBlock_ = wouldDelete && !wouldConnect;
|
||||
|
||||
if (!this.wouldDeleteBlock_ && closestConnectionChanged &&
|
||||
this.closestConnection_) {
|
||||
this.addHighlighting_();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove highlighting from the currently highlighted connection, if it exists.
|
||||
* @private
|
||||
*/
|
||||
Blockly.DraggedConnectionManager.prototype.removeHighlighting_ = function() {
|
||||
if (this.closestConnection_) {
|
||||
this.closestConnection_.unhighlight();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Add highlighting to the closest connection, if it exists.
|
||||
* @private
|
||||
*/
|
||||
Blockly.DraggedConnectionManager.prototype.addHighlighting_ = function() {
|
||||
if (this.closestConnection_) {
|
||||
this.closestConnection_.highlight();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Populate the list of available connections on this block stack. This should
|
||||
* only be called once, at the beginning of a drag.
|
||||
* @return {!Array.<!Blockly.RenderedConnection>} a list of available
|
||||
* connections.
|
||||
* @private
|
||||
*/
|
||||
Blockly.DraggedConnectionManager.prototype.initAvailableConnections_ = function() {
|
||||
var available = this.topBlock_.getConnections_(false);
|
||||
// Also check the last connection on this stack
|
||||
var lastOnStack = this.topBlock_.lastConnectionInStack_();
|
||||
if (lastOnStack && lastOnStack != this.topBlock_.nextConnection) {
|
||||
available.push(lastOnStack);
|
||||
}
|
||||
return available;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the new closest connection, and update internal state in response.
|
||||
* @param {!goog.math.Coordinate} dxy Position relative to the drag start,
|
||||
* in workspace units.
|
||||
* @return {boolean} Whether the closest connection has changed.
|
||||
* @private
|
||||
*/
|
||||
Blockly.DraggedConnectionManager.prototype.updateClosest_ = function(dxy) {
|
||||
var oldClosestConnection = this.closestConnection_;
|
||||
|
||||
this.closestConnection_ = null;
|
||||
this.localConnection_ = null;
|
||||
this.radiusConnection_ = Blockly.SNAP_RADIUS;
|
||||
for (var i = 0; i < this.availableConnections_.length; i++) {
|
||||
var myConnection = this.availableConnections_[i];
|
||||
var neighbour = myConnection.closest(this.radiusConnection_, dxy);
|
||||
if (neighbour.connection) {
|
||||
this.closestConnection_ = neighbour.connection;
|
||||
this.localConnection_ = myConnection;
|
||||
this.radiusConnection_ = neighbour.radius;
|
||||
}
|
||||
}
|
||||
return oldClosestConnection != this.closestConnection_;
|
||||
};
|
||||
+297
-14
@@ -55,29 +55,71 @@ Blockly.Events.recordUndo = true;
|
||||
Blockly.Events.disabled_ = 0;
|
||||
|
||||
/**
|
||||
* Name of event that creates a block.
|
||||
* Name of event that creates a block. Will be deprecated for BLOCK_CREATE.
|
||||
* @const
|
||||
*/
|
||||
Blockly.Events.CREATE = 'create';
|
||||
|
||||
/**
|
||||
* Name of event that deletes a block.
|
||||
* Name of event that creates a block.
|
||||
* @const
|
||||
*/
|
||||
Blockly.Events.BLOCK_CREATE = Blockly.Events.CREATE;
|
||||
|
||||
/**
|
||||
* Name of event that deletes a block. Will be deprecated for BLOCK_DELETE.
|
||||
* @const
|
||||
*/
|
||||
Blockly.Events.DELETE = 'delete';
|
||||
|
||||
/**
|
||||
* Name of event that changes a block.
|
||||
* Name of event that deletes a block.
|
||||
* @const
|
||||
*/
|
||||
Blockly.Events.BLOCK_DELETE = Blockly.Events.DELETE;
|
||||
|
||||
/**
|
||||
* Name of event that changes a block. Will be deprecated for BLOCK_CHANGE.
|
||||
* @const
|
||||
*/
|
||||
Blockly.Events.CHANGE = 'change';
|
||||
|
||||
/**
|
||||
* Name of event that moves a block.
|
||||
* Name of event that changes a block.
|
||||
* @const
|
||||
*/
|
||||
Blockly.Events.BLOCK_CHANGE = Blockly.Events.CHANGE;
|
||||
|
||||
/**
|
||||
* Name of event that moves a block. Will be deprecated for BLOCK_MOVE.
|
||||
* @const
|
||||
*/
|
||||
Blockly.Events.MOVE = 'move';
|
||||
|
||||
/**
|
||||
* Name of event that moves a block.
|
||||
* @const
|
||||
*/
|
||||
Blockly.Events.BLOCK_MOVE = Blockly.Events.MOVE;
|
||||
|
||||
/**
|
||||
* Name of event that creates a variable.
|
||||
* @const
|
||||
*/
|
||||
Blockly.Events.VAR_CREATE = 'var_create';
|
||||
|
||||
/**
|
||||
* Name of event that deletes a variable.
|
||||
* @const
|
||||
*/
|
||||
Blockly.Events.VAR_DELETE = 'var_delete';
|
||||
|
||||
/**
|
||||
* Name of event that renames a variable.
|
||||
* @const
|
||||
*/
|
||||
Blockly.Events.VAR_RENAME = 'var_rename';
|
||||
|
||||
/**
|
||||
* Name of event that records a UI change.
|
||||
* @const
|
||||
@@ -276,6 +318,15 @@ Blockly.Events.fromJson = function(json, workspace) {
|
||||
case Blockly.Events.MOVE:
|
||||
event = new Blockly.Events.Move(null);
|
||||
break;
|
||||
case Blockly.Events.VAR_CREATE:
|
||||
event = new Blockly.Events.VarCreate(null);
|
||||
break;
|
||||
case Blockly.Events.VAR_DELETE:
|
||||
event = new Blockly.Events.VarDelete(null);
|
||||
break;
|
||||
case Blockly.Events.VAR_RENAME:
|
||||
event = new Blockly.Events.VarRename(null);
|
||||
break;
|
||||
case Blockly.Events.UI:
|
||||
event = new Blockly.Events.Ui(null);
|
||||
break;
|
||||
@@ -289,13 +340,17 @@ Blockly.Events.fromJson = function(json, workspace) {
|
||||
|
||||
/**
|
||||
* Abstract class for an event.
|
||||
* @param {Blockly.Block} block The block.
|
||||
* @param {Blockly.Block|Blockly.VariableModel} elem The block or variable.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Events.Abstract = function(block) {
|
||||
if (block) {
|
||||
this.blockId = block.id;
|
||||
this.workspaceId = block.workspace.id;
|
||||
Blockly.Events.Abstract = function(elem) {
|
||||
if (elem instanceof Blockly.Block) {
|
||||
this.blockId = elem.id;
|
||||
this.workspaceId = elem.workspace.id;
|
||||
}
|
||||
else if (elem instanceof Blockly.VariableModel){
|
||||
this.workspaceId = elem.workspace.id;
|
||||
this.varId = elem.getId();
|
||||
}
|
||||
this.group = Blockly.Events.group_;
|
||||
this.recordUndo = Blockly.Events.recordUndo;
|
||||
@@ -312,6 +367,9 @@ Blockly.Events.Abstract.prototype.toJson = function() {
|
||||
if (this.blockId) {
|
||||
json['blockId'] = this.blockId;
|
||||
}
|
||||
if (this.varId) {
|
||||
json['varId'] = this.varId;
|
||||
}
|
||||
if (this.group) {
|
||||
json['group'] = this.group;
|
||||
}
|
||||
@@ -324,6 +382,7 @@ Blockly.Events.Abstract.prototype.toJson = function() {
|
||||
*/
|
||||
Blockly.Events.Abstract.prototype.fromJson = function(json) {
|
||||
this.blockId = json['blockId'];
|
||||
this.varId = json['varId'];
|
||||
this.group = json['group'];
|
||||
};
|
||||
|
||||
@@ -343,6 +402,21 @@ Blockly.Events.Abstract.prototype.run = function(forward) {
|
||||
// Defined by subclasses.
|
||||
};
|
||||
|
||||
/**
|
||||
* Get workspace the event belongs to.
|
||||
* @return {Blockly.Workspace} The workspace the event belongs to.
|
||||
* @throws {Error} if workspace is null.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Events.Abstract.prototype.getEventWorkspace_ = function() {
|
||||
var workspace = Blockly.Workspace.getById(this.workspaceId);
|
||||
if (!workspace) {
|
||||
throw Error('Workspace is null. Event must have been generated from real' +
|
||||
' Blockly events.');
|
||||
}
|
||||
return workspace;
|
||||
};
|
||||
|
||||
/**
|
||||
* Class for a block creation event.
|
||||
* @param {Blockly.Block} block The created block. Null for a blank event.
|
||||
@@ -364,6 +438,14 @@ Blockly.Events.Create = function(block) {
|
||||
};
|
||||
goog.inherits(Blockly.Events.Create, Blockly.Events.Abstract);
|
||||
|
||||
/**
|
||||
* Class for a block creation event.
|
||||
* @param {Blockly.Block} block The created block. Null for a blank event.
|
||||
* @extends {Blockly.Events.Abstract}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Events.BlockCreate = Blockly.Events.Create;
|
||||
|
||||
/**
|
||||
* Type of this event.
|
||||
* @type {string}
|
||||
@@ -396,7 +478,7 @@ Blockly.Events.Create.prototype.fromJson = function(json) {
|
||||
* @param {boolean} forward True if run forward, false if run backward (undo).
|
||||
*/
|
||||
Blockly.Events.Create.prototype.run = function(forward) {
|
||||
var workspace = Blockly.Workspace.getById(this.workspaceId);
|
||||
var workspace = this.getEventWorkspace_();
|
||||
if (forward) {
|
||||
var xml = goog.dom.createDom('xml');
|
||||
xml.appendChild(this.xml);
|
||||
@@ -438,6 +520,14 @@ Blockly.Events.Delete = function(block) {
|
||||
};
|
||||
goog.inherits(Blockly.Events.Delete, Blockly.Events.Abstract);
|
||||
|
||||
/**
|
||||
* Class for a block deletion event.
|
||||
* @param {Blockly.Block} block The deleted block. Null for a blank event.
|
||||
* @extends {Blockly.Events.Abstract}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Events.BlockDelete = Blockly.Events.Delete;
|
||||
|
||||
/**
|
||||
* Type of this event.
|
||||
* @type {string}
|
||||
@@ -468,7 +558,7 @@ Blockly.Events.Delete.prototype.fromJson = function(json) {
|
||||
* @param {boolean} forward True if run forward, false if run backward (undo).
|
||||
*/
|
||||
Blockly.Events.Delete.prototype.run = function(forward) {
|
||||
var workspace = Blockly.Workspace.getById(this.workspaceId);
|
||||
var workspace = this.getEventWorkspace_();
|
||||
if (forward) {
|
||||
for (var i = 0, id; id = this.ids[i]; i++) {
|
||||
var block = workspace.getBlockById(id);
|
||||
@@ -508,6 +598,18 @@ Blockly.Events.Change = function(block, element, name, oldValue, newValue) {
|
||||
};
|
||||
goog.inherits(Blockly.Events.Change, Blockly.Events.Abstract);
|
||||
|
||||
/**
|
||||
* Class for a block change event.
|
||||
* @param {Blockly.Block} block The changed block. Null for a blank event.
|
||||
* @param {string} element One of 'field', 'comment', 'disabled', etc.
|
||||
* @param {?string} name Name of input or field affected, or null.
|
||||
* @param {string} oldValue Previous value of element.
|
||||
* @param {string} newValue New value of element.
|
||||
* @extends {Blockly.Events.Abstract}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Events.BlockChange = Blockly.Events.Change;
|
||||
|
||||
/**
|
||||
* Type of this event.
|
||||
* @type {string}
|
||||
@@ -552,7 +654,7 @@ Blockly.Events.Change.prototype.isNull = function() {
|
||||
* @param {boolean} forward True if run forward, false if run backward (undo).
|
||||
*/
|
||||
Blockly.Events.Change.prototype.run = function(forward) {
|
||||
var workspace = Blockly.Workspace.getById(this.workspaceId);
|
||||
var workspace = this.getEventWorkspace_();
|
||||
var block = workspace.getBlockById(this.blockId);
|
||||
if (!block) {
|
||||
console.warn("Can't change non-existant block: " + this.blockId);
|
||||
@@ -624,6 +726,15 @@ Blockly.Events.Move = function(block) {
|
||||
};
|
||||
goog.inherits(Blockly.Events.Move, Blockly.Events.Abstract);
|
||||
|
||||
|
||||
/**
|
||||
* Class for a block move event. Created before the move.
|
||||
* @param {Blockly.Block} block The moved block. Null for a blank event.
|
||||
* @extends {Blockly.Events.Abstract}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Events.BlockMove = Blockly.Events.Move;
|
||||
|
||||
/**
|
||||
* Type of this event.
|
||||
* @type {string}
|
||||
@@ -712,7 +823,7 @@ Blockly.Events.Move.prototype.isNull = function() {
|
||||
* @param {boolean} forward True if run forward, false if run backward (undo).
|
||||
*/
|
||||
Blockly.Events.Move.prototype.run = function(forward) {
|
||||
var workspace = Blockly.Workspace.getById(this.workspaceId);
|
||||
var workspace = this.getEventWorkspace_();
|
||||
var block = workspace.getBlockById(this.blockId);
|
||||
if (!block) {
|
||||
console.warn("Can't move non-existant block: " + this.blockId);
|
||||
@@ -801,6 +912,178 @@ Blockly.Events.Ui.prototype.fromJson = function(json) {
|
||||
this.newValue = json['newValue'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Class for a variable creation event.
|
||||
* @param {Blockly.VariableModel} variable The created variable.
|
||||
* Null for a blank event.
|
||||
* @extends {Blockly.Events.Abstract}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Events.VarCreate = function(variable) {
|
||||
if (!variable) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
Blockly.Events.VarCreate.superClass_.constructor.call(this, variable);
|
||||
this.varType = variable.type;
|
||||
this.varName = variable.name;
|
||||
};
|
||||
goog.inherits(Blockly.Events.VarCreate, Blockly.Events.Abstract);
|
||||
|
||||
/**
|
||||
* Type of this event.
|
||||
* @type {string}
|
||||
*/
|
||||
Blockly.Events.VarCreate.prototype.type = Blockly.Events.VAR_CREATE;
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
Blockly.Events.VarCreate.prototype.toJson = function() {
|
||||
var json = Blockly.Events.VarCreate.superClass_.toJson.call(this);
|
||||
json['varType'] = this.varType;
|
||||
json['varName'] = this.varName;
|
||||
return json;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
Blockly.Events.VarCreate.prototype.fromJson = function(json) {
|
||||
Blockly.Events.VarCreate.superClass_.fromJson.call(this, json);
|
||||
this.varType = json['varType'];
|
||||
this.varName = json['varName'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Run a variable creation event.
|
||||
* @param {boolean} forward True if run forward, false if run backward (undo).
|
||||
*/
|
||||
Blockly.Events.VarCreate.prototype.run = function(forward) {
|
||||
var workspace = this.getEventWorkspace_();
|
||||
if (forward) {
|
||||
workspace.createVariable(this.varName, this.varType, this.varId);
|
||||
} else {
|
||||
workspace.deleteVariableById(this.varId);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Class for a variable deletion event.
|
||||
* @param {Blockly.VariableModel} variable The deleted variable.
|
||||
* Null for a blank event.
|
||||
* @extends {Blockly.Events.Abstract}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Events.VarDelete = function(variable) {
|
||||
if (!variable) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
Blockly.Events.VarDelete.superClass_.constructor.call(this, variable);
|
||||
this.varType = variable.type;
|
||||
this.varName = variable.name;
|
||||
};
|
||||
goog.inherits(Blockly.Events.VarDelete, Blockly.Events.Abstract);
|
||||
|
||||
/**
|
||||
* Type of this event.
|
||||
* @type {string}
|
||||
*/
|
||||
Blockly.Events.VarDelete.prototype.type = Blockly.Events.VAR_DELETE;
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
Blockly.Events.VarDelete.prototype.toJson = function() {
|
||||
var json = Blockly.Events.VarDelete.superClass_.toJson.call(this);
|
||||
json['varType'] = this.varType;
|
||||
json['varName'] = this.varName;
|
||||
return json;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
Blockly.Events.VarDelete.prototype.fromJson = function(json) {
|
||||
Blockly.Events.VarDelete.superClass_.fromJson.call(this, json);
|
||||
this.varType = json['varType'];
|
||||
this.varName = json['varName'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Run a variable deletion event.
|
||||
* @param {boolean} forward True if run forward, false if run backward (undo).
|
||||
*/
|
||||
Blockly.Events.VarDelete.prototype.run = function(forward) {
|
||||
var workspace = this.getEventWorkspace_();
|
||||
if (forward) {
|
||||
workspace.deleteVariableById(this.varId);
|
||||
} else {
|
||||
workspace.createVariable(this.varName, this.varType, this.varId);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Class for a variable rename event.
|
||||
* @param {Blockly.VariableModel} variable The renamed variable.
|
||||
* Null for a blank event.
|
||||
* @param {string} newName The new name the variable will be changed to.
|
||||
* @extends {Blockly.Events.Abstract}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Events.VarRename = function(variable, newName) {
|
||||
if (!variable) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
Blockly.Events.VarRename.superClass_.constructor.call(this, variable);
|
||||
this.oldName = variable.name;
|
||||
this.newName = newName;
|
||||
};
|
||||
goog.inherits(Blockly.Events.VarRename, Blockly.Events.Abstract);
|
||||
|
||||
/**
|
||||
* Type of this event.
|
||||
* @type {string}
|
||||
*/
|
||||
Blockly.Events.VarRename.prototype.type = Blockly.Events.VAR_RENAME;
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
Blockly.Events.VarRename.prototype.toJson = function() {
|
||||
var json = Blockly.Events.VarRename.superClass_.toJson.call(this);
|
||||
json['oldName'] = this.oldName;
|
||||
json['newName'] = this.newName;
|
||||
return json;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
Blockly.Events.VarRename.prototype.fromJson = function(json) {
|
||||
Blockly.Events.VarRename.superClass_.fromJson.call(this, json);
|
||||
this.oldName = json['oldName'];
|
||||
this.newName = json['newName'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Run a variable rename event.
|
||||
* @param {boolean} forward True if run forward, false if run backward (undo).
|
||||
*/
|
||||
Blockly.Events.VarRename.prototype.run = function(forward) {
|
||||
var workspace = this.getEventWorkspace_();
|
||||
if (forward) {
|
||||
workspace.renameVariableById(this.varId, this.newName);
|
||||
} else {
|
||||
workspace.renameVariableById(this.varId, this.oldName);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@@ -821,7 +1104,7 @@ Blockly.Events.disableOrphans = function(event) {
|
||||
child.setDisabled(false);
|
||||
}
|
||||
} else if ((block.outputConnection || block.previousConnection) &&
|
||||
Blockly.dragMode_ == Blockly.DRAG_NONE) {
|
||||
!workspace.isDragging()) {
|
||||
do {
|
||||
block.setDisabled(true);
|
||||
block = block.getNextBlock();
|
||||
|
||||
+11
-12
@@ -55,7 +55,7 @@ Blockly.Extensions.MUTATOR_PROPERTIES_ =
|
||||
* handlers and mutators. These are applied using Block.applyExtension(), or
|
||||
* the JSON "extensions" array attribute.
|
||||
* @param {string} name The name of this extension.
|
||||
* @param {function} initFn The function to initialize an extended block.
|
||||
* @param {Function} initFn The function to initialize an extended block.
|
||||
* @throws {Error} if the extension name is empty, the extension is already
|
||||
* registered, or extensionFn is not a function.
|
||||
*/
|
||||
@@ -92,7 +92,7 @@ Blockly.Extensions.registerMixin = function(name, mixinObj) {
|
||||
* decompose are defined on the mixin.
|
||||
* @param {string} name The name of this mutator extension.
|
||||
* @param {!Object} mixinObj The values to mix in.
|
||||
* @param {function()=} opt_helperFn An optional function to apply after mixing
|
||||
* @param {(function())=} opt_helperFn An optional function to apply after mixing
|
||||
* in the object.
|
||||
* @param {Array.<string>=} opt_blockList A list of blocks to appear in the
|
||||
* flyout of the mutator dialog.
|
||||
@@ -103,8 +103,10 @@ Blockly.Extensions.registerMutator = function(name, mixinObj, opt_helperFn,
|
||||
var errorPrefix = 'Error when registering mutator "' + name + '": ';
|
||||
|
||||
// Sanity check the mixin object before registering it.
|
||||
Blockly.Extensions.checkHasFunction_(errorPrefix, mixinObj, 'domToMutation');
|
||||
Blockly.Extensions.checkHasFunction_(errorPrefix, mixinObj, 'mutationToDom');
|
||||
Blockly.Extensions.checkHasFunction_(errorPrefix, mixinObj.domToMutation,
|
||||
'domToMutation');
|
||||
Blockly.Extensions.checkHasFunction_(errorPrefix, mixinObj.mutationToDom,
|
||||
'mutationToDom');
|
||||
|
||||
var hasMutatorDialog = Blockly.Extensions.checkMutatorDialog_(mixinObj,
|
||||
errorPrefix);
|
||||
@@ -162,20 +164,19 @@ Blockly.Extensions.apply = function(name, block, isMutator) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Check that the given object has a property with the given name, and that the
|
||||
* property is a function.
|
||||
* Check that the given value is a function.
|
||||
* @param {string} errorPrefix The string to prepend to any error message.
|
||||
* @param {!Object} object The object to check.
|
||||
* @param {*} func Function to check.
|
||||
* @param {string} propertyName Which property to check.
|
||||
* @throws {Error} if the property does not exist or is not a function.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Extensions.checkHasFunction_ = function(errorPrefix, object,
|
||||
Blockly.Extensions.checkHasFunction_ = function(errorPrefix, func,
|
||||
propertyName) {
|
||||
if (!object.hasOwnProperty(propertyName)) {
|
||||
if (!func) {
|
||||
throw new Error(errorPrefix +
|
||||
'missing required property "' + propertyName + '"');
|
||||
} else if (typeof object[propertyName] !== "function") {
|
||||
} else if (typeof func != 'function') {
|
||||
throw new Error(errorPrefix +
|
||||
'" required property "' + propertyName + '" must be a function');
|
||||
}
|
||||
@@ -442,5 +443,3 @@ Blockly.Extensions.extensionParentTooltip_ = function() {
|
||||
};
|
||||
Blockly.Extensions.register('parent_tooltip_when_inline',
|
||||
Blockly.Extensions.extensionParentTooltip_);
|
||||
|
||||
|
||||
|
||||
+25
-27
@@ -28,6 +28,8 @@
|
||||
|
||||
goog.provide('Blockly.Field');
|
||||
|
||||
goog.require('Blockly.Gesture');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.math.Size');
|
||||
@@ -152,20 +154,27 @@ Blockly.Field.prototype.init = function() {
|
||||
|
||||
this.updateEditable();
|
||||
this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_);
|
||||
this.mouseUpWrapper_ =
|
||||
Blockly.bindEventWithChecks_(this.fieldGroup_, 'mouseup', this,
|
||||
this.onMouseUp_);
|
||||
this.mouseDownWrapper_ =
|
||||
Blockly.bindEventWithChecks_(this.fieldGroup_, 'mousedown', this,
|
||||
this.onMouseDown_);
|
||||
// Force a render.
|
||||
this.render_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes the model of the field after it has been installed on a block.
|
||||
* No-op by default.
|
||||
*/
|
||||
Blockly.Field.prototype.initModel = function() {
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispose of all DOM objects belonging to this editable field.
|
||||
*/
|
||||
Blockly.Field.prototype.dispose = function() {
|
||||
if (this.mouseUpWrapper_) {
|
||||
Blockly.unbindEvent_(this.mouseUpWrapper_);
|
||||
this.mouseUpWrapper_ = null;
|
||||
if (this.mouseDownWrapper_) {
|
||||
Blockly.unbindEvent_(this.mouseDownWrapper_);
|
||||
this.mouseDownWrapper_ = null;
|
||||
}
|
||||
this.sourceBlock_ = null;
|
||||
goog.dom.removeNode(this.fieldGroup_);
|
||||
@@ -486,39 +495,28 @@ Blockly.Field.prototype.setValue = function(newValue) {
|
||||
return;
|
||||
}
|
||||
if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
|
||||
Blockly.Events.fire(new Blockly.Events.Change(
|
||||
Blockly.Events.fire(new Blockly.Events.BlockChange(
|
||||
this.sourceBlock_, 'field', this.name, oldValue, newValue));
|
||||
}
|
||||
this.setText(newValue);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a mouse up event on an editable field.
|
||||
* @param {!Event} e Mouse up event.
|
||||
* Handle a mouse down event on a field.
|
||||
* @param {!Event} e Mouse down event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Field.prototype.onMouseUp_ = function(e) {
|
||||
if ((goog.userAgent.IPHONE || goog.userAgent.IPAD) &&
|
||||
!goog.userAgent.isVersionOrHigher('537.51.2') &&
|
||||
e.layerX !== 0 && e.layerY !== 0) {
|
||||
// Old iOS spawns a bogus event on the next touch after a 'prompt()' edit.
|
||||
// Unlike the real events, these have a layerX and layerY set.
|
||||
Blockly.Field.prototype.onMouseDown_ = function(e) {
|
||||
if (!this.sourceBlock_ || !this.sourceBlock_.workspace) {
|
||||
return;
|
||||
} else if (Blockly.utils.isRightButton(e)) {
|
||||
// Right-click.
|
||||
return;
|
||||
} else if (this.sourceBlock_.workspace.isDragging()) {
|
||||
// Drag operation is concluding. Don't open the editor.
|
||||
return;
|
||||
} else if (this.sourceBlock_.isEditable()) {
|
||||
// Non-abstract sub-classes must define a showEditor_ method.
|
||||
this.showEditor_();
|
||||
// The field is handling the touch, but we also want the blockSvg onMouseUp
|
||||
// handler to fire, so we will leave the touch identifier as it is.
|
||||
// The next onMouseUp is responsible for nulling it out.
|
||||
}
|
||||
var gesture = this.sourceBlock_.workspace.getGesture(e);
|
||||
if (gesture) {
|
||||
gesture.setStartField(this);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Change the tooltip text for this field.
|
||||
* @param {string|!Element} newTip Text for tooltip or a parent element to
|
||||
|
||||
+24
-8
@@ -93,13 +93,35 @@ Blockly.FieldAngle.OFFSET = 0;
|
||||
*/
|
||||
Blockly.FieldAngle.WRAP = 360;
|
||||
|
||||
|
||||
/**
|
||||
* Radius of protractor circle. Slightly smaller than protractor size since
|
||||
* otherwise SVG crops off half the border at the edges.
|
||||
*/
|
||||
Blockly.FieldAngle.RADIUS = Blockly.FieldAngle.HALF - 1;
|
||||
|
||||
/**
|
||||
* Adds degree symbol and recalculates width.
|
||||
* Saves the computed width in a property.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldAngle.prototype.render_ = function() {
|
||||
if (!this.visible_) {
|
||||
this.size_.width = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Update textElement.
|
||||
this.textElement_.textContent = this.getDisplayText_();
|
||||
|
||||
// Insert degree symbol.
|
||||
if (this.sourceBlock_.RTL) {
|
||||
this.textElement_.insertBefore(this.symbol_, this.textElement_.firstChild);
|
||||
} else {
|
||||
this.textElement_.appendChild(this.symbol_);
|
||||
}
|
||||
this.updateWidth();
|
||||
};
|
||||
|
||||
/**
|
||||
* Clean up this FieldAngle, as well as the inherited FieldTextInput.
|
||||
* @return {!Function} Closure to call on destruction of the WidgetDiv.
|
||||
@@ -233,12 +255,6 @@ Blockly.FieldAngle.prototype.setText = function(text) {
|
||||
return;
|
||||
}
|
||||
this.updateGraph_();
|
||||
// Insert degree symbol.
|
||||
if (this.sourceBlock_.RTL) {
|
||||
this.textElement_.insertBefore(this.symbol_, this.textElement_.firstChild);
|
||||
} else {
|
||||
this.textElement_.appendChild(this.symbol_);
|
||||
}
|
||||
// Cached width is obsolete. Clear it.
|
||||
this.size_.width = 0;
|
||||
};
|
||||
@@ -301,4 +317,4 @@ Blockly.FieldAngle.prototype.classValidator = function(text) {
|
||||
n -= 360;
|
||||
}
|
||||
return String(n);
|
||||
};
|
||||
};
|
||||
@@ -93,7 +93,7 @@ Blockly.FieldCheckbox.prototype.setValue = function(newBool) {
|
||||
(newBool.toUpperCase() == 'TRUE') : !!newBool;
|
||||
if (this.state_ !== newState) {
|
||||
if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
|
||||
Blockly.Events.fire(new Blockly.Events.Change(
|
||||
Blockly.Events.fire(new Blockly.Events.BlockChange(
|
||||
this.sourceBlock_, 'field', this.name, this.state_, newState));
|
||||
}
|
||||
this.state_ = newState;
|
||||
|
||||
@@ -101,7 +101,7 @@ Blockly.FieldColour.prototype.getValue = function() {
|
||||
Blockly.FieldColour.prototype.setValue = function(colour) {
|
||||
if (this.sourceBlock_ && Blockly.Events.isEnabled() &&
|
||||
this.colour_ != colour) {
|
||||
Blockly.Events.fire(new Blockly.Events.Change(
|
||||
Blockly.Events.fire(new Blockly.Events.BlockChange(
|
||||
this.sourceBlock_, 'field', this.name, this.colour_, colour));
|
||||
}
|
||||
this.colour_ = colour;
|
||||
|
||||
@@ -328,7 +328,7 @@ Blockly.FieldDropdown.prototype.setValue = function(newValue) {
|
||||
return; // No change if null.
|
||||
}
|
||||
if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
|
||||
Blockly.Events.fire(new Blockly.Events.Change(
|
||||
Blockly.Events.fire(new Blockly.Events.BlockChange(
|
||||
this.sourceBlock_, 'field', this.name, this.value_, newValue));
|
||||
}
|
||||
this.value_ = newValue;
|
||||
|
||||
+17
-1
@@ -38,10 +38,11 @@ goog.require('goog.userAgent');
|
||||
* @param {number} width Width of the image.
|
||||
* @param {number} height Height of the image.
|
||||
* @param {string=} opt_alt Optional alt text for when block is collapsed.
|
||||
* @param {Function=} opt_onClick Optional function to be called when image is clicked
|
||||
* @extends {Blockly.Field}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.FieldImage = function(src, width, height, opt_alt) {
|
||||
Blockly.FieldImage = function(src, width, height, opt_alt, opt_onClick) {
|
||||
this.sourceBlock_ = null;
|
||||
|
||||
// Ensure height and width are numbers. Strings are bad at math.
|
||||
@@ -51,6 +52,10 @@ Blockly.FieldImage = function(src, width, height, opt_alt) {
|
||||
this.height_ + 2 * Blockly.BlockSvg.INLINE_PADDING_Y);
|
||||
this.text_ = opt_alt || '';
|
||||
this.setValue(src);
|
||||
|
||||
if (typeof opt_onClick === "function") {
|
||||
this.clickHandler_ = opt_onClick;
|
||||
}
|
||||
};
|
||||
goog.inherits(Blockly.FieldImage, Blockly.Field);
|
||||
|
||||
@@ -153,6 +158,7 @@ Blockly.FieldImage.prototype.setText = function(alt) {
|
||||
Blockly.FieldImage.prototype.render_ = function() {
|
||||
// NOP
|
||||
};
|
||||
|
||||
/**
|
||||
* Images are fixed width, no need to update.
|
||||
* @private
|
||||
@@ -160,3 +166,13 @@ Blockly.FieldImage.prototype.render_ = function() {
|
||||
Blockly.FieldImage.prototype.updateWidth = function() {
|
||||
// NOP
|
||||
};
|
||||
|
||||
/**
|
||||
* If field click is called, and click handler defined,
|
||||
* call the handler.
|
||||
*/
|
||||
Blockly.FieldImage.prototype.showEditor = function() {
|
||||
if (this.clickHandler_){
|
||||
this.clickHandler_(this);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -109,7 +109,7 @@ Blockly.FieldTextInput.prototype.setText = function(newText) {
|
||||
return;
|
||||
}
|
||||
if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
|
||||
Blockly.Events.fire(new Blockly.Events.Change(
|
||||
Blockly.Events.fire(new Blockly.Events.BlockChange(
|
||||
this.sourceBlock_, 'field', this.name, this.text_, newText));
|
||||
}
|
||||
Blockly.Field.prototype.setText.call(this, newText);
|
||||
|
||||
+107
-55
@@ -28,7 +28,9 @@ goog.provide('Blockly.FieldVariable');
|
||||
|
||||
goog.require('Blockly.FieldDropdown');
|
||||
goog.require('Blockly.Msg');
|
||||
goog.require('Blockly.VariableModel');
|
||||
goog.require('Blockly.Variables');
|
||||
goog.require('Blockly.VariableModel');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.string');
|
||||
|
||||
@@ -39,31 +41,19 @@ goog.require('goog.string');
|
||||
* a unique variable name will be generated.
|
||||
* @param {Function=} opt_validator A function that is executed when a new
|
||||
* option is selected. Its sole argument is the new option value.
|
||||
* @param {Array.<string>} opt_variableTypes A list of the types of variables to
|
||||
* include in the dropdown.
|
||||
* @extends {Blockly.FieldDropdown}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.FieldVariable = function(varname, opt_validator) {
|
||||
Blockly.FieldVariable = function(varname, opt_validator, opt_variableTypes) {
|
||||
Blockly.FieldVariable.superClass_.constructor.call(this,
|
||||
Blockly.FieldVariable.dropdownCreate, opt_validator);
|
||||
this.setValue(varname || '');
|
||||
this.variableTypes = opt_variableTypes;
|
||||
};
|
||||
goog.inherits(Blockly.FieldVariable, Blockly.FieldDropdown);
|
||||
|
||||
/**
|
||||
* The menu item index for the rename variable option.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldVariable.prototype.renameVarItemIndex_ = -1;
|
||||
|
||||
/**
|
||||
* The menu item index for the delete variable option.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldVariable.prototype.deleteVarItemIndex_ = -1;
|
||||
|
||||
|
||||
/**
|
||||
* Install this dropdown on a block.
|
||||
*/
|
||||
@@ -73,6 +63,12 @@ Blockly.FieldVariable.prototype.init = function() {
|
||||
return;
|
||||
}
|
||||
Blockly.FieldVariable.superClass_.init.call(this);
|
||||
|
||||
// TODO (1010): Change from init/initModel to initView/initModel
|
||||
this.initModel();
|
||||
};
|
||||
|
||||
Blockly.FieldVariable.prototype.initModel = function() {
|
||||
if (!this.getValue()) {
|
||||
// Variables without names get uniquely named for this workspace.
|
||||
var workspace =
|
||||
@@ -110,15 +106,54 @@ Blockly.FieldVariable.prototype.getValue = function() {
|
||||
|
||||
/**
|
||||
* Set the variable name.
|
||||
* @param {string} newValue New text.
|
||||
* @param {string} value New text.
|
||||
*/
|
||||
Blockly.FieldVariable.prototype.setValue = function(newValue) {
|
||||
if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
|
||||
Blockly.Events.fire(new Blockly.Events.Change(
|
||||
this.sourceBlock_, 'field', this.name, this.value_, newValue));
|
||||
Blockly.FieldVariable.prototype.setValue = function(value) {
|
||||
var newValue = value;
|
||||
var newText = value;
|
||||
|
||||
if (this.sourceBlock_) {
|
||||
var variable = this.sourceBlock_.workspace.getVariableById(value);
|
||||
if (variable) {
|
||||
newText = variable.name;
|
||||
}
|
||||
// TODO(marisaleung): Remove name lookup after converting all Field Variable
|
||||
// instances to use id instead of name.
|
||||
else if (variable = this.sourceBlock_.workspace.getVariable(value)) {
|
||||
newValue = variable.getId();
|
||||
}
|
||||
if (Blockly.Events.isEnabled()) {
|
||||
Blockly.Events.fire(new Blockly.Events.BlockChange(
|
||||
this.sourceBlock_, 'field', this.name, this.value_, newValue));
|
||||
}
|
||||
}
|
||||
this.value_ = newValue;
|
||||
this.setText(newValue);
|
||||
this.setText(newText);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a list of variable types to include in the dropdown.
|
||||
* @return {!Array.<string>} Array of variable types.
|
||||
* @throws {Error} if variableTypes is an empty array.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldVariable.prototype.getVariableTypes_ = function() {
|
||||
var variableTypes = this.variableTypes;
|
||||
if (variableTypes === null) {
|
||||
// If variableTypes is null, return all variable types.
|
||||
if (this.sourceBlock_) {
|
||||
var workspace = this.sourceBlock_.workspace;
|
||||
return workspace.getVariableTypes();
|
||||
}
|
||||
}
|
||||
variableTypes = variableTypes || [''];
|
||||
if (variableTypes.length == 0) {
|
||||
// Throw an error if variableTypes is an empty list.
|
||||
var name = this.getText();
|
||||
throw new Error('\'variableTypes\' of field variable ' +
|
||||
name + ' was an empty list');
|
||||
}
|
||||
return variableTypes;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -128,30 +163,47 @@ Blockly.FieldVariable.prototype.setValue = function(newValue) {
|
||||
* @this {Blockly.FieldVariable}
|
||||
*/
|
||||
Blockly.FieldVariable.dropdownCreate = function() {
|
||||
if (this.sourceBlock_ && this.sourceBlock_.workspace) {
|
||||
var variableModelList = [];
|
||||
var name = this.getText();
|
||||
// Don't create a new variable if there is nothing selected.
|
||||
var createSelectedVariable = name ? true : false;
|
||||
var workspace = null;
|
||||
if (this.sourceBlock_) {
|
||||
workspace = this.sourceBlock_.workspace;
|
||||
}
|
||||
if (workspace) {
|
||||
var variableTypes = this.getVariableTypes_();
|
||||
var variableModelList = [];
|
||||
// Get a copy of the list, so that adding rename and new variable options
|
||||
// doesn't modify the workspace's list.
|
||||
var variableList = this.sourceBlock_.workspace.variableList.slice(0);
|
||||
} else {
|
||||
var variableList = [];
|
||||
for (var i = 0; i < variableTypes.length; i++) {
|
||||
var variableType = variableTypes[i];
|
||||
var variables = workspace.getVariablesOfType(variableType);
|
||||
variableModelList = variableModelList.concat(variables);
|
||||
}
|
||||
for (var i = 0; i < variableModelList.length; i++){
|
||||
if (createSelectedVariable &&
|
||||
goog.string.caseInsensitiveEquals(variableModelList[i].name, name)) {
|
||||
createSelectedVariable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Ensure that the currently selected variable is an option.
|
||||
var name = this.getText();
|
||||
if (name && variableList.indexOf(name) == -1) {
|
||||
variableList.push(name);
|
||||
if (createSelectedVariable && workspace) {
|
||||
var newVar = workspace.createVariable(name);
|
||||
variableModelList.push(newVar);
|
||||
}
|
||||
variableList.sort(goog.string.caseInsensitiveCompare);
|
||||
|
||||
this.renameVarItemIndex_ = variableList.length;
|
||||
variableList.push(Blockly.Msg.RENAME_VARIABLE);
|
||||
|
||||
this.deleteVarItemIndex_ = variableList.length;
|
||||
variableList.push(Blockly.Msg.DELETE_VARIABLE.replace('%1', name));
|
||||
// Variables are not language-specific, use the name as both the user-facing
|
||||
// text and the internal representation.
|
||||
variableModelList.sort(Blockly.VariableModel.compareByName);
|
||||
var options = [];
|
||||
for (var i = 0; i < variableList.length; i++) {
|
||||
options[i] = [variableList[i], variableList[i]];
|
||||
for (var i = 0; i < variableModelList.length; i++) {
|
||||
// Set the uuid as the internal representation of the variable.
|
||||
options[i] = [variableModelList[i].name, variableModelList[i].getId()];
|
||||
}
|
||||
options.push([Blockly.Msg.RENAME_VARIABLE, Blockly.RENAME_VARIABLE_ID]);
|
||||
if (Blockly.Msg.DELETE_VARIABLE) {
|
||||
options.push([Blockly.Msg.DELETE_VARIABLE.replace('%1', name),
|
||||
Blockly.DELETE_VARIABLE_ID]);
|
||||
}
|
||||
return options;
|
||||
};
|
||||
@@ -164,24 +216,24 @@ Blockly.FieldVariable.dropdownCreate = function() {
|
||||
* @param {!goog.ui.MenuItem} menuItem The MenuItem selected within menu.
|
||||
*/
|
||||
Blockly.FieldVariable.prototype.onItemSelected = function(menu, menuItem) {
|
||||
var itemText = menuItem.getValue();
|
||||
if (this.sourceBlock_) {
|
||||
var id = menuItem.getValue();
|
||||
// TODO(marisaleung): change setValue() to take in an id as the parameter.
|
||||
// Then remove itemText.
|
||||
var itemText;
|
||||
if (this.sourceBlock_ && this.sourceBlock_.workspace) {
|
||||
var workspace = this.sourceBlock_.workspace;
|
||||
if (this.renameVarItemIndex_ >= 0 &&
|
||||
menu.getChildAt(this.renameVarItemIndex_) === menuItem) {
|
||||
var variable = workspace.getVariableById(id);
|
||||
// If the item selected is a variable, set itemText to the variable name.
|
||||
if (variable) {
|
||||
itemText = variable.name;
|
||||
}
|
||||
else if (id == Blockly.RENAME_VARIABLE_ID) {
|
||||
// Rename variable.
|
||||
var oldName = this.getText();
|
||||
Blockly.hideChaff();
|
||||
Blockly.Variables.promptName(
|
||||
Blockly.Msg.RENAME_VARIABLE_TITLE.replace('%1', oldName), oldName,
|
||||
function(newName) {
|
||||
if (newName) {
|
||||
workspace.renameVariable(oldName, newName);
|
||||
}
|
||||
});
|
||||
var currentName = this.getText();
|
||||
variable = workspace.getVariable(currentName);
|
||||
Blockly.Variables.renameVariable(workspace, variable);
|
||||
return;
|
||||
} else if (this.deleteVarItemIndex_ >= 0 &&
|
||||
menu.getChildAt(this.deleteVarItemIndex_) === menuItem) {
|
||||
} else if (id == Blockly.DELETE_VARIABLE_ID) {
|
||||
// Delete variable.
|
||||
workspace.deleteVariable(this.getText());
|
||||
return;
|
||||
|
||||
-1486
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
@@ -0,0 +1,813 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2011 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Flyout tray containing blocks which may be created.
|
||||
* @author fraser@google.com (Neil Fraser)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Flyout');
|
||||
|
||||
goog.require('Blockly.Block');
|
||||
goog.require('Blockly.Events');
|
||||
goog.require('Blockly.FlyoutButton');
|
||||
goog.require('Blockly.Gesture');
|
||||
goog.require('Blockly.Touch');
|
||||
goog.require('Blockly.WorkspaceSvg');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.math.Rect');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a flyout.
|
||||
* @param {!Object} workspaceOptions Dictionary of options for the workspace.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Flyout = function(workspaceOptions) {
|
||||
workspaceOptions.getMetrics = this.getMetrics_.bind(this);
|
||||
workspaceOptions.setMetrics = this.setMetrics_.bind(this);
|
||||
|
||||
/**
|
||||
* @type {!Blockly.Workspace}
|
||||
* @private
|
||||
*/
|
||||
this.workspace_ = new Blockly.WorkspaceSvg(workspaceOptions);
|
||||
this.workspace_.isFlyout = true;
|
||||
|
||||
/**
|
||||
* Is RTL vs LTR.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.RTL = !!workspaceOptions.RTL;
|
||||
|
||||
/**
|
||||
* Position of the toolbox and flyout relative to the workspace.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.toolboxPosition_ = workspaceOptions.toolboxPosition;
|
||||
|
||||
/**
|
||||
* Opaque data that can be passed to Blockly.unbindEvent_.
|
||||
* @type {!Array.<!Array>}
|
||||
* @private
|
||||
*/
|
||||
this.eventWrappers_ = [];
|
||||
|
||||
/**
|
||||
* List of background buttons that lurk behind each block to catch clicks
|
||||
* landing in the blocks' lakes and bays.
|
||||
* @type {!Array.<!Element>}
|
||||
* @private
|
||||
*/
|
||||
this.backgroundButtons_ = [];
|
||||
|
||||
/**
|
||||
* List of visible buttons.
|
||||
* @type {!Array.<!Blockly.FlyoutButton>}
|
||||
* @private
|
||||
*/
|
||||
this.buttons_ = [];
|
||||
|
||||
/**
|
||||
* List of event listeners.
|
||||
* @type {!Array.<!Array>}
|
||||
* @private
|
||||
*/
|
||||
this.listeners_ = [];
|
||||
|
||||
/**
|
||||
* List of blocks that should always be disabled.
|
||||
* @type {!Array.<!Blockly.Block>}
|
||||
* @private
|
||||
*/
|
||||
this.permanentlyDisabled_ = [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Does the flyout automatically close when a block is created?
|
||||
* @type {boolean}
|
||||
*/
|
||||
Blockly.Flyout.prototype.autoClose = true;
|
||||
|
||||
/**
|
||||
* Whether the flyout is visible.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.isVisible_ = false;
|
||||
|
||||
/**
|
||||
* Whether the workspace containing this flyout is visible.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.containerVisible_ = true;
|
||||
|
||||
/**
|
||||
* Corner radius of the flyout background.
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
Blockly.Flyout.prototype.CORNER_RADIUS = 8;
|
||||
|
||||
/**
|
||||
* Margin around the edges of the blocks in the flyout.
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
Blockly.Flyout.prototype.MARGIN = Blockly.Flyout.prototype.CORNER_RADIUS;
|
||||
|
||||
/**
|
||||
* TODO: Move GAP_X and GAP_Y to their appropriate files.
|
||||
* Gap between items in horizontal flyouts. Can be overridden with the "sep"
|
||||
* element.
|
||||
* @const {number}
|
||||
*/
|
||||
Blockly.Flyout.prototype.GAP_X = Blockly.Flyout.prototype.MARGIN * 3;
|
||||
|
||||
/**
|
||||
* Gap between items in vertical flyouts. Can be overridden with the "sep"
|
||||
* element.
|
||||
* @const {number}
|
||||
*/
|
||||
Blockly.Flyout.prototype.GAP_Y = Blockly.Flyout.prototype.MARGIN * 3;
|
||||
|
||||
/**
|
||||
* Top/bottom padding between scrollbar and edge of flyout background.
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
Blockly.Flyout.prototype.SCROLLBAR_PADDING = 2;
|
||||
|
||||
/**
|
||||
* Width of flyout.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.width_ = 0;
|
||||
|
||||
/**
|
||||
* Height of flyout.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.height_ = 0;
|
||||
|
||||
/**
|
||||
* Range of a drag angle from a flyout considered "dragging toward workspace".
|
||||
* Drags that are within the bounds of this many degrees from the orthogonal
|
||||
* line to the flyout edge are considered to be "drags toward the workspace".
|
||||
* Example:
|
||||
* Flyout Edge Workspace
|
||||
* [block] / <-within this angle, drags "toward workspace" |
|
||||
* [block] ---- orthogonal to flyout boundary ---- |
|
||||
* [block] \ |
|
||||
* The angle is given in degrees from the orthogonal.
|
||||
*
|
||||
* This is used to know when to create a new block and when to scroll the
|
||||
* flyout. Setting it to 360 means that all drags create a new block.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.dragAngleRange_ = 70;
|
||||
|
||||
/**
|
||||
* Creates the flyout's DOM. Only needs to be called once. The flyout can
|
||||
* either exist as its own svg element or be a g element nested inside a
|
||||
* separate svg element.
|
||||
* @param {string} tagName The type of tag to put the flyout in. This
|
||||
* should be <svg> or <g>.
|
||||
* @return {!Element} The flyout's SVG group.
|
||||
*/
|
||||
Blockly.Flyout.prototype.createDom = function(tagName) {
|
||||
/*
|
||||
<svg | g>
|
||||
<path class="blocklyFlyoutBackground"/>
|
||||
<g class="blocklyFlyout"></g>
|
||||
</ svg | g>
|
||||
*/
|
||||
// Setting style to display:none to start. The toolbox and flyout
|
||||
// hide/show code will set up proper visibility and size later.
|
||||
this.svgGroup_ = Blockly.utils.createSvgElement(tagName,
|
||||
{'class': 'blocklyFlyout', 'style': 'display: none'}, null);
|
||||
this.svgBackground_ = Blockly.utils.createSvgElement('path',
|
||||
{'class': 'blocklyFlyoutBackground'}, this.svgGroup_);
|
||||
this.svgGroup_.appendChild(this.workspace_.createDom());
|
||||
return this.svgGroup_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes the flyout.
|
||||
* @param {!Blockly.Workspace} targetWorkspace The workspace in which to create
|
||||
* new blocks.
|
||||
*/
|
||||
Blockly.Flyout.prototype.init = function(targetWorkspace) {
|
||||
this.targetWorkspace_ = targetWorkspace;
|
||||
this.workspace_.targetWorkspace = targetWorkspace;
|
||||
// Add scrollbar.
|
||||
this.scrollbar_ = new Blockly.Scrollbar(this.workspace_,
|
||||
this.horizontalLayout_, false, 'blocklyFlyoutScrollbar');
|
||||
|
||||
this.hide();
|
||||
|
||||
Array.prototype.push.apply(this.eventWrappers_,
|
||||
Blockly.bindEventWithChecks_(this.svgGroup_, 'wheel', this, this.wheel_));
|
||||
if (!this.autoClose) {
|
||||
this.filterWrapper_ = this.filterForCapacity_.bind(this);
|
||||
this.targetWorkspace_.addChangeListener(this.filterWrapper_);
|
||||
}
|
||||
|
||||
// Dragging the flyout up and down.
|
||||
Array.prototype.push.apply(this.eventWrappers_,
|
||||
Blockly.bindEventWithChecks_(this.svgBackground_, 'mousedown', this,
|
||||
this.onMouseDown_));
|
||||
|
||||
// A flyout connected to a workspace doesn't have its own current gesture.
|
||||
this.workspace_.getGesture =
|
||||
this.targetWorkspace_.getGesture.bind(this.targetWorkspace_);
|
||||
|
||||
// Get variables from the main workspace rather than the target workspace.
|
||||
this.workspace_.getVariable =
|
||||
this.targetWorkspace_.getVariable.bind(this.targetWorkspace_);
|
||||
|
||||
this.workspace_.getVariableById =
|
||||
this.targetWorkspace_.getVariableById.bind(this.targetWorkspace_);
|
||||
|
||||
this.workspace_.getVariablesOfType =
|
||||
this.targetWorkspace_.getVariablesOfType.bind(this.targetWorkspace_);
|
||||
|
||||
this.workspace_.deleteVariable =
|
||||
this.targetWorkspace_.deleteVariable.bind(this.targetWorkspace_);
|
||||
|
||||
this.workspace_.deleteVariableById =
|
||||
this.targetWorkspace_.deleteVariableById.bind(this.targetWorkspace_);
|
||||
|
||||
this.workspace_.renameVariable =
|
||||
this.targetWorkspace_.renameVariable.bind(this.targetWorkspace_);
|
||||
|
||||
this.workspace_.renameVariableById =
|
||||
this.targetWorkspace_.renameVariableById.bind(this.targetWorkspace_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispose of this flyout.
|
||||
* Unlink from all DOM elements to prevent memory leaks.
|
||||
*/
|
||||
Blockly.Flyout.prototype.dispose = function() {
|
||||
this.hide();
|
||||
Blockly.unbindEvent_(this.eventWrappers_);
|
||||
if (this.filterWrapper_) {
|
||||
this.targetWorkspace_.removeChangeListener(this.filterWrapper_);
|
||||
this.filterWrapper_ = null;
|
||||
}
|
||||
if (this.scrollbar_) {
|
||||
this.scrollbar_.dispose();
|
||||
this.scrollbar_ = null;
|
||||
}
|
||||
if (this.workspace_) {
|
||||
this.workspace_.targetWorkspace = null;
|
||||
this.workspace_.dispose();
|
||||
this.workspace_ = null;
|
||||
}
|
||||
if (this.svgGroup_) {
|
||||
goog.dom.removeNode(this.svgGroup_);
|
||||
this.svgGroup_ = null;
|
||||
}
|
||||
this.svgBackground_ = null;
|
||||
this.targetWorkspace_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the width of the flyout.
|
||||
* @return {number} The width of the flyout.
|
||||
*/
|
||||
Blockly.Flyout.prototype.getWidth = function() {
|
||||
return this.width_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the height of the flyout.
|
||||
* @return {number} The width of the flyout.
|
||||
*/
|
||||
Blockly.Flyout.prototype.getHeight = function() {
|
||||
return this.height_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the workspace inside the flyout.
|
||||
* @return {!Blockly.WorkspaceSvg} The workspace inside the flyout.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Flyout.prototype.getWorkspace = function() {
|
||||
return this.workspace_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Is the flyout visible?
|
||||
* @return {boolean} True if visible.
|
||||
*/
|
||||
Blockly.Flyout.prototype.isVisible = function() {
|
||||
return this.isVisible_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set whether the flyout is visible. A value of true does not necessarily mean
|
||||
* that the flyout is shown. It could be hidden because its container is hidden.
|
||||
* @param {boolean} visible True if visible.
|
||||
*/
|
||||
Blockly.Flyout.prototype.setVisible = function(visible) {
|
||||
var visibilityChanged = (visible != this.isVisible());
|
||||
|
||||
this.isVisible_ = visible;
|
||||
if (visibilityChanged) {
|
||||
this.updateDisplay_();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set whether this flyout's container is visible.
|
||||
* @param {boolean} visible Whether the container is visible.
|
||||
*/
|
||||
Blockly.Flyout.prototype.setContainerVisible = function(visible) {
|
||||
var visibilityChanged = (visible != this.containerVisible_);
|
||||
this.containerVisible_ = visible;
|
||||
if (visibilityChanged) {
|
||||
this.updateDisplay_();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the display property of the flyout based whether it thinks it should
|
||||
* be visible and whether its containing workspace is visible.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.updateDisplay_ = function() {
|
||||
var show = true;
|
||||
if (!this.containerVisible_) {
|
||||
show = false;
|
||||
} else {
|
||||
show = this.isVisible();
|
||||
}
|
||||
this.svgGroup_.style.display = show ? 'block' : 'none';
|
||||
// Update the scrollbar's visiblity too since it should mimic the
|
||||
// flyout's visibility.
|
||||
this.scrollbar_.setContainerVisible(show);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the view based on coordinates calculated in position().
|
||||
* @param {number} width The computed width of the flyout's SVG group
|
||||
* @param {number} height The computed height of the flyout's SVG group.
|
||||
* @param {number} x The computed x origin of the flyout's SVG group.
|
||||
* @param {number} y The computed y origin of the flyout's SVG group.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.positionAt_ = function(width, height, x, y) {
|
||||
this.svgGroup_.setAttribute("width", width);
|
||||
this.svgGroup_.setAttribute("height", height);
|
||||
var transform = 'translate(' + x + 'px,' + y + 'px)';
|
||||
Blockly.utils.setCssTransform(this.svgGroup_, transform);
|
||||
|
||||
// Update the scrollbar (if one exists).
|
||||
if (this.scrollbar_) {
|
||||
// Set the scrollbars origin to be the top left of the flyout.
|
||||
this.scrollbar_.setOrigin(x, y);
|
||||
this.scrollbar_.resize();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Hide and empty the flyout.
|
||||
*/
|
||||
Blockly.Flyout.prototype.hide = function() {
|
||||
if (!this.isVisible()) {
|
||||
return;
|
||||
}
|
||||
this.setVisible(false);
|
||||
// Delete all the event listeners.
|
||||
for (var x = 0, listen; listen = this.listeners_[x]; x++) {
|
||||
Blockly.unbindEvent_(listen);
|
||||
}
|
||||
this.listeners_.length = 0;
|
||||
if (this.reflowWrapper_) {
|
||||
this.workspace_.removeChangeListener(this.reflowWrapper_);
|
||||
this.reflowWrapper_ = null;
|
||||
}
|
||||
// Do NOT delete the blocks here. Wait until Flyout.show.
|
||||
// https://neil.fraser.name/news/2014/08/09/
|
||||
};
|
||||
|
||||
/**
|
||||
* Show and populate the flyout.
|
||||
* @param {!Array|string} xmlList List of blocks to show.
|
||||
* Variables and procedures have a custom set of blocks.
|
||||
*/
|
||||
Blockly.Flyout.prototype.show = function(xmlList) {
|
||||
this.workspace_.setResizesEnabled(false);
|
||||
this.hide();
|
||||
this.clearOldBlocks_();
|
||||
|
||||
// Handle dynamic categories, represented by a name instead of a list of XML.
|
||||
// Look up the correct category generation function and call that to get a
|
||||
// valid XML list.
|
||||
if (typeof xmlList == 'string') {
|
||||
var fnToApply = this.workspace_.targetWorkspace.getToolboxCategoryCallback(
|
||||
xmlList);
|
||||
goog.asserts.assert(goog.isFunction(fnToApply),
|
||||
'Couldn\'t find a callback function when opening a toolbox category.');
|
||||
xmlList = fnToApply(this.workspace_.targetWorkspace);
|
||||
goog.asserts.assert(goog.isArray(xmlList),
|
||||
'The result of a toolbox category callback must be an array.');
|
||||
}
|
||||
|
||||
this.setVisible(true);
|
||||
// Create the blocks to be shown in this flyout.
|
||||
var contents = [];
|
||||
var gaps = [];
|
||||
this.permanentlyDisabled_.length = 0;
|
||||
for (var i = 0, xml; xml = xmlList[i]; i++) {
|
||||
if (xml.tagName) {
|
||||
var tagName = xml.tagName.toUpperCase();
|
||||
var default_gap = this.horizontalLayout_ ? this.GAP_X : this.GAP_Y;
|
||||
if (tagName == 'BLOCK') {
|
||||
var curBlock = Blockly.Xml.domToBlock(xml, this.workspace_);
|
||||
if (curBlock.disabled) {
|
||||
// Record blocks that were initially disabled.
|
||||
// Do not enable these blocks as a result of capacity filtering.
|
||||
this.permanentlyDisabled_.push(curBlock);
|
||||
}
|
||||
contents.push({type: 'block', block: curBlock});
|
||||
var gap = parseInt(xml.getAttribute('gap'), 10);
|
||||
gaps.push(isNaN(gap) ? default_gap : gap);
|
||||
} else if (xml.tagName.toUpperCase() == 'SEP') {
|
||||
// Change the gap between two blocks.
|
||||
// <sep gap="36"></sep>
|
||||
// The default gap is 24, can be set larger or smaller.
|
||||
// This overwrites the gap attribute on the previous block.
|
||||
// Note that a deprecated method is to add a gap to a block.
|
||||
// <block type="math_arithmetic" gap="8"></block>
|
||||
var newGap = parseInt(xml.getAttribute('gap'), 10);
|
||||
// Ignore gaps before the first block.
|
||||
if (!isNaN(newGap) && gaps.length > 0) {
|
||||
gaps[gaps.length - 1] = newGap;
|
||||
} else {
|
||||
gaps.push(default_gap);
|
||||
}
|
||||
} else if (tagName == 'BUTTON' || tagName == 'LABEL') {
|
||||
// Labels behave the same as buttons, but are styled differently.
|
||||
var isLabel = tagName == 'LABEL';
|
||||
var curButton = new Blockly.FlyoutButton(this.workspace_,
|
||||
this.targetWorkspace_, xml, isLabel);
|
||||
contents.push({type: 'button', button: curButton});
|
||||
gaps.push(default_gap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.layout_(contents, gaps);
|
||||
|
||||
// IE 11 is an incompetent browser that fails to fire mouseout events.
|
||||
// When the mouse is over the background, deselect all blocks.
|
||||
var deselectAll = function() {
|
||||
var topBlocks = this.workspace_.getTopBlocks(false);
|
||||
for (var i = 0, block; block = topBlocks[i]; i++) {
|
||||
block.removeSelect();
|
||||
}
|
||||
};
|
||||
|
||||
this.listeners_.push(Blockly.bindEventWithChecks_(this.svgBackground_,
|
||||
'mouseover', this, deselectAll));
|
||||
|
||||
if (this.horizontalLayout_) {
|
||||
this.height_ = 0;
|
||||
} else {
|
||||
this.width_ = 0;
|
||||
}
|
||||
this.workspace_.setResizesEnabled(true);
|
||||
this.reflow();
|
||||
|
||||
this.filterForCapacity_();
|
||||
|
||||
// Correctly position the flyout's scrollbar when it opens.
|
||||
this.position();
|
||||
|
||||
this.reflowWrapper_ = this.reflow.bind(this);
|
||||
this.workspace_.addChangeListener(this.reflowWrapper_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete blocks and background buttons from a previous showing of the flyout.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.clearOldBlocks_ = function() {
|
||||
// Delete any blocks from a previous showing.
|
||||
var oldBlocks = this.workspace_.getTopBlocks(false);
|
||||
for (var i = 0, block; block = oldBlocks[i]; i++) {
|
||||
if (block.workspace == this.workspace_) {
|
||||
block.dispose(false, false);
|
||||
}
|
||||
}
|
||||
// Delete any background buttons from a previous showing.
|
||||
for (var j = 0, rect; rect = this.backgroundButtons_[j]; j++) {
|
||||
goog.dom.removeNode(rect);
|
||||
}
|
||||
this.backgroundButtons_.length = 0;
|
||||
|
||||
for (var i = 0, button; button = this.buttons_[i]; i++) {
|
||||
button.dispose();
|
||||
}
|
||||
this.buttons_.length = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add listeners to a block that has been added to the flyout.
|
||||
* @param {!Element} root The root node of the SVG group the block is in.
|
||||
* @param {!Blockly.Block} block The block to add listeners for.
|
||||
* @param {!Element} rect The invisible rectangle under the block that acts as
|
||||
* a button for that block.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.addBlockListeners_ = function(root, block, rect) {
|
||||
this.listeners_.push(Blockly.bindEventWithChecks_(root, 'mousedown', null,
|
||||
this.blockMouseDown_(block)));
|
||||
this.listeners_.push(Blockly.bindEventWithChecks_(rect, 'mousedown', null,
|
||||
this.blockMouseDown_(block)));
|
||||
this.listeners_.push(Blockly.bindEvent_(root, 'mouseover', block,
|
||||
block.addSelect));
|
||||
this.listeners_.push(Blockly.bindEvent_(root, 'mouseout', block,
|
||||
block.removeSelect));
|
||||
this.listeners_.push(Blockly.bindEvent_(rect, 'mouseover', block,
|
||||
block.addSelect));
|
||||
this.listeners_.push(Blockly.bindEvent_(rect, 'mouseout', block,
|
||||
block.removeSelect));
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a mouse-down on an SVG block in a non-closing flyout.
|
||||
* @param {!Blockly.Block} block The flyout block to copy.
|
||||
* @return {!Function} Function to call when block is clicked.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.blockMouseDown_ = function(block) {
|
||||
var flyout = this;
|
||||
return function(e) {
|
||||
var gesture = flyout.targetWorkspace_.getGesture(e);
|
||||
if (gesture) {
|
||||
gesture.setStartBlock(block);
|
||||
gesture.handleFlyoutStart(e, flyout);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Mouse down on the flyout background. Start a vertical scroll drag.
|
||||
* @param {!Event} e Mouse down event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.onMouseDown_ = function(e) {
|
||||
var gesture = this.targetWorkspace_.getGesture(e);
|
||||
if (gesture) {
|
||||
gesture.handleFlyoutStart(e, this);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a copy of this block on the workspace.
|
||||
* @param {!Blockly.BlockSvg} originalBlock The block to copy from the flyout.
|
||||
* @return {Blockly.BlockSvg} The newly created block, or null if something
|
||||
* went wrong with deserialization.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Flyout.prototype.createBlock = function(originalBlock) {
|
||||
var newBlock = null;
|
||||
Blockly.Events.disable();
|
||||
this.targetWorkspace_.setResizesEnabled(false);
|
||||
try {
|
||||
newBlock = this.placeNewBlock_(originalBlock);
|
||||
//Force a render on IE and Edge to get around the issue described in
|
||||
//Blockly.Field.getCachedWidth
|
||||
if (goog.userAgent.IE || goog.userAgent.EDGE) {
|
||||
var blocks = newBlock.getDescendants();
|
||||
for (var i = blocks.length - 1; i >= 0; i--) {
|
||||
blocks[i].render(false);
|
||||
}
|
||||
}
|
||||
// Close the flyout.
|
||||
Blockly.hideChaff();
|
||||
} finally {
|
||||
Blockly.Events.enable();
|
||||
}
|
||||
|
||||
if (Blockly.Events.isEnabled()) {
|
||||
Blockly.Events.setGroup(true);
|
||||
Blockly.Events.fire(new Blockly.Events.Create(newBlock));
|
||||
}
|
||||
if (this.autoClose) {
|
||||
this.hide();
|
||||
} else {
|
||||
this.filterForCapacity_();
|
||||
}
|
||||
return newBlock;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize the given button: move it to the correct location,
|
||||
* add listeners, etc.
|
||||
* @param {!Blockly.FlyoutButton} button The button to initialize and place.
|
||||
* @param {number} x The x position of the cursor during this layout pass.
|
||||
* @param {number} y The y position of the cursor during this layout pass.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.initFlyoutButton_ = function(button, x, y) {
|
||||
var buttonSvg = button.createDom();
|
||||
button.moveTo(x, y);
|
||||
button.show();
|
||||
// Clicking on a flyout button or label is a lot like clicking on the
|
||||
// flyout background.
|
||||
this.listeners_.push(Blockly.bindEventWithChecks_(buttonSvg, 'mousedown',
|
||||
this, this.onMouseDown_));
|
||||
|
||||
this.buttons_.push(button);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create and place a rectangle corresponding to the given block.
|
||||
* @param {!Blockly.Block} block The block to associate the rect to.
|
||||
* @param {number} x The x position of the cursor during this layout pass.
|
||||
* @param {number} y The y position of the cursor during this layout pass.
|
||||
* @param {!{height: number, width: number}} blockHW The height and width of the
|
||||
* block.
|
||||
* @param {number} index The index into the background buttons list where this
|
||||
* rect should be placed.
|
||||
* @return {!SVGElement} Newly created SVG element for the rectangle behind the
|
||||
* block.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.createRect_ = function(block, x, y, blockHW, index) {
|
||||
// Create an invisible rectangle under the block to act as a button. Just
|
||||
// using the block as a button is poor, since blocks have holes in them.
|
||||
var rect = Blockly.utils.createSvgElement('rect',
|
||||
{
|
||||
'fill-opacity': 0,
|
||||
'x': x,
|
||||
'y': y,
|
||||
'height': blockHW.height,
|
||||
'width': blockHW.width
|
||||
}, null);
|
||||
rect.tooltip = block;
|
||||
Blockly.Tooltip.bindMouseEvents(rect);
|
||||
// Add the rectangles under the blocks, so that the blocks' tooltips work.
|
||||
this.workspace_.getCanvas().insertBefore(rect, block.getSvgRoot());
|
||||
|
||||
block.flyoutRect_ = rect;
|
||||
this.backgroundButtons_[index] = rect;
|
||||
return rect;
|
||||
};
|
||||
|
||||
/**
|
||||
* Move a rectangle to sit exactly behind a block, taking into account tabs,
|
||||
* hats, and any other protrusions we invent.
|
||||
* @param {!SVGElement} rect The rectangle to move directly behind the block.
|
||||
* @param {!Blockly.BlockSvg} block The block the rectangle should be behind.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.moveRectToBlock_ = function(rect, block) {
|
||||
var blockHW = block.getHeightWidth();
|
||||
rect.setAttribute('width', blockHW.width);
|
||||
rect.setAttribute('height', blockHW.height);
|
||||
|
||||
// For hat blocks we want to shift them down by the hat height
|
||||
// since the y coordinate is the corner, not the top of the hat.
|
||||
var hatOffset =
|
||||
block.startHat_ ? Blockly.BlockSvg.START_HAT_HEIGHT : 0;
|
||||
if (hatOffset) {
|
||||
block.moveBy(0, hatOffset);
|
||||
}
|
||||
|
||||
// Blocks with output tabs are shifted a bit.
|
||||
var tab = block.outputConnection ? Blockly.BlockSvg.TAB_WIDTH : 0;
|
||||
var blockXY = block.getRelativeToSurfaceXY();
|
||||
rect.setAttribute('y', blockXY.y);
|
||||
rect.setAttribute('x',
|
||||
this.RTL ? blockXY.x - blockHW.width + tab : blockXY.x - tab);
|
||||
};
|
||||
|
||||
/**
|
||||
* Filter the blocks on the flyout to disable the ones that are above the
|
||||
* capacity limit. For instance, if the user may only place two more blocks on
|
||||
* the workspace, an "a + b" block that has two shadow blocks would be disabled.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.filterForCapacity_ = function() {
|
||||
var remainingCapacity = this.targetWorkspace_.remainingCapacity();
|
||||
var blocks = this.workspace_.getTopBlocks(false);
|
||||
for (var i = 0, block; block = blocks[i]; i++) {
|
||||
if (this.permanentlyDisabled_.indexOf(block) == -1) {
|
||||
var allBlocks = block.getDescendants();
|
||||
block.setDisabled(allBlocks.length > remainingCapacity);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Reflow blocks and their buttons.
|
||||
*/
|
||||
Blockly.Flyout.prototype.reflow = function() {
|
||||
if (this.reflowWrapper_) {
|
||||
this.workspace_.removeChangeListener(this.reflowWrapper_);
|
||||
}
|
||||
var blocks = this.workspace_.getTopBlocks(false);
|
||||
this.reflowInternal_(blocks);
|
||||
if (this.reflowWrapper_) {
|
||||
this.workspace_.addChangeListener(this.reflowWrapper_);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {boolean} True if this flyout may be scrolled with a scrollbar or by
|
||||
* dragging.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Flyout.prototype.isScrollable = function() {
|
||||
return this.scrollbar_ ? this.scrollbar_.isVisible() : false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Copy a block from the flyout to the workspace and position it correctly.
|
||||
* @param {!Blockly.Block} oldBlock The flyout block to copy.
|
||||
* @return {!Blockly.Block} The new block in the main workspace.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.placeNewBlock_ = function(oldBlock) {
|
||||
var targetWorkspace = this.targetWorkspace_;
|
||||
var svgRootOld = oldBlock.getSvgRoot();
|
||||
if (!svgRootOld) {
|
||||
throw 'oldBlock is not rendered.';
|
||||
}
|
||||
|
||||
// Create the new block by cloning the block in the flyout (via XML).
|
||||
var xml = Blockly.Xml.blockToDom(oldBlock);
|
||||
// The target workspace would normally resize during domToBlock, which will
|
||||
// lead to weird jumps. Save it for terminateDrag.
|
||||
targetWorkspace.setResizesEnabled(false);
|
||||
|
||||
// Using domToBlock instead of domToWorkspace means that the new block will be
|
||||
// placed at position (0, 0) in main workspace units.
|
||||
var block = Blockly.Xml.domToBlock(xml, targetWorkspace);
|
||||
var svgRootNew = block.getSvgRoot();
|
||||
if (!svgRootNew) {
|
||||
throw 'block is not rendered.';
|
||||
}
|
||||
|
||||
// The offset in pixels between the main workspace's origin and the upper left
|
||||
// corner of the injection div.
|
||||
var mainOffsetPixels = targetWorkspace.getOriginOffsetInPixels();
|
||||
|
||||
// The offset in pixels between the flyout workspace's origin and the upper
|
||||
// left corner of the injection div.
|
||||
var flyoutOffsetPixels = this.workspace_.getOriginOffsetInPixels();
|
||||
|
||||
// The position of the old block in flyout workspace coordinates.
|
||||
var oldBlockPosWs = oldBlock.getRelativeToSurfaceXY();
|
||||
|
||||
// The position of the old block in pixels relative to the flyout
|
||||
// workspace's origin.
|
||||
var oldBlockPosPixels = oldBlockPosWs.scale(this.workspace_.scale);
|
||||
|
||||
// The position of the old block in pixels relative to the upper left corner
|
||||
// of the injection div.
|
||||
var oldBlockOffsetPixels = goog.math.Coordinate.sum(flyoutOffsetPixels,
|
||||
oldBlockPosPixels);
|
||||
|
||||
// The position of the old block in pixels relative to the origin of the
|
||||
// main workspace.
|
||||
var finalOffsetPixels = goog.math.Coordinate.difference(oldBlockOffsetPixels,
|
||||
mainOffsetPixels);
|
||||
|
||||
// The position of the old block in main workspace coordinates.
|
||||
var finalOffsetMainWs = finalOffsetPixels.scale(1 / targetWorkspace.scale);
|
||||
|
||||
block.moveBy(finalOffsetMainWs.x, finalOffsetMainWs.y);
|
||||
return block;
|
||||
};
|
||||
+19
-8
@@ -115,6 +115,13 @@ Blockly.FlyoutButton.prototype.width = 0;
|
||||
*/
|
||||
Blockly.FlyoutButton.prototype.height = 0;
|
||||
|
||||
/**
|
||||
* Opaque data that can be passed to Blockly.unbindEvent_.
|
||||
* @type {Array.<!Array>}
|
||||
* @private
|
||||
*/
|
||||
Blockly.FlyoutButton.prototype.onMouseUpWrapper_ = null;
|
||||
|
||||
/**
|
||||
* Create the button elements.
|
||||
* @return {!Element} The button's SVG group.
|
||||
@@ -163,6 +170,9 @@ Blockly.FlyoutButton.prototype.createDom = function() {
|
||||
svgText.setAttribute('y', this.height - Blockly.FlyoutButton.MARGIN);
|
||||
|
||||
this.updateTransform_();
|
||||
|
||||
this.mouseUpWrapper_ = Blockly.bindEventWithChecks_(this.svgGroup_, 'mouseup',
|
||||
this, this.onMouseUp_);
|
||||
return this.svgGroup_;
|
||||
};
|
||||
|
||||
@@ -207,6 +217,9 @@ Blockly.FlyoutButton.prototype.getTargetWorkspace = function() {
|
||||
* Dispose of this button.
|
||||
*/
|
||||
Blockly.FlyoutButton.prototype.dispose = function() {
|
||||
if (this.onMouseUpWrapper_) {
|
||||
Blockly.unbindEvent_(this.onMouseUpWrapper_);
|
||||
}
|
||||
if (this.svgGroup_) {
|
||||
goog.dom.removeNode(this.svgGroup_);
|
||||
this.svgGroup_ = null;
|
||||
@@ -218,15 +231,13 @@ Blockly.FlyoutButton.prototype.dispose = function() {
|
||||
/**
|
||||
* Do something when the button is clicked.
|
||||
* @param {!Event} e Mouse up event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FlyoutButton.prototype.onMouseUp = function(e) {
|
||||
// Don't scroll the page.
|
||||
e.preventDefault();
|
||||
// Don't propagate mousewheel event (zooming).
|
||||
e.stopPropagation();
|
||||
// Stop binding to mouseup and mousemove events--flyout mouseup would normally
|
||||
// do this, but we're skipping that.
|
||||
Blockly.Flyout.terminateDrag_();
|
||||
Blockly.FlyoutButton.prototype.onMouseUp_ = function(e) {
|
||||
var gesture = this.targetWorkspace_.getGesture(e);
|
||||
if (gesture) {
|
||||
gesture.cancel();
|
||||
}
|
||||
|
||||
// Call the callback registered to this button.
|
||||
if (this.callback_) {
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2017 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Methods for dragging a flyout visually.
|
||||
* @author fenichel@google.com (Rachel Fenichel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.FlyoutDragger');
|
||||
|
||||
goog.require('Blockly.WorkspaceDragger');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.math.Coordinate');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a flyout dragger. It moves a flyout workspace around when it is
|
||||
* being dragged by a mouse or touch.
|
||||
* Note that the workspace itself manages whether or not it has a drag surface
|
||||
* and how to do translations based on that. This simply passes the right
|
||||
* commands based on events.
|
||||
* @param {!Blockly.Flyout} flyout The flyout to drag.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.FlyoutDragger = function(flyout) {
|
||||
Blockly.FlyoutDragger.superClass_.constructor.call(this,
|
||||
flyout.getWorkspace());
|
||||
|
||||
/**
|
||||
* The scrollbar to update to move the flyout.
|
||||
* Unlike the main workspace, the flyout has only one scrollbar, in either the
|
||||
* horizontal or the vertical direction.
|
||||
* @type {!Blockly.Scrollbar}
|
||||
* @private
|
||||
*/
|
||||
this.scrollbar_ = flyout.scrollbar_;
|
||||
|
||||
/**
|
||||
* Whether the flyout scrolls horizontally. If false, the flyout scrolls
|
||||
* vertically.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.horizontalLayout_ = flyout.horizontalLayout_;
|
||||
};
|
||||
goog.inherits(Blockly.FlyoutDragger, Blockly.WorkspaceDragger);
|
||||
|
||||
/**
|
||||
* Move the appropriate scrollbar to drag the flyout.
|
||||
* Since flyouts only scroll in one direction at a time, this will discard one
|
||||
* of the calculated values.
|
||||
* x and y are in pixels.
|
||||
* @param {number} x The new x position to move the scrollbar to.
|
||||
* @param {number} y The new y position to move the scrollbar to.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FlyoutDragger.prototype.updateScroll_ = function(x, y) {
|
||||
// Move the scrollbar and the flyout will scroll automatically.
|
||||
if (this.horizontalLayout_) {
|
||||
this.scrollbar_.set(x);
|
||||
} else {
|
||||
this.scrollbar_.set(y);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,380 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2017 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Horizontal flyout tray containing blocks which may be created.
|
||||
* @author fenichel@google.com (Rachel Fenichel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.HorizontalFlyout');
|
||||
|
||||
goog.require('Blockly.Block');
|
||||
goog.require('Blockly.Events');
|
||||
goog.require('Blockly.FlyoutButton');
|
||||
goog.require('Blockly.Flyout');
|
||||
goog.require('Blockly.WorkspaceSvg');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.math.Rect');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a flyout.
|
||||
* @param {!Object} workspaceOptions Dictionary of options for the workspace.
|
||||
* @extends {Blockly.Flyout}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.HorizontalFlyout = function(workspaceOptions) {
|
||||
workspaceOptions.getMetrics = this.getMetrics_.bind(this);
|
||||
workspaceOptions.setMetrics = this.setMetrics_.bind(this);
|
||||
|
||||
Blockly.HorizontalFlyout.superClass_.constructor.call(this, workspaceOptions);
|
||||
/**
|
||||
* Flyout should be laid out horizontally.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.horizontalLayout_ = true;
|
||||
};
|
||||
goog.inherits(Blockly.HorizontalFlyout, Blockly.Flyout);
|
||||
|
||||
/**
|
||||
* Return an object with all the metrics required to size scrollbars for the
|
||||
* flyout. The following properties are computed:
|
||||
* .viewHeight: Height of the visible rectangle,
|
||||
* .viewWidth: Width of the visible rectangle,
|
||||
* .contentHeight: Height of the contents,
|
||||
* .contentWidth: Width of the contents,
|
||||
* .viewTop: Offset of top edge of visible rectangle from parent,
|
||||
* .contentTop: Offset of the top-most content from the y=0 coordinate,
|
||||
* .absoluteTop: Top-edge of view.
|
||||
* .viewLeft: Offset of the left edge of visible rectangle from parent,
|
||||
* .contentLeft: Offset of the left-most content from the x=0 coordinate,
|
||||
* .absoluteLeft: Left-edge of view.
|
||||
* @return {Object} Contains size and position metrics of the flyout.
|
||||
* @private
|
||||
*/
|
||||
Blockly.HorizontalFlyout.prototype.getMetrics_ = function() {
|
||||
if (!this.isVisible()) {
|
||||
// Flyout is hidden.
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
var optionBox = this.workspace_.getCanvas().getBBox();
|
||||
} catch (e) {
|
||||
// Firefox has trouble with hidden elements (Bug 528969).
|
||||
var optionBox = {height: 0, y: 0, width: 0, x: 0};
|
||||
}
|
||||
|
||||
var absoluteTop = this.SCROLLBAR_PADDING;
|
||||
var absoluteLeft = this.SCROLLBAR_PADDING;
|
||||
if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_BOTTOM) {
|
||||
absoluteTop = 0;
|
||||
}
|
||||
var viewHeight = this.height_;
|
||||
if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP) {
|
||||
viewHeight -= this.SCROLLBAR_PADDING;
|
||||
}
|
||||
var viewWidth = this.width_ - 2 * this.SCROLLBAR_PADDING;
|
||||
|
||||
var metrics = {
|
||||
viewHeight: viewHeight,
|
||||
viewWidth: viewWidth,
|
||||
contentHeight: (optionBox.height + 2 * this.MARGIN) * this.workspace_.scale,
|
||||
contentWidth: (optionBox.width + 2 * this.MARGIN) * this.workspace_.scale,
|
||||
viewTop: -this.workspace_.scrollY,
|
||||
viewLeft: -this.workspace_.scrollX,
|
||||
contentTop: optionBox.y,
|
||||
contentLeft: optionBox.x,
|
||||
absoluteTop: absoluteTop,
|
||||
absoluteLeft: absoluteLeft
|
||||
};
|
||||
return metrics;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the translation of the flyout to match the scrollbars.
|
||||
* @param {!Object} xyRatio Contains a y property which is a float
|
||||
* between 0 and 1 specifying the degree of scrolling and a
|
||||
* similar x property.
|
||||
* @private
|
||||
*/
|
||||
Blockly.HorizontalFlyout.prototype.setMetrics_ = function(xyRatio) {
|
||||
var metrics = this.getMetrics_();
|
||||
// This is a fix to an apparent race condition.
|
||||
if (!metrics) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (goog.isNumber(xyRatio.x)) {
|
||||
this.workspace_.scrollX = -metrics.contentWidth * xyRatio.x;
|
||||
}
|
||||
|
||||
this.workspace_.translate(this.workspace_.scrollX + metrics.absoluteLeft,
|
||||
this.workspace_.scrollY + metrics.absoluteTop);
|
||||
};
|
||||
|
||||
/**
|
||||
* Move the flyout to the edge of the workspace.
|
||||
*/
|
||||
Blockly.HorizontalFlyout.prototype.position = function() {
|
||||
if (!this.isVisible()) {
|
||||
return;
|
||||
}
|
||||
var targetWorkspaceMetrics = this.targetWorkspace_.getMetrics();
|
||||
if (!targetWorkspaceMetrics) {
|
||||
// Hidden components will return null.
|
||||
return;
|
||||
}
|
||||
// Record the width for Blockly.Flyout.getMetrics_.
|
||||
this.width_ = targetWorkspaceMetrics.viewWidth;
|
||||
|
||||
var edgeWidth = targetWorkspaceMetrics.viewWidth - 2 * this.CORNER_RADIUS;
|
||||
var edgeHeight = this.height_ - this.CORNER_RADIUS;
|
||||
this.setBackgroundPath_(edgeWidth, edgeHeight);
|
||||
|
||||
var x = targetWorkspaceMetrics.absoluteLeft;
|
||||
var y = targetWorkspaceMetrics.absoluteTop;
|
||||
if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_BOTTOM) {
|
||||
y += (targetWorkspaceMetrics.viewHeight - this.height_);
|
||||
}
|
||||
this.positionAt_(this.width_, this.height_, x, y);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create and set the path for the visible boundaries of the flyout.
|
||||
* @param {number} width The width of the flyout, not including the
|
||||
* rounded corners.
|
||||
* @param {number} height The height of the flyout, not including
|
||||
* rounded corners.
|
||||
* @private
|
||||
*/
|
||||
Blockly.HorizontalFlyout.prototype.setBackgroundPath_ = function(width,
|
||||
height) {
|
||||
var atTop = this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP;
|
||||
// Start at top left.
|
||||
var path = ['M 0,' + (atTop ? 0 : this.CORNER_RADIUS)];
|
||||
|
||||
if (atTop) {
|
||||
// Top.
|
||||
path.push('h', width + 2 * this.CORNER_RADIUS);
|
||||
// Right.
|
||||
path.push('v', height);
|
||||
// Bottom.
|
||||
path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1,
|
||||
-this.CORNER_RADIUS, this.CORNER_RADIUS);
|
||||
path.push('h', -1 * width);
|
||||
// Left.
|
||||
path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1,
|
||||
-this.CORNER_RADIUS, -this.CORNER_RADIUS);
|
||||
path.push('z');
|
||||
} else {
|
||||
// Top.
|
||||
path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1,
|
||||
this.CORNER_RADIUS, -this.CORNER_RADIUS);
|
||||
path.push('h', width);
|
||||
// Right.
|
||||
path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1,
|
||||
this.CORNER_RADIUS, this.CORNER_RADIUS);
|
||||
path.push('v', height);
|
||||
// Bottom.
|
||||
path.push('h', -width - 2 * this.CORNER_RADIUS);
|
||||
// Left.
|
||||
path.push('z');
|
||||
}
|
||||
this.svgBackground_.setAttribute('d', path.join(' '));
|
||||
};
|
||||
|
||||
/**
|
||||
* Scroll the flyout to the top.
|
||||
*/
|
||||
Blockly.HorizontalFlyout.prototype.scrollToStart = function() {
|
||||
this.scrollbar_.set(this.RTL ? Infinity : 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Scroll the flyout.
|
||||
* @param {!Event} e Mouse wheel scroll event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.HorizontalFlyout.prototype.wheel_ = function(e) {
|
||||
var delta = e.deltaX;
|
||||
|
||||
if (delta) {
|
||||
if (goog.userAgent.GECKO) {
|
||||
// Firefox's deltas are a tenth that of Chrome/Safari.
|
||||
delta *= 10;
|
||||
}
|
||||
// TODO: #1093
|
||||
var metrics = this.getMetrics_();
|
||||
var pos = metrics.viewLeft + delta;
|
||||
var limit = metrics.contentWidth - metrics.viewWidth;
|
||||
pos = Math.min(pos, limit);
|
||||
pos = Math.max(pos, 0);
|
||||
this.scrollbar_.set(pos);
|
||||
// When the flyout moves from a wheel event, hide WidgetDiv.
|
||||
Blockly.WidgetDiv.hide();
|
||||
}
|
||||
|
||||
// Don't scroll the page.
|
||||
e.preventDefault();
|
||||
// Don't propagate mousewheel event (zooming).
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
/**
|
||||
* Lay out the blocks in the flyout.
|
||||
* @param {!Array.<!Object>} contents The blocks and buttons to lay out.
|
||||
* @param {!Array.<number>} gaps The visible gaps between blocks.
|
||||
* @private
|
||||
*/
|
||||
Blockly.HorizontalFlyout.prototype.layout_ = function(contents, gaps) {
|
||||
this.workspace_.scale = this.targetWorkspace_.scale;
|
||||
var margin = this.MARGIN;
|
||||
var cursorX = this.RTL ? margin : margin + Blockly.BlockSvg.TAB_WIDTH;
|
||||
var cursorY = margin;
|
||||
if (this.RTL) {
|
||||
contents = contents.reverse();
|
||||
}
|
||||
|
||||
for (var i = 0, item; item = contents[i]; i++) {
|
||||
if (item.type == 'block') {
|
||||
var block = item.block;
|
||||
var allBlocks = block.getDescendants();
|
||||
for (var j = 0, child; child = allBlocks[j]; j++) {
|
||||
// Mark blocks as being inside a flyout. This is used to detect and
|
||||
// prevent the closure of the flyout if the user right-clicks on such a
|
||||
// block.
|
||||
child.isInFlyout = true;
|
||||
}
|
||||
block.render();
|
||||
var root = block.getSvgRoot();
|
||||
var blockHW = block.getHeightWidth();
|
||||
|
||||
// Figure out where to place the block.
|
||||
var tab = block.outputConnection ? Blockly.BlockSvg.TAB_WIDTH : 0;
|
||||
if (this.RTL) {
|
||||
var moveX = cursorX + blockHW.width;
|
||||
} else {
|
||||
var moveX = cursorX + tab;
|
||||
}
|
||||
block.moveBy(moveX, cursorY);
|
||||
|
||||
var rect = this.createRect_(block, moveX, cursorY, blockHW, i);
|
||||
cursorX += (blockHW.width + gaps[i]);
|
||||
|
||||
this.addBlockListeners_(root, block, rect);
|
||||
} else if (item.type == 'button') {
|
||||
this.initFlyoutButton_(item.button, cursorX, cursorY);
|
||||
cursorX += (item.button.width + gaps[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine if a drag delta is toward the workspace, based on the position
|
||||
* and orientation of the flyout. This is used in determineDragIntention_ to
|
||||
* determine if a new block should be created or if the flyout should scroll.
|
||||
* @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has
|
||||
* moved from the position at mouse down, in pixel units.
|
||||
* @return {boolean} true if the drag is toward the workspace.
|
||||
* @package
|
||||
*/
|
||||
Blockly.HorizontalFlyout.prototype.isDragTowardWorkspace = function(
|
||||
currentDragDeltaXY) {
|
||||
var dx = currentDragDeltaXY.x;
|
||||
var dy = currentDragDeltaXY.y;
|
||||
// Direction goes from -180 to 180, with 0 toward the right and 90 on top.
|
||||
var dragDirection = Math.atan2(dy, dx) / Math.PI * 180;
|
||||
|
||||
var range = this.dragAngleRange_;
|
||||
if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP) {
|
||||
// Horizontal at top.
|
||||
if (dragDirection < 90 + range && dragDirection > 90 - range) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// Horizontal at bottom.
|
||||
if (dragDirection > -90 - range && dragDirection < -90 + range) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the deletion rectangle for this flyout in viewport coordinates.
|
||||
* @return {goog.math.Rect} Rectangle in which to delete.
|
||||
*/
|
||||
Blockly.HorizontalFlyout.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,
|
||||
// but be smaller than half Number.MAX_SAFE_INTEGER (not available on IE).
|
||||
var BIG_NUM = 1000000000;
|
||||
var y = flyoutRect.top;
|
||||
var height = flyoutRect.height;
|
||||
|
||||
if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP) {
|
||||
return new goog.math.Rect(-BIG_NUM, y - BIG_NUM, BIG_NUM * 2,
|
||||
BIG_NUM + height);
|
||||
} else if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_BOTTOM) {
|
||||
return new goog.math.Rect(-BIG_NUM, y, BIG_NUM * 2,
|
||||
BIG_NUM + height);
|
||||
}
|
||||
// TODO: Else throw error (should never happen).
|
||||
};
|
||||
|
||||
/**
|
||||
* Compute height of flyout. Position button under each block.
|
||||
* For RTL: Lay out the blocks right-aligned.
|
||||
* @param {!Array<!Blockly.Block>} blocks The blocks to reflow.
|
||||
* @private
|
||||
*/
|
||||
Blockly.HorizontalFlyout.prototype.reflowInternal_ = function(blocks) {
|
||||
this.workspace_.scale = this.targetWorkspace_.scale;
|
||||
var flyoutHeight = 0;
|
||||
for (var i = 0, block; block = blocks[i]; i++) {
|
||||
flyoutHeight = Math.max(flyoutHeight, block.getHeightWidth().height);
|
||||
}
|
||||
flyoutHeight += this.MARGIN * 1.5;
|
||||
flyoutHeight *= this.workspace_.scale;
|
||||
flyoutHeight += Blockly.Scrollbar.scrollbarThickness;
|
||||
|
||||
if (this.height_ != flyoutHeight) {
|
||||
for (var i = 0, block; block = blocks[i]; i++) {
|
||||
if (block.flyoutRect_) {
|
||||
this.moveRectToBlock_(block.flyoutRect_, block);
|
||||
}
|
||||
}
|
||||
// Record the height for .getMetrics_ and .position.
|
||||
this.height_ = flyoutHeight;
|
||||
// Call this since it is possible the trash and zoom buttons need
|
||||
// to move. e.g. on a bottom positioned flyout when zoom is clicked.
|
||||
this.targetWorkspace_.resize();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,370 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2017 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Layout code for a vertical variant of the flyout.
|
||||
* @author fenichel@google.com (Rachel Fenichel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.VerticalFlyout');
|
||||
|
||||
goog.require('Blockly.Block');
|
||||
goog.require('Blockly.Events');
|
||||
goog.require('Blockly.Flyout');
|
||||
goog.require('Blockly.FlyoutButton');
|
||||
goog.require('Blockly.utils');
|
||||
goog.require('Blockly.WorkspaceSvg');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.math.Rect');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a flyout.
|
||||
* @param {!Object} workspaceOptions Dictionary of options for the workspace.
|
||||
* @extends {Blockly.Flyout}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.VerticalFlyout = function(workspaceOptions) {
|
||||
workspaceOptions.getMetrics = this.getMetrics_.bind(this);
|
||||
workspaceOptions.setMetrics = this.setMetrics_.bind(this);
|
||||
|
||||
Blockly.VerticalFlyout.superClass_.constructor.call(this, workspaceOptions);
|
||||
/**
|
||||
* Flyout should be laid out vertically.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.horizontalLayout_ = false;
|
||||
};
|
||||
goog.inherits(Blockly.VerticalFlyout, Blockly.Flyout);
|
||||
|
||||
/**
|
||||
* Return an object with all the metrics required to size scrollbars for the
|
||||
* flyout. The following properties are computed:
|
||||
* .viewHeight: Height of the visible rectangle,
|
||||
* .viewWidth: Width of the visible rectangle,
|
||||
* .contentHeight: Height of the contents,
|
||||
* .contentWidth: Width of the contents,
|
||||
* .viewTop: Offset of top edge of visible rectangle from parent,
|
||||
* .contentTop: Offset of the top-most content from the y=0 coordinate,
|
||||
* .absoluteTop: Top-edge of view.
|
||||
* .viewLeft: Offset of the left edge of visible rectangle from parent,
|
||||
* .contentLeft: Offset of the left-most content from the x=0 coordinate,
|
||||
* .absoluteLeft: Left-edge of view.
|
||||
* @return {Object} Contains size and position metrics of the flyout.
|
||||
* @private
|
||||
*/
|
||||
Blockly.VerticalFlyout.prototype.getMetrics_ = function() {
|
||||
if (!this.isVisible()) {
|
||||
// Flyout is hidden.
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
var optionBox = this.workspace_.getCanvas().getBBox();
|
||||
} catch (e) {
|
||||
// Firefox has trouble with hidden elements (Bug 528969).
|
||||
var optionBox = {height: 0, y: 0, width: 0, x: 0};
|
||||
}
|
||||
|
||||
// Padding for the end of the scrollbar.
|
||||
var absoluteTop = this.SCROLLBAR_PADDING;
|
||||
var absoluteLeft = 0;
|
||||
|
||||
var viewHeight = this.height_ - 2 * this.SCROLLBAR_PADDING;
|
||||
var viewWidth = this.width_;
|
||||
if (!this.RTL) {
|
||||
viewWidth -= this.SCROLLBAR_PADDING;
|
||||
}
|
||||
|
||||
var metrics = {
|
||||
viewHeight: viewHeight,
|
||||
viewWidth: viewWidth,
|
||||
contentHeight: optionBox.height * this.workspace_.scale + 2 * this.MARGIN,
|
||||
contentWidth: optionBox.width * this.workspace_.scale + 2 * this.MARGIN,
|
||||
viewTop: -this.workspace_.scrollY + optionBox.y,
|
||||
viewLeft: -this.workspace_.scrollX,
|
||||
contentTop: optionBox.y,
|
||||
contentLeft: optionBox.x,
|
||||
absoluteTop: absoluteTop,
|
||||
absoluteLeft: absoluteLeft
|
||||
};
|
||||
return metrics;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the translation of the flyout to match the scrollbars.
|
||||
* @param {!Object} xyRatio Contains a y property which is a float
|
||||
* between 0 and 1 specifying the degree of scrolling and a
|
||||
* similar x property.
|
||||
* @private
|
||||
*/
|
||||
Blockly.VerticalFlyout.prototype.setMetrics_ = function(xyRatio) {
|
||||
var metrics = this.getMetrics_();
|
||||
// This is a fix to an apparent race condition.
|
||||
if (!metrics) {
|
||||
return;
|
||||
}
|
||||
if (goog.isNumber(xyRatio.y)) {
|
||||
this.workspace_.scrollY = -metrics.contentHeight * xyRatio.y;
|
||||
}
|
||||
this.workspace_.translate(this.workspace_.scrollX + metrics.absoluteLeft,
|
||||
this.workspace_.scrollY + metrics.absoluteTop);
|
||||
};
|
||||
|
||||
/**
|
||||
* Move the flyout to the edge of the workspace.
|
||||
*/
|
||||
Blockly.VerticalFlyout.prototype.position = function() {
|
||||
if (!this.isVisible()) {
|
||||
return;
|
||||
}
|
||||
var targetWorkspaceMetrics = this.targetWorkspace_.getMetrics();
|
||||
if (!targetWorkspaceMetrics) {
|
||||
// Hidden components will return null.
|
||||
return;
|
||||
}
|
||||
// Record the height for Blockly.Flyout.getMetrics_
|
||||
this.height_ = targetWorkspaceMetrics.viewHeight;
|
||||
|
||||
var edgeWidth = this.width_ - this.CORNER_RADIUS;
|
||||
var edgeHeight = targetWorkspaceMetrics.viewHeight - 2 * this.CORNER_RADIUS;
|
||||
this.setBackgroundPath_(edgeWidth, edgeHeight);
|
||||
|
||||
var y = targetWorkspaceMetrics.absoluteTop;
|
||||
var x = targetWorkspaceMetrics.absoluteLeft;
|
||||
if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_RIGHT) {
|
||||
x += (targetWorkspaceMetrics.viewWidth - this.width_);
|
||||
}
|
||||
this.positionAt_(this.width_, this.height_, x, y);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create and set the path for the visible boundaries of the flyout.
|
||||
* @param {number} width The width of the flyout, not including the
|
||||
* rounded corners.
|
||||
* @param {number} height The height of the flyout, not including
|
||||
* rounded corners.
|
||||
* @private
|
||||
*/
|
||||
Blockly.VerticalFlyout.prototype.setBackgroundPath_ = function(width, height) {
|
||||
var atRight = this.toolboxPosition_ == Blockly.TOOLBOX_AT_RIGHT;
|
||||
var totalWidth = width + this.CORNER_RADIUS;
|
||||
|
||||
// Decide whether to start on the left or right.
|
||||
var path = ['M ' + (atRight ? totalWidth : 0) + ',0'];
|
||||
// Top.
|
||||
path.push('h', atRight ? -width : width);
|
||||
// Rounded corner.
|
||||
path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0,
|
||||
atRight ? 0 : 1,
|
||||
atRight ? -this.CORNER_RADIUS : this.CORNER_RADIUS,
|
||||
this.CORNER_RADIUS);
|
||||
// Side closest to workspace.
|
||||
path.push('v', Math.max(0, height));
|
||||
// Rounded corner.
|
||||
path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0,
|
||||
atRight ? 0 : 1,
|
||||
atRight ? this.CORNER_RADIUS : -this.CORNER_RADIUS,
|
||||
this.CORNER_RADIUS);
|
||||
// Bottom.
|
||||
path.push('h', atRight ? width : -width);
|
||||
path.push('z');
|
||||
this.svgBackground_.setAttribute('d', path.join(' '));
|
||||
};
|
||||
|
||||
/**
|
||||
* Scroll the flyout to the top.
|
||||
*/
|
||||
Blockly.VerticalFlyout.prototype.scrollToStart = function() {
|
||||
this.scrollbar_.set(0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Scroll the flyout.
|
||||
* @param {!Event} e Mouse wheel scroll event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.VerticalFlyout.prototype.wheel_ = function(e) {
|
||||
var delta = e.deltaY;
|
||||
|
||||
if (delta) {
|
||||
if (goog.userAgent.GECKO) {
|
||||
// Firefox's deltas are a tenth that of Chrome/Safari.
|
||||
delta *= 10;
|
||||
}
|
||||
var metrics = this.getMetrics_();
|
||||
var pos = metrics.viewTop + delta;
|
||||
var limit = metrics.contentHeight - metrics.viewHeight;
|
||||
pos = Math.min(pos, limit);
|
||||
pos = Math.max(pos, 0);
|
||||
this.scrollbar_.set(pos);
|
||||
// When the flyout moves from a wheel event, hide WidgetDiv.
|
||||
Blockly.WidgetDiv.hide();
|
||||
}
|
||||
|
||||
// Don't scroll the page.
|
||||
e.preventDefault();
|
||||
// Don't propagate mousewheel event (zooming).
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
/**
|
||||
* Lay out the blocks in the flyout.
|
||||
* @param {!Array.<!Object>} contents The blocks and buttons to lay out.
|
||||
* @param {!Array.<number>} gaps The visible gaps between blocks.
|
||||
* @private
|
||||
*/
|
||||
Blockly.VerticalFlyout.prototype.layout_ = function(contents, gaps) {
|
||||
this.workspace_.scale = this.targetWorkspace_.scale;
|
||||
var margin = this.MARGIN;
|
||||
var cursorX = this.RTL ? margin : margin + Blockly.BlockSvg.TAB_WIDTH;
|
||||
var cursorY = margin;
|
||||
|
||||
for (var i = 0, item; item = contents[i]; i++) {
|
||||
if (item.type == 'block') {
|
||||
var block = item.block;
|
||||
var allBlocks = block.getDescendants();
|
||||
for (var j = 0, child; child = allBlocks[j]; j++) {
|
||||
// Mark blocks as being inside a flyout. This is used to detect and
|
||||
// prevent the closure of the flyout if the user right-clicks on such a
|
||||
// block.
|
||||
child.isInFlyout = true;
|
||||
}
|
||||
block.render();
|
||||
var root = block.getSvgRoot();
|
||||
var blockHW = block.getHeightWidth();
|
||||
block.moveBy(cursorX, cursorY);
|
||||
|
||||
var rect = this.createRect_(block,
|
||||
this.RTL ? cursorX - blockHW.width : cursorX, cursorY, blockHW, i);
|
||||
|
||||
this.addBlockListeners_(root, block, rect);
|
||||
|
||||
cursorY += blockHW.height + gaps[i];
|
||||
} else if (item.type == 'button') {
|
||||
this.initFlyoutButton_(item.button, cursorX, cursorY);
|
||||
cursorY += item.button.height + gaps[i];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine if a drag delta is toward the workspace, based on the position
|
||||
* and orientation of the flyout. This is used in determineDragIntention_ to
|
||||
* determine if a new block should be created or if the flyout should scroll.
|
||||
* @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has
|
||||
* moved from the position at mouse down, in pixel units.
|
||||
* @return {boolean} true if the drag is toward the workspace.
|
||||
* @package
|
||||
*/
|
||||
Blockly.VerticalFlyout.prototype.isDragTowardWorkspace = function(
|
||||
currentDragDeltaXY) {
|
||||
var dx = currentDragDeltaXY.x;
|
||||
var dy = currentDragDeltaXY.y;
|
||||
// Direction goes from -180 to 180, with 0 toward the right and 90 on top.
|
||||
var dragDirection = Math.atan2(dy, dx) / Math.PI * 180;
|
||||
|
||||
var range = this.dragAngleRange_;
|
||||
if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_LEFT) {
|
||||
// Vertical at left.
|
||||
if (dragDirection < range && dragDirection > -range) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// Vertical at right.
|
||||
if (dragDirection < -180 + range || dragDirection > 180 - range) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the deletion rectangle for this flyout in viewport coordinates.
|
||||
* @return {goog.math.Rect} Rectangle in which to delete.
|
||||
*/
|
||||
Blockly.VerticalFlyout.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,
|
||||
// but be smaller than half Number.MAX_SAFE_INTEGER (not available on IE).
|
||||
var BIG_NUM = 1000000000;
|
||||
var x = flyoutRect.left;
|
||||
var width = flyoutRect.width;
|
||||
|
||||
if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_LEFT) {
|
||||
return new goog.math.Rect(x - BIG_NUM, -BIG_NUM, BIG_NUM + width,
|
||||
BIG_NUM * 2);
|
||||
} else { // Right
|
||||
return new goog.math.Rect(x, -BIG_NUM, BIG_NUM + width, BIG_NUM * 2);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Compute width of flyout. Position button under each block.
|
||||
* For RTL: Lay out the blocks right-aligned.
|
||||
* @param {!Array<!Blockly.Block>} blocks The blocks to reflow.
|
||||
* @private
|
||||
*/
|
||||
Blockly.VerticalFlyout.prototype.reflowInternal_ = function(blocks) {
|
||||
this.workspace_.scale = this.targetWorkspace_.scale;
|
||||
var flyoutWidth = 0;
|
||||
for (var i = 0, block; block = blocks[i]; i++) {
|
||||
var width = block.getHeightWidth().width;
|
||||
if (block.outputConnection) {
|
||||
width -= Blockly.BlockSvg.TAB_WIDTH;
|
||||
}
|
||||
flyoutWidth = Math.max(flyoutWidth, width);
|
||||
}
|
||||
for (var i = 0, button; button = this.buttons_[i]; i++) {
|
||||
flyoutWidth = Math.max(flyoutWidth, button.width);
|
||||
}
|
||||
flyoutWidth += this.MARGIN * 1.5 + Blockly.BlockSvg.TAB_WIDTH;
|
||||
flyoutWidth *= this.workspace_.scale;
|
||||
flyoutWidth += Blockly.Scrollbar.scrollbarThickness;
|
||||
|
||||
if (this.width_ != flyoutWidth) {
|
||||
for (var i = 0, block; block = blocks[i]; i++) {
|
||||
if (this.RTL) {
|
||||
// With the flyoutWidth known, right-align the blocks.
|
||||
var oldX = block.getRelativeToSurfaceXY().x;
|
||||
var newX = flyoutWidth / this.workspace_.scale - this.MARGIN;
|
||||
newX -= Blockly.BlockSvg.TAB_WIDTH;
|
||||
block.moveBy(newX - oldX, 0);
|
||||
}
|
||||
if (block.flyoutRect_) {
|
||||
this.moveRectToBlock_(block.flyoutRect_, block);
|
||||
}
|
||||
}
|
||||
// Record the width for .getMetrics_ and .position.
|
||||
this.width_ = flyoutWidth;
|
||||
// Call this since it is possible the trash and zoom buttons need
|
||||
// to move. e.g. on a bottom positioned flyout when zoom is clicked.
|
||||
this.targetWorkspace_.resize();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,783 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2017 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview The class representing an in-progress gesture, usually a drag
|
||||
* or a tap.
|
||||
* @author fenichel@google.com (Rachel Fenichel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Gesture');
|
||||
|
||||
goog.require('Blockly.BlockDragger');
|
||||
goog.require('Blockly.constants');
|
||||
goog.require('Blockly.FlyoutDragger');
|
||||
goog.require('Blockly.Tooltip');
|
||||
goog.require('Blockly.Touch');
|
||||
goog.require('Blockly.WorkspaceDragger');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.math.Coordinate');
|
||||
|
||||
|
||||
/**
|
||||
* NB: In this file "start" refers to touchstart, mousedown, and pointerstart
|
||||
* events. "End" refers to touchend, mouseup, and pointerend events.
|
||||
* TODO: Consider touchcancel/pointercancel.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for one gesture.
|
||||
* @param {!Event} e The event that kicked off this gesture.
|
||||
* @param {!Blockly.WorkspaceSvg} creatorWorkspace The workspace that created
|
||||
* this gesture and has a reference to it.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Gesture = function(e, creatorWorkspace) {
|
||||
|
||||
/**
|
||||
* The position of the mouse when the gesture started. Units are css pixels,
|
||||
* with (0, 0) at the top left of the browser window (mouseEvent clientX/Y).
|
||||
* @type {goog.math.Coordinate}
|
||||
*/
|
||||
this.mouseDownXY_ = null;
|
||||
|
||||
/**
|
||||
* How far the mouse has moved during this drag, in pixel units.
|
||||
* (0, 0) is at this.mouseDownXY_.
|
||||
* @type {goog.math.Coordinate}
|
||||
* private
|
||||
*/
|
||||
this.currentDragDeltaXY_ = 0;
|
||||
|
||||
/**
|
||||
* The field that the gesture started on, or null if it did not start on a
|
||||
* field.
|
||||
* @type {Blockly.Field}
|
||||
* @private
|
||||
*/
|
||||
this.startField_ = null;
|
||||
|
||||
/**
|
||||
* The block that the gesture started on, or null if it did not start on a
|
||||
* block.
|
||||
* @type {Blockly.BlockSvg}
|
||||
* @private
|
||||
*/
|
||||
this.startBlock_ = null;
|
||||
|
||||
/**
|
||||
* The block that this gesture targets. If the gesture started on a
|
||||
* shadow block, this is the first non-shadow parent of the block. If the
|
||||
* gesture started in the flyout, this is the root block of the block group
|
||||
* that was clicked or dragged.
|
||||
* @type {Blockly.BlockSvg}
|
||||
* @private
|
||||
*/
|
||||
this.targetBlock_ = null;
|
||||
|
||||
/**
|
||||
* The workspace that the gesture started on. There may be multiple
|
||||
* workspaces on a page; this is more accurate than using
|
||||
* Blockly.getMainWorkspace().
|
||||
* @type {Blockly.WorkspaceSvg}
|
||||
* @private
|
||||
*/
|
||||
this.startWorkspace_ = null;
|
||||
|
||||
/**
|
||||
* The workspace that created this gesture. This workspace keeps a reference
|
||||
* to the gesture, which will need to be cleared at deletion.
|
||||
* This may be different from the start workspace. For instance, a flyout is
|
||||
* a workspace, but its parent workspace manages gestures for it.
|
||||
* @type {Blockly.WorkspaceSvg}
|
||||
* @private
|
||||
*/
|
||||
this.creatorWorkspace_ = creatorWorkspace;
|
||||
|
||||
/**
|
||||
* Whether the pointer has at any point moved out of the drag radius.
|
||||
* A gesture that exceeds the drag radius is a drag even if it ends exactly at
|
||||
* its start point.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.hasExceededDragRadius_ = false;
|
||||
|
||||
/**
|
||||
* Whether the workspace is currently being dragged.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.isDraggingWorkspace_ = false;
|
||||
|
||||
/**
|
||||
* Whether the block is currently being dragged.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.isDraggingBlock_ = false;
|
||||
|
||||
/**
|
||||
* The event that most recently updated this gesture.
|
||||
* @type {!Event}
|
||||
* @private
|
||||
*/
|
||||
this.mostRecentEvent_ = e;
|
||||
|
||||
/**
|
||||
* A handle to use to unbind a mouse move listener at the end of a drag.
|
||||
* Opaque data returned from Blockly.bindEventWithChecks_.
|
||||
* @type {Array.<!Array>}
|
||||
* @private
|
||||
*/
|
||||
this.onMoveWrapper_ = null;
|
||||
|
||||
/**
|
||||
* A handle to use to unbind a mouse up listener at the end of a drag.
|
||||
* Opaque data returned from Blockly.bindEventWithChecks_.
|
||||
* @type {Array.<!Array>}
|
||||
* @private
|
||||
*/
|
||||
this.onUpWrapper_ = null;
|
||||
|
||||
/**
|
||||
* The object tracking a block drag, or null if none is in progress.
|
||||
* @type {Blockly.BlockDragger}
|
||||
* @private
|
||||
*/
|
||||
this.blockDragger_ = null;
|
||||
|
||||
/**
|
||||
* The object tracking a workspace or flyout workspace drag, or null if none
|
||||
* is in progress.
|
||||
* @type {Blockly.WorkspaceDragger}
|
||||
* @private
|
||||
*/
|
||||
this.workspaceDragger_ = null;
|
||||
|
||||
/**
|
||||
* The flyout a gesture started in, if any.
|
||||
* @type {Blockly.Flyout}
|
||||
* @private
|
||||
*/
|
||||
this.flyout_ = null;
|
||||
|
||||
/**
|
||||
* Boolean for sanity-checking that some code is only called once.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.calledUpdateIsDragging_ = false;
|
||||
|
||||
/**
|
||||
* Boolean for sanity-checking that some code is only called once.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.hasStarted_ = false;
|
||||
|
||||
/**
|
||||
* Boolean used internally to break a cycle in disposal.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.isEnding_ = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sever all links from this object.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Gesture.prototype.dispose = function() {
|
||||
Blockly.Touch.clearTouchIdentifier();
|
||||
Blockly.Tooltip.unblock();
|
||||
// Clear the owner's reference to this gesture.
|
||||
this.creatorWorkspace_.clearGesture();
|
||||
|
||||
if (this.onMoveWrapper_) {
|
||||
Blockly.unbindEvent_(this.onMoveWrapper_);
|
||||
}
|
||||
if (this.onUpWrapper_) {
|
||||
Blockly.unbindEvent_(this.onUpWrapper_);
|
||||
}
|
||||
|
||||
|
||||
this.startField_ = null;
|
||||
this.startBlock_ = null;
|
||||
this.targetBlock_ = null;
|
||||
this.startWorkspace_ = null;
|
||||
this.flyout_ = null;
|
||||
|
||||
if (this.blockDragger_) {
|
||||
this.blockDragger_.dispose();
|
||||
this.blockDragger_ = null;
|
||||
}
|
||||
if (this.workspaceDragger_) {
|
||||
this.workspaceDragger_.dispose();
|
||||
this.workspaceDragger_ = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update internal state based on an event.
|
||||
* @param {!Event} e The most recent mouse or touch event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.updateFromEvent_ = function(e) {
|
||||
var currentXY = new goog.math.Coordinate(e.clientX, e.clientY);
|
||||
var changed = this.updateDragDelta_(currentXY);
|
||||
// Exceeded the drag radius for the first time.
|
||||
if (changed){
|
||||
this.updateIsDragging_();
|
||||
Blockly.longStop_();
|
||||
}
|
||||
this.mostRecentEvent_ = e;
|
||||
};
|
||||
|
||||
/**
|
||||
* DO MATH to set currentDragDeltaXY_ based on the most recent mouse position.
|
||||
* @param {!goog.math.Coordinate} currentXY The most recent mouse/pointer
|
||||
* position, in pixel units, with (0, 0) at the window's top left corner.
|
||||
* @return {boolean} True if the drag just exceeded the drag radius for the
|
||||
* first time.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.updateDragDelta_ = function(currentXY) {
|
||||
this.currentDragDeltaXY_ = goog.math.Coordinate.difference(currentXY,
|
||||
this.mouseDownXY_);
|
||||
|
||||
if (!this.hasExceededDragRadius_) {
|
||||
var currentDragDelta = goog.math.Coordinate.magnitude(
|
||||
this.currentDragDeltaXY_);
|
||||
|
||||
// The flyout has a different drag radius from the rest of Blockly.
|
||||
var limitRadius = this.flyout_ ? Blockly.FLYOUT_DRAG_RADIUS :
|
||||
Blockly.DRAG_RADIUS;
|
||||
|
||||
this.hasExceededDragRadius_ = currentDragDelta > limitRadius;
|
||||
return this.hasExceededDragRadius_;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update this gesture to record whether a block is being dragged from the
|
||||
* flyout.
|
||||
* This function should be called on a mouse/touch move event the first time the
|
||||
* drag radius is exceeded. It should be called no more than once per gesture.
|
||||
* If a block should be dragged from the flyout this function creates the new
|
||||
* block on the main workspace and updates targetBlock_ and startWorkspace_.
|
||||
* @return {boolean} True if a block is being dragged from the flyout.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.updateIsDraggingFromFlyout_ = function() {
|
||||
// Disabled blocks may not be dragged from the flyout.
|
||||
if (this.targetBlock_.disabled) {
|
||||
return false;
|
||||
}
|
||||
if (!this.flyout_.isScrollable() ||
|
||||
this.flyout_.isDragTowardWorkspace(this.currentDragDeltaXY_)) {
|
||||
this.startWorkspace_ = this.flyout_.targetWorkspace_;
|
||||
this.startWorkspace_.updateScreenCalculationsIfScrolled();
|
||||
// Start the event group now, so that the same event group is used for block
|
||||
// creation and block dragging.
|
||||
if (!Blockly.Events.getGroup()) {
|
||||
Blockly.Events.setGroup(true);
|
||||
}
|
||||
// The start block is no longer relevant, because this is a drag.
|
||||
this.startBlock_ = null;
|
||||
this.targetBlock_ = this.flyout_.createBlock(this.targetBlock_);
|
||||
this.targetBlock_.select();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update this gesture to record whether a block is being dragged.
|
||||
* This function should be called on a mouse/touch move event the first time the
|
||||
* drag radius is exceeded. It should be called no more than once per gesture.
|
||||
* If a block should be dragged, either from the flyout or in the workspace,
|
||||
* this function creates the necessary BlockDragger and starts the drag.
|
||||
* @return {boolean} true if a block is being dragged.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.updateIsDraggingBlock_ = function() {
|
||||
if (!this.targetBlock_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.flyout_) {
|
||||
this.isDraggingBlock_ = this.updateIsDraggingFromFlyout_();
|
||||
} else if (this.targetBlock_.isMovable()){
|
||||
this.isDraggingBlock_ = true;
|
||||
}
|
||||
|
||||
if (this.isDraggingBlock_) {
|
||||
this.startDraggingBlock_();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update this gesture to record whether a workspace is being dragged.
|
||||
* This function should be called on a mouse/touch move event the first time the
|
||||
* drag radius is exceeded. It should be called no more than once per gesture.
|
||||
* If a workspace is being dragged this function creates the necessary
|
||||
* WorkspaceDragger or FlyoutDragger and starts the drag.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.updateIsDraggingWorkspace_ = function() {
|
||||
var wsMovable = this.flyout_ ? this.flyout_.isScrollable() :
|
||||
this.startWorkspace_ && this.startWorkspace_.isDraggable();
|
||||
|
||||
if (!wsMovable) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.flyout_) {
|
||||
this.workspaceDragger_ = new Blockly.FlyoutDragger(this.flyout_);
|
||||
} else {
|
||||
this.workspaceDragger_ = new Blockly.WorkspaceDragger(this.startWorkspace_);
|
||||
}
|
||||
|
||||
this.isDraggingWorkspace_ = true;
|
||||
this.workspaceDragger_.startDrag();
|
||||
};
|
||||
|
||||
/**
|
||||
* Update this gesture to record whether anything is being dragged.
|
||||
* This function should be called on a mouse/touch move event the first time the
|
||||
* drag radius is exceeded. It should be called no more than once per gesture.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.updateIsDragging_ = function() {
|
||||
// Sanity check.
|
||||
goog.asserts.assert(!this.calledUpdateIsDragging_,
|
||||
'updateIsDragging_ should only be called once per gesture.');
|
||||
this.calledUpdateIsDragging_ = true;
|
||||
|
||||
// First check if it was a block drag.
|
||||
if (this.updateIsDraggingBlock_()) {
|
||||
return;
|
||||
}
|
||||
// Then check if it's a workspace drag.
|
||||
this.updateIsDraggingWorkspace_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a block dragger and start dragging the selected block.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.startDraggingBlock_ = function() {
|
||||
this.blockDragger_ = new Blockly.BlockDragger(this.targetBlock_,
|
||||
this.startWorkspace_);
|
||||
this.blockDragger_.startBlockDrag(this.currentDragDeltaXY_);
|
||||
this.blockDragger_.dragBlock(this.mostRecentEvent_,
|
||||
this.currentDragDeltaXY_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Start a gesture: update the workspace to indicate that a gesture is in
|
||||
* progress and bind mousemove and mouseup handlers.
|
||||
* @param {!Event} e A mouse down or touch start event.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Gesture.prototype.doStart = function(e) {
|
||||
if (Blockly.utils.isTargetInput(e)) {
|
||||
this.cancel();
|
||||
return;
|
||||
}
|
||||
this.hasStarted_ = true;
|
||||
|
||||
Blockly.BlockSvg.disconnectUiStop_();
|
||||
this.startWorkspace_.updateScreenCalculationsIfScrolled();
|
||||
if (this.startWorkspace_.isMutator) {
|
||||
// Mutator's coordinate system could be out of date because the bubble was
|
||||
// dragged, the block was moved, the parent workspace zoomed, etc.
|
||||
this.startWorkspace_.resize();
|
||||
}
|
||||
this.startWorkspace_.markFocused();
|
||||
this.mostRecentEvent_ = e;
|
||||
|
||||
// Hide chaff also hides the flyout, so don't do it if the click is in a flyout.
|
||||
Blockly.hideChaff(!!this.flyout_);
|
||||
Blockly.Tooltip.block();
|
||||
|
||||
if (this.targetBlock_) {
|
||||
this.targetBlock_.select();
|
||||
}
|
||||
|
||||
if (Blockly.utils.isRightButton(e)) {
|
||||
this.handleRightClick(e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (goog.string.caseInsensitiveEquals(e.type, 'touchstart')) {
|
||||
Blockly.longStart_(e, this);
|
||||
}
|
||||
|
||||
this.mouseDownXY_ = new goog.math.Coordinate(e.clientX, e.clientY);
|
||||
|
||||
this.onMoveWrapper_ = Blockly.bindEventWithChecks_(
|
||||
document, 'mousemove', null, this.handleMove.bind(this));
|
||||
this.onUpWrapper_ = Blockly.bindEventWithChecks_(
|
||||
document, 'mouseup', null, this.handleUp.bind(this));
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a mouse move or touch move event.
|
||||
* @param {!Event} e A mouse move or touch move event.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Gesture.prototype.handleMove = function(e) {
|
||||
this.updateFromEvent_(e);
|
||||
if (this.isDraggingWorkspace_) {
|
||||
this.workspaceDragger_.drag(this.currentDragDeltaXY_);
|
||||
} else if (this.isDraggingBlock_) {
|
||||
this.blockDragger_.dragBlock(this.mostRecentEvent_,
|
||||
this.currentDragDeltaXY_);
|
||||
}
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a mouse up or touch end event.
|
||||
* @param {!Event} e A mouse up or touch end event.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Gesture.prototype.handleUp = function(e) {
|
||||
this.updateFromEvent_(e);
|
||||
Blockly.longStop_();
|
||||
|
||||
if (this.isEnding_) {
|
||||
console.log('Trying to end a gesture recursively.');
|
||||
return;
|
||||
}
|
||||
this.isEnding_ = true;
|
||||
// The ordering of these checks is important: drags have higher priority than
|
||||
// clicks. Fields have higher priority than blocks; blocks have higher
|
||||
// priority than workspaces.
|
||||
if (this.isDraggingBlock_) {
|
||||
this.blockDragger_.endBlockDrag(e, this.currentDragDeltaXY_);
|
||||
} else if (this.isDraggingWorkspace_) {
|
||||
this.workspaceDragger_.endDrag(this.currentDragDeltaXY_);
|
||||
} else if (this.isFieldClick_()) {
|
||||
this.doFieldClick_();
|
||||
} else if (this.isBlockClick_()) {
|
||||
this.doBlockClick_();
|
||||
} else if (this.isWorkspaceClick_()) {
|
||||
this.doWorkspaceClick_();
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.dispose();
|
||||
};
|
||||
|
||||
/**
|
||||
* Cancel an in-progress gesture. If a workspace or block drag is in progress,
|
||||
* end the drag at the most recent location.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Gesture.prototype.cancel = function() {
|
||||
// Disposing of a block cancels in-progress drags, but dragging to a delete
|
||||
// area disposes of a block and leads to recursive disposal. Break that cycle.
|
||||
if (this.isEnding_) {
|
||||
return;
|
||||
}
|
||||
Blockly.longStop_();
|
||||
if (this.isDraggingBlock_) {
|
||||
this.blockDragger_.endBlockDrag(this.mostRecentEvent_,
|
||||
this.currentDragDeltaXY_);
|
||||
} else if (this.isDraggingWorkspace_) {
|
||||
this.workspaceDragger_.endDrag(this.currentDragDeltaXY_);
|
||||
}
|
||||
this.dispose();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a real or faked right-click event by showing a context menu.
|
||||
* @param {!Event} e A mouse move or touch move event.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Gesture.prototype.handleRightClick = function(e) {
|
||||
if (this.targetBlock_) {
|
||||
this.bringBlockToFront_();
|
||||
Blockly.hideChaff(this.flyout_);
|
||||
this.targetBlock_.showContextMenu_(e);
|
||||
} else if (this.startWorkspace_ && !this.flyout_) {
|
||||
Blockly.hideChaff();
|
||||
this.startWorkspace_.showContextMenu_(e);
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.dispose();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a mousedown/touchstart event on a workspace.
|
||||
* @param {!Event} e A mouse down or touch start event.
|
||||
* @param {!Blockly.Workspace} ws The workspace the event hit.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Gesture.prototype.handleWsStart = function(e, ws) {
|
||||
goog.asserts.assert(!this.hasStarted_,
|
||||
'Tried to call gesture.handleWsStart, but the gesture had already been ' +
|
||||
'started.');
|
||||
this.setStartWorkspace_(ws);
|
||||
this.mostRecentEvent_ = e;
|
||||
this.doStart(e);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a mousedown/touchstart event on a flyout.
|
||||
* @param {!Event} e A mouse down or touch start event.
|
||||
* @param {!Blockly.Flyout} flyout The flyout the event hit.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Gesture.prototype.handleFlyoutStart = function(e, flyout) {
|
||||
goog.asserts.assert(!this.hasStarted_,
|
||||
'Tried to call gesture.handleFlyoutStart, but the gesture had already been ' +
|
||||
'started.');
|
||||
this.setStartFlyout_(flyout);
|
||||
this.handleWsStart(e, flyout.getWorkspace());
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a mousedown/touchstart event on a block.
|
||||
* @param {!Event} e A mouse down or touch start event.
|
||||
* @param {!Blockly.BlockSvg} block The block the event hit.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Gesture.prototype.handleBlockStart = function(e, block) {
|
||||
goog.asserts.assert(!this.hasStarted_,
|
||||
'Tried to call gesture.handleBlockStart, but the gesture had already been ' +
|
||||
'started.');
|
||||
this.setStartBlock(block);
|
||||
this.mostRecentEvent_ = e;
|
||||
};
|
||||
|
||||
/* Begin functions defining what actions to take to execute clicks on each type
|
||||
* of target. Any developer wanting to add behaviour on clicks should modify
|
||||
* only this code. */
|
||||
|
||||
/**
|
||||
* Execute a field click.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.doFieldClick_ = function() {
|
||||
this.startField_.showEditor_();
|
||||
this.bringBlockToFront_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute a block click.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.doBlockClick_ = function() {
|
||||
// Block click in an autoclosing flyout.
|
||||
if (this.flyout_ && this.flyout_.autoClose) {
|
||||
if (!this.targetBlock_.disabled) {
|
||||
if (!Blockly.Events.getGroup()) {
|
||||
Blockly.Events.setGroup(true);
|
||||
}
|
||||
var newBlock = this.flyout_.createBlock(this.targetBlock_);
|
||||
newBlock.scheduleSnapAndBump();
|
||||
}
|
||||
} else {
|
||||
// Clicks events are on the start block, even if it was a shadow.
|
||||
Blockly.Events.fire(
|
||||
new Blockly.Events.Ui(this.startBlock_, 'click', undefined, undefined));
|
||||
}
|
||||
this.bringBlockToFront_();
|
||||
Blockly.Events.setGroup(false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute a workspace click.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.doWorkspaceClick_ = function() {
|
||||
if (Blockly.selected) {
|
||||
Blockly.selected.unselect();
|
||||
}
|
||||
};
|
||||
|
||||
/* End functions defining what actions to take to execute clicks on each type
|
||||
* of target. */
|
||||
|
||||
/**
|
||||
* Move the dragged/clicked block to the front of the workspace so that it is
|
||||
* not occluded by other blocks.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.bringBlockToFront_ = function() {
|
||||
// Blocks in the flyout don't overlap, so skip the work.
|
||||
if (this.targetBlock_ && !this.flyout_) {
|
||||
this.targetBlock_.bringToFront();
|
||||
}
|
||||
};
|
||||
|
||||
/* Begin functions for populating a gesture at mouse down. */
|
||||
|
||||
/**
|
||||
* Record the field that a gesture started on.
|
||||
* @param {Blockly.Field} field The field the gesture started on.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Gesture.prototype.setStartField = function(field) {
|
||||
goog.asserts.assert(!this.hasStarted_,
|
||||
'Tried to call gesture.setStartField, but the gesture had already been ' +
|
||||
'started.');
|
||||
if (!this.startField_) {
|
||||
this.startField_ = field;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Record the block that a gesture started on, and set the target block
|
||||
* appropriately.
|
||||
* @param {Blockly.BlockSvg} block The block the gesture started on.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Gesture.prototype.setStartBlock = function(block) {
|
||||
if (!this.startBlock_) {
|
||||
this.startBlock_ = block;
|
||||
if (block.isInFlyout && block != block.getRootBlock()) {
|
||||
this.setTargetBlock_(block.getRootBlock());
|
||||
} else {
|
||||
this.setTargetBlock_(block);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Record the block that a gesture targets, meaning the block that will be
|
||||
* dragged if this turns into a drag. If this block is a shadow, that will be
|
||||
* its first non-shadow parent.
|
||||
* @param {Blockly.BlockSvg} block The block the gesture targets.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.setTargetBlock_ = function(block) {
|
||||
if (block.isShadow()) {
|
||||
this.setTargetBlock_(block.getParent());
|
||||
} else {
|
||||
this.targetBlock_ = block;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Record the workspace that a gesture started on.
|
||||
* @param {Blockly.WorkspaceSvg} ws The workspace the gesture started on.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.setStartWorkspace_ = function(ws) {
|
||||
if (!this.startWorkspace_) {
|
||||
this.startWorkspace_ = ws;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Record the flyout that a gesture started on.
|
||||
* @param {Blockly.Flyout} flyout The flyout the gesture started on.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.setStartFlyout_ = function(flyout) {
|
||||
if (!this.flyout_) {
|
||||
this.flyout_ = flyout;
|
||||
}
|
||||
};
|
||||
|
||||
/* End functions for populating a gesture at mouse down. */
|
||||
|
||||
/* Begin helper functions defining types of clicks. Any developer wanting
|
||||
* to change the definition of a click should modify only this code. */
|
||||
|
||||
/**
|
||||
* Whether this gesture is a click on a block. This should only be called when
|
||||
* ending a gesture (mouse up, touch end).
|
||||
* @return {boolean} whether this gesture was a click on a block.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.isBlockClick_ = function() {
|
||||
// A block click starts on a block, never escapes the drag radius, and is not
|
||||
// a field click.
|
||||
var hasStartBlock = !!this.startBlock_;
|
||||
return hasStartBlock && !this.hasExceededDragRadius_ && !this.isFieldClick_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether this gesture is a click on a field. This should only be called when
|
||||
* ending a gesture (mouse up, touch end).
|
||||
* @return {boolean} whether this gesture was a click on a field.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.isFieldClick_ = function() {
|
||||
var fieldEditable = this.startField_ ?
|
||||
this.startField_.isCurrentlyEditable() : false;
|
||||
return fieldEditable && !this.hasExceededDragRadius_ && (!this.flyout_ ||
|
||||
!this.flyout_.autoClose);
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether this gesture is a click on a workspace. This should only be called
|
||||
* when ending a gesture (mouse up, touch end).
|
||||
* @return {boolean} whether this gesture was a click on a workspace.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.isWorkspaceClick_ = function() {
|
||||
var onlyTouchedWorkspace = !this.startBlock_ && !this.startField_;
|
||||
return onlyTouchedWorkspace && !this.hasExceededDragRadius_;
|
||||
};
|
||||
|
||||
/* End helper functions defining types of clicks. */
|
||||
|
||||
/**
|
||||
* Whether this gesture is a drag of either a workspace or block.
|
||||
* This function is called externally to block actions that cannot be taken
|
||||
* mid-drag (e.g. using the keyboard to delete the selected blocks).
|
||||
* @return {boolean} true if this gesture is a drag of a workspace or block.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Gesture.prototype.isDragging = function() {
|
||||
return this.isDraggingWorkspace_ || this.isDraggingBlock_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether this gesture has already been started. In theory every mouse down
|
||||
* has a corresponding mouse up, but in reality it is possible to lose a
|
||||
* mouse up, leaving an in-process gesture hanging.
|
||||
* @return {boolean} whether this gesture was a click on a workspace.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Gesture.prototype.hasStarted = function() {
|
||||
return this.hasStarted_;
|
||||
};
|
||||
+222
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2017 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Object for configuring and updating a workspace grid in
|
||||
* Blockly.
|
||||
* @author fenichel@google.com (Rachel Fenichel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Grid');
|
||||
|
||||
goog.require('Blockly.utils');
|
||||
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a workspace's grid.
|
||||
* @param {!SVGElement} pattern The grid's SVG pattern, created during injection.
|
||||
* @param {!Object} options A dictionary of normalized options for the grid.
|
||||
* See grid documentation:
|
||||
* https://developers.google.com/blockly/guides/configure/web/grid
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Grid = function(pattern, options) {
|
||||
/**
|
||||
* The grid's SVG pattern, created during injection.
|
||||
* @type {!SVGElement}
|
||||
* @private
|
||||
*/
|
||||
this.gridPattern_ = pattern;
|
||||
|
||||
/**
|
||||
* The spacing of the grid lines (in px).
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.spacing_ = options['spacing'];
|
||||
|
||||
/**
|
||||
* How long the grid lines should be (in px).
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.length_ = options['length'];
|
||||
|
||||
/**
|
||||
* The horizontal grid line, if it exists.
|
||||
* @type {SVGElement}
|
||||
* @private
|
||||
*/
|
||||
this.line1_ = pattern.firstChild;
|
||||
|
||||
/**
|
||||
* The vertical grid line, if it exists.
|
||||
* @type {SVGElement}
|
||||
* @private
|
||||
*/
|
||||
this.line2_ = this.line1_ && this.line1_.nextSibling;
|
||||
|
||||
/**
|
||||
* Whether blocks should snap to the grid.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.snapToGrid_ = options['snap'];
|
||||
};
|
||||
|
||||
/**
|
||||
* The scale of the grid, used to set stroke width on grid lines.
|
||||
* This should always be the same as the workspace scale.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Grid.prototype.scale_ = 1;
|
||||
|
||||
/**
|
||||
* Dispose of this grid and unlink from the DOM.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Grid.prototype.dispose = function() {
|
||||
this.gridPattern_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether blocks should snap to the grid, based on the initial configuration.
|
||||
* @return {boolean} True if blocks should snap, false otherwise.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Grid.prototype.shouldSnap = function() {
|
||||
return this.snapToGrid_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the spacing of the grid points (in px).
|
||||
* @return {number} The spacing of the grid points.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Grid.prototype.getSpacing = function() {
|
||||
return this.spacing_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the id of the pattern element, which should be randomized to avoid
|
||||
* conflicts with other Blockly instances on the page.
|
||||
* @return {string} The pattern id.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Grid.prototype.getPatternId = function() {
|
||||
return this.gridPattern_.id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the grid with a new scale.
|
||||
* @param {number} scale The new workspace scale.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Grid.prototype.update = function(scale) {
|
||||
this.scale_ = scale;
|
||||
// MSIE freaks if it sees a 0x0 pattern, so set empty patterns to 100x100.
|
||||
var safeSpacing = (this.spacing_ * scale) || 100;
|
||||
|
||||
this.gridPattern_.setAttribute('width', safeSpacing);
|
||||
this.gridPattern_.setAttribute('height', safeSpacing);
|
||||
|
||||
var half = Math.floor(this.spacing_ / 2) + 0.5;
|
||||
var start = half - this.length_ / 2;
|
||||
var end = half + this.length_ / 2;
|
||||
|
||||
half *= scale;
|
||||
start *= scale;
|
||||
end *= scale;
|
||||
|
||||
this.setLineAttributes_(this.line1_, scale, start, end, half, half);
|
||||
this.setLineAttributes_(this.line2_, scale, half, half, start, end);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the attributes on one of the lines in the grid. Use this to update the
|
||||
* length and stroke width of the grid lines.
|
||||
* @param {!SVGElement} line Which line to update.
|
||||
* @param {number} width The new stroke size (in px).
|
||||
* @param {number} x1 The new x start position of the line (in px).
|
||||
* @param {number} x2 The new x end position of the line (in px).
|
||||
* @param {number} y1 The new y start position of the line (in px).
|
||||
* @param {number} y2 The new y end position of the line (in px).
|
||||
* @private
|
||||
*/
|
||||
Blockly.Grid.prototype.setLineAttributes_ = function(line, width, x1, x2, y1, y2) {
|
||||
if (line) {
|
||||
line.setAttribute('stroke-width', width);
|
||||
line.setAttribute('x1', x1);
|
||||
line.setAttribute('y1', y1);
|
||||
line.setAttribute('x2', x2);
|
||||
line.setAttribute('y2', y2);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Move the grid to a new x and y position, and make sure that change is visible.
|
||||
* @param {number} x The new x position of the grid (in px).
|
||||
* @param {number} y The new y position ofthe grid (in px).
|
||||
* @package
|
||||
*/
|
||||
Blockly.Grid.prototype.moveTo = function(x, y) {
|
||||
this.gridPattern_.setAttribute('x', x);
|
||||
this.gridPattern_.setAttribute('y', y);
|
||||
|
||||
if (goog.userAgent.IE || goog.userAgent.EDGE) {
|
||||
// IE/Edge doesn't notice that the x/y offsets have changed.
|
||||
// Force an update.
|
||||
this.update(this.scale_);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the DOM for the grid described by options.
|
||||
* @param {string} rnd A random ID to append to the pattern's ID.
|
||||
* @param {!Object} gridOptions The object containing grid configuration.
|
||||
* @param {!SVGElement} defs The root SVG element for this workspace's defs.
|
||||
* @return {!SVGElement} The SVG element for the grid pattern.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Grid.createDom = function(rnd, gridOptions, defs) {
|
||||
/*
|
||||
<pattern id="blocklyGridPattern837493" patternUnits="userSpaceOnUse">
|
||||
<rect stroke="#888" />
|
||||
<rect stroke="#888" />
|
||||
</pattern>
|
||||
*/
|
||||
var gridPattern = Blockly.utils.createSvgElement('pattern',
|
||||
{'id': 'blocklyGridPattern' + rnd,
|
||||
'patternUnits': 'userSpaceOnUse'}, defs);
|
||||
if (gridOptions['length'] > 0 && gridOptions['spacing'] > 0) {
|
||||
Blockly.utils.createSvgElement('line',
|
||||
{'stroke': gridOptions['colour']}, gridPattern);
|
||||
if (gridOptions['length'] > 1) {
|
||||
Blockly.utils.createSvgElement('line',
|
||||
{'stroke': gridOptions['colour']}, gridPattern);
|
||||
}
|
||||
// x1, y1, x1, x2 properties will be set later in update.
|
||||
}
|
||||
return gridPattern;
|
||||
};
|
||||
+3
-2
@@ -170,7 +170,7 @@ Blockly.Icon.prototype.renderIcon = function(cursorX) {
|
||||
|
||||
/**
|
||||
* Notification that the icon has moved. Update the arrow accordingly.
|
||||
* @param {!goog.math.Coordinate} xy Absolute location.
|
||||
* @param {!goog.math.Coordinate} xy Absolute location in workspace coordinates.
|
||||
*/
|
||||
Blockly.Icon.prototype.setIconLocation = function(xy) {
|
||||
this.iconXY_ = xy;
|
||||
@@ -197,7 +197,8 @@ Blockly.Icon.prototype.computeIconLocation = function() {
|
||||
|
||||
/**
|
||||
* Returns the center of the block's icon relative to the surface.
|
||||
* @return {!goog.math.Coordinate} Object with x and y properties.
|
||||
* @return {!goog.math.Coordinate} Object with x and y properties in workspace
|
||||
* coordinates.
|
||||
*/
|
||||
Blockly.Icon.prototype.getIconLocation = function() {
|
||||
return this.iconXY_;
|
||||
|
||||
+11
-32
@@ -28,6 +28,7 @@ goog.provide('Blockly.inject');
|
||||
|
||||
goog.require('Blockly.BlockDragSurfaceSvg');
|
||||
goog.require('Blockly.Css');
|
||||
goog.require('Blockly.Grid');
|
||||
goog.require('Blockly.Options');
|
||||
goog.require('Blockly.WorkspaceSvg');
|
||||
goog.require('Blockly.WorkspaceDragSurfaceSvg');
|
||||
@@ -60,7 +61,7 @@ Blockly.inject = function(container, opt_options) {
|
||||
// Create surfaces for dragging things. These are optimizations
|
||||
// so that the broowser does not repaint during the drag.
|
||||
var blockDragSurface = new Blockly.BlockDragSurfaceSvg(subContainer);
|
||||
var workspaceDragSurface = new Blockly.workspaceDragSurfaceSvg(subContainer);
|
||||
var workspaceDragSurface = new Blockly.WorkspaceDragSurfaceSvg(subContainer);
|
||||
|
||||
var workspace = Blockly.createMainWorkspace_(svg, options, blockDragSurface,
|
||||
workspaceDragSurface);
|
||||
@@ -164,27 +165,8 @@ Blockly.createDom_ = function(container, options) {
|
||||
Blockly.utils.createSvgElement('path',
|
||||
{'d': 'M 0 0 L 10 10 M 10 0 L 0 10', 'stroke': '#cc0'}, disabledPattern);
|
||||
options.disabledPatternId = disabledPattern.id;
|
||||
/*
|
||||
<pattern id="blocklyGridPattern837493" patternUnits="userSpaceOnUse">
|
||||
<rect stroke="#888" />
|
||||
<rect stroke="#888" />
|
||||
</pattern>
|
||||
*/
|
||||
var gridPattern = Blockly.utils.createSvgElement('pattern',
|
||||
{'id': 'blocklyGridPattern' + rnd,
|
||||
'patternUnits': 'userSpaceOnUse'}, defs);
|
||||
if (options.gridOptions['length'] > 0 && options.gridOptions['spacing'] > 0) {
|
||||
Blockly.utils.createSvgElement('line',
|
||||
{'stroke': options.gridOptions['colour']},
|
||||
gridPattern);
|
||||
if (options.gridOptions['length'] > 1) {
|
||||
Blockly.utils.createSvgElement('line',
|
||||
{'stroke': options.gridOptions['colour']},
|
||||
gridPattern);
|
||||
}
|
||||
// x1, y1, x1, x2 properties will be set later in updateGridPattern_.
|
||||
}
|
||||
options.gridPattern = gridPattern;
|
||||
|
||||
options.gridPattern = Blockly.Grid.createDom(rnd, options.gridOptions, defs);
|
||||
return svg;
|
||||
};
|
||||
|
||||
@@ -217,7 +199,7 @@ Blockly.createMainWorkspace_ = function(svg, options, blockDragSurface, workspac
|
||||
|
||||
if (!options.readOnly && !options.hasScrollbars) {
|
||||
var workspaceChanged = function() {
|
||||
if (Blockly.dragMode_ == Blockly.DRAG_NONE) {
|
||||
if (!mainWorkspace.isDragging()) {
|
||||
var metrics = mainWorkspace.getMetrics();
|
||||
var edgeLeft = metrics.viewLeft + metrics.absoluteLeft;
|
||||
var edgeTop = metrics.viewTop + metrics.absoluteTop;
|
||||
@@ -280,7 +262,7 @@ Blockly.init_ = function(mainWorkspace) {
|
||||
var svg = mainWorkspace.getParentSvg();
|
||||
|
||||
// Suppress the browser's context menu.
|
||||
Blockly.bindEventWithChecks_(svg, 'contextmenu', null,
|
||||
Blockly.bindEventWithChecks_(svg.parentNode, 'contextmenu', null,
|
||||
function(e) {
|
||||
if (!Blockly.utils.isTargetInput(e)) {
|
||||
e.preventDefault();
|
||||
@@ -343,10 +325,6 @@ Blockly.inject.bindDocumentEvents_ = function() {
|
||||
// should run regardless of what other touch event handlers have run.
|
||||
Blockly.bindEvent_(document, 'touchend', null, Blockly.longStop_);
|
||||
Blockly.bindEvent_(document, 'touchcancel', null, Blockly.longStop_);
|
||||
// Don't use bindEvent_ for document's mouseup since that would create a
|
||||
// corresponding touch handler that would squelch the ability to interact
|
||||
// with non-Blockly elements.
|
||||
document.addEventListener('mouseup', Blockly.onMouseUp_, false);
|
||||
// Some iPad versions don't fire resize after portrait to landscape change.
|
||||
if (goog.userAgent.IPAD) {
|
||||
Blockly.bindEventWithChecks_(window, 'orientationchange', document,
|
||||
@@ -366,15 +344,16 @@ Blockly.inject.bindDocumentEvents_ = function() {
|
||||
* @private
|
||||
*/
|
||||
Blockly.inject.loadSounds_ = function(pathToMedia, workspace) {
|
||||
workspace.loadAudio_(
|
||||
var audioMgr = workspace.getAudioManager();
|
||||
audioMgr.load(
|
||||
[pathToMedia + 'click.mp3',
|
||||
pathToMedia + 'click.wav',
|
||||
pathToMedia + 'click.ogg'], 'click');
|
||||
workspace.loadAudio_(
|
||||
audioMgr.load(
|
||||
[pathToMedia + 'disconnect.wav',
|
||||
pathToMedia + 'disconnect.mp3',
|
||||
pathToMedia + 'disconnect.ogg'], 'disconnect');
|
||||
workspace.loadAudio_(
|
||||
audioMgr.load(
|
||||
[pathToMedia + 'delete.mp3',
|
||||
pathToMedia + 'delete.ogg',
|
||||
pathToMedia + 'delete.wav'], 'delete');
|
||||
@@ -385,7 +364,7 @@ Blockly.inject.loadSounds_ = function(pathToMedia, workspace) {
|
||||
while (soundBinds.length) {
|
||||
Blockly.unbindEvent_(soundBinds.pop());
|
||||
}
|
||||
workspace.preloadAudio_();
|
||||
audioMgr.preload();
|
||||
};
|
||||
|
||||
// These are bound on mouse/touch events with Blockly.bindEventWithChecks_, so
|
||||
|
||||
@@ -41,6 +41,9 @@ goog.require('goog.asserts');
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Input = function(type, name, block, connection) {
|
||||
if (type != Blockly.DUMMY_INPUT && !name) {
|
||||
throw 'Value inputs and statement inputs must have non-empty name.';
|
||||
}
|
||||
/** @type {number} */
|
||||
this.type = type;
|
||||
/** @type {string} */
|
||||
|
||||
+12
-4
@@ -137,7 +137,11 @@ Blockly.Mutator.prototype.createEditor_ = function() {
|
||||
// To fix this, scale needs to be applied at a different level in the dom.
|
||||
var flyoutSvg = this.workspace_.addFlyout_('g');
|
||||
var background = this.workspace_.createDom('blocklyMutatorBackground');
|
||||
background.appendChild(flyoutSvg);
|
||||
|
||||
// Insert the flyout after the <rect> but before the block canvas so that
|
||||
// the flyout is underneath in z-order. This makes blocks layering during
|
||||
// dragging work properly.
|
||||
background.insertBefore(flyoutSvg, this.workspace_.svgBlockCanvas_);
|
||||
this.svgDialog_.appendChild(background);
|
||||
|
||||
return this.svgDialog_;
|
||||
@@ -285,7 +289,7 @@ Blockly.Mutator.prototype.setVisible = function(visible) {
|
||||
* @private
|
||||
*/
|
||||
Blockly.Mutator.prototype.workspaceChanged_ = function() {
|
||||
if (Blockly.dragMode_ == Blockly.DRAG_NONE) {
|
||||
if (!this.workspace_.isDragging()) {
|
||||
var blocks = this.workspace_.getTopBlocks(false);
|
||||
var MARGIN = 20;
|
||||
for (var b = 0, block; block = blocks[b]; b++) {
|
||||
@@ -316,7 +320,7 @@ Blockly.Mutator.prototype.workspaceChanged_ = function() {
|
||||
var newMutationDom = block.mutationToDom();
|
||||
var newMutation = newMutationDom && Blockly.Xml.domToText(newMutationDom);
|
||||
if (oldMutation != newMutation) {
|
||||
Blockly.Events.fire(new Blockly.Events.Change(
|
||||
Blockly.Events.fire(new Blockly.Events.BlockChange(
|
||||
block, 'mutation', null, oldMutation, newMutation));
|
||||
// Ensure that any bump is part of this mutation's event group.
|
||||
var group = Blockly.Events.getGroup();
|
||||
@@ -329,7 +333,11 @@ Blockly.Mutator.prototype.workspaceChanged_ = function() {
|
||||
if (block.rendered) {
|
||||
block.render();
|
||||
}
|
||||
this.resizeBubble_();
|
||||
// Don't update the bubble until the drag has ended, to avoid moving blocks
|
||||
// under the cursor.
|
||||
if (!this.workspace_.isDragging()) {
|
||||
this.resizeBubble_();
|
||||
}
|
||||
Blockly.Events.setGroup(false);
|
||||
}
|
||||
};
|
||||
|
||||
+22
-3
@@ -37,6 +37,13 @@ goog.require('Blockly.Names');
|
||||
goog.require('Blockly.Workspace');
|
||||
|
||||
|
||||
/**
|
||||
* Constant to separate procedure names from variables and generated functions
|
||||
* when running generators.
|
||||
* @deprecated Use Blockly.PROCEDURE_CATEGORY_NAME
|
||||
*/
|
||||
Blockly.Procedures.NAME_TYPE = Blockly.PROCEDURE_CATEGORY_NAME;
|
||||
|
||||
/**
|
||||
* Find all user-created procedure definitions in a workspace.
|
||||
* @param {!Blockly.Workspace} root Root workspace.
|
||||
@@ -112,6 +119,18 @@ Blockly.Procedures.findLegalName = function(name, block) {
|
||||
* @private
|
||||
*/
|
||||
Blockly.Procedures.isLegalName_ = function(name, workspace, opt_exclude) {
|
||||
return !Blockly.Procedures.isNameUsed(name, workspace, opt_exclude);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return if the given name is already a procedure name.
|
||||
* @param {string} name The questionable name.
|
||||
* @param {!Blockly.Workspace} workspace The workspace to scan for collisions.
|
||||
* @param {Blockly.Block=} opt_exclude Optional block to exclude from
|
||||
* comparisons (one doesn't want to collide with oneself).
|
||||
* @return {boolean} True if the name is used, otherwise return false.
|
||||
*/
|
||||
Blockly.Procedures.isNameUsed = function(name, workspace, opt_exclude) {
|
||||
var blocks = workspace.getAllBlocks();
|
||||
// Iterate through every block and check the name.
|
||||
for (var i = 0; i < blocks.length; i++) {
|
||||
@@ -121,11 +140,11 @@ Blockly.Procedures.isLegalName_ = function(name, workspace, opt_exclude) {
|
||||
if (blocks[i].getProcedureDef) {
|
||||
var procName = blocks[i].getProcedureDef();
|
||||
if (Blockly.Names.equals(procName[0], name)) {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -271,7 +290,7 @@ Blockly.Procedures.mutateCallers = function(defBlock) {
|
||||
// undo action since it is deterministically tied to the procedure's
|
||||
// definition mutation.
|
||||
Blockly.Events.recordUndo = false;
|
||||
Blockly.Events.fire(new Blockly.Events.Change(
|
||||
Blockly.Events.fire(new Blockly.Events.BlockChange(
|
||||
caller, 'mutation', null, oldMutation, newMutation));
|
||||
Blockly.Events.recordUndo = oldRecordUndo;
|
||||
}
|
||||
|
||||
@@ -38,15 +38,22 @@ goog.require('Blockly.Connection');
|
||||
*/
|
||||
Blockly.RenderedConnection = function(source, type) {
|
||||
Blockly.RenderedConnection.superClass_.constructor.call(this, source, type);
|
||||
|
||||
/**
|
||||
* Workspace units, (0, 0) is top left of block.
|
||||
* @type {!goog.math.Coordinate}
|
||||
* @private
|
||||
*/
|
||||
this.offsetInBlock_ = new goog.math.Coordinate(0, 0);
|
||||
};
|
||||
goog.inherits(Blockly.RenderedConnection, Blockly.Connection);
|
||||
|
||||
/**
|
||||
* Returns the distance between this connection and another connection.
|
||||
* Returns the distance between this connection and another connection in
|
||||
* workspace units.
|
||||
* @param {!Blockly.Connection} otherConnection The other connection to measure
|
||||
* the distance to.
|
||||
* @return {number} The distance between connections.
|
||||
* @return {number} The distance between connections, in workspace units.
|
||||
*/
|
||||
Blockly.RenderedConnection.prototype.distanceFrom = function(otherConnection) {
|
||||
var xDiff = this.x_ - otherConnection.x_;
|
||||
@@ -62,7 +69,7 @@ Blockly.RenderedConnection.prototype.distanceFrom = function(otherConnection) {
|
||||
* @private
|
||||
*/
|
||||
Blockly.RenderedConnection.prototype.bumpAwayFrom_ = function(staticConnection) {
|
||||
if (Blockly.dragMode_ != Blockly.DRAG_NONE) {
|
||||
if (this.sourceBlock_.workspace.isDragging()) {
|
||||
// Don't move blocks around while the user is doing the same.
|
||||
return;
|
||||
}
|
||||
@@ -102,8 +109,8 @@ Blockly.RenderedConnection.prototype.bumpAwayFrom_ = function(staticConnection)
|
||||
|
||||
/**
|
||||
* Change the connection's coordinates.
|
||||
* @param {number} x New absolute x coordinate.
|
||||
* @param {number} y New absolute y coordinate.
|
||||
* @param {number} x New absolute x coordinate, in workspace coordinates.
|
||||
* @param {number} y New absolute y coordinate, in workspace coordinates.
|
||||
*/
|
||||
Blockly.RenderedConnection.prototype.moveTo = function(x, y) {
|
||||
// Remove it from its old location in the database (if already present)
|
||||
@@ -120,8 +127,8 @@ Blockly.RenderedConnection.prototype.moveTo = function(x, y) {
|
||||
|
||||
/**
|
||||
* Change the connection's coordinates.
|
||||
* @param {number} dx Change to x coordinate.
|
||||
* @param {number} dy Change to y coordinate.
|
||||
* @param {number} dx Change to x coordinate, in workspace units.
|
||||
* @param {number} dy Change to y coordinate, in workspace units.
|
||||
*/
|
||||
Blockly.RenderedConnection.prototype.moveBy = function(dx, dy) {
|
||||
this.moveTo(this.x_ + dx, this.y_ + dy);
|
||||
@@ -129,9 +136,9 @@ Blockly.RenderedConnection.prototype.moveBy = function(dx, dy) {
|
||||
|
||||
/**
|
||||
* Move this connection to the location given by its offset within the block and
|
||||
* the coordinate of the block's top left corner.
|
||||
* @param {!goog.math.Coordinate} blockTL The coordinate of the top left corner
|
||||
* of the block.
|
||||
* the location of the block's top left corner.
|
||||
* @param {!goog.math.Coordinate} blockTL The location of the top left corner
|
||||
* of the block, in workspace coordinates.
|
||||
*/
|
||||
Blockly.RenderedConnection.prototype.moveToOffset = function(blockTL) {
|
||||
this.moveTo(blockTL.x + this.offsetInBlock_.x,
|
||||
@@ -140,8 +147,8 @@ Blockly.RenderedConnection.prototype.moveToOffset = function(blockTL) {
|
||||
|
||||
/**
|
||||
* Set the offset of this connection relative to the top left of its block.
|
||||
* @param {number} x The new relative x.
|
||||
* @param {number} y The new relative y.
|
||||
* @param {number} x The new relative x, in workspace units.
|
||||
* @param {number} y The new relative y, in workspace units.
|
||||
*/
|
||||
Blockly.RenderedConnection.prototype.setOffsetInBlock = function(x, y) {
|
||||
this.offsetInBlock_.x = x;
|
||||
@@ -161,6 +168,7 @@ Blockly.RenderedConnection.prototype.tighten_ = function() {
|
||||
if (!svgRoot) {
|
||||
throw 'block is not rendered.';
|
||||
}
|
||||
// Workspace coordinates.
|
||||
var xy = Blockly.utils.getRelativeXY(svgRoot);
|
||||
block.getSvgRoot().setAttribute('transform',
|
||||
'translate(' + (xy.x - dx) + ',' + (xy.y - dy) + ')');
|
||||
@@ -170,6 +178,7 @@ Blockly.RenderedConnection.prototype.tighten_ = function() {
|
||||
|
||||
/**
|
||||
* Find the closest compatible connection to this connection.
|
||||
* All parameters are in workspace units.
|
||||
* @param {number} maxLimit The maximum radius to another connection.
|
||||
* @param {number} dx Horizontal offset between this connection's location
|
||||
* in the database and the current location (as a result of dragging).
|
||||
@@ -294,7 +303,8 @@ Blockly.RenderedConnection.prototype.hideAll = function() {
|
||||
/**
|
||||
* Check if the two connections can be dragged to connect to each other.
|
||||
* @param {!Blockly.Connection} candidate A nearby connection to check.
|
||||
* @param {number} maxRadius The maximum radius allowed for connections.
|
||||
* @param {number} maxRadius The maximum radius allowed for connections, in
|
||||
* workspace units.
|
||||
* @return {boolean} True if the connection is allowed, false otherwise.
|
||||
*/
|
||||
Blockly.RenderedConnection.prototype.isConnectionAllowed = function(candidate,
|
||||
@@ -353,7 +363,8 @@ Blockly.RenderedConnection.prototype.respawnShadow_ = function() {
|
||||
/**
|
||||
* Find all nearby compatible connections to this connection.
|
||||
* Type checking does not apply, since this function is used for bumping.
|
||||
* @param {number} maxLimit The maximum radius to another connection.
|
||||
* @param {number} maxLimit The maximum radius to another connection, in
|
||||
* workspace units.
|
||||
* @return {!Array.<!Blockly.Connection>} List of connections.
|
||||
* @private
|
||||
*/
|
||||
|
||||
+3
-1
@@ -240,6 +240,7 @@ Blockly.Scrollbar.prototype.origin_ = new goog.math.Coordinate(0, 0);
|
||||
|
||||
/**
|
||||
* The size of the area within which the scrollbar handle can move.
|
||||
* Coordinate system: pixel coordinates.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
@@ -247,6 +248,7 @@ Blockly.Scrollbar.prototype.scrollViewSize_ = 0;
|
||||
|
||||
/**
|
||||
* The length of the scrollbar handle.
|
||||
* Coordinate system: pixel coordinates.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
@@ -254,6 +256,7 @@ Blockly.Scrollbar.prototype.handleLength_ = 0;
|
||||
|
||||
/**
|
||||
* The offset of the start of the handle from the start of the scrollbar range.
|
||||
* Coordinate system: pixel coordinates.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
@@ -276,7 +279,6 @@ Blockly.Scrollbar.prototype.containerVisible_ = true;
|
||||
/**
|
||||
* Width of vertical scrollbar or height of horizontal scrollbar.
|
||||
* Increase the size of scrollbars on touch devices.
|
||||
* Don't define if there is no document object (e.g. node.js).
|
||||
*/
|
||||
Blockly.Scrollbar.scrollbarThickness = 15;
|
||||
if (goog.events.BrowserFeature.TOUCH_ENABLED) {
|
||||
|
||||
+35
-3
@@ -27,7 +27,9 @@
|
||||
goog.provide('Blockly.Toolbox');
|
||||
|
||||
goog.require('Blockly.Flyout');
|
||||
goog.require('Blockly.HorizontalFlyout');
|
||||
goog.require('Blockly.Touch');
|
||||
goog.require('Blockly.VerticalFlyout');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.TagName');
|
||||
goog.require('goog.events');
|
||||
@@ -181,7 +183,12 @@ Blockly.Toolbox.prototype.init = function() {
|
||||
* @type {!Blockly.Flyout}
|
||||
* @private
|
||||
*/
|
||||
this.flyout_ = new Blockly.Flyout(workspaceOptions);
|
||||
this.flyout_ = null;
|
||||
if (workspace.horizontalLayout) {
|
||||
this.flyout_ = new Blockly.HorizontalFlyout(workspaceOptions);
|
||||
} else {
|
||||
this.flyout_ = new Blockly.VerticalFlyout(workspaceOptions);
|
||||
}
|
||||
goog.dom.insertSiblingAfter(this.flyout_.createDom('svg'),
|
||||
this.workspace_.getParentSvg());
|
||||
this.flyout_.init(workspace);
|
||||
@@ -304,7 +311,11 @@ Blockly.Toolbox.prototype.syncTrees_ = function(treeIn, treeOut, pathToMedia) {
|
||||
}
|
||||
switch (childIn.tagName.toUpperCase()) {
|
||||
case 'CATEGORY':
|
||||
var childOut = this.tree_.createNode(childIn.getAttribute('name'));
|
||||
// Decode the category name for any potential message references
|
||||
// (eg. `%{BKY_CATEGORY_NAME_LOGIC}`).
|
||||
var categoryName = Blockly.utils.replaceMessageReferences(
|
||||
childIn.getAttribute('name'));
|
||||
var childOut = this.tree_.createNode(categoryName);
|
||||
childOut.blocks = [];
|
||||
treeOut.add(childOut);
|
||||
var custom = childIn.getAttribute('custom');
|
||||
@@ -317,7 +328,10 @@ Blockly.Toolbox.prototype.syncTrees_ = function(treeIn, treeOut, pathToMedia) {
|
||||
openNode = newOpenNode;
|
||||
}
|
||||
}
|
||||
var colour = childIn.getAttribute('colour');
|
||||
// Decode the colour for any potential message references
|
||||
// (eg. `%{BKY_MATH_HUE}`).
|
||||
var colour = Blockly.utils.replaceMessageReferences(
|
||||
childIn.getAttribute('colour'));
|
||||
if (goog.isString(colour)) {
|
||||
if (colour.match(/^#[0-9a-fA-F]{6}$/)) {
|
||||
childOut.hexColour = colour;
|
||||
@@ -406,6 +420,24 @@ Blockly.Toolbox.prototype.clearSelection = function() {
|
||||
this.tree_.setSelectedItem(null);
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds styles on the toolbox indicating blocks will be deleted.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Toolbox.prototype.addDeleteStyle = function() {
|
||||
Blockly.utils.addClass(/** @type {!Element} */ (this.HtmlDiv),
|
||||
'blocklyToolboxDelete');
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove styles from the toolbox that indicate blocks will be deleted.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Toolbox.prototype.removeDeleteStyle = function() {
|
||||
Blockly.utils.removeClass(/** @type {!Element} */ (this.HtmlDiv),
|
||||
'blocklyToolboxDelete');
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the deletion rectangle for this toolbox.
|
||||
* @return {goog.math.Rect} Rectangle in which to delete.
|
||||
|
||||
+42
-3
@@ -44,6 +44,13 @@ goog.require('goog.dom.TagName');
|
||||
*/
|
||||
Blockly.Tooltip.visible = false;
|
||||
|
||||
/**
|
||||
* Is someone else blocking the tooltip from being shown?
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Tooltip.blocked_ = false;
|
||||
|
||||
/**
|
||||
* Maximum width (in characters) of a tooltip.
|
||||
*/
|
||||
@@ -153,6 +160,10 @@ Blockly.Tooltip.bindMouseEvents = function(element) {
|
||||
* @private
|
||||
*/
|
||||
Blockly.Tooltip.onMouseOver_ = function(e) {
|
||||
if (Blockly.Tooltip.blocked_) {
|
||||
// Someone doesn't want us to show tooltips.
|
||||
return;
|
||||
}
|
||||
// If the tooltip is an object, treat it as a pointer to the next object in
|
||||
// the chain to look at. Terminate when a string or function is found.
|
||||
var element = e.target;
|
||||
@@ -174,6 +185,10 @@ Blockly.Tooltip.onMouseOver_ = function(e) {
|
||||
* @private
|
||||
*/
|
||||
Blockly.Tooltip.onMouseOut_ = function(e) {
|
||||
if (Blockly.Tooltip.blocked_) {
|
||||
// Someone doesn't want us to show tooltips.
|
||||
return;
|
||||
}
|
||||
// Moving from one element to another (overlapping or with no gap) generates
|
||||
// a mouseOut followed instantly by a mouseOver. Fork off the mouseOut
|
||||
// event and kill it if a mouseOver is received immediately.
|
||||
@@ -196,12 +211,13 @@ Blockly.Tooltip.onMouseMove_ = function(e) {
|
||||
if (!Blockly.Tooltip.element_ || !Blockly.Tooltip.element_.tooltip) {
|
||||
// No tooltip here to show.
|
||||
return;
|
||||
} else if (Blockly.dragMode_ != Blockly.DRAG_NONE) {
|
||||
// Don't display a tooltip during a drag.
|
||||
return;
|
||||
} else if (Blockly.WidgetDiv.isVisible()) {
|
||||
// Don't display a tooltip if a widget is open (tooltip would be under it).
|
||||
return;
|
||||
} else if (Blockly.Tooltip.blocked_) {
|
||||
// Someone doesn't want us to show tooltips. We are probably handling a
|
||||
// user gesture, such as a click or drag.
|
||||
return;
|
||||
}
|
||||
if (Blockly.Tooltip.visible) {
|
||||
// Compute the distance between the mouse position when the tooltip was
|
||||
@@ -235,11 +251,34 @@ Blockly.Tooltip.hide = function() {
|
||||
clearTimeout(Blockly.Tooltip.showPid_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Hide any in-progress tooltips and block showing new tooltips until the next
|
||||
* call to unblock().
|
||||
* @package
|
||||
*/
|
||||
Blockly.Tooltip.block = function() {
|
||||
Blockly.Tooltip.hide();
|
||||
Blockly.Tooltip.blocked_ = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Unblock tooltips: allow them to be scheduled and shown according to their own
|
||||
* logic.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Tooltip.unblock = function() {
|
||||
Blockly.Tooltip.blocked_ = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the tooltip and show it.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Tooltip.show_ = function() {
|
||||
if (Blockly.Tooltip.blocked_) {
|
||||
// Someone doesn't want us to show tooltips.
|
||||
return;
|
||||
}
|
||||
Blockly.Tooltip.poisonedElement_ = Blockly.Tooltip.element_;
|
||||
if (!Blockly.Tooltip.DIV) {
|
||||
return;
|
||||
|
||||
+23
-76
@@ -41,13 +41,6 @@ goog.require('goog.string');
|
||||
*/
|
||||
Blockly.Touch.touchIdentifier_ = null;
|
||||
|
||||
/**
|
||||
* Wrapper function called when a touch mouseUp occurs during a drag operation.
|
||||
* @type {Array.<!Array>}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Touch.onTouchUpWrapper_ = null;
|
||||
|
||||
/**
|
||||
* The TOUCH_MAP lookup dictionary specifies additional touch events to fire,
|
||||
* in conjunction with mouse events.
|
||||
@@ -75,11 +68,10 @@ Blockly.longPid_ = 0;
|
||||
* which after about a second opens the context menu. The tasks is killed
|
||||
* if the touch event terminates early.
|
||||
* @param {!Event} e Touch start event.
|
||||
* @param {!Blockly.Block|!Blockly.WorkspaceSvg} uiObject The block or workspace
|
||||
* under the touchstart event.
|
||||
* @param {Blockly.Gesture} gesture The gesture that triggered this longStart.
|
||||
* @private
|
||||
*/
|
||||
Blockly.longStart_ = function(e, uiObject) {
|
||||
Blockly.longStart_ = function(e, gesture) {
|
||||
Blockly.longStop_();
|
||||
// Punt on multitouch events.
|
||||
if (e.changedTouches.length != 1) {
|
||||
@@ -90,7 +82,12 @@ Blockly.longStart_ = function(e, uiObject) {
|
||||
// e was a touch event. It needs to pretend to be a mouse event.
|
||||
e.clientX = e.changedTouches[0].clientX;
|
||||
e.clientY = e.changedTouches[0].clientY;
|
||||
uiObject.onMouseDown_(e);
|
||||
|
||||
// Let the gesture route the right-click correctly.
|
||||
if (gesture) {
|
||||
gesture.handleRightClick(e);
|
||||
}
|
||||
|
||||
}, Blockly.LONGPRESS);
|
||||
};
|
||||
|
||||
@@ -106,67 +103,6 @@ Blockly.longStop_ = function() {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle a mouse-up anywhere on the page.
|
||||
* @param {!Event} e Mouse up event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.onMouseUp_ = function(e) {
|
||||
var workspace = Blockly.getMainWorkspace();
|
||||
if (workspace.dragMode_ == Blockly.DRAG_NONE) {
|
||||
return;
|
||||
}
|
||||
Blockly.Touch.clearTouchIdentifier();
|
||||
|
||||
// TODO(#781): Check whether this needs to be called for all drag modes.
|
||||
workspace.resetDragSurface();
|
||||
Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN);
|
||||
workspace.dragMode_ = Blockly.DRAG_NONE;
|
||||
// Unbind the touch event if it exists.
|
||||
if (Blockly.Touch.onTouchUpWrapper_) {
|
||||
Blockly.unbindEvent_(Blockly.Touch.onTouchUpWrapper_);
|
||||
Blockly.Touch.onTouchUpWrapper_ = null;
|
||||
}
|
||||
if (Blockly.onMouseMoveWrapper_) {
|
||||
Blockly.unbindEvent_(Blockly.onMouseMoveWrapper_);
|
||||
Blockly.onMouseMoveWrapper_ = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a mouse-move on SVG drawing surface.
|
||||
* @param {!Event} e Mouse move event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.onMouseMove_ = function(e) {
|
||||
var workspace = Blockly.getMainWorkspace();
|
||||
if (workspace.dragMode_ != Blockly.DRAG_NONE) {
|
||||
var dx = e.clientX - workspace.startDragMouseX;
|
||||
var dy = e.clientY - workspace.startDragMouseY;
|
||||
var metrics = workspace.startDragMetrics;
|
||||
var x = workspace.startScrollX + dx;
|
||||
var y = workspace.startScrollY + dy;
|
||||
x = Math.min(x, -metrics.contentLeft);
|
||||
y = Math.min(y, -metrics.contentTop);
|
||||
x = Math.max(x, metrics.viewWidth - metrics.contentLeft -
|
||||
metrics.contentWidth);
|
||||
y = Math.max(y, metrics.viewHeight - metrics.contentTop -
|
||||
metrics.contentHeight);
|
||||
|
||||
// Move the scrollbars and the page will scroll automatically.
|
||||
workspace.scrollbar.set(-x - metrics.contentLeft,
|
||||
-y - metrics.contentTop);
|
||||
// Cancel the long-press if the drag has moved too far.
|
||||
if (Math.sqrt(dx * dx + dy * dy) > Blockly.DRAG_RADIUS) {
|
||||
Blockly.longStop_();
|
||||
workspace.dragMode_ = Blockly.DRAG_FREE;
|
||||
}
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear the touch identifier that tracks which touch stream to pay attention
|
||||
* to. This ends the current drag/gesture and allows other pointers to be
|
||||
@@ -189,6 +125,20 @@ Blockly.Touch.shouldHandleEvent = function(e) {
|
||||
Blockly.Touch.checkTouchIdentifier(e);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the touch identifier from the given event. If it was a mouse event, the
|
||||
* identifier is the string 'mouse'.
|
||||
* @param {!Event} e Mouse event or touch event.
|
||||
* @return {string} The touch identifier from the first changed touch, if
|
||||
* defined. Otherwise 'mouse'.
|
||||
*/
|
||||
Blockly.Touch.getTouchIdentifierFromEvent = function(e) {
|
||||
return (e.changedTouches && e.changedTouches[0] &&
|
||||
e.changedTouches[0].identifier != undefined &&
|
||||
e.changedTouches[0].identifier != null) ?
|
||||
e.changedTouches[0].identifier : 'mouse';
|
||||
};
|
||||
|
||||
/**
|
||||
* Check whether the touch identifier on the event matches the current saved
|
||||
* identifier. If there is no identifier, that means it's a mouse event and
|
||||
@@ -202,10 +152,7 @@ Blockly.Touch.shouldHandleEvent = function(e) {
|
||||
* saved identifier.
|
||||
*/
|
||||
Blockly.Touch.checkTouchIdentifier = function(e) {
|
||||
var identifier = (e.changedTouches && e.changedTouches[0] &&
|
||||
e.changedTouches[0].identifier != undefined &&
|
||||
e.changedTouches[0].identifier != null) ?
|
||||
e.changedTouches[0].identifier : 'mouse';
|
||||
var identifier = Blockly.Touch.getTouchIdentifierFromEvent(e);
|
||||
|
||||
// if (Blockly.touchIdentifier_ )is insufficient because android touch
|
||||
// identifiers may be zero.
|
||||
|
||||
+3
-2
@@ -166,8 +166,9 @@ Blockly.Trashcan.prototype.createDom = function() {
|
||||
*/
|
||||
this.svgGroup_ = Blockly.utils.createSvgElement('g',
|
||||
{'class': 'blocklyTrash'}, null);
|
||||
var clip;
|
||||
var rnd = String(Math.random()).substring(2);
|
||||
var clip = Blockly.utils.createSvgElement('clipPath',
|
||||
clip = Blockly.utils.createSvgElement('clipPath',
|
||||
{'id': 'blocklyTrashBodyClipPath' + rnd},
|
||||
this.svgGroup_);
|
||||
Blockly.utils.createSvgElement('rect',
|
||||
@@ -182,7 +183,7 @@ Blockly.Trashcan.prototype.createDom = function() {
|
||||
body.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href',
|
||||
this.workspace_.options.pathToMedia + Blockly.SPRITE.url);
|
||||
|
||||
var clip = Blockly.utils.createSvgElement('clipPath',
|
||||
clip = Blockly.utils.createSvgElement('clipPath',
|
||||
{'id': 'blocklyTrashLidClipPath' + rnd},
|
||||
this.svgGroup_);
|
||||
Blockly.utils.createSvgElement('rect',
|
||||
|
||||
+1
-1
@@ -197,7 +197,7 @@ Blockly.utils.getInjectionDivXY_ = function(element) {
|
||||
var scale = 1;
|
||||
while (element) {
|
||||
var xy = Blockly.utils.getRelativeXY(element);
|
||||
var scale = Blockly.utils.getScale_(element);
|
||||
scale = Blockly.utils.getScale_(element);
|
||||
x = (x * scale) + xy.x;
|
||||
y = (y * scale) + xy.y;
|
||||
var classes = element.getAttribute('class') || '';
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2017 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Object representing a map of variables and their types.
|
||||
* @author marisaleung@google.com (Marisa Leung)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.VariableMap');
|
||||
|
||||
/**
|
||||
* Class for a variable map. This contains a dictionary data structure with
|
||||
* variable types as keys and lists of variables as values. The list of
|
||||
* variables are the type indicated by the key.
|
||||
* @param {!Blockly.Workspace} workspace The workspace this map belongs to.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.VariableMap = function(workspace) {
|
||||
/**
|
||||
* @type {!Object<string, !Array.<Blockly.VariableModel>>}
|
||||
* A map from variable type to list of variable names. The lists contain all
|
||||
* of the named variables in the workspace, including variables
|
||||
* that are not currently in use.
|
||||
* @private
|
||||
*/
|
||||
this.variableMap_ = {};
|
||||
|
||||
/**
|
||||
* The workspace this map belongs to.
|
||||
* @type {!Blockly.Workspace}
|
||||
*/
|
||||
this.workspace = workspace;
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear the variable map.
|
||||
*/
|
||||
Blockly.VariableMap.prototype.clear = function() {
|
||||
this.variableMap_ = new Object(null);
|
||||
};
|
||||
|
||||
/**
|
||||
* Rename the given variable by updating its name in the variable map.
|
||||
* @param {?Blockly.VariableModel} variable Variable to rename.
|
||||
* @param {string} newName New variable name.
|
||||
*/
|
||||
Blockly.VariableMap.prototype.renameVariable = function(variable, newName) {
|
||||
var newVariable = this.getVariable(newName);
|
||||
var variableIndex = -1;
|
||||
var newVariableIndex = -1;
|
||||
var type = '';
|
||||
if (variable || newVariable) {
|
||||
type = (variable || newVariable).type;
|
||||
}
|
||||
|
||||
var variableList = this.getVariablesOfType(type);
|
||||
if (variable) {
|
||||
variableIndex = variableList.indexOf(variable);
|
||||
}
|
||||
if (newVariable){ // see if I can get rid of newVariable dependency
|
||||
newVariableIndex = variableList.indexOf(newVariable);
|
||||
}
|
||||
|
||||
if (variableIndex == -1 && newVariableIndex == -1) {
|
||||
this.createVariable(newName, '');
|
||||
console.log('Tried to rename an non-existent variable.');
|
||||
} else if (variableIndex == newVariableIndex ||
|
||||
variableIndex != -1 && newVariableIndex == -1) {
|
||||
// Only changing case, or renaming to a completely novel name.
|
||||
var variableToRename = this.variableMap_[type][variableIndex];
|
||||
Blockly.Events.fire(new Blockly.Events.VarRename(variableToRename,
|
||||
newName));
|
||||
variableToRename.name = newName;
|
||||
} else if (variableIndex != -1 && newVariableIndex != -1) {
|
||||
// Renaming one existing variable to another existing variable.
|
||||
// The case might have changed, so we update the destination ID.
|
||||
var variableToRename = this.variableMap_[type][newVariableIndex];
|
||||
Blockly.Events.fire(new Blockly.Events.VarRename(variableToRename,
|
||||
newName));
|
||||
var variableToDelete = this.variableMap_[type][variableIndex];
|
||||
Blockly.Events.fire(new Blockly.Events.VarDelete(variableToDelete));
|
||||
variableToRename.name = newName;
|
||||
this.variableMap_[type].splice(variableIndex, 1);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a variable with a given name, optional type, and optional id.
|
||||
* @param {!string} name The name of the variable. This must be unique across
|
||||
* variables and procedures.
|
||||
* @param {?string} opt_type The type of the variable like 'int' or 'string'.
|
||||
* Does not need to be unique. Field_variable can filter variables based on
|
||||
* their type. This will default to '' which is a specific type.
|
||||
* @param {?string} opt_id The unique id of the variable. This will default to
|
||||
* a UUID.
|
||||
* @return {?Blockly.VariableModel} The newly created variable.
|
||||
*/
|
||||
Blockly.VariableMap.prototype.createVariable = function(name, opt_type, opt_id) {
|
||||
var variable = this.getVariable(name);
|
||||
if (variable) {
|
||||
if (opt_type && variable.type != opt_type) {
|
||||
throw Error('Variable "' + name + '" is already in use and its type is "'
|
||||
+ variable.type + '" which conflicts with the passed in ' +
|
||||
'type, "' + opt_type + '".');
|
||||
}
|
||||
if (opt_id && variable.getId() != opt_id) {
|
||||
throw Error('Variable "' + name + '" is already in use and its id is "'
|
||||
+ variable.getId() + '" which conflicts with the passed in ' +
|
||||
'id, "' + opt_id + '".');
|
||||
}
|
||||
// The variable already exists and has the same id and type.
|
||||
return variable;
|
||||
}
|
||||
if (opt_id && this.getVariableById(opt_id)) {
|
||||
throw Error('Variable id, "' + opt_id + '", is already in use.');
|
||||
}
|
||||
opt_id = opt_id || Blockly.utils.genUid();
|
||||
opt_type = opt_type || '';
|
||||
|
||||
variable = new Blockly.VariableModel(this.workspace, name, opt_type, opt_id);
|
||||
// If opt_type is not a key, create a new list.
|
||||
if (!this.variableMap_[opt_type]) {
|
||||
this.variableMap_[opt_type] = [variable];
|
||||
} else {
|
||||
// Else append the variable to the preexisting list.
|
||||
this.variableMap_[opt_type].push(variable);
|
||||
}
|
||||
return variable;
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a variable.
|
||||
* @param {Blockly.VariableModel} variable Variable to delete.
|
||||
*/
|
||||
Blockly.VariableMap.prototype.deleteVariable = function(variable) {
|
||||
var variableList = this.variableMap_[variable.type];
|
||||
for (var i = 0, tempVar; tempVar = variableList[i]; i++) {
|
||||
if (tempVar.getId() == variable.getId()) {
|
||||
variableList.splice(i, 1);
|
||||
Blockly.Events.fire(new Blockly.Events.VarDelete(variable));
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the variable by the given name and return it. Return null if it is not
|
||||
* found.
|
||||
* @param {!string} name The name to check for.
|
||||
* @return {?Blockly.VariableModel} The variable with the given name, or null if
|
||||
* it was not found.
|
||||
*/
|
||||
Blockly.VariableMap.prototype.getVariable = function(name) {
|
||||
var keys = Object.keys(this.variableMap_);
|
||||
for (var i = 0; i < keys.length; i++ ) {
|
||||
var key = keys[i];
|
||||
for (var j = 0, variable; variable = this.variableMap_[key][j]; j++) {
|
||||
if (Blockly.Names.equals(variable.name, name)) {
|
||||
return variable;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the variable by the given id and return it. Return null if it is not
|
||||
* found.
|
||||
* @param {!string} id The id to check for.
|
||||
* @return {?Blockly.VariableModel} The variable with the given id.
|
||||
*/
|
||||
Blockly.VariableMap.prototype.getVariableById = function(id) {
|
||||
var keys = Object.keys(this.variableMap_);
|
||||
for (var i = 0; i < keys.length; i++ ) {
|
||||
var key = keys[i];
|
||||
for (var j = 0, variable; variable = this.variableMap_[key][j]; j++) {
|
||||
if (variable.getId() == id) {
|
||||
return variable;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a list containing all of the variables of a specified type. If type is
|
||||
* null, return list of variables with empty string type.
|
||||
* @param {?string} type Type of the variables to find.
|
||||
* @return {Array.<Blockly.VariableModel>} The sought after variables of the
|
||||
* passed in type. An empty array if none are found.
|
||||
*/
|
||||
Blockly.VariableMap.prototype.getVariablesOfType = function(type) {
|
||||
type = type || '';
|
||||
var variable_list = this.variableMap_[type];
|
||||
if (variable_list) {
|
||||
return variable_list.slice();
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Return all variable types.
|
||||
* @return {!Array.<string>} List of variable types.
|
||||
*/
|
||||
Blockly.VariableMap.prototype.getVariableTypes = function() {
|
||||
return Object.keys(this.variableMap_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return all variables of all types.
|
||||
* @return {!Array.<Blockly.VariableModel>} List of variable models.
|
||||
*/
|
||||
Blockly.VariableMap.prototype.getAllVariables = function() {
|
||||
var all_variables = [];
|
||||
var keys = Object.keys(this.variableMap_);
|
||||
for (var i = 0; i < keys.length; i++ ) {
|
||||
all_variables = all_variables.concat(this.variableMap_[keys[i]]);
|
||||
}
|
||||
return all_variables;
|
||||
};
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2017 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Components for the variable model.
|
||||
* @author marisaleung@google.com (Marisa Leung)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.VariableModel');
|
||||
|
||||
goog.require('goog.string');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a variable model.
|
||||
* Holds information for the variable including name, id, and type.
|
||||
* @param {!Blockly.Workspace} workspace The variable's workspace.
|
||||
* @param {!string} name The name of the variable. This must be unique across
|
||||
* variables and procedures.
|
||||
* @param {?string} opt_type The type of the variable like 'int' or 'string'.
|
||||
* Does not need to be unique. Field_variable can filter variables based on
|
||||
* their type. This will default to '' which is a specific type.
|
||||
* @param {?string} opt_id The unique id of the variable. This will default to
|
||||
* a UUID.
|
||||
* @see {Blockly.FieldVariable}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.VariableModel = function(workspace, name, opt_type, opt_id) {
|
||||
/**
|
||||
* The workspace the variable is in.
|
||||
* @type {!Blockly.Workspace}
|
||||
*/
|
||||
this.workspace = workspace;
|
||||
|
||||
/**
|
||||
* The name of the variable, typically defined by the user. It must be
|
||||
* unique across all names used for procedures and variables. It may be
|
||||
* changed by the user.
|
||||
* @type {string}
|
||||
*/
|
||||
this.name = name;
|
||||
|
||||
/**
|
||||
* The type of the variable, such as 'int' or 'sound_effect'. This may be
|
||||
* used to build a list of variables of a specific type. By default this is
|
||||
* the empty string '', which is a specific type.
|
||||
* @see {Blockly.FieldVariable}
|
||||
* @type {string}
|
||||
*/
|
||||
this.type = opt_type || '';
|
||||
|
||||
/**
|
||||
* A unique id for the variable. This should be defined at creation and
|
||||
* not change, even if the name changes. In most cases this should be a
|
||||
* UUID.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.id_ = opt_id || Blockly.utils.genUid();
|
||||
|
||||
Blockly.Events.fire(new Blockly.Events.VarCreate(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {!string} The id for the variable.
|
||||
*/
|
||||
Blockly.VariableModel.prototype.getId = function() {
|
||||
return this.id_;
|
||||
};
|
||||
|
||||
/**
|
||||
* A custom compare function for the VariableModel objects.
|
||||
* @param {Blockly.VariableModel} var1 First variable to compare.
|
||||
* @param {Blockly.VariableModel} var2 Second variable to compare.
|
||||
* @return {number} -1 if name of var1 is less than name of var2, 0 if equal,
|
||||
* and 1 if greater.
|
||||
* @package
|
||||
*/
|
||||
Blockly.VariableModel.compareByName = function(var1, var2) {
|
||||
return goog.string.caseInsensitiveCompare(var1.name, var2.name);
|
||||
};
|
||||
+116
-18
@@ -32,10 +32,18 @@ goog.provide('Blockly.Variables');
|
||||
|
||||
goog.require('Blockly.Blocks');
|
||||
goog.require('Blockly.constants');
|
||||
goog.require('Blockly.VariableModel');
|
||||
goog.require('Blockly.Workspace');
|
||||
goog.require('goog.string');
|
||||
|
||||
|
||||
/**
|
||||
* Constant to separate variable names from procedures and generated functions
|
||||
* when running generators.
|
||||
* @deprecated Use Blockly.VARIABLE_CATEGORY_NAME
|
||||
*/
|
||||
Blockly.Variables.NAME_TYPE = Blockly.VARIABLE_CATEGORY_NAME;
|
||||
|
||||
/**
|
||||
* Find all user-created variables that are in use in the workspace.
|
||||
* For use by generators.
|
||||
@@ -79,7 +87,7 @@ Blockly.Variables.allUsedVariables = function(root) {
|
||||
* Find all variables that the user has created through the workspace or
|
||||
* toolbox. For use by generators.
|
||||
* @param {!Blockly.Workspace} root The workspace to inspect.
|
||||
* @return {!Array.<string>} Array of variable names.
|
||||
* @return {!Array.<Blockly.VariableModel>} Array of variable models.
|
||||
*/
|
||||
Blockly.Variables.allVariables = function(root) {
|
||||
if (root instanceof Blockly.Block) {
|
||||
@@ -87,19 +95,18 @@ Blockly.Variables.allVariables = function(root) {
|
||||
console.warn('Deprecated call to Blockly.Variables.allVariables ' +
|
||||
'with a block instead of a workspace. You may want ' +
|
||||
'Blockly.Variables.allUsedVariables');
|
||||
return {};
|
||||
}
|
||||
return root.variableList;
|
||||
return root.getAllVariables();
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct the blocks required by the flyout for the variable category.
|
||||
* Construct the elements (blocks and button) required by the flyout for the
|
||||
* variable category.
|
||||
* @param {!Blockly.Workspace} workspace The workspace contianing variables.
|
||||
* @return {!Array.<!Element>} Array of XML block elements.
|
||||
* @return {!Array.<!Element>} Array of XML elements.
|
||||
*/
|
||||
Blockly.Variables.flyoutCategory = function(workspace) {
|
||||
var variableList = workspace.variableList;
|
||||
variableList.sort(goog.string.caseInsensitiveCompare);
|
||||
|
||||
var xmlList = [];
|
||||
var button = goog.dom.createDom('button');
|
||||
button.setAttribute('text', Blockly.Msg.NEW_VARIABLE);
|
||||
@@ -111,12 +118,28 @@ Blockly.Variables.flyoutCategory = function(workspace) {
|
||||
|
||||
xmlList.push(button);
|
||||
|
||||
if (variableList.length > 0) {
|
||||
var blockList = Blockly.Variables.flyoutCategoryBlocks(workspace);
|
||||
xmlList = xmlList.concat(blockList);
|
||||
return xmlList;
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct the blocks required by the flyout for the variable category.
|
||||
* @param {!Blockly.Workspace} workspace The workspace contianing variables.
|
||||
* @return {!Array.<!Element>} Array of XML block elements.
|
||||
*/
|
||||
Blockly.Variables.flyoutCategoryBlocks = function(workspace) {
|
||||
var variableModelList = workspace.getVariablesOfType('');
|
||||
variableModelList.sort(Blockly.VariableModel.compareByName);
|
||||
|
||||
var xmlList = [];
|
||||
if (variableModelList.length > 0) {
|
||||
var firstVariable = variableModelList[0];
|
||||
if (Blockly.Blocks['variables_set']) {
|
||||
var gap = Blockly.Blocks['math_change'] ? 8 : 24;
|
||||
var blockText = '<xml>' +
|
||||
'<block type="variables_set" gap="' + gap + '">' +
|
||||
'<field name="VAR">' + variableList[0] + '</field>' +
|
||||
Blockly.Variables.generateVariableFieldXml_(firstVariable) +
|
||||
'</block>' +
|
||||
'</xml>';
|
||||
var block = Blockly.Xml.textToDom(blockText).firstChild;
|
||||
@@ -126,7 +149,7 @@ Blockly.Variables.flyoutCategory = function(workspace) {
|
||||
var gap = Blockly.Blocks['variables_get'] ? 20 : 8;
|
||||
var blockText = '<xml>' +
|
||||
'<block type="math_change" gap="' + gap + '">' +
|
||||
'<field name="VAR">' + variableList[0] + '</field>' +
|
||||
Blockly.Variables.generateVariableFieldXml_(firstVariable) +
|
||||
'<value name="DELTA">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM">1</field>' +
|
||||
@@ -138,11 +161,11 @@ Blockly.Variables.flyoutCategory = function(workspace) {
|
||||
xmlList.push(block);
|
||||
}
|
||||
|
||||
for (var i = 0; i < variableList.length; i++) {
|
||||
for (var i = 0, variable; variable = variableModelList[i]; i++) {
|
||||
if (Blockly.Blocks['variables_get']) {
|
||||
var blockText = '<xml>' +
|
||||
'<block type="variables_get" gap="8">' +
|
||||
'<field name="VAR">' + variableList[i] + '</field>' +
|
||||
Blockly.Variables.generateVariableFieldXml_(variable) +
|
||||
'</block>' +
|
||||
'</xml>';
|
||||
var block = Blockly.Xml.textToDom(blockText).firstChild;
|
||||
@@ -162,7 +185,7 @@ Blockly.Variables.flyoutCategory = function(workspace) {
|
||||
* @return {string} New variable name.
|
||||
*/
|
||||
Blockly.Variables.generateUniqueName = function(workspace) {
|
||||
var variableList = workspace.variableList;
|
||||
var variableList = workspace.getAllVariables();
|
||||
var newName = '';
|
||||
if (variableList.length) {
|
||||
var nameSuffix = 1;
|
||||
@@ -172,7 +195,7 @@ Blockly.Variables.generateUniqueName = function(workspace) {
|
||||
while (!newName) {
|
||||
var inUse = false;
|
||||
for (var i = 0; i < variableList.length; i++) {
|
||||
if (variableList[i].toLowerCase() == potName) {
|
||||
if (variableList[i].name.toLowerCase() == potName) {
|
||||
// This potential name is already used.
|
||||
inUse = true;
|
||||
break;
|
||||
@@ -209,20 +232,31 @@ Blockly.Variables.generateUniqueName = function(workspace) {
|
||||
* @param {function(?string=)=} opt_callback A callback. It will
|
||||
* be passed an acceptable new variable name, or null if change is to be
|
||||
* aborted (cancel button), or undefined if an existing variable was chosen.
|
||||
* @param {?string} opt_type The type of the variable like 'int', 'string', or
|
||||
* ''. This will default to '', which is a specific type.
|
||||
*/
|
||||
Blockly.Variables.createVariable = function(workspace, opt_callback) {
|
||||
Blockly.Variables.createVariable = function(workspace, opt_callback, opt_type) {
|
||||
// This function needs to be named so it can be called recursively.
|
||||
var promptAndCheckWithAlert = function(defaultName) {
|
||||
Blockly.Variables.promptName(Blockly.Msg.NEW_VARIABLE_TITLE, defaultName,
|
||||
function(text) {
|
||||
if (text) {
|
||||
if (workspace.variableIndexOf(text) != -1) {
|
||||
if (workspace.getVariable(text)) {
|
||||
Blockly.alert(Blockly.Msg.VARIABLE_ALREADY_EXISTS.replace('%1',
|
||||
text.toLowerCase()),
|
||||
function() {
|
||||
promptAndCheckWithAlert(text); // Recurse
|
||||
});
|
||||
} else {
|
||||
workspace.createVariable(text);
|
||||
}
|
||||
else if (Blockly.Procedures.isNameUsed(text, workspace)) {
|
||||
Blockly.alert(Blockly.Msg.PROCEDURE_ALREADY_EXISTS.replace('%1',
|
||||
text.toLowerCase()),
|
||||
function() {
|
||||
promptAndCheckWithAlert(text); // Recurse
|
||||
});
|
||||
}
|
||||
else {
|
||||
workspace.createVariable(text, opt_type);
|
||||
if (opt_callback) {
|
||||
opt_callback(text);
|
||||
}
|
||||
@@ -238,6 +272,55 @@ Blockly.Variables.createVariable = function(workspace, opt_callback) {
|
||||
promptAndCheckWithAlert('');
|
||||
};
|
||||
|
||||
/**
|
||||
* Rename a variable with the given workspace, variableType, and oldName.
|
||||
* @param {!Blockly.Workspace} workspace The workspace on which to rename the
|
||||
* variable.
|
||||
* @param {?Blockly.VariableModel} variable Variable to rename.
|
||||
* @param {function(?string=)=} opt_callback A callback. It will
|
||||
* be passed an acceptable new variable name, or null if change is to be
|
||||
* aborted (cancel button), or undefined if an existing variable was chosen.
|
||||
*/
|
||||
Blockly.Variables.renameVariable = function(workspace, variable,
|
||||
opt_callback) {
|
||||
// This function needs to be named so it can be called recursively.
|
||||
var promptAndCheckWithAlert = function(defaultName) {
|
||||
Blockly.Variables.promptName(
|
||||
Blockly.Msg.RENAME_VARIABLE_TITLE.replace('%1', variable.name), defaultName,
|
||||
function(newName) {
|
||||
if (newName) {
|
||||
var newVariable = workspace.getVariable(newName);
|
||||
if (newVariable && newVariable.type != variable.type) {
|
||||
Blockly.alert(Blockly.Msg.VARIABLE_ALREADY_EXISTS_FOR_ANOTHER_TYPE.replace('%1',
|
||||
newName.toLowerCase()).replace('%2', newVariable.type),
|
||||
function() {
|
||||
promptAndCheckWithAlert(newName); // Recurse
|
||||
});
|
||||
}
|
||||
else if (Blockly.Procedures.isNameUsed(newName, workspace)) {
|
||||
Blockly.alert(Blockly.Msg.PROCEDURE_ALREADY_EXISTS.replace('%1',
|
||||
newName.toLowerCase()),
|
||||
function() {
|
||||
promptAndCheckWithAlert(newName); // Recurse
|
||||
});
|
||||
}
|
||||
else {
|
||||
workspace.renameVariable(variable.name, newName);
|
||||
if (opt_callback) {
|
||||
opt_callback(newName);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// User canceled prompt without a value.
|
||||
if (opt_callback) {
|
||||
opt_callback(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
promptAndCheckWithAlert('');
|
||||
};
|
||||
|
||||
/**
|
||||
* Prompt the user for a new variable name.
|
||||
* @param {string} promptText The string of the prompt.
|
||||
@@ -260,3 +343,18 @@ Blockly.Variables.promptName = function(promptText, defaultText, callback) {
|
||||
callback(newVar);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate XML string for variable field.
|
||||
* @param {!Blockly.VariableModel} variableModel The variable model to generate
|
||||
* an XML string from.
|
||||
* @return {string} The generated XML.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Variables.generateVariableFieldXml_ = function(variableModel) {
|
||||
var xmlString = '<field name="VAR" ' + 'variableType="' +
|
||||
variableModel.type + '" id="' + variableModel.getId() + '">'+
|
||||
variableModel.name +
|
||||
'</field>';
|
||||
return xmlString;
|
||||
};
|
||||
|
||||
+192
-86
@@ -26,6 +26,7 @@
|
||||
|
||||
goog.provide('Blockly.Workspace');
|
||||
|
||||
goog.require('Blockly.VariableMap');
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.math');
|
||||
|
||||
@@ -74,12 +75,15 @@ Blockly.Workspace = function(opt_options) {
|
||||
* @private
|
||||
*/
|
||||
this.blockDB_ = Object.create(null);
|
||||
/*
|
||||
* @type {!Array.<string>}
|
||||
* A list of all of the named variables in the workspace, including variables
|
||||
|
||||
/**
|
||||
* @type {!Blockly.VariableMap}
|
||||
* A map from variable type to list of variable names. The lists contain all
|
||||
* of the named variables in the workspace, including variables
|
||||
* that are not currently in use.
|
||||
* @private
|
||||
*/
|
||||
this.variableList = [];
|
||||
this.variableMap_ = new Blockly.VariableMap(this);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -115,19 +119,20 @@ Blockly.Workspace.SCAN_ANGLE = 3;
|
||||
|
||||
/**
|
||||
* Add a block to the list of top blocks.
|
||||
* @param {!Blockly.Block} block Block to remove.
|
||||
* @param {!Blockly.Block} block Block to add.
|
||||
*/
|
||||
Blockly.Workspace.prototype.addTopBlock = function(block) {
|
||||
this.topBlocks_.push(block);
|
||||
if (this.isFlyout) {
|
||||
// This is for the (unlikely) case where you have a variable in a block in
|
||||
// an always-open flyout. It needs to be possible to edit the block in the
|
||||
// flyout, so the contents of the dropdown need to be correct.
|
||||
var variables = Blockly.Variables.allUsedVariables(block);
|
||||
for (var i = 0; i < variables.length; i++) {
|
||||
if (this.variableList.indexOf(variables[i]) == -1) {
|
||||
this.variableList.push(variables[i]);
|
||||
}
|
||||
if (!this.isFlyout) {
|
||||
return;
|
||||
}
|
||||
// This is for the (unlikely) case where you have a variable in a block in
|
||||
// an always-open flyout. It needs to be possible to edit the block in the
|
||||
// flyout, so the contents of the dropdown need to be correct.
|
||||
var variableNames = Blockly.Variables.allUsedVariables(block);
|
||||
for (var i = 0, name; name = variableNames[i]; i++) {
|
||||
if (!this.getVariable(name)) {
|
||||
this.createVariable(name);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -191,85 +196,117 @@ Blockly.Workspace.prototype.clear = function() {
|
||||
if (!existingGroup) {
|
||||
Blockly.Events.setGroup(false);
|
||||
}
|
||||
|
||||
this.variableList.length = 0;
|
||||
this.variableMap_.clear();
|
||||
};
|
||||
|
||||
/**
|
||||
* Walk the workspace and update the list of variables to only contain ones in
|
||||
* Walk the workspace and update the map of variables to only contain ones in
|
||||
* use on the workspace. Use when loading new workspaces from disk.
|
||||
* @param {boolean} clearList True if the old variable list should be cleared.
|
||||
* @param {boolean} clear True if the old variable map should be cleared.
|
||||
*/
|
||||
Blockly.Workspace.prototype.updateVariableList = function(clearList) {
|
||||
Blockly.Workspace.prototype.updateVariableStore = function(clear) {
|
||||
// TODO: Sort
|
||||
if (!this.isFlyout) {
|
||||
// Update the list in place so that the flyout's references stay correct.
|
||||
if (clearList) {
|
||||
this.variableList.length = 0;
|
||||
if (this.isFlyout) {
|
||||
return;
|
||||
}
|
||||
var variableNames = Blockly.Variables.allUsedVariables(this);
|
||||
var varList = [];
|
||||
for (var i = 0, name; name = variableNames[i]; i++) {
|
||||
// Get variable model with the used variable name.
|
||||
var tempVar = this.getVariable(name);
|
||||
if (tempVar) {
|
||||
varList.push({'name': tempVar.name, 'type': tempVar.type,
|
||||
'id': tempVar.getId()});
|
||||
}
|
||||
var allVariables = Blockly.Variables.allUsedVariables(this);
|
||||
for (var i = 0; i < allVariables.length; i++) {
|
||||
this.createVariable(allVariables[i]);
|
||||
else {
|
||||
varList.push({'name': name, 'type': null, 'id': null});
|
||||
// TODO(marisaleung): Use variable.type and variable.getId() once variable
|
||||
// instances are storing more than just name.
|
||||
}
|
||||
}
|
||||
if (clear) {
|
||||
this.variableMap_.clear();
|
||||
}
|
||||
// Update the list in place so that the flyout's references stay correct.
|
||||
for (var i = 0, varDict; varDict = varList[i]; i++) {
|
||||
if (!this.getVariable(varDict.name)) {
|
||||
this.createVariable(varDict.name, varDict.type, varDict.id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Rename a variable by updating its name in the variable list.
|
||||
* TODO: #468
|
||||
* @param {string} oldName Variable to rename.
|
||||
* Rename a variable by updating its name in the variable map. Identify the
|
||||
* variable to rename with the given variable.
|
||||
* @param {?Blockly.VariableModel} variable Variable to rename.
|
||||
* @param {string} newName New variable name.
|
||||
*/
|
||||
Blockly.Workspace.prototype.renameVariable = function(oldName, newName) {
|
||||
// Find the old name in the list.
|
||||
var variableIndex = this.variableIndexOf(oldName);
|
||||
var newVariableIndex = this.variableIndexOf(newName);
|
||||
Blockly.Workspace.prototype.renameVariableInternal_ = function(variable, newName) {
|
||||
var newVariable = this.getVariable(newName);
|
||||
var oldCase;
|
||||
|
||||
// We might be renaming to an existing name but with different case. If so,
|
||||
// we will also update all of the blocks using the new name to have the
|
||||
// correct case.
|
||||
if (newVariableIndex != -1 &&
|
||||
this.variableList[newVariableIndex] != newName) {
|
||||
var oldCase = this.variableList[newVariableIndex];
|
||||
// If they are different types, throw an error.
|
||||
if (variable && newVariable && variable.type != newVariable.type) {
|
||||
throw Error('Variable "' + variable.name + '" is type "' + variable.type +
|
||||
'" and variable "' + newName + '" is type "' + newVariable.type +
|
||||
'". Both must be the same type.');
|
||||
}
|
||||
|
||||
// Find if newVariable case is different.
|
||||
if (newVariable && newVariable.name != newName) {
|
||||
oldCase = newVariable.name;
|
||||
}
|
||||
|
||||
Blockly.Events.setGroup(true);
|
||||
var blocks = this.getAllBlocks();
|
||||
// Iterate through every block.
|
||||
// Iterate through every block and update name.
|
||||
for (var i = 0; i < blocks.length; i++) {
|
||||
blocks[i].renameVar(oldName, newName);
|
||||
blocks[i].renameVar(variable.name, newName);
|
||||
if (oldCase) {
|
||||
blocks[i].renameVar(oldCase, newName);
|
||||
}
|
||||
}
|
||||
this.variableMap_.renameVariable(variable, newName);
|
||||
Blockly.Events.setGroup(false);
|
||||
};
|
||||
|
||||
|
||||
if (variableIndex == newVariableIndex ||
|
||||
variableIndex != -1 && newVariableIndex == -1) {
|
||||
// Only changing case, or renaming to a completely novel name.
|
||||
this.variableList[variableIndex] = newName;
|
||||
} else if (variableIndex != -1 && newVariableIndex != -1) {
|
||||
// Renaming one existing variable to another existing variable.
|
||||
// The case might have changed, so we update the destination ID.
|
||||
this.variableList[newVariableIndex] = newName;
|
||||
this.variableList.splice(variableIndex, 1);
|
||||
} else {
|
||||
this.variableList.push(newName);
|
||||
console.log('Tried to rename an non-existent variable.');
|
||||
}
|
||||
/**
|
||||
* Rename a variable by updating its name in the variable map. Identify the
|
||||
* variable to rename with the given name.
|
||||
* @param {string} oldName Variable to rename.
|
||||
* @param {string} newName New variable name.
|
||||
*/
|
||||
Blockly.Workspace.prototype.renameVariable = function(oldName, newName) {
|
||||
// Warning: Prefer to use renameVariableById.
|
||||
var variable = this.getVariable(oldName);
|
||||
this.renameVariableInternal_(variable, newName);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a variable with the given name.
|
||||
* TODO: #468
|
||||
* @param {string} name The new variable's name.
|
||||
* Rename a variable by updating its name in the variable map. Identify the
|
||||
* variable to rename with the given id.
|
||||
* @param {string} id Id of the variable to rename.
|
||||
* @param {string} newName New variable name.
|
||||
*/
|
||||
Blockly.Workspace.prototype.createVariable = function(name) {
|
||||
var index = this.variableIndexOf(name);
|
||||
if (index == -1) {
|
||||
this.variableList.push(name);
|
||||
}
|
||||
Blockly.Workspace.prototype.renameVariableById = function(id, newName) {
|
||||
var variable = this.getVariableById(id);
|
||||
this.renameVariableInternal_(variable, newName);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a variable with a given name, optional type, and optional id.
|
||||
* @param {!string} name The name of the variable. This must be unique across
|
||||
* variables and procedures.
|
||||
* @param {?string} opt_type The type of the variable like 'int' or 'string'.
|
||||
* Does not need to be unique. Field_variable can filter variables based on
|
||||
* their type. This will default to '' which is a specific type.
|
||||
* @param {?string} opt_id The unique id of the variable. This will default to
|
||||
* a UUID.
|
||||
* @return {?Blockly.VariableModel} The newly created variable.
|
||||
*/
|
||||
Blockly.Workspace.prototype.createVariable = function(name, opt_type, opt_id) {
|
||||
return this.variableMap_.createVariable(name, opt_type, opt_id);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -287,7 +324,7 @@ Blockly.Workspace.prototype.getVariableUses = function(name) {
|
||||
for (var j = 0; j < blockVariables.length; j++) {
|
||||
var varName = blockVariables[j];
|
||||
// Variable name may be null if the block is only half-built.
|
||||
if (varName && Blockly.Names.equals(varName, name)) {
|
||||
if (varName && name && Blockly.Names.equals(varName, name)) {
|
||||
uses.push(blocks[i]);
|
||||
}
|
||||
}
|
||||
@@ -297,14 +334,11 @@ Blockly.Workspace.prototype.getVariableUses = function(name) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a variables and all of its uses from this workspace.
|
||||
* Delete a variable by the passed in name and all of its uses from this
|
||||
* workspace. May prompt the user for confirmation.
|
||||
* @param {string} name Name of variable to delete.
|
||||
*/
|
||||
Blockly.Workspace.prototype.deleteVariable = function(name) {
|
||||
var variableIndex = this.variableIndexOf(name);
|
||||
if (variableIndex == -1) {
|
||||
return;
|
||||
}
|
||||
// Check whether this variable is a function parameter before deleting.
|
||||
var uses = this.getVariableUses(name);
|
||||
for (var i = 0, block; block = uses[i]; i++) {
|
||||
@@ -320,14 +354,7 @@ Blockly.Workspace.prototype.deleteVariable = function(name) {
|
||||
}
|
||||
|
||||
var workspace = this;
|
||||
function doDeletion() {
|
||||
Blockly.Events.setGroup(true);
|
||||
for (var i = 0; i < uses.length; i++) {
|
||||
uses[i].dispose(true, false);
|
||||
}
|
||||
Blockly.Events.setGroup(false);
|
||||
workspace.variableList.splice(variableIndex, 1);
|
||||
}
|
||||
var variable = workspace.getVariable(name);
|
||||
if (uses.length > 1) {
|
||||
// Confirm before deleting multiple blocks.
|
||||
Blockly.confirm(
|
||||
@@ -335,31 +362,79 @@ Blockly.Workspace.prototype.deleteVariable = function(name) {
|
||||
replace('%2', name),
|
||||
function(ok) {
|
||||
if (ok) {
|
||||
doDeletion();
|
||||
workspace.deleteVariableInternal_(variable);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// No confirmation necessary for a single block.
|
||||
doDeletion();
|
||||
this.deleteVariableInternal_(variable);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a variables by the passed in id and all of its uses from this
|
||||
* workspace. May prompt the user for confirmation.
|
||||
* @param {string} id Id of variable to delete.
|
||||
*/
|
||||
Blockly.Workspace.prototype.deleteVariableById = function(id) {
|
||||
var variable = this.getVariableById(id);
|
||||
if (variable) {
|
||||
this.deleteVariableInternal_(variable);
|
||||
} else {
|
||||
console.warn("Can't delete non-existant variable: " + id);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Deletes a variable and all of its uses from this workspace without asking the
|
||||
* user for confirmation.
|
||||
* @param {Blockly.VariableModel} variable Variable to delete.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Workspace.prototype.deleteVariableInternal_ = function(variable) {
|
||||
var uses = this.getVariableUses(variable.name);
|
||||
Blockly.Events.setGroup(true);
|
||||
for (var i = 0; i < uses.length; i++) {
|
||||
uses[i].dispose(true, false);
|
||||
}
|
||||
this.variableMap_.deleteVariable(variable);
|
||||
Blockly.Events.setGroup(false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check whether a variable exists with the given name. The check is
|
||||
* case-insensitive.
|
||||
* @param {string} name The name to check for.
|
||||
* @return {number} The index of the name in the variable list, or -1 if it is
|
||||
* not present.
|
||||
* @deprecated April 2017
|
||||
*/
|
||||
Blockly.Workspace.prototype.variableIndexOf = function(name) {
|
||||
for (var i = 0, varname; varname = this.variableList[i]; i++) {
|
||||
if (Blockly.Names.equals(varname, name)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
console.warn(
|
||||
'Deprecated call to Blockly.Workspace.prototype.variableIndexOf');
|
||||
return -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the variable by the given name and return it. Return null if it is not
|
||||
* found.
|
||||
* @param {!string} name The name to check for.
|
||||
* @return {?Blockly.VariableModel} the variable with the given name.
|
||||
*/
|
||||
Blockly.Workspace.prototype.getVariable = function(name) {
|
||||
return this.variableMap_.getVariable(name);
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the variable by the given id and return it. Return null if it is not
|
||||
* found.
|
||||
* @param {!string} id The id to check for.
|
||||
* @return {?Blockly.VariableModel} The variable with the given id.
|
||||
*/
|
||||
Blockly.Workspace.prototype.getVariableById = function(id) {
|
||||
return this.variableMap_.getVariableById(id);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the horizontal offset of the workspace.
|
||||
* Intended for LTR/RTL compatibility in XML.
|
||||
@@ -417,10 +492,14 @@ Blockly.Workspace.prototype.undo = function(redo) {
|
||||
}
|
||||
events = Blockly.Events.filter(events, redo);
|
||||
Blockly.Events.recordUndo = false;
|
||||
for (var i = 0, event; event = events[i]; i++) {
|
||||
event.run(redo);
|
||||
try {
|
||||
for (var i = 0, event; event = events[i]; i++) {
|
||||
event.run(redo);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Blockly.Events.recordUndo = true;
|
||||
}
|
||||
Blockly.Events.recordUndo = true;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -495,6 +574,33 @@ Blockly.Workspace.prototype.allInputsFilled = function(opt_shadowBlocksAreFilled
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the variable with the specified type. If type is null, return list of
|
||||
* variables with empty string type.
|
||||
* @param {?string} type Type of the variables to find.
|
||||
* @return {Array.<Blockly.VariableModel>} The sought after variables of the
|
||||
* passed in type. An empty array if none are found.
|
||||
*/
|
||||
Blockly.Workspace.prototype.getVariablesOfType = function(type) {
|
||||
return this.variableMap_.getVariablesOfType(type);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return all variable types.
|
||||
* @return {!Array.<string>} List of variable types.
|
||||
*/
|
||||
Blockly.Workspace.prototype.getVariableTypes = function() {
|
||||
return this.variableMap_.getVariableTypes();
|
||||
};
|
||||
|
||||
/**
|
||||
* Return all variables of all types.
|
||||
* @return {!Array.<Blockly.VariableModel>} List of variable models.
|
||||
*/
|
||||
Blockly.Workspace.prototype.getAllVariables = function() {
|
||||
return this.variableMap_.getAllVariables();
|
||||
};
|
||||
|
||||
/**
|
||||
* Database of all workspaces.
|
||||
* @private
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2017 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Object in charge of loading, storing, and playing audio for a
|
||||
* workspace.
|
||||
* @author fenichel@google.com (Rachel Fenichel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.WorkspaceAudio');
|
||||
|
||||
/**
|
||||
* Class for loading, storing, and playing audio for a workspace.
|
||||
* @param {Blockly.WorkspaceSvg} parentWorkspace The parent of the workspace
|
||||
* this audio object belongs to, or null.
|
||||
*/
|
||||
Blockly.WorkspaceAudio = function(parentWorkspace) {
|
||||
|
||||
/**
|
||||
* The parent of the workspace this object belongs to, or null. May be
|
||||
* checked for sounds that this object can't find.
|
||||
* @type {Blockly.WorkspaceSvg}
|
||||
* @private
|
||||
*/
|
||||
this.parentWorkspace_ = parentWorkspace;
|
||||
|
||||
/**
|
||||
* Database of pre-loaded sounds.
|
||||
* @private
|
||||
* @const
|
||||
*/
|
||||
this.SOUNDS_ = Object.create(null);
|
||||
};
|
||||
|
||||
/**
|
||||
* Time that the last sound was played.
|
||||
* @type {Date}
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceAudio.prototype.lastSound_ = null;
|
||||
|
||||
/**
|
||||
* Dispose of this audio manager.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceAudio.prototype.dispose = function() {
|
||||
this.parentWorkspace_ = null;
|
||||
this.SOUNDS_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Load an audio file. Cache it, ready for instantaneous playing.
|
||||
* @param {!Array.<string>} filenames List of file types in decreasing order of
|
||||
* preference (i.e. increasing size). E.g. ['media/go.mp3', 'media/go.wav']
|
||||
* Filenames include path from Blockly's root. File extensions matter.
|
||||
* @param {string} name Name of sound.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceAudio.prototype.load = function(filenames, name) {
|
||||
if (!filenames.length) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
var audioTest = new window['Audio']();
|
||||
} catch (e) {
|
||||
// No browser support for Audio.
|
||||
// IE can throw an error even if the Audio object exists.
|
||||
return;
|
||||
}
|
||||
var sound;
|
||||
for (var i = 0; i < filenames.length; i++) {
|
||||
var filename = filenames[i];
|
||||
var ext = filename.match(/\.(\w+)$/);
|
||||
if (ext && audioTest.canPlayType('audio/' + ext[1])) {
|
||||
// Found an audio format we can play.
|
||||
sound = new window['Audio'](filename);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (sound && sound.play) {
|
||||
this.SOUNDS_[name] = sound;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Preload all the audio files so that they play quickly when asked for.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceAudio.prototype.preload = function() {
|
||||
for (var name in this.SOUNDS_) {
|
||||
var sound = this.SOUNDS_[name];
|
||||
sound.volume = .01;
|
||||
sound.play();
|
||||
sound.pause();
|
||||
// iOS can only process one sound at a time. Trying to load more than one
|
||||
// corrupts the earlier ones. Just load one and leave the others uncached.
|
||||
if (goog.userAgent.IPAD || goog.userAgent.IPHONE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Play a named sound at specified volume. If volume is not specified,
|
||||
* use full volume (1).
|
||||
* @param {string} name Name of sound.
|
||||
* @param {number=} opt_volume Volume of sound (0-1).
|
||||
*/
|
||||
Blockly.WorkspaceAudio.prototype.play = function(name, opt_volume) {
|
||||
var sound = this.SOUNDS_[name];
|
||||
if (sound) {
|
||||
// Don't play one sound on top of another.
|
||||
var now = new Date;
|
||||
if (this.lastSound_ != null &&
|
||||
now - this.lastSound_ < Blockly.SOUND_LIMIT) {
|
||||
return;
|
||||
}
|
||||
this.lastSound_ = now;
|
||||
var mySound;
|
||||
var ie9 = goog.userAgent.DOCUMENT_MODE &&
|
||||
goog.userAgent.DOCUMENT_MODE === 9;
|
||||
if (ie9 || goog.userAgent.IPAD || goog.userAgent.ANDROID) {
|
||||
// Creating a new audio node causes lag in IE9, Android and iPad. Android
|
||||
// and IE9 refetch the file from the server, iPad uses a singleton audio
|
||||
// node which must be deleted and recreated for each new audio tag.
|
||||
mySound = sound;
|
||||
} else {
|
||||
mySound = sound.cloneNode();
|
||||
}
|
||||
mySound.volume = (opt_volume === undefined ? 1 : opt_volume);
|
||||
mySound.play();
|
||||
} else if (this.parentWorkspace_) {
|
||||
// Maybe a workspace on a lower level knows about this sound.
|
||||
this.parentWorkspace_.getAudioManager().play(name, opt_volume);
|
||||
}
|
||||
};
|
||||
@@ -43,17 +43,17 @@ goog.require('goog.math.Coordinate');
|
||||
* @param {!Element} container Containing element.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.workspaceDragSurfaceSvg = function(container) {
|
||||
Blockly.WorkspaceDragSurfaceSvg = function(container) {
|
||||
this.container_ = container;
|
||||
this.createDom();
|
||||
};
|
||||
|
||||
/**
|
||||
* The SVG drag surface. Set once by Blockly.workspaceDragSurfaceSvg.createDom.
|
||||
* The SVG drag surface. Set once by Blockly.WorkspaceDragSurfaceSvg.createDom.
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
Blockly.workspaceDragSurfaceSvg.prototype.SVG_ = null;
|
||||
Blockly.WorkspaceDragSurfaceSvg.prototype.SVG_ = null;
|
||||
|
||||
/**
|
||||
* SVG group inside the drag surface that holds blocks while a drag is in
|
||||
@@ -63,19 +63,19 @@ Blockly.workspaceDragSurfaceSvg.prototype.SVG_ = null;
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
Blockly.workspaceDragSurfaceSvg.prototype.dragGroup_ = null;
|
||||
Blockly.WorkspaceDragSurfaceSvg.prototype.dragGroup_ = null;
|
||||
|
||||
/**
|
||||
* Containing HTML element; parent of the workspace and the drag surface.
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
Blockly.workspaceDragSurfaceSvg.prototype.container_ = null;
|
||||
Blockly.WorkspaceDragSurfaceSvg.prototype.container_ = null;
|
||||
|
||||
/**
|
||||
* Create the drag surface and inject it into the container.
|
||||
*/
|
||||
Blockly.workspaceDragSurfaceSvg.prototype.createDom = function() {
|
||||
Blockly.WorkspaceDragSurfaceSvg.prototype.createDom = function() {
|
||||
if (this.SVG_) {
|
||||
return; // Already created.
|
||||
}
|
||||
@@ -107,7 +107,7 @@ Blockly.workspaceDragSurfaceSvg.prototype.createDom = function() {
|
||||
* @param {number} y Y translation for the entire surface
|
||||
* @package
|
||||
*/
|
||||
Blockly.workspaceDragSurfaceSvg.prototype.translateSurface = function(x, y) {
|
||||
Blockly.WorkspaceDragSurfaceSvg.prototype.translateSurface = function(x, y) {
|
||||
// This is a work-around to prevent a the blocks from rendering
|
||||
// fuzzy while they are being moved on the drag surface.
|
||||
x = x.toFixed(0);
|
||||
@@ -124,7 +124,7 @@ Blockly.workspaceDragSurfaceSvg.prototype.translateSurface = function(x, y) {
|
||||
* @return {!goog.math.Coordinate} Current translation of the surface
|
||||
* @package
|
||||
*/
|
||||
Blockly.workspaceDragSurfaceSvg.prototype.getSurfaceTranslation = function() {
|
||||
Blockly.WorkspaceDragSurfaceSvg.prototype.getSurfaceTranslation = function() {
|
||||
return Blockly.utils.getRelativeXY(this.SVG_);
|
||||
};
|
||||
|
||||
@@ -135,7 +135,7 @@ Blockly.workspaceDragSurfaceSvg.prototype.getSurfaceTranslation = function() {
|
||||
* into.
|
||||
* @package
|
||||
*/
|
||||
Blockly.workspaceDragSurfaceSvg.prototype.clearAndHide = function(newSurface) {
|
||||
Blockly.WorkspaceDragSurfaceSvg.prototype.clearAndHide = function(newSurface) {
|
||||
var blockCanvas = this.SVG_.childNodes[0];
|
||||
var bubbleCanvas = this.SVG_.childNodes[1];
|
||||
if (!blockCanvas || !bubbleCanvas ||
|
||||
@@ -174,7 +174,7 @@ Blockly.workspaceDragSurfaceSvg.prototype.clearAndHide = function(newSurface) {
|
||||
* @param {number} scale The scale of the workspace being dragged.
|
||||
* @package
|
||||
*/
|
||||
Blockly.workspaceDragSurfaceSvg.prototype.setContentsAndShow = function(
|
||||
Blockly.WorkspaceDragSurfaceSvg.prototype.setContentsAndShow = function(
|
||||
blockCanvas, bubbleCanvas, previousSibling, width, height, scale) {
|
||||
goog.asserts.assert(this.SVG_.childNodes.length == 0,
|
||||
'Already dragging a block.');
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2017 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Methods for dragging a workspace visually.
|
||||
* @author fenichel@google.com (Rachel Fenichel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.WorkspaceDragger');
|
||||
|
||||
goog.require('goog.math.Coordinate');
|
||||
goog.require('goog.asserts');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a workspace dragger. It moves the workspace around when it is
|
||||
* being dragged by a mouse or touch.
|
||||
* Note that the workspace itself manages whether or not it has a drag surface
|
||||
* and how to do translations based on that. This simply passes the right
|
||||
* commands based on events.
|
||||
* @param {!Blockly.WorkspaceSvg} workspace The workspace to drag.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.WorkspaceDragger = function(workspace) {
|
||||
/**
|
||||
* @type {!Blockly.WorkspaceSvg}
|
||||
* @private
|
||||
*/
|
||||
this.workspace_ = workspace;
|
||||
|
||||
/**
|
||||
* The workspace's metrics object at the beginning of the drag. Contains size
|
||||
* and position metrics of a workspace.
|
||||
* Coordinate system: pixel coordinates.
|
||||
* @type {!Object}
|
||||
* @private
|
||||
*/
|
||||
this.startDragMetrics_ = workspace.getMetrics();
|
||||
|
||||
/**
|
||||
* The scroll position of the workspace at the beginning of the drag.
|
||||
* Coordinate system: pixel coordinates.
|
||||
* @type {!goog.math.Coordinate}
|
||||
* @private
|
||||
*/
|
||||
this.startScrollXY_ = new goog.math.Coordinate(workspace.scrollX,
|
||||
workspace.scrollY);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sever all links from this object.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceDragger.prototype.dispose = function() {
|
||||
this.workspace_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Start dragging the workspace.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceDragger.prototype.startDrag = function() {
|
||||
if (Blockly.selected) {
|
||||
Blockly.selected.unselect();
|
||||
}
|
||||
this.workspace_.setupDragSurface();
|
||||
};
|
||||
|
||||
/**
|
||||
* Finish dragging the workspace and put everything back where it belongs.
|
||||
* @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has
|
||||
* moved from the position at the start of the drag, in pixel coordinates.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceDragger.prototype.endDrag = function(currentDragDeltaXY) {
|
||||
// Make sure everything is up to date.
|
||||
this.drag(currentDragDeltaXY);
|
||||
this.workspace_.resetDragSurface();
|
||||
};
|
||||
|
||||
/**
|
||||
* Move the workspace based on the most recent mouse movements.
|
||||
* @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has
|
||||
* moved from the position at the start of the drag, in pixel coordinates.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceDragger.prototype.drag = function(currentDragDeltaXY) {
|
||||
var metrics = this.startDragMetrics_;
|
||||
var newXY = goog.math.Coordinate.sum(this.startScrollXY_, currentDragDeltaXY);
|
||||
|
||||
// Bound the new XY based on workspace bounds.
|
||||
var x = Math.min(newXY.x, -metrics.contentLeft);
|
||||
var y = Math.min(newXY.y, -metrics.contentTop);
|
||||
x = Math.max(x, metrics.viewWidth - metrics.contentLeft -
|
||||
metrics.contentWidth);
|
||||
y = Math.max(y, metrics.viewHeight - metrics.contentTop -
|
||||
metrics.contentHeight);
|
||||
|
||||
x = -x - metrics.contentLeft;
|
||||
y = -y - metrics.contentTop;
|
||||
|
||||
this.updateScroll_(x, y);
|
||||
};
|
||||
|
||||
/**
|
||||
* Move the scrollbars to drag the workspace.
|
||||
* x and y are in pixels.
|
||||
* @param {number} x The new x position to move the scrollbar to.
|
||||
* @param {number} y The new y position to move the scrollbar to.
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceDragger.prototype.updateScroll_ = function(x, y) {
|
||||
this.workspace_.scrollbar.set(x, y);
|
||||
};
|
||||
+246
-238
@@ -30,11 +30,14 @@ goog.provide('Blockly.WorkspaceSvg');
|
||||
//goog.require('Blockly.BlockSvg');
|
||||
goog.require('Blockly.ConnectionDB');
|
||||
goog.require('Blockly.constants');
|
||||
goog.require('Blockly.Gesture');
|
||||
goog.require('Blockly.Grid');
|
||||
goog.require('Blockly.Options');
|
||||
goog.require('Blockly.ScrollbarPair');
|
||||
goog.require('Blockly.Touch');
|
||||
goog.require('Blockly.Trashcan');
|
||||
goog.require('Blockly.Workspace');
|
||||
goog.require('Blockly.WorkspaceAudio');
|
||||
goog.require('Blockly.WorkspaceDragSurfaceSvg');
|
||||
goog.require('Blockly.Xml');
|
||||
goog.require('Blockly.ZoomControls');
|
||||
@@ -51,7 +54,7 @@ goog.require('goog.userAgent');
|
||||
* @param {!Blockly.Options} options Dictionary of options.
|
||||
* @param {Blockly.BlockDragSurfaceSvg=} opt_blockDragSurface Drag surface for
|
||||
* blocks.
|
||||
* @param {Blockly.workspaceDragSurfaceSvg=} opt_wsDragSurface Drag surface for
|
||||
* @param {Blockly.WorkspaceDragSurfaceSvg=} opt_wsDragSurface Drag surface for
|
||||
* the workspace.
|
||||
* @extends {Blockly.Workspace}
|
||||
* @constructor
|
||||
@@ -76,12 +79,6 @@ Blockly.WorkspaceSvg = function(options, opt_blockDragSurface, opt_wsDragSurface
|
||||
this.useWorkspaceDragSurface_ =
|
||||
this.workspaceDragSurface_ && Blockly.utils.is3dSupported();
|
||||
|
||||
/**
|
||||
* Database of pre-loaded sounds.
|
||||
* @private
|
||||
* @const
|
||||
*/
|
||||
this.SOUNDS_ = Object.create(null);
|
||||
/**
|
||||
* List of currently highlighted blocks. Block highlighting is often used to
|
||||
* visually mark blocks currently being executed.
|
||||
@@ -90,6 +87,21 @@ Blockly.WorkspaceSvg = function(options, opt_blockDragSurface, opt_wsDragSurface
|
||||
*/
|
||||
this.highlightedBlocks_ = [];
|
||||
|
||||
/**
|
||||
* Object in charge of loading, storing, and playing audio for a workspace.
|
||||
* @type {Blockly.WorkspaceAudio}
|
||||
* @private
|
||||
*/
|
||||
this.audioManager_ = new Blockly.WorkspaceAudio(options.parentWorkspace);
|
||||
|
||||
/**
|
||||
* This workspace's grid object or null.
|
||||
* @type {Blockly.Grid}
|
||||
* @private
|
||||
*/
|
||||
this.grid_ = this.options.gridPattern ?
|
||||
new Blockly.Grid(options.gridPattern, options.gridOptions) : null;
|
||||
|
||||
this.registerToolboxCategoryCallback(Blockly.VARIABLE_CATEGORY_NAME,
|
||||
Blockly.Variables.flyoutCategory);
|
||||
this.registerToolboxCategoryCallback(Blockly.PROCEDURE_CATEGORY_NAME,
|
||||
@@ -125,15 +137,6 @@ Blockly.WorkspaceSvg.prototype.isFlyout = false;
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.isMutator = false;
|
||||
|
||||
/**
|
||||
* Is this workspace currently being dragged around?
|
||||
* DRAG_NONE - No drag operation.
|
||||
* DRAG_BEGIN - Still inside the initial DRAG_RADIUS.
|
||||
* DRAG_FREE - Workspace has been dragged further than DRAG_RADIUS.
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.dragMode_ = Blockly.DRAG_NONE;
|
||||
|
||||
/**
|
||||
* Whether this workspace has resizes enabled.
|
||||
* Disable during batch operations for a performance improvement.
|
||||
@@ -143,25 +146,25 @@ Blockly.WorkspaceSvg.prototype.dragMode_ = Blockly.DRAG_NONE;
|
||||
Blockly.WorkspaceSvg.prototype.resizesEnabled_ = true;
|
||||
|
||||
/**
|
||||
* Current horizontal scrolling offset.
|
||||
* Current horizontal scrolling offset in pixel units.
|
||||
* @type {number}
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.scrollX = 0;
|
||||
|
||||
/**
|
||||
* Current vertical scrolling offset.
|
||||
* Current vertical scrolling offset in pixel units.
|
||||
* @type {number}
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.scrollY = 0;
|
||||
|
||||
/**
|
||||
* Horizontal scroll value when scrolling started.
|
||||
* Horizontal scroll value when scrolling started in pixel units.
|
||||
* @type {number}
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.startScrollX = 0;
|
||||
|
||||
/**
|
||||
* Vertical scroll value when scrolling started.
|
||||
* Vertical scroll value when scrolling started in pixel units.
|
||||
* @type {number}
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.startScrollY = 0;
|
||||
@@ -191,6 +194,13 @@ Blockly.WorkspaceSvg.prototype.trashcan = null;
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.scrollbar = null;
|
||||
|
||||
/**
|
||||
* The current gesture in progress on this workspace, if any.
|
||||
* @type {Blockly.Gesture}
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.currentGesture_ = null;
|
||||
|
||||
/**
|
||||
* This workspace's surface for dragging blocks, if it exists.
|
||||
* @type {Blockly.BlockDragSurfaceSvg}
|
||||
@@ -223,13 +233,6 @@ Blockly.WorkspaceSvg.prototype.useWorkspaceDragSurface_ = false;
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.isDragSurfaceActive_ = false;
|
||||
|
||||
/**
|
||||
* Time that the last sound was played.
|
||||
* @type {Date}
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.lastSound_ = null;
|
||||
|
||||
/**
|
||||
* Last known position of the page scroll.
|
||||
* This is used to determine whether we have recalculated screen coordinate
|
||||
@@ -312,6 +315,18 @@ Blockly.WorkspaceSvg.prototype.getSvgXY = function(element) {
|
||||
return new goog.math.Coordinate(x, y);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the position of the workspace origin relative to the injection div
|
||||
* origin in pixels.
|
||||
* The workspace origin is where a block would render at position (0, 0).
|
||||
* It is not the upper left corner of the workspace SVG.
|
||||
* @return {!goog.math.Coordinate} Offset in pixels.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.getOriginOffsetInPixels = function() {
|
||||
return Blockly.utils.getInjectionDivXY_(this.svgBlockCanvas_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Save resize handler data so we can delete it later in dispose.
|
||||
* @param {!Array.<!Array>} handler Data that can be passed to unbindEvent_.
|
||||
@@ -338,14 +353,19 @@ Blockly.WorkspaceSvg.prototype.createDom = function(opt_backgroundClass) {
|
||||
*/
|
||||
this.svgGroup_ = Blockly.utils.createSvgElement('g',
|
||||
{'class': 'blocklyWorkspace'}, null);
|
||||
|
||||
// Note that a <g> alone does not receive mouse events--it must have a
|
||||
// valid target inside it. If no background class is specified, as in the
|
||||
// flyout, the workspace will not receive mouse events.
|
||||
if (opt_backgroundClass) {
|
||||
/** @type {SVGElement} */
|
||||
this.svgBackground_ = Blockly.utils.createSvgElement('rect',
|
||||
{'height': '100%', 'width': '100%', 'class': opt_backgroundClass},
|
||||
this.svgGroup_);
|
||||
if (opt_backgroundClass == 'blocklyMainBackground') {
|
||||
|
||||
if (opt_backgroundClass == 'blocklyMainBackground' && this.grid_) {
|
||||
this.svgBackground_.style.fill =
|
||||
'url(#' + this.options.gridPattern.id + ')';
|
||||
'url(#' + this.grid_.getPatternId() + ')';
|
||||
}
|
||||
}
|
||||
/** @type {SVGElement} */
|
||||
@@ -365,9 +385,6 @@ Blockly.WorkspaceSvg.prototype.createDom = function(opt_backgroundClass) {
|
||||
if (!this.isFlyout) {
|
||||
Blockly.bindEventWithChecks_(this.svgGroup_, 'mousedown', this,
|
||||
this.onMouseDown_);
|
||||
var thisWorkspace = this;
|
||||
Blockly.bindEvent_(this.svgGroup_, 'touchstart', null,
|
||||
function(e) {Blockly.longStart_(e, thisWorkspace);});
|
||||
if (this.options.zoomOptions && this.options.zoomOptions.wheel) {
|
||||
// Mouse-wheel.
|
||||
Blockly.bindEventWithChecks_(this.svgGroup_, 'wheel', this,
|
||||
@@ -384,7 +401,9 @@ Blockly.WorkspaceSvg.prototype.createDom = function(opt_backgroundClass) {
|
||||
*/
|
||||
this.toolbox_ = new Blockly.Toolbox(this);
|
||||
}
|
||||
this.updateGridPattern_();
|
||||
if (this.grid_) {
|
||||
this.grid_.update(this.scale);
|
||||
}
|
||||
this.recordDeleteAreas();
|
||||
return this.svgGroup_;
|
||||
};
|
||||
@@ -396,6 +415,9 @@ Blockly.WorkspaceSvg.prototype.createDom = function(opt_backgroundClass) {
|
||||
Blockly.WorkspaceSvg.prototype.dispose = function() {
|
||||
// Stop rerendering.
|
||||
this.rendered = false;
|
||||
if (this.currentGesture_) {
|
||||
this.currentGesture_.cancel();
|
||||
}
|
||||
Blockly.WorkspaceSvg.superClass_.dispose.call(this);
|
||||
if (this.svgGroup_) {
|
||||
goog.dom.removeNode(this.svgGroup_);
|
||||
@@ -424,6 +446,16 @@ Blockly.WorkspaceSvg.prototype.dispose = function() {
|
||||
this.zoomControls_ = null;
|
||||
}
|
||||
|
||||
if (this.audioManager_) {
|
||||
this.audioManager_.dispose();
|
||||
this.audioManager_ = null;
|
||||
}
|
||||
|
||||
if (this.grid_) {
|
||||
this.grid_.dispose();
|
||||
this.grid_ = null;
|
||||
}
|
||||
|
||||
if (this.toolboxCategoryCallbacks_) {
|
||||
this.toolboxCategoryCallbacks_ = null;
|
||||
}
|
||||
@@ -496,8 +528,16 @@ Blockly.WorkspaceSvg.prototype.addFlyout_ = function(tagName) {
|
||||
horizontalLayout: this.horizontalLayout,
|
||||
toolboxPosition: this.options.toolboxPosition
|
||||
};
|
||||
/** @type {Blockly.Flyout} */
|
||||
this.flyout_ = new Blockly.Flyout(workspaceOptions);
|
||||
/**
|
||||
* @type {!Blockly.Flyout}
|
||||
* @private
|
||||
*/
|
||||
this.flyout_ = null;
|
||||
if (this.horizontalLayout) {
|
||||
this.flyout_ = new Blockly.HorizontalFlyout(workspaceOptions);
|
||||
} else {
|
||||
this.flyout_ = new Blockly.VerticalFlyout(workspaceOptions);
|
||||
}
|
||||
this.flyout_.autoClose = false;
|
||||
|
||||
// Return the element so that callers can place it in their desired
|
||||
@@ -811,10 +851,20 @@ Blockly.WorkspaceSvg.prototype.paste = function(xmlBlock) {
|
||||
this.remainingCapacity()) {
|
||||
return;
|
||||
}
|
||||
Blockly.terminateDrag_(); // Dragging while pasting? No.
|
||||
if (this.currentGesture_) {
|
||||
this.currentGesture_.cancel(); // Dragging while pasting? No.
|
||||
}
|
||||
Blockly.Events.disable();
|
||||
try {
|
||||
var block = Blockly.Xml.domToBlock(xmlBlock, this);
|
||||
// Rerender to get around problem with IE and Edge not measuring text
|
||||
// correctly when it is hidden.
|
||||
if (goog.userAgent.IE || goog.userAgent.EDGE) {
|
||||
var blocks = block.getDescendants();
|
||||
for (var i = blocks.length - 1; i >= 0; i--) {
|
||||
blocks[i].render(false);
|
||||
}
|
||||
}
|
||||
// Move the duplicate to original position.
|
||||
var blockX = parseInt(xmlBlock.getAttribute('x'), 10);
|
||||
var blockY = parseInt(xmlBlock.getAttribute('y'), 10);
|
||||
@@ -862,25 +912,86 @@ Blockly.WorkspaceSvg.prototype.paste = function(xmlBlock) {
|
||||
Blockly.Events.enable();
|
||||
}
|
||||
if (Blockly.Events.isEnabled() && !block.isShadow()) {
|
||||
Blockly.Events.fire(new Blockly.Events.Create(block));
|
||||
Blockly.Events.fire(new Blockly.Events.BlockCreate(block));
|
||||
}
|
||||
block.select();
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new variable with the given name. Update the flyout to show the new
|
||||
* variable immediately.
|
||||
* TODO: #468
|
||||
* @param {string} name The new variable's name.
|
||||
* Refresh the toolbox unless there's a drag in progress.
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.createVariable = function(name) {
|
||||
Blockly.WorkspaceSvg.superClass_.createVariable.call(this, name);
|
||||
// Don't refresh the toolbox if there's a drag in progress.
|
||||
if (this.toolbox_ && this.toolbox_.flyout_ && !Blockly.Flyout.startFlyout_) {
|
||||
Blockly.WorkspaceSvg.prototype.refreshToolboxSelection_ = function() {
|
||||
if (this.toolbox_ && this.toolbox_.flyout_ && !this.currentGesture_) {
|
||||
this.toolbox_.refreshSelection();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Rename a variable by updating its name in the variable list.
|
||||
* TODO: google/blockly:#468
|
||||
* @param {string} oldName Variable to rename.
|
||||
* @param {string} newName New variable name.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.renameVariable = function(oldName, newName) {
|
||||
Blockly.WorkspaceSvg.superClass_.renameVariable.call(this, oldName, newName);
|
||||
this.refreshToolboxSelection_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Rename a variable by updating its name in the variable map. Update the
|
||||
* flyout to show the renamed variable immediately.
|
||||
* @param {string} id Id of the variable to rename.
|
||||
* @param {string} newName New variable name.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.renameVariableById = function(id, newName) {
|
||||
Blockly.WorkspaceSvg.superClass_.renameVariableById.call(this, id, newName);
|
||||
this.refreshToolboxSelection_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a variable by the passed in name. Update the flyout to show
|
||||
* immediately that the variable is deleted.
|
||||
* @param {string} name Name of variable to delete.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.deleteVariable = function(name) {
|
||||
Blockly.WorkspaceSvg.superClass_.deleteVariable.call(this, name);
|
||||
this.refreshToolboxSelection_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a variable by the passed in id. Update the flyout to show
|
||||
* immediately that the variable is deleted.
|
||||
* @param {string} id Id of variable to delete.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.deleteVariableById = function(id) {
|
||||
Blockly.WorkspaceSvg.superClass_.deleteVariableById.call(this, id);
|
||||
this.refreshToolboxSelection_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new variable with the given name. Update the flyout to show the new
|
||||
* variable immediately.
|
||||
* @param {string} name The new variable's name.
|
||||
* @param {string=} opt_type The type of the variable like 'int' or 'string'.
|
||||
* Does not need to be unique. Field_variable can filter variables based on
|
||||
* their type. This will default to '' which is a specific type.
|
||||
* @param {string=} opt_id The unique id of the variable. This will default to
|
||||
* a UUID.
|
||||
* @return {?Blockly.VariableModel} The newly created variable.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.createVariable = function(name, opt_type, opt_id) {
|
||||
var newVar = Blockly.WorkspaceSvg.superClass_.createVariable.call(this, name,
|
||||
opt_type, opt_id);
|
||||
this.refreshToolboxSelection_();
|
||||
return newVar;
|
||||
};
|
||||
|
||||
/**
|
||||
* Make a list of all the delete areas for this workspace.
|
||||
*/
|
||||
@@ -901,7 +1012,6 @@ Blockly.WorkspaceSvg.prototype.recordDeleteAreas = function() {
|
||||
|
||||
/**
|
||||
* Is the mouse event over a delete area (toolbox or non-closing flyout)?
|
||||
* Opens or closes the trashcan and sets the cursor as a side effect.
|
||||
* @param {!Event} e Mouse move event.
|
||||
* @return {?number} Null if not over a delete area, or an enum representing
|
||||
* which delete area the event is over.
|
||||
@@ -914,7 +1024,7 @@ Blockly.WorkspaceSvg.prototype.isDeleteArea = function(e) {
|
||||
if (this.deleteAreaToolbox_ && this.deleteAreaToolbox_.contains(xy)) {
|
||||
return Blockly.DELETE_AREA_TOOLBOX;
|
||||
}
|
||||
return null;
|
||||
return Blockly.DELETE_AREA_NONE;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -923,60 +1033,10 @@ Blockly.WorkspaceSvg.prototype.isDeleteArea = function(e) {
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.onMouseDown_ = function(e) {
|
||||
this.markFocused();
|
||||
if (Blockly.utils.isTargetInput(e)) {
|
||||
Blockly.Touch.clearTouchIdentifier();
|
||||
return;
|
||||
var gesture = this.getGesture(e);
|
||||
if (gesture) {
|
||||
gesture.handleWsStart(e, this);
|
||||
}
|
||||
Blockly.terminateDrag_(); // In case mouse-up event was lost.
|
||||
Blockly.hideChaff();
|
||||
var isTargetWorkspace = e.target && e.target.nodeName &&
|
||||
(e.target.nodeName.toLowerCase() == 'svg' ||
|
||||
e.target == this.svgBackground_);
|
||||
if (isTargetWorkspace && Blockly.selected && !this.options.readOnly) {
|
||||
// Clicking on the document clears the selection.
|
||||
Blockly.selected.unselect();
|
||||
}
|
||||
if (Blockly.utils.isRightButton(e)) {
|
||||
// Right-click.
|
||||
this.showContextMenu_(e);
|
||||
// This is to handle the case where the event is pretending to be a right
|
||||
// click event but it was really a long press. In that case, we want to make
|
||||
// sure any in progress drags are stopped.
|
||||
Blockly.onMouseUp_(e);
|
||||
// Since this was a click, not a drag, end the gesture immediately.
|
||||
Blockly.Touch.clearTouchIdentifier();
|
||||
} else if (this.scrollbar) {
|
||||
this.dragMode_ = Blockly.DRAG_BEGIN;
|
||||
// Record the current mouse position.
|
||||
this.startDragMouseX = e.clientX;
|
||||
this.startDragMouseY = e.clientY;
|
||||
this.startDragMetrics = this.getMetrics();
|
||||
this.startScrollX = this.scrollX;
|
||||
this.startScrollY = this.scrollY;
|
||||
|
||||
this.setupDragSurface();
|
||||
// If this is a touch event then bind to the mouseup so workspace drag mode
|
||||
// is turned off and double move events are not performed on a block.
|
||||
// See comment in inject.js Blockly.init_ as to why mouseup events are
|
||||
// bound to the document instead of the SVG's surface.
|
||||
if ('mouseup' in Blockly.Touch.TOUCH_MAP) {
|
||||
Blockly.Touch.onTouchUpWrapper_ = Blockly.Touch.onTouchUpWrapper_ || [];
|
||||
Blockly.Touch.onTouchUpWrapper_ = Blockly.Touch.onTouchUpWrapper_.concat(
|
||||
Blockly.bindEventWithChecks_(document, 'mouseup', null,
|
||||
Blockly.onMouseUp_));
|
||||
}
|
||||
Blockly.onMouseMoveWrapper_ = Blockly.onMouseMoveWrapper_ || [];
|
||||
Blockly.onMouseMoveWrapper_ = Blockly.onMouseMoveWrapper_.concat(
|
||||
Blockly.bindEventWithChecks_(document, 'mousemove', null,
|
||||
Blockly.onMouseMove_));
|
||||
} else {
|
||||
// It was a click, but the workspace isn't draggable.
|
||||
Blockly.Touch.clearTouchIdentifier();
|
||||
}
|
||||
// This event has been handled. No need to bubble up to the document.
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1013,10 +1073,7 @@ Blockly.WorkspaceSvg.prototype.moveDrag = function(e) {
|
||||
* @return {boolean} True if currently dragging or scrolling.
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.isDragging = function() {
|
||||
return Blockly.dragMode_ == Blockly.DRAG_FREE ||
|
||||
(Blockly.Flyout.startFlyout_ &&
|
||||
Blockly.Flyout.startFlyout_.dragMode_ == Blockly.DRAG_FREE) ||
|
||||
this.dragMode_ == Blockly.DRAG_FREE;
|
||||
return this.currentGesture_ && this.currentGesture_.isDragging();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1033,8 +1090,11 @@ Blockly.WorkspaceSvg.prototype.isDraggable = function() {
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.onMouseWheel_ = function(e) {
|
||||
// TODO: Remove terminateDrag and compensate for coordinate skew during zoom.
|
||||
Blockly.terminateDrag_();
|
||||
// TODO: Remove gesture cancellation and compensate for coordinate skew during
|
||||
// zoom.
|
||||
if (this.currentGesture_) {
|
||||
this.currentGesture_.cancel();
|
||||
}
|
||||
// The vertical scroll distance that corresponds to a click of a zoom button.
|
||||
var PIXELS_PER_ZOOM_STEP = 50;
|
||||
var delta = -e.deltaY / PIXELS_PER_ZOOM_STEP;
|
||||
@@ -1046,6 +1106,7 @@ Blockly.WorkspaceSvg.prototype.onMouseWheel_ = function(e) {
|
||||
|
||||
/**
|
||||
* Calculate the bounding box for the blocks on the workspace.
|
||||
* Coordinate system: workspace coordinates.
|
||||
*
|
||||
* @return {Object} Contains the position and size of the bounding box
|
||||
* containing the blocks on the workspace.
|
||||
@@ -1115,6 +1176,7 @@ Blockly.WorkspaceSvg.prototype.showContextMenu_ = function(e) {
|
||||
var menuOptions = [];
|
||||
var topBlocks = this.getTopBlocks(true);
|
||||
var eventGroup = Blockly.utils.genUid();
|
||||
var ws = this;
|
||||
|
||||
// Options to undo/redo previous action.
|
||||
var undoOption = {};
|
||||
@@ -1224,6 +1286,9 @@ Blockly.WorkspaceSvg.prototype.showContextMenu_ = function(e) {
|
||||
Blockly.Msg.DELETE_X_BLOCKS.replace('%1', String(deleteList.length)),
|
||||
enabled: deleteList.length > 0,
|
||||
callback: function() {
|
||||
if (ws.currentGesture_) {
|
||||
ws.currentGesture_.cancel();
|
||||
}
|
||||
if (deleteList.length < 2 ) {
|
||||
deleteNext();
|
||||
} else {
|
||||
@@ -1242,92 +1307,6 @@ Blockly.WorkspaceSvg.prototype.showContextMenu_ = function(e) {
|
||||
Blockly.ContextMenu.show(e, menuOptions, this.RTL);
|
||||
};
|
||||
|
||||
/**
|
||||
* Load an audio file. Cache it, ready for instantaneous playing.
|
||||
* @param {!Array.<string>} filenames List of file types in decreasing order of
|
||||
* preference (i.e. increasing size). E.g. ['media/go.mp3', 'media/go.wav']
|
||||
* Filenames include path from Blockly's root. File extensions matter.
|
||||
* @param {string} name Name of sound.
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.loadAudio_ = function(filenames, name) {
|
||||
if (!filenames.length) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
var audioTest = new window['Audio']();
|
||||
} catch (e) {
|
||||
// No browser support for Audio.
|
||||
// IE can throw an error even if the Audio object exists.
|
||||
return;
|
||||
}
|
||||
var sound;
|
||||
for (var i = 0; i < filenames.length; i++) {
|
||||
var filename = filenames[i];
|
||||
var ext = filename.match(/\.(\w+)$/);
|
||||
if (ext && audioTest.canPlayType('audio/' + ext[1])) {
|
||||
// Found an audio format we can play.
|
||||
sound = new window['Audio'](filename);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (sound && sound.play) {
|
||||
this.SOUNDS_[name] = sound;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Preload all the audio files so that they play quickly when asked for.
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.preloadAudio_ = function() {
|
||||
for (var name in this.SOUNDS_) {
|
||||
var sound = this.SOUNDS_[name];
|
||||
sound.volume = .01;
|
||||
sound.play();
|
||||
sound.pause();
|
||||
// iOS can only process one sound at a time. Trying to load more than one
|
||||
// corrupts the earlier ones. Just load one and leave the others uncached.
|
||||
if (goog.userAgent.IPAD || goog.userAgent.IPHONE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Play a named sound at specified volume. If volume is not specified,
|
||||
* use full volume (1).
|
||||
* @param {string} name Name of sound.
|
||||
* @param {number=} opt_volume Volume of sound (0-1).
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.playAudio = function(name, opt_volume) {
|
||||
var sound = this.SOUNDS_[name];
|
||||
if (sound) {
|
||||
// Don't play one sound on top of another.
|
||||
var now = new Date;
|
||||
if (now - this.lastSound_ < Blockly.SOUND_LIMIT) {
|
||||
return;
|
||||
}
|
||||
this.lastSound_ = now;
|
||||
var mySound;
|
||||
var ie9 = goog.userAgent.DOCUMENT_MODE &&
|
||||
goog.userAgent.DOCUMENT_MODE === 9;
|
||||
if (ie9 || goog.userAgent.IPAD || goog.userAgent.ANDROID) {
|
||||
// Creating a new audio node causes lag in IE9, Android and iPad. Android
|
||||
// and IE9 refetch the file from the server, iPad uses a singleton audio
|
||||
// node which must be deleted and recreated for each new audio tag.
|
||||
mySound = sound;
|
||||
} else {
|
||||
mySound = sound.cloneNode();
|
||||
}
|
||||
mySound.volume = (opt_volume === undefined ? 1 : opt_volume);
|
||||
mySound.play();
|
||||
} else if (this.options.parentWorkspace) {
|
||||
// Maybe a workspace on a lower level knows about this sound.
|
||||
this.options.parentWorkspace.playAudio(name, opt_volume);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Modify the block tree on the existing toolbox.
|
||||
* @param {Node|string} tree DOM tree of blocks, or text representation of same.
|
||||
@@ -1511,7 +1490,9 @@ Blockly.WorkspaceSvg.prototype.setScale = function(newScale) {
|
||||
newScale = this.options.zoomOptions.minScale;
|
||||
}
|
||||
this.scale = newScale;
|
||||
this.updateGridPattern_();
|
||||
if (this.grid_) {
|
||||
this.grid_.update(this.scale);
|
||||
}
|
||||
if (this.scrollbar) {
|
||||
this.scrollbar.resize();
|
||||
} else {
|
||||
@@ -1524,46 +1505,10 @@ Blockly.WorkspaceSvg.prototype.setScale = function(newScale) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the grid pattern.
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.updateGridPattern_ = function() {
|
||||
if (!this.options.gridPattern) {
|
||||
return; // No grid.
|
||||
}
|
||||
// MSIE freaks if it sees a 0x0 pattern, so set empty patterns to 100x100.
|
||||
var safeSpacing = (this.options.gridOptions['spacing'] * this.scale) || 100;
|
||||
this.options.gridPattern.setAttribute('width', safeSpacing);
|
||||
this.options.gridPattern.setAttribute('height', safeSpacing);
|
||||
var half = Math.floor(this.options.gridOptions['spacing'] / 2) + 0.5;
|
||||
var start = half - this.options.gridOptions['length'] / 2;
|
||||
var end = half + this.options.gridOptions['length'] / 2;
|
||||
var line1 = this.options.gridPattern.firstChild;
|
||||
var line2 = line1 && line1.nextSibling;
|
||||
half *= this.scale;
|
||||
start *= this.scale;
|
||||
end *= this.scale;
|
||||
if (line1) {
|
||||
line1.setAttribute('stroke-width', this.scale);
|
||||
line1.setAttribute('x1', start);
|
||||
line1.setAttribute('y1', half);
|
||||
line1.setAttribute('x2', end);
|
||||
line1.setAttribute('y2', half);
|
||||
}
|
||||
if (line2) {
|
||||
line2.setAttribute('stroke-width', this.scale);
|
||||
line2.setAttribute('x1', half);
|
||||
line2.setAttribute('y1', start);
|
||||
line2.setAttribute('x2', half);
|
||||
line2.setAttribute('y2', end);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Return an object with all the metrics required to size scrollbars for a
|
||||
* top level workspace. The following properties are computed:
|
||||
* Coordinate system: pixel coordinates.
|
||||
* .viewHeight: Height of the visible rectangle,
|
||||
* .viewWidth: Width of the visible rectangle,
|
||||
* .contentHeight: Height of the contents,
|
||||
@@ -1600,6 +1545,7 @@ Blockly.WorkspaceSvg.getTopLevelWorkspaceMetrics_ = function() {
|
||||
var MARGIN = Blockly.Flyout.prototype.CORNER_RADIUS - 1;
|
||||
var viewWidth = svgSize.width - MARGIN;
|
||||
var viewHeight = svgSize.height - MARGIN;
|
||||
|
||||
var blockBox = this.getBlocksBoundingBox();
|
||||
|
||||
// Fix scale.
|
||||
@@ -1674,14 +1620,8 @@ Blockly.WorkspaceSvg.setTopLevelWorkspaceMetrics_ = function(xyRatio) {
|
||||
var x = this.scrollX + metrics.absoluteLeft;
|
||||
var y = this.scrollY + metrics.absoluteTop;
|
||||
this.translate(x, y);
|
||||
if (this.options.gridPattern) {
|
||||
this.options.gridPattern.setAttribute('x', x);
|
||||
this.options.gridPattern.setAttribute('y', y);
|
||||
if (goog.userAgent.IE || goog.userAgent.EDGE) {
|
||||
// IE/Edge doesn't notice that the x/y offsets have changed.
|
||||
// Force an update.
|
||||
this.updateGridPattern_();
|
||||
}
|
||||
if (this.grid_) {
|
||||
this.grid_.moveTo(x, y);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1783,6 +1723,74 @@ Blockly.WorkspaceSvg.prototype.removeToolboxCategoryCallback = function(key) {
|
||||
this.toolboxCategoryCallbacks_[key] = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Look up the gesture that is tracking this touch stream on this workspace.
|
||||
* May create a new gesture.
|
||||
* @param {!Event} e Mouse event or touch event
|
||||
* @return {Blockly.Gesture} The gesture that is tracking this touch stream,
|
||||
* or null if no valid gesture exists.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.getGesture = function(e) {
|
||||
var isStart = (e.type == 'mousedown' || e.type == 'touchstart');
|
||||
|
||||
var gesture = this.currentGesture_;
|
||||
if (gesture) {
|
||||
if (isStart && gesture.hasStarted()) {
|
||||
console.warn('tried to start the same gesture twice');
|
||||
// That's funny. We must have missed a mouse up.
|
||||
// Cancel it, rather than try to retrieve all of the state we need.
|
||||
gesture.cancel();
|
||||
return null;
|
||||
}
|
||||
return gesture;
|
||||
}
|
||||
|
||||
// No gesture existed on this workspace, but this looks like the start of a
|
||||
// new gesture.
|
||||
if (isStart) {
|
||||
this.currentGesture_ = new Blockly.Gesture(e, this);
|
||||
return this.currentGesture_;
|
||||
}
|
||||
// No gesture existed and this event couldn't be the start of a new gesture.
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear the reference to the current gesture.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.clearGesture = function() {
|
||||
this.currentGesture_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Cancel the current gesture, if one exists.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.cancelCurrentGesture = function() {
|
||||
if (this.currentGesture_) {
|
||||
this.currentGesture_.cancel();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the audio manager for this workspace.
|
||||
* @return {Blockly.WorkspaceAudio} The audio manager for this workspace.
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.getAudioManager = function() {
|
||||
return this.audioManager_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the grid object for this workspace, or null if there is none.
|
||||
* @return {Blockly.Grid} The grid object for this workspace.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.getGrid = function() {
|
||||
return this.grid_;
|
||||
};
|
||||
|
||||
// Export symbols that would otherwise be renamed by Closure compiler.
|
||||
Blockly.WorkspaceSvg.prototype['setVisible'] =
|
||||
Blockly.WorkspaceSvg.prototype.setVisible;
|
||||
|
||||
+104
-24
@@ -42,6 +42,7 @@ goog.require('goog.dom');
|
||||
*/
|
||||
Blockly.Xml.workspaceToDom = function(workspace, opt_noId) {
|
||||
var xml = goog.dom.createDom('xml');
|
||||
xml.appendChild(Blockly.Xml.variablesToDom(workspace.getAllVariables()));
|
||||
var blocks = workspace.getTopBlocks(true);
|
||||
for (var i = 0, block; block = blocks[i]; i++) {
|
||||
xml.appendChild(Blockly.Xml.blockToDomWithXY(block, opt_noId));
|
||||
@@ -49,6 +50,23 @@ Blockly.Xml.workspaceToDom = function(workspace, opt_noId) {
|
||||
return xml;
|
||||
};
|
||||
|
||||
/**
|
||||
* Encode a list of variables as XML.
|
||||
* @param {!Array.<!Blockly.VariableModel>} variableList List of all variable
|
||||
* models.
|
||||
* @return {!Element} List of XML elements.
|
||||
*/
|
||||
Blockly.Xml.variablesToDom = function(variableList) {
|
||||
var variables = goog.dom.createDom('variables');
|
||||
for (var i = 0, variable; variable = variableList[i]; i++) {
|
||||
var element = goog.dom.createDom('variable', null, variable.name);
|
||||
element.setAttribute('type', variable.type);
|
||||
element.setAttribute('id', variable.getId());
|
||||
variables.appendChild(element);
|
||||
}
|
||||
return variables;
|
||||
};
|
||||
|
||||
/**
|
||||
* Encode a block subtree as XML with XY coordinates.
|
||||
* @param {!Blockly.Block} block The root block to encode.
|
||||
@@ -91,6 +109,13 @@ Blockly.Xml.blockToDom = function(block, opt_noId) {
|
||||
if (field.name && field.EDITABLE) {
|
||||
var container = goog.dom.createDom('field', null, field.getValue());
|
||||
container.setAttribute('name', field.name);
|
||||
if (field instanceof Blockly.FieldVariable) {
|
||||
var variable = block.workspace.getVariable(field.getValue());
|
||||
if (variable) {
|
||||
container.setAttribute('id', variable.getId());
|
||||
container.setAttribute('variableType', variable.type);
|
||||
}
|
||||
}
|
||||
element.appendChild(container);
|
||||
}
|
||||
}
|
||||
@@ -307,31 +332,47 @@ Blockly.Xml.domToWorkspace = function(xml, workspace) {
|
||||
if (workspace.setResizesEnabled) {
|
||||
workspace.setResizesEnabled(false);
|
||||
}
|
||||
for (var i = 0; i < childCount; i++) {
|
||||
var xmlChild = xml.childNodes[i];
|
||||
var name = xmlChild.nodeName.toLowerCase();
|
||||
if (name == 'block' ||
|
||||
(name == 'shadow' && !Blockly.Events.recordUndo)) {
|
||||
// Allow top-level shadow blocks if recordUndo is disabled since
|
||||
// that means an undo is in progress. Such a block is expected
|
||||
// to be moved to a nested destination in the next operation.
|
||||
var block = Blockly.Xml.domToBlock(xmlChild, workspace);
|
||||
newBlockIds.push(block.id);
|
||||
var blockX = parseInt(xmlChild.getAttribute('x'), 10);
|
||||
var blockY = parseInt(xmlChild.getAttribute('y'), 10);
|
||||
if (!isNaN(blockX) && !isNaN(blockY)) {
|
||||
block.moveBy(workspace.RTL ? width - blockX : blockX, blockY);
|
||||
var variablesFirst = true;
|
||||
try {
|
||||
for (var i = 0; i < childCount; i++) {
|
||||
var xmlChild = xml.childNodes[i];
|
||||
var name = xmlChild.nodeName.toLowerCase();
|
||||
if (name == 'block' ||
|
||||
(name == 'shadow' && !Blockly.Events.recordUndo)) {
|
||||
// Allow top-level shadow blocks if recordUndo is disabled since
|
||||
// that means an undo is in progress. Such a block is expected
|
||||
// to be moved to a nested destination in the next operation.
|
||||
var block = Blockly.Xml.domToBlock(xmlChild, workspace);
|
||||
newBlockIds.push(block.id);
|
||||
var blockX = parseInt(xmlChild.getAttribute('x'), 10);
|
||||
var blockY = parseInt(xmlChild.getAttribute('y'), 10);
|
||||
if (!isNaN(blockX) && !isNaN(blockY)) {
|
||||
block.moveBy(workspace.RTL ? width - blockX : blockX, blockY);
|
||||
}
|
||||
variablesFirst = false;
|
||||
} else if (name == 'shadow') {
|
||||
goog.asserts.fail('Shadow block cannot be a top-level block.');
|
||||
variablesFirst = false;
|
||||
} else if (name == 'variables') {
|
||||
if (variablesFirst) {
|
||||
Blockly.Xml.domToVariables(xmlChild, workspace);
|
||||
}
|
||||
else {
|
||||
throw Error('\'variables\' tag must exist once before block and ' +
|
||||
'shadow tag elements in the workspace XML, but it was found in ' +
|
||||
'another location.');
|
||||
}
|
||||
variablesFirst = false;
|
||||
}
|
||||
} else if (name == 'shadow') {
|
||||
goog.asserts.fail('Shadow block cannot be a top-level block.');
|
||||
}
|
||||
}
|
||||
if (!existingGroup) {
|
||||
Blockly.Events.setGroup(false);
|
||||
finally {
|
||||
if (!existingGroup) {
|
||||
Blockly.Events.setGroup(false);
|
||||
}
|
||||
Blockly.Field.stopCache();
|
||||
}
|
||||
Blockly.Field.stopCache();
|
||||
|
||||
workspace.updateVariableList(false);
|
||||
workspace.updateVariableStore(false);
|
||||
// Re-enable workspace resizing.
|
||||
if (workspace.setResizesEnabled) {
|
||||
workspace.setResizesEnabled(true);
|
||||
@@ -354,7 +395,7 @@ Blockly.Xml.appendDomToWorkspace = function(xml, workspace) {
|
||||
var savetab = Blockly.BlockSvg.TAB_WIDTH;
|
||||
try {
|
||||
Blockly.BlockSvg.TAB_WIDTH = 0;
|
||||
var bbox = workspace.getBlocksBoundingBox();
|
||||
bbox = workspace.getBlocksBoundingBox();
|
||||
} finally {
|
||||
Blockly.BlockSvg.TAB_WIDTH = savetab;
|
||||
}
|
||||
@@ -440,11 +481,30 @@ Blockly.Xml.domToBlock = function(xmlBlock, workspace) {
|
||||
Blockly.Events.enable();
|
||||
}
|
||||
if (Blockly.Events.isEnabled()) {
|
||||
Blockly.Events.fire(new Blockly.Events.Create(topBlock));
|
||||
Blockly.Events.fire(new Blockly.Events.BlockCreate(topBlock));
|
||||
}
|
||||
return topBlock;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode an XML list of variables and add the variables to the workspace.
|
||||
* @param {!Element} xmlVariables List of XML variable elements.
|
||||
* @param {!Blockly.Workspace} workspace The workspace to which the variable
|
||||
* should be added.
|
||||
*/
|
||||
Blockly.Xml.domToVariables = function(xmlVariables, workspace) {
|
||||
for (var i = 0, xmlChild; xmlChild = xmlVariables.children[i]; i++) {
|
||||
var type = xmlChild.getAttribute('type');
|
||||
var id = xmlChild.getAttribute('id');
|
||||
var name = xmlChild.textContent;
|
||||
|
||||
if (typeof(type) === undefined || type === null) {
|
||||
throw Error('Variable with id, ' + id + ' is without a type');
|
||||
}
|
||||
workspace.createVariable(name, type, id);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode an XML block tag and create a block (and possibly sub blocks) on the
|
||||
* workspace.
|
||||
@@ -526,12 +586,32 @@ Blockly.Xml.domToBlockHeadless_ = function(xmlBlock, workspace) {
|
||||
// Fall through.
|
||||
case 'field':
|
||||
var field = block.getField(name);
|
||||
var text = xmlChild.textContent;
|
||||
if (field instanceof Blockly.FieldVariable) {
|
||||
// TODO (marisaleung): When we change setValue and getValue to
|
||||
// interact with id's instead of names, update this so that we get
|
||||
// the variable based on id instead of textContent.
|
||||
var type = xmlChild.getAttribute('variableType') || '';
|
||||
var variable = workspace.getVariable(text);
|
||||
if (!variable) {
|
||||
variable = workspace.createVariable(text, type,
|
||||
xmlChild.getAttribute(id));
|
||||
}
|
||||
if (typeof(type) !== undefined && type !== null) {
|
||||
if (type !== variable.type) {
|
||||
throw Error('Serialized variable type with id \'' +
|
||||
variable.getId() + '\' had type ' + variable.type + ', and ' +
|
||||
'does not match variable field that references it: ' +
|
||||
Blockly.Xml.domToText(xmlChild) + '.');
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!field) {
|
||||
console.warn('Ignoring non-existent field ' + name + ' in block ' +
|
||||
prototypeName);
|
||||
break;
|
||||
}
|
||||
field.setValue(xmlChild.textContent);
|
||||
field.setValue(text);
|
||||
break;
|
||||
case 'value':
|
||||
case 'statement':
|
||||
|
||||
@@ -115,9 +115,10 @@ Blockly.ZoomControls.prototype.createDom = function() {
|
||||
*/
|
||||
this.svgGroup_ = Blockly.utils.createSvgElement('g',
|
||||
{'class': 'blocklyZoom'}, null);
|
||||
var clip;
|
||||
var rnd = String(Math.random()).substring(2);
|
||||
|
||||
var clip = Blockly.utils.createSvgElement('clipPath',
|
||||
clip = Blockly.utils.createSvgElement('clipPath',
|
||||
{'id': 'blocklyZoomoutClipPath' + rnd},
|
||||
this.svgGroup_);
|
||||
Blockly.utils.createSvgElement('rect',
|
||||
@@ -132,7 +133,7 @@ Blockly.ZoomControls.prototype.createDom = function() {
|
||||
zoomoutSvg.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href',
|
||||
workspace.options.pathToMedia + Blockly.SPRITE.url);
|
||||
|
||||
var clip = Blockly.utils.createSvgElement('clipPath',
|
||||
clip = Blockly.utils.createSvgElement('clipPath',
|
||||
{'id': 'blocklyZoominClipPath' + rnd},
|
||||
this.svgGroup_);
|
||||
Blockly.utils.createSvgElement('rect',
|
||||
@@ -148,7 +149,7 @@ Blockly.ZoomControls.prototype.createDom = function() {
|
||||
zoominSvg.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href',
|
||||
workspace.options.pathToMedia + Blockly.SPRITE.url);
|
||||
|
||||
var clip = Blockly.utils.createSvgElement('clipPath',
|
||||
clip = Blockly.utils.createSvgElement('clipPath',
|
||||
{'id': 'blocklyZoomresetClipPath' + rnd},
|
||||
this.svgGroup_);
|
||||
Blockly.utils.createSvgElement('rect',
|
||||
|
||||
+32
-15
@@ -1,12 +1,29 @@
|
||||
// Do not edit this file; automatically generated by build.py.
|
||||
'use strict';
|
||||
|
||||
/*
|
||||
|
||||
// Copyright 2014 Google Inc. Apache License 2.0
|
||||
Visual Blocks Language
|
||||
|
||||
Copyright 2014 Google Inc.
|
||||
https://developers.google.com/blockly/
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
Blockly.Dart=new Blockly.Generator("Dart");Blockly.Dart.addReservedWords("assert,break,case,catch,class,const,continue,default,do,else,enum,extends,false,final,finally,for,if,in,is,new,null,rethrow,return,super,switch,this,throw,true,try,var,void,while,with,print,identityHashCode,identical,BidirectionalIterator,Comparable,double,Function,int,Invocation,Iterable,Iterator,List,Map,Match,num,Pattern,RegExp,Set,StackTrace,String,StringSink,Type,bool,DateTime,Deprecated,Duration,Expando,Null,Object,RuneIterator,Runes,Stopwatch,StringBuffer,Symbol,Uri,Comparator,AbstractClassInstantiationError,ArgumentError,AssertionError,CastError,ConcurrentModificationError,CyclicInitializationError,Error,Exception,FallThroughError,FormatException,IntegerDivisionByZeroException,NoSuchMethodError,NullThrownError,OutOfMemoryError,RangeError,StackOverflowError,StateError,TypeError,UnimplementedError,UnsupportedError");
|
||||
Blockly.Dart.ORDER_ATOMIC=0;Blockly.Dart.ORDER_UNARY_POSTFIX=1;Blockly.Dart.ORDER_UNARY_PREFIX=2;Blockly.Dart.ORDER_MULTIPLICATIVE=3;Blockly.Dart.ORDER_ADDITIVE=4;Blockly.Dart.ORDER_SHIFT=5;Blockly.Dart.ORDER_BITWISE_AND=6;Blockly.Dart.ORDER_BITWISE_XOR=7;Blockly.Dart.ORDER_BITWISE_OR=8;Blockly.Dart.ORDER_RELATIONAL=9;Blockly.Dart.ORDER_EQUALITY=10;Blockly.Dart.ORDER_LOGICAL_AND=11;Blockly.Dart.ORDER_LOGICAL_OR=12;Blockly.Dart.ORDER_IF_NULL=13;Blockly.Dart.ORDER_CONDITIONAL=14;
|
||||
Blockly.Dart.ORDER_CASCADE=15;Blockly.Dart.ORDER_ASSIGNMENT=16;Blockly.Dart.ORDER_NONE=99;
|
||||
Blockly.Dart.init=function(a){Blockly.Dart.definitions_=Object.create(null);Blockly.Dart.functionNames_=Object.create(null);Blockly.Dart.variableDB_?Blockly.Dart.variableDB_.reset():Blockly.Dart.variableDB_=new Blockly.Names(Blockly.Dart.RESERVED_WORDS_);var b=[];a=a.variableList;if(a.length){for(var c=0;c<a.length;c++)b[c]=Blockly.Dart.variableDB_.getName(a[c],Blockly.Variables.NAME_TYPE);Blockly.Dart.definitions_.variables="var "+b.join(", ")+";"}};
|
||||
Blockly.Dart.init=function(a){Blockly.Dart.definitions_=Object.create(null);Blockly.Dart.functionNames_=Object.create(null);Blockly.Dart.variableDB_?Blockly.Dart.variableDB_.reset():Blockly.Dart.variableDB_=new Blockly.Names(Blockly.Dart.RESERVED_WORDS_);var b=[];a=a.getAllVariables();if(a.length){for(var c=0;c<a.length;c++)b[c]=Blockly.Dart.variableDB_.getName(a[c].name,Blockly.Variables.NAME_TYPE);Blockly.Dart.definitions_.variables="var "+b.join(", ")+";"}};
|
||||
Blockly.Dart.finish=function(a){a&&(a=Blockly.Dart.prefixLines(a,Blockly.Dart.INDENT));a="main() {\n"+a+"}";var b=[],c=[],d;for(d in Blockly.Dart.definitions_){var e=Blockly.Dart.definitions_[d];e.match(/^import\s/)?b.push(e):c.push(e)}delete Blockly.Dart.definitions_;delete Blockly.Dart.functionNames_;Blockly.Dart.variableDB_.reset();return(b.join("\n")+"\n\n"+c.join("\n\n")).replace(/\n\n+/g,"\n\n").replace(/\n*$/,"\n\n\n")+a};Blockly.Dart.scrubNakedValue=function(a){return a+";\n"};
|
||||
Blockly.Dart.quote_=function(a){a=a.replace(/\\/g,"\\\\").replace(/\n/g,"\\\n").replace(/\$/g,"\\$").replace(/'/g,"\\'");return"'"+a+"'"};
|
||||
Blockly.Dart.scrub_=function(a,b){var c="";if(!a.outputConnection||!a.outputConnection.targetConnection){var d=a.getCommentText();(d=Blockly.utils.wrap(d,Blockly.Dart.COMMENT_WRAP-3))&&(c=a.getProcedureDef?c+Blockly.Dart.prefixLines(d+"\n","/// "):c+Blockly.Dart.prefixLines(d+"\n","// "));for(var e=0;e<a.inputList.length;e++)a.inputList[e].type==Blockly.INPUT_VALUE&&(d=a.inputList[e].connection.targetBlock())&&(d=Blockly.Dart.allNestedComments(d))&&(c+=Blockly.Dart.prefixLines(d,"// "))}e=a.nextConnection&&
|
||||
@@ -14,15 +31,15 @@ a.nextConnection.targetBlock();e=Blockly.Dart.blockToCode(e);return c+b+e};
|
||||
Blockly.Dart.getAdjusted=function(a,b,c,d,e){c=c||0;e=e||Blockly.Dart.ORDER_NONE;a.workspace.options.oneBasedIndex&&c--;var f=a.workspace.options.oneBasedIndex?"1":"0";a=c?Blockly.Dart.valueToCode(a,b,Blockly.Dart.ORDER_ADDITIVE)||f:d?Blockly.Dart.valueToCode(a,b,Blockly.Dart.ORDER_UNARY_PREFIX)||f:Blockly.Dart.valueToCode(a,b,e)||f;if(Blockly.isNumber(a))a=parseInt(a,10)+c,d&&(a=-a);else{if(0<c){a=a+" + "+c;var g=Blockly.Dart.ORDER_ADDITIVE}else 0>c&&(a=a+" - "+-c,g=Blockly.Dart.ORDER_ADDITIVE);
|
||||
d&&(a=c?"-("+a+")":"-"+a,g=Blockly.Dart.ORDER_UNARY_PREFIX);g=Math.floor(g);e=Math.floor(e);g&&e>=g&&(a="("+a+")")}return a};Blockly.Dart.colour={};Blockly.Dart.addReservedWords("Math");Blockly.Dart.colour_picker=function(a){return["'"+a.getFieldValue("COLOUR")+"'",Blockly.Dart.ORDER_ATOMIC]};
|
||||
Blockly.Dart.colour_random=function(a){Blockly.Dart.definitions_.import_dart_math="import 'dart:math' as Math;";return[Blockly.Dart.provideFunction_("colour_random",["String "+Blockly.Dart.FUNCTION_NAME_PLACEHOLDER_+"() {"," String hex = '0123456789abcdef';"," var rnd = new Math.Random();"," return '#${hex[rnd.nextInt(16)]}${hex[rnd.nextInt(16)]}'"," '${hex[rnd.nextInt(16)]}${hex[rnd.nextInt(16)]}'"," '${hex[rnd.nextInt(16)]}${hex[rnd.nextInt(16)]}';","}"])+"()",Blockly.Dart.ORDER_UNARY_POSTFIX]};
|
||||
Blockly.Dart.colour_rgb=function(a){var b=Blockly.Dart.valueToCode(a,"RED",Blockly.Dart.ORDER_NONE)||0,c=Blockly.Dart.valueToCode(a,"GREEN",Blockly.Dart.ORDER_NONE)||0;a=Blockly.Dart.valueToCode(a,"BLUE",Blockly.Dart.ORDER_NONE)||0;Blockly.Dart.definitions_.import_dart_math="import 'dart:math' as Math;";return[Blockly.Dart.provideFunction_("colour_rgb",["String "+Blockly.Dart.FUNCTION_NAME_PLACEHOLDER_+"(num r, num g, num b) {"," num rn = (Math.max(Math.min(r, 1), 0) * 255).round();"," String rs = rn.toInt().toRadixString(16);",
|
||||
" rs = '0$rs';"," rs = rs.substring(rs.length - 2);"," num gn = (Math.max(Math.min(g, 1), 0) * 255).round();"," String gs = gn.toInt().toRadixString(16);"," gs = '0$gs';"," gs = gs.substring(gs.length - 2);"," num bn = (Math.max(Math.min(b, 1), 0) * 255).round();"," String bs = bn.toInt().toRadixString(16);"," bs = '0$bs';"," bs = bs.substring(bs.length - 2);"," return '#$rs$gs$bs';","}"])+"("+b+", "+c+", "+a+")",Blockly.Dart.ORDER_UNARY_POSTFIX]};
|
||||
Blockly.Dart.colour_rgb=function(a){var b=Blockly.Dart.valueToCode(a,"RED",Blockly.Dart.ORDER_NONE)||0,c=Blockly.Dart.valueToCode(a,"GREEN",Blockly.Dart.ORDER_NONE)||0;a=Blockly.Dart.valueToCode(a,"BLUE",Blockly.Dart.ORDER_NONE)||0;Blockly.Dart.definitions_.import_dart_math="import 'dart:math' as Math;";return[Blockly.Dart.provideFunction_("colour_rgb",["String "+Blockly.Dart.FUNCTION_NAME_PLACEHOLDER_+"(num r, num g, num b) {"," num rn = (Math.max(Math.min(r, 100), 0) * 2.55).round();"," String rs = rn.toInt().toRadixString(16);",
|
||||
" rs = '0$rs';"," rs = rs.substring(rs.length - 2);"," num gn = (Math.max(Math.min(g, 100), 0) * 2.55).round();"," String gs = gn.toInt().toRadixString(16);"," gs = '0$gs';"," gs = gs.substring(gs.length - 2);"," num bn = (Math.max(Math.min(b, 100), 0) * 2.55).round();"," String bs = bn.toInt().toRadixString(16);"," bs = '0$bs';"," bs = bs.substring(bs.length - 2);"," return '#$rs$gs$bs';","}"])+"("+b+", "+c+", "+a+")",Blockly.Dart.ORDER_UNARY_POSTFIX]};
|
||||
Blockly.Dart.colour_blend=function(a){var b=Blockly.Dart.valueToCode(a,"COLOUR1",Blockly.Dart.ORDER_NONE)||"'#000000'",c=Blockly.Dart.valueToCode(a,"COLOUR2",Blockly.Dart.ORDER_NONE)||"'#000000'";a=Blockly.Dart.valueToCode(a,"RATIO",Blockly.Dart.ORDER_NONE)||.5;Blockly.Dart.definitions_.import_dart_math="import 'dart:math' as Math;";return[Blockly.Dart.provideFunction_("colour_blend",["String "+Blockly.Dart.FUNCTION_NAME_PLACEHOLDER_+"(String c1, String c2, num ratio) {"," ratio = Math.max(Math.min(ratio, 1), 0);",
|
||||
" int r1 = int.parse('0x${c1.substring(1, 3)}');"," int g1 = int.parse('0x${c1.substring(3, 5)}');"," int b1 = int.parse('0x${c1.substring(5, 7)}');"," int r2 = int.parse('0x${c2.substring(1, 3)}');"," int g2 = int.parse('0x${c2.substring(3, 5)}');"," int b2 = int.parse('0x${c2.substring(5, 7)}');"," num rn = (r1 * (1 - ratio) + r2 * ratio).round();"," String rs = rn.toInt().toRadixString(16);"," num gn = (g1 * (1 - ratio) + g2 * ratio).round();"," String gs = gn.toInt().toRadixString(16);",
|
||||
" 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<a.itemCount_;c++)b[c]=Blockly.Dart.valueToCode(a,"ADD"+c,Blockly.Dart.ORDER_NONE)||"null";return["["+b.join(", ")+"]",Blockly.Dart.ORDER_ATOMIC]};
|
||||
Blockly.Dart.lists_repeat=function(a){var b=Blockly.Dart.valueToCode(a,"ITEM",Blockly.Dart.ORDER_NONE)||"null";return["new List.filled("+(Blockly.Dart.valueToCode(a,"NUM",Blockly.Dart.ORDER_NONE)||"0")+", "+b+")",Blockly.Dart.ORDER_UNARY_POSTFIX]};Blockly.Dart.lists_length=function(a){return[(Blockly.Dart.valueToCode(a,"VALUE",Blockly.Dart.ORDER_UNARY_POSTFIX)||"[]")+".length",Blockly.Dart.ORDER_UNARY_POSTFIX]};
|
||||
Blockly.Dart.lists_isEmpty=function(a){return[(Blockly.Dart.valueToCode(a,"VALUE",Blockly.Dart.ORDER_UNARY_POSTFIX)||"[]")+".isEmpty",Blockly.Dart.ORDER_UNARY_POSTFIX]};
|
||||
Blockly.Dart.lists_indexOf=function(a){var b="FIRST"==a.getFieldValue("END")?"indexOf":"lastIndexOf",c=Blockly.Dart.valueToCode(a,"FIND",Blockly.Dart.ORDER_NONE)||"''",b=(Blockly.Dart.valueToCode(a,"VALUE",Blockly.Dart.ORDER_UNARY_POSTFIX)||"[]")+"."+b+"("+c+")";return a.workspace.options.oneBasedIndex?[b+" + 1",Blockly.Dart.ORDER_ADDITIVE]:[b,Blockly.Dart.ORDER_UNARY_POSTFIX]};
|
||||
Blockly.Dart.lists_getIndex=function(a){function b(){var a=Blockly.Dart.variableDB_.getDistinctName("tmp_list",Blockly.Variables.NAME_TYPE),b="List "+a+" = "+e+";\n";e=a;return b}var c=a.getFieldValue("MODE")||"GET",d=a.getFieldValue("WHERE")||"FROM_START",e=Blockly.Dart.valueToCode(a,"VALUE","RANDOM"==d||"FROM_END"==d?Blockly.Dart.ORDER_NONE:Blockly.Dart.ORDER_UNARY_POSTFIX)||"[]";if(("RANDOM"!=d||"REMOVE"!=c)&&"FROM_END"!=d||e.match(/^\w+$/))switch(d){case "FIRST":if("GET"==c)return[e+".first",
|
||||
Blockly.Dart.lists_getIndex=function(a){function b(){var a=Blockly.Dart.variableDB_.getDistinctName("tmp_list",Blockly.Variables.NAME_TYPE),b="List "+a+" = "+e+";\n";e=a;return b}var c=a.getFieldValue("MODE")||"GET";var d=a.getFieldValue("WHERE")||"FROM_START";var e=Blockly.Dart.valueToCode(a,"VALUE","RANDOM"==d||"FROM_END"==d?Blockly.Dart.ORDER_NONE:Blockly.Dart.ORDER_UNARY_POSTFIX)||"[]";if(("RANDOM"!=d||"REMOVE"!=c)&&"FROM_END"!=d||e.match(/^\w+$/))switch(d){case "FIRST":if("GET"==c)return[e+".first",
|
||||
Blockly.Dart.ORDER_UNARY_POSTFIX];if("GET_REMOVE"==c)return[e+".removeAt(0)",Blockly.Dart.ORDER_UNARY_POSTFIX];if("REMOVE"==c)return e+".removeAt(0);\n";break;case "LAST":if("GET"==c)return[e+".last",Blockly.Dart.ORDER_UNARY_POSTFIX];if("GET_REMOVE"==c)return[e+".removeLast()",Blockly.Dart.ORDER_UNARY_POSTFIX];if("REMOVE"==c)return e+".removeLast();\n";break;case "FROM_START":d=Blockly.Dart.getAdjusted(a,"AT");if("GET"==c)return[e+"["+d+"]",Blockly.Dart.ORDER_UNARY_POSTFIX];if("GET_REMOVE"==c)return[e+
|
||||
".removeAt("+d+")",Blockly.Dart.ORDER_UNARY_POSTFIX];if("REMOVE"==c)return e+".removeAt("+d+");\n";break;case "FROM_END":d=Blockly.Dart.getAdjusted(a,"AT",1,!1,Blockly.Dart.ORDER_ADDITIVE);if("GET"==c)return[e+"["+e+".length - "+d+"]",Blockly.Dart.ORDER_UNARY_POSTFIX];if("GET_REMOVE"==c||"REMOVE"==c){a=e+".removeAt("+e+".length - "+d+")";if("GET_REMOVE"==c)return[a,Blockly.Dart.ORDER_UNARY_POSTFIX];if("REMOVE"==c)return a+";\n"}break;case "RANDOM":Blockly.Dart.definitions_.import_dart_math="import 'dart:math' as Math;";
|
||||
if("REMOVE"==c)return c=Blockly.Dart.variableDB_.getDistinctName("tmp_x",Blockly.Variables.NAME_TYPE),"int "+c+" = new Math.Random().nextInt("+e+".length);\n"+(e+".removeAt("+c+");\n");if("GET"==c)return c=Blockly.Dart.provideFunction_("lists_get_random_item",["dynamic "+Blockly.Dart.FUNCTION_NAME_PLACEHOLDER_+"(List my_list) {"," int x = new Math.Random().nextInt(my_list.length);"," return my_list[x];","}"]),[c+"("+e+")",Blockly.Dart.ORDER_UNARY_POSTFIX];if("GET_REMOVE"==c)return c=Blockly.Dart.provideFunction_("lists_remove_random_item",
|
||||
@@ -38,27 +55,27 @@ Blockly.Dart.getAdjusted(a,"AT2",1);break;case "FROM_END":f=Blockly.Dart.getAdju
|
||||
Blockly.Dart.lists_sort=function(a){var b=Blockly.Dart.valueToCode(a,"LIST",Blockly.Dart.ORDER_NONE)||"[]",c="1"===a.getFieldValue("DIRECTION")?1:-1;a=a.getFieldValue("TYPE");return[Blockly.Dart.provideFunction_("lists_sort",["List "+Blockly.Dart.FUNCTION_NAME_PLACEHOLDER_+"(list, type, direction) {"," var compareFuncs = {",' "NUMERIC": (a, b) => direction * a.compareTo(b),',' "TEXT": (a, b) => direction * a.toString().compareTo(b.toString()),',' "IGNORE_CASE": '," (a, b) => direction * ",
|
||||
" a.toString().toLowerCase().compareTo(b.toString().toLowerCase())"," };"," list = new List.from(list);"," var compare = compareFuncs[type];"," list.sort(compare);"," return list;","}"])+"("+b+', "'+a+'", '+c+")",Blockly.Dart.ORDER_UNARY_POSTFIX]};
|
||||
Blockly.Dart.lists_split=function(a){var b=Blockly.Dart.valueToCode(a,"INPUT",Blockly.Dart.ORDER_UNARY_POSTFIX),c=Blockly.Dart.valueToCode(a,"DELIM",Blockly.Dart.ORDER_NONE)||"''";a=a.getFieldValue("MODE");if("SPLIT"==a)b||(b="''"),a="split";else if("JOIN"==a)b||(b="[]"),a="join";else throw"Unknown mode: "+a;return[b+"."+a+"("+c+")",Blockly.Dart.ORDER_UNARY_POSTFIX]};
|
||||
Blockly.Dart.lists_reverse=function(a){return["new List.from("+(Blockly.Dart.valueToCode(a,"LIST",Blockly.Dart.ORDER_NONE)||"[]")+".reversed)",Blockly.Dart.ORDER_UNARY_POSTFIX]};Blockly.Dart.logic={};Blockly.Dart.controls_if=function(a){var b=0,c="",d,e;do e=Blockly.Dart.valueToCode(a,"IF"+b,Blockly.Dart.ORDER_NONE)||"false",d=Blockly.Dart.statementToCode(a,"DO"+b),c+=(0<b?"else ":"")+"if ("+e+") {\n"+d+"}",++b;while(a.getInput("IF"+b));a.getInput("ELSE")&&(d=Blockly.Dart.statementToCode(a,"ELSE"),c+=" else {\n"+d+"}");return c+"\n"};Blockly.Dart.controls_ifelse=Blockly.Dart.controls_if;
|
||||
Blockly.Dart.lists_reverse=function(a){return["new List.from("+(Blockly.Dart.valueToCode(a,"LIST",Blockly.Dart.ORDER_NONE)||"[]")+".reversed)",Blockly.Dart.ORDER_UNARY_POSTFIX]};Blockly.Dart.logic={};Blockly.Dart.controls_if=function(a){var b=0,c="";do{var d=Blockly.Dart.valueToCode(a,"IF"+b,Blockly.Dart.ORDER_NONE)||"false";var e=Blockly.Dart.statementToCode(a,"DO"+b);c+=(0<b?"else ":"")+"if ("+d+") {\n"+e+"}";++b}while(a.getInput("IF"+b));a.getInput("ELSE")&&(e=Blockly.Dart.statementToCode(a,"ELSE"),c+=" else {\n"+e+"}");return c+"\n"};Blockly.Dart.controls_ifelse=Blockly.Dart.controls_if;
|
||||
Blockly.Dart.logic_compare=function(a){var b={EQ:"==",NEQ:"!=",LT:"<",LTE:"<=",GT:">",GTE:">="}[a.getFieldValue("OP")],c="=="==b||"!="==b?Blockly.Dart.ORDER_EQUALITY:Blockly.Dart.ORDER_RELATIONAL,d=Blockly.Dart.valueToCode(a,"A",c)||"0";a=Blockly.Dart.valueToCode(a,"B",c)||"0";return[d+" "+b+" "+a,c]};
|
||||
Blockly.Dart.logic_operation=function(a){var b="AND"==a.getFieldValue("OP")?"&&":"||",c="&&"==b?Blockly.Dart.ORDER_LOGICAL_AND:Blockly.Dart.ORDER_LOGICAL_OR,d=Blockly.Dart.valueToCode(a,"A",c);a=Blockly.Dart.valueToCode(a,"B",c);if(d||a){var e="&&"==b?"true":"false";d||(d=e);a||(a=e)}else a=d="false";return[d+" "+b+" "+a,c]};Blockly.Dart.logic_negate=function(a){var b=Blockly.Dart.ORDER_UNARY_PREFIX;return["!"+(Blockly.Dart.valueToCode(a,"BOOL",b)||"true"),b]};
|
||||
Blockly.Dart.logic_boolean=function(a){return["TRUE"==a.getFieldValue("BOOL")?"true":"false",Blockly.Dart.ORDER_ATOMIC]};Blockly.Dart.logic_null=function(a){return["null",Blockly.Dart.ORDER_ATOMIC]};Blockly.Dart.logic_ternary=function(a){var b=Blockly.Dart.valueToCode(a,"IF",Blockly.Dart.ORDER_CONDITIONAL)||"false",c=Blockly.Dart.valueToCode(a,"THEN",Blockly.Dart.ORDER_CONDITIONAL)||"null";a=Blockly.Dart.valueToCode(a,"ELSE",Blockly.Dart.ORDER_CONDITIONAL)||"null";return[b+" ? "+c+" : "+a,Blockly.Dart.ORDER_CONDITIONAL]};Blockly.Dart.loops={};
|
||||
Blockly.Dart.controls_repeat_ext=function(a){var b=a.getField("TIMES")?String(Number(a.getFieldValue("TIMES"))):Blockly.Dart.valueToCode(a,"TIMES",Blockly.Dart.ORDER_ASSIGNMENT)||"0",c=Blockly.Dart.statementToCode(a,"DO"),c=Blockly.Dart.addLoopTrap(c,a.id);a="";var d=Blockly.Dart.variableDB_.getDistinctName("count",Blockly.Variables.NAME_TYPE),e=b;b.match(/^\w+$/)||Blockly.isNumber(b)||(e=Blockly.Dart.variableDB_.getDistinctName("repeat_end",Blockly.Variables.NAME_TYPE),a+="var "+e+" = "+b+";\n");
|
||||
return a+("for (int "+d+" = 0; "+d+" < "+e+"; "+d+"++) {\n"+c+"}\n")};Blockly.Dart.controls_repeat=Blockly.Dart.controls_repeat_ext;Blockly.Dart.controls_whileUntil=function(a){var b="UNTIL"==a.getFieldValue("MODE"),c=Blockly.Dart.valueToCode(a,"BOOL",b?Blockly.Dart.ORDER_UNARY_PREFIX:Blockly.Dart.ORDER_NONE)||"false",d=Blockly.Dart.statementToCode(a,"DO"),d=Blockly.Dart.addLoopTrap(d,a.id);b&&(c="!"+c);return"while ("+c+") {\n"+d+"}\n"};
|
||||
Blockly.Dart.controls_for=function(a){var b=Blockly.Dart.variableDB_.getName(a.getFieldValue("VAR"),Blockly.Variables.NAME_TYPE),c=Blockly.Dart.valueToCode(a,"FROM",Blockly.Dart.ORDER_ASSIGNMENT)||"0",d=Blockly.Dart.valueToCode(a,"TO",Blockly.Dart.ORDER_ASSIGNMENT)||"0",e=Blockly.Dart.valueToCode(a,"BY",Blockly.Dart.ORDER_ASSIGNMENT)||"1",f=Blockly.Dart.statementToCode(a,"DO"),f=Blockly.Dart.addLoopTrap(f,a.id);if(Blockly.isNumber(c)&&Blockly.isNumber(d)&&Blockly.isNumber(e)){var g=parseFloat(c)<=
|
||||
Blockly.Dart.controls_repeat_ext=function(a){var b=a.getField("TIMES")?String(Number(a.getFieldValue("TIMES"))):Blockly.Dart.valueToCode(a,"TIMES",Blockly.Dart.ORDER_ASSIGNMENT)||"0";var c=Blockly.Dart.statementToCode(a,"DO"),c=Blockly.Dart.addLoopTrap(c,a.id),d="",e=Blockly.Dart.variableDB_.getDistinctName("count",Blockly.Variables.NAME_TYPE);a=b;b.match(/^\w+$/)||Blockly.isNumber(b)||(a=Blockly.Dart.variableDB_.getDistinctName("repeat_end",Blockly.Variables.NAME_TYPE),d+="var "+a+" = "+b+";\n");
|
||||
return d+("for (int "+e+" = 0; "+e+" < "+a+"; "+e+"++) {\n"+c+"}\n")};Blockly.Dart.controls_repeat=Blockly.Dart.controls_repeat_ext;Blockly.Dart.controls_whileUntil=function(a){var b="UNTIL"==a.getFieldValue("MODE"),c=Blockly.Dart.valueToCode(a,"BOOL",b?Blockly.Dart.ORDER_UNARY_PREFIX:Blockly.Dart.ORDER_NONE)||"false",d=Blockly.Dart.statementToCode(a,"DO"),d=Blockly.Dart.addLoopTrap(d,a.id);b&&(c="!"+c);return"while ("+c+") {\n"+d+"}\n"};
|
||||
Blockly.Dart.controls_for=function(a){var b=Blockly.Dart.variableDB_.getName(a.getFieldValue("VAR"),Blockly.Variables.NAME_TYPE);var c=Blockly.Dart.valueToCode(a,"FROM",Blockly.Dart.ORDER_ASSIGNMENT)||"0";var d=Blockly.Dart.valueToCode(a,"TO",Blockly.Dart.ORDER_ASSIGNMENT)||"0",e=Blockly.Dart.valueToCode(a,"BY",Blockly.Dart.ORDER_ASSIGNMENT)||"1",f=Blockly.Dart.statementToCode(a,"DO"),f=Blockly.Dart.addLoopTrap(f,a.id);if(Blockly.isNumber(c)&&Blockly.isNumber(d)&&Blockly.isNumber(e)){var g=parseFloat(c)<=
|
||||
parseFloat(d);a="for ("+b+" = "+c+"; "+b+(g?" <= ":" >= ")+d+"; "+b;b=Math.abs(parseFloat(e));a=(1==b?a+(g?"++":"--"):a+((g?" += ":" -= ")+b))+(") {\n"+f+"}\n")}else a="",g=c,c.match(/^\w+$/)||Blockly.isNumber(c)||(g=Blockly.Dart.variableDB_.getDistinctName(b+"_start",Blockly.Variables.NAME_TYPE),a+="var "+g+" = "+c+";\n"),c=d,d.match(/^\w+$/)||Blockly.isNumber(d)||(c=Blockly.Dart.variableDB_.getDistinctName(b+"_end",Blockly.Variables.NAME_TYPE),a+="var "+c+" = "+d+";\n"),d=Blockly.Dart.variableDB_.getDistinctName(b+
|
||||
"_inc",Blockly.Variables.NAME_TYPE),a+="num "+d+" = ",a=Blockly.isNumber(e)?a+(Math.abs(e)+";\n"):a+("("+e+").abs();\n"),a=a+("if ("+g+" > "+c+") {\n")+(Blockly.Dart.INDENT+d+" = -"+d+";\n"),a+="}\n",a+="for ("+b+" = "+g+"; "+d+" >= 0 ? "+b+" <= "+c+" : "+b+" >= "+c+"; "+b+" += "+d+") {\n"+f+"}\n";return a};
|
||||
Blockly.Dart.controls_forEach=function(a){var b=Blockly.Dart.variableDB_.getName(a.getFieldValue("VAR"),Blockly.Variables.NAME_TYPE),c=Blockly.Dart.valueToCode(a,"LIST",Blockly.Dart.ORDER_ASSIGNMENT)||"[]",d=Blockly.Dart.statementToCode(a,"DO"),d=Blockly.Dart.addLoopTrap(d,a.id);return"for (var "+b+" in "+c+") {\n"+d+"}\n"};
|
||||
Blockly.Dart.controls_flow_statements=function(a){switch(a.getFieldValue("FLOW")){case "BREAK":return"break;\n";case "CONTINUE":return"continue;\n"}throw"Unknown flow statement.";};Blockly.Dart.math={};Blockly.Dart.addReservedWords("Math");Blockly.Dart.math_number=function(a){a=parseFloat(a.getFieldValue("NUM"));var b;Infinity==a?(a="double.INFINITY",b=Blockly.Dart.ORDER_UNARY_POSTFIX):-Infinity==a?(a="-double.INFINITY",b=Blockly.Dart.ORDER_UNARY_PREFIX):b=0>a?Blockly.Dart.ORDER_UNARY_PREFIX:Blockly.Dart.ORDER_ATOMIC;return[a,b]};
|
||||
Blockly.Dart.controls_flow_statements=function(a){switch(a.getFieldValue("FLOW")){case "BREAK":return"break;\n";case "CONTINUE":return"continue;\n"}throw"Unknown flow statement.";};Blockly.Dart.math={};Blockly.Dart.addReservedWords("Math");Blockly.Dart.math_number=function(a){a=parseFloat(a.getFieldValue("NUM"));if(Infinity==a){a="double.INFINITY";var b=Blockly.Dart.ORDER_UNARY_POSTFIX}else-Infinity==a?(a="-double.INFINITY",b=Blockly.Dart.ORDER_UNARY_PREFIX):b=0>a?Blockly.Dart.ORDER_UNARY_PREFIX:Blockly.Dart.ORDER_ATOMIC;return[a,b]};
|
||||
Blockly.Dart.math_arithmetic=function(a){var b={ADD:[" + ",Blockly.Dart.ORDER_ADDITIVE],MINUS:[" - ",Blockly.Dart.ORDER_ADDITIVE],MULTIPLY:[" * ",Blockly.Dart.ORDER_MULTIPLICATIVE],DIVIDE:[" / ",Blockly.Dart.ORDER_MULTIPLICATIVE],POWER:[null,Blockly.Dart.ORDER_NONE]}[a.getFieldValue("OP")],c=b[0],b=b[1],d=Blockly.Dart.valueToCode(a,"A",b)||"0";a=Blockly.Dart.valueToCode(a,"B",b)||"0";return c?[d+c+a,b]:(Blockly.Dart.definitions_.import_dart_math="import 'dart:math' as Math;",["Math.pow("+d+", "+a+
|
||||
")",Blockly.Dart.ORDER_UNARY_POSTFIX])};
|
||||
Blockly.Dart.math_single=function(a){var b=a.getFieldValue("OP"),c;if("NEG"==b)return a=Blockly.Dart.valueToCode(a,"NUM",Blockly.Dart.ORDER_UNARY_PREFIX)||"0","-"==a[0]&&(a=" "+a),["-"+a,Blockly.Dart.ORDER_UNARY_PREFIX];Blockly.Dart.definitions_.import_dart_math="import 'dart:math' as Math;";a="ABS"==b||"ROUND"==b.substring(0,5)?Blockly.Dart.valueToCode(a,"NUM",Blockly.Dart.ORDER_UNARY_POSTFIX)||"0":"SIN"==b||"COS"==b||"TAN"==b?Blockly.Dart.valueToCode(a,"NUM",Blockly.Dart.ORDER_MULTIPLICATIVE)||
|
||||
"0":Blockly.Dart.valueToCode(a,"NUM",Blockly.Dart.ORDER_NONE)||"0";switch(b){case "ABS":c=a+".abs()";break;case "ROOT":c="Math.sqrt("+a+")";break;case "LN":c="Math.log("+a+")";break;case "EXP":c="Math.exp("+a+")";break;case "POW10":c="Math.pow(10,"+a+")";break;case "ROUND":c=a+".round()";break;case "ROUNDUP":c=a+".ceil()";break;case "ROUNDDOWN":c=a+".floor()";break;case "SIN":c="Math.sin("+a+" / 180 * Math.PI)";break;case "COS":c="Math.cos("+a+" / 180 * Math.PI)";break;case "TAN":c="Math.tan("+a+
|
||||
Blockly.Dart.math_single=function(a){var b=a.getFieldValue("OP");if("NEG"==b)return a=Blockly.Dart.valueToCode(a,"NUM",Blockly.Dart.ORDER_UNARY_PREFIX)||"0","-"==a[0]&&(a=" "+a),["-"+a,Blockly.Dart.ORDER_UNARY_PREFIX];Blockly.Dart.definitions_.import_dart_math="import 'dart:math' as Math;";a="ABS"==b||"ROUND"==b.substring(0,5)?Blockly.Dart.valueToCode(a,"NUM",Blockly.Dart.ORDER_UNARY_POSTFIX)||"0":"SIN"==b||"COS"==b||"TAN"==b?Blockly.Dart.valueToCode(a,"NUM",Blockly.Dart.ORDER_MULTIPLICATIVE)||"0":
|
||||
Blockly.Dart.valueToCode(a,"NUM",Blockly.Dart.ORDER_NONE)||"0";switch(b){case "ABS":var c=a+".abs()";break;case "ROOT":c="Math.sqrt("+a+")";break;case "LN":c="Math.log("+a+")";break;case "EXP":c="Math.exp("+a+")";break;case "POW10":c="Math.pow(10,"+a+")";break;case "ROUND":c=a+".round()";break;case "ROUNDUP":c=a+".ceil()";break;case "ROUNDDOWN":c=a+".floor()";break;case "SIN":c="Math.sin("+a+" / 180 * Math.PI)";break;case "COS":c="Math.cos("+a+" / 180 * Math.PI)";break;case "TAN":c="Math.tan("+a+
|
||||
" / 180 * Math.PI)"}if(c)return[c,Blockly.Dart.ORDER_UNARY_POSTFIX];switch(b){case "LOG10":c="Math.log("+a+") / Math.log(10)";break;case "ASIN":c="Math.asin("+a+") / Math.PI * 180";break;case "ACOS":c="Math.acos("+a+") / Math.PI * 180";break;case "ATAN":c="Math.atan("+a+") / Math.PI * 180";break;default:throw"Unknown math operator: "+b;}return[c,Blockly.Dart.ORDER_MULTIPLICATIVE]};
|
||||
Blockly.Dart.math_constant=function(a){var b={PI:["Math.PI",Blockly.Dart.ORDER_UNARY_POSTFIX],E:["Math.E",Blockly.Dart.ORDER_UNARY_POSTFIX],GOLDEN_RATIO:["(1 + Math.sqrt(5)) / 2",Blockly.Dart.ORDER_MULTIPLICATIVE],SQRT2:["Math.SQRT2",Blockly.Dart.ORDER_UNARY_POSTFIX],SQRT1_2:["Math.SQRT1_2",Blockly.Dart.ORDER_UNARY_POSTFIX],INFINITY:["double.INFINITY",Blockly.Dart.ORDER_ATOMIC]};a=a.getFieldValue("CONSTANT");"INFINITY"!=a&&(Blockly.Dart.definitions_.import_dart_math="import 'dart:math' as Math;");
|
||||
return b[a]};
|
||||
Blockly.Dart.math_number_property=function(a){var b=Blockly.Dart.valueToCode(a,"NUMBER_TO_CHECK",Blockly.Dart.ORDER_MULTIPLICATIVE);if(!b)return["false",Blockly.Python.ORDER_ATOMIC];var c=a.getFieldValue("PROPERTY"),d;if("PRIME"==c)return Blockly.Dart.definitions_.import_dart_math="import 'dart:math' as Math;",[Blockly.Dart.provideFunction_("math_isPrime",["bool "+Blockly.Dart.FUNCTION_NAME_PLACEHOLDER_+"(n) {"," // https://en.wikipedia.org/wiki/Primality_test#Naive_methods"," if (n == 2 || n == 3) {"," return true;",
|
||||
" }"," // False if n is null, negative, is 1, or not whole."," // And false if n is divisible by 2 or 3."," if (n == null || n <= 1 || n % 1 != 0 || n % 2 == 0 || n % 3 == 0) {"," return false;"," }"," // Check all the numbers of form 6k +/- 1, up to sqrt(n)."," for (var x = 6; x <= Math.sqrt(n) + 1; x += 6) {"," if (n % (x - 1) == 0 || n % (x + 1) == 0) {"," return false;"," }"," }"," return true;","}"])+"("+b+")",Blockly.Dart.ORDER_UNARY_POSTFIX];switch(c){case "EVEN":d=b+
|
||||
" % 2 == 0";break;case "ODD":d=b+" % 2 == 1";break;case "WHOLE":d=b+" % 1 == 0";break;case "POSITIVE":d=b+" > 0";break;case "NEGATIVE":d=b+" < 0";break;case "DIVISIBLE_BY":a=Blockly.Dart.valueToCode(a,"DIVISOR",Blockly.Dart.ORDER_MULTIPLICATIVE);if(!a)return["false",Blockly.Python.ORDER_ATOMIC];d=b+" % "+a+" == 0"}return[d,Blockly.Dart.ORDER_EQUALITY]};
|
||||
Blockly.Dart.math_number_property=function(a){var b=Blockly.Dart.valueToCode(a,"NUMBER_TO_CHECK",Blockly.Dart.ORDER_MULTIPLICATIVE);if(!b)return["false",Blockly.Python.ORDER_ATOMIC];var c=a.getFieldValue("PROPERTY");if("PRIME"==c)return Blockly.Dart.definitions_.import_dart_math="import 'dart:math' as Math;",[Blockly.Dart.provideFunction_("math_isPrime",["bool "+Blockly.Dart.FUNCTION_NAME_PLACEHOLDER_+"(n) {"," // https://en.wikipedia.org/wiki/Primality_test#Naive_methods"," if (n == 2 || n == 3) {"," return true;",
|
||||
" }"," // False if n is null, negative, is 1, or not whole."," // And false if n is divisible by 2 or 3."," if (n == null || n <= 1 || n % 1 != 0 || n % 2 == 0 || n % 3 == 0) {"," return false;"," }"," // Check all the numbers of form 6k +/- 1, up to sqrt(n)."," for (var x = 6; x <= Math.sqrt(n) + 1; x += 6) {"," if (n % (x - 1) == 0 || n % (x + 1) == 0) {"," return false;"," }"," }"," return true;","}"])+"("+b+")",Blockly.Dart.ORDER_UNARY_POSTFIX];switch(c){case "EVEN":var d=
|
||||
b+" % 2 == 0";break;case "ODD":d=b+" % 2 == 1";break;case "WHOLE":d=b+" % 1 == 0";break;case "POSITIVE":d=b+" > 0";break;case "NEGATIVE":d=b+" < 0";break;case "DIVISIBLE_BY":a=Blockly.Dart.valueToCode(a,"DIVISOR",Blockly.Dart.ORDER_MULTIPLICATIVE);if(!a)return["false",Blockly.Python.ORDER_ATOMIC];d=b+" % "+a+" == 0"}return[d,Blockly.Dart.ORDER_EQUALITY]};
|
||||
Blockly.Dart.math_change=function(a){var b=Blockly.Dart.valueToCode(a,"DELTA",Blockly.Dart.ORDER_ADDITIVE)||"0";a=Blockly.Dart.variableDB_.getName(a.getFieldValue("VAR"),Blockly.Variables.NAME_TYPE);return a+" = ("+a+" is num ? "+a+" : 0) + "+b+";\n"};Blockly.Dart.math_round=Blockly.Dart.math_single;Blockly.Dart.math_trig=Blockly.Dart.math_single;
|
||||
Blockly.Dart.math_on_list=function(a){var b=a.getFieldValue("OP");a=Blockly.Dart.valueToCode(a,"LIST",Blockly.Dart.ORDER_NONE)||"[]";switch(b){case "SUM":b=Blockly.Dart.provideFunction_("math_sum",["num "+Blockly.Dart.FUNCTION_NAME_PLACEHOLDER_+"(List myList) {"," num sumVal = 0;"," myList.forEach((num entry) {sumVal += entry;});"," return sumVal;","}"]);b=b+"("+a+")";break;case "MIN":Blockly.Dart.definitions_.import_dart_math="import 'dart:math' as Math;";b=Blockly.Dart.provideFunction_("math_min",
|
||||
["num "+Blockly.Dart.FUNCTION_NAME_PLACEHOLDER_+"(List myList) {"," if (myList.isEmpty) return null;"," num minVal = myList[0];"," myList.forEach((num entry) {minVal = Math.min(minVal, entry);});"," return minVal;","}"]);b=b+"("+a+")";break;case "MAX":Blockly.Dart.definitions_.import_dart_math="import 'dart:math' as Math;";b=Blockly.Dart.provideFunction_("math_max",["num "+Blockly.Dart.FUNCTION_NAME_PLACEHOLDER_+"(List myList) {"," if (myList.isEmpty) return null;"," num maxVal = myList[0];",
|
||||
|
||||
@@ -4,39 +4,18 @@
|
||||
<meta charset="utf-8">
|
||||
<title>Accessible Blockly Demo</title>
|
||||
|
||||
<!-- Load Blockly -->
|
||||
<script src="../../blockly_compressed.js"></script>
|
||||
<script src="../../blocks_compressed.js"></script>
|
||||
<script src="../../javascript_compressed.js"></script>
|
||||
<script src="../../msg/js/en.js"></script>
|
||||
<script src="../../msg/messages.js"></script>
|
||||
<script src="../../accessible/messages.js"></script>
|
||||
|
||||
<!-- Load accessibleBlockly -->
|
||||
<script src="../../accessible/libs/es6-shim.min.js"></script>
|
||||
<script src="../../accessible/libs/angular2-polyfills.min.js"></script>
|
||||
<script src="../../accessible/libs/Rx.umd.min.js"></script>
|
||||
<script src="../../accessible/libs/angular2-all.umd.min.js"></script>
|
||||
|
||||
<script src="../../accessible/utils.service.js"></script>
|
||||
<script src="../../accessible/notifications.service.js"></script>
|
||||
<script src="../../accessible/audio.service.js"></script>
|
||||
<script src="../../accessible/block-connection.service.js"></script>
|
||||
<script src="../../accessible/block-options-modal.service.js"></script>
|
||||
<script src="../../accessible/keyboard-input.service.js"></script>
|
||||
<script src="../../accessible/tree.service.js"></script>
|
||||
<script src="../../accessible/toolbox-modal.service.js"></script>
|
||||
<script src="../../accessible/translate.pipe.js"></script>
|
||||
<script src="../../accessible/variable-modal.service.js"></script>
|
||||
<!-- Load accessibleBlockly -->
|
||||
<script src="../../blockly_accessible_compressed.js"></script>
|
||||
|
||||
<script src="../../accessible/field-segment.component.js"></script>
|
||||
<script src="../../accessible/block-options-modal.component.js"></script>
|
||||
<script src="../../accessible/toolbox-modal.component.js"></script>
|
||||
<script src="../../accessible/variable-modal.component.js"></script>
|
||||
<script src="../../accessible/sidebar.component.js"></script>
|
||||
<script src="../../accessible/workspace-block.component.js"></script>
|
||||
<script src="../../accessible/workspace.component.js"></script>
|
||||
<script src="../../accessible/app.component.js"></script>
|
||||
<!-- Load Blockly -->
|
||||
<script src="../../msg/js/en.js"></script>
|
||||
<script src="../../accessible/messages.js"></script>
|
||||
<script src="../../blocks_compressed.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="../../accessible/media/accessible.css">
|
||||
<style>
|
||||
@@ -165,13 +144,6 @@
|
||||
</block>
|
||||
</value>
|
||||
</block>
|
||||
<block type="math_change">
|
||||
<value name="DELTA">
|
||||
<block type="math_number">
|
||||
<field name="NUM">1</field>
|
||||
</block>
|
||||
</value>
|
||||
</block>
|
||||
<block type="math_round">
|
||||
<value name="NUM">
|
||||
<block type="math_number">
|
||||
@@ -350,6 +322,7 @@
|
||||
</value>
|
||||
</block>
|
||||
</category>
|
||||
<category name="Variables" colour="330" custom="VARIABLE"></category>
|
||||
</xml>
|
||||
|
||||
</body>
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
/**
|
||||
* @license
|
||||
* Blockly Demos: Block Factory
|
||||
*
|
||||
* Copyright 2017 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Stubbed interface functions for analytics integration.
|
||||
*/
|
||||
|
||||
goog.provide('BlocklyDevTools.Analytics');
|
||||
|
||||
/**
|
||||
* Whether these stub methods should log analytics calls to the console.
|
||||
* @private
|
||||
* @const
|
||||
*/
|
||||
BlocklyDevTools.Analytics.LOG_TO_CONSOLE_ = false;
|
||||
|
||||
/**
|
||||
* An import/export type id for a library of BlockFactory's original block
|
||||
* save files (each a serialized workspace of block definition blocks).
|
||||
* @package
|
||||
* @const
|
||||
*/
|
||||
BlocklyDevTools.Analytics.BLOCK_FACTORY_LIBRARY = "Block Factory library";
|
||||
/**
|
||||
* An import/export type id for a standard Blockly library of block
|
||||
* definitions.
|
||||
* @package
|
||||
* @const
|
||||
*/
|
||||
BlocklyDevTools.Analytics.BLOCK_DEFINITIONS = "Block definitions";
|
||||
/**
|
||||
* An import/export type id for a code generation function, or a
|
||||
* boilerplate stub of the same.
|
||||
*
|
||||
* @package
|
||||
* @const
|
||||
*/
|
||||
BlocklyDevTools.Analytics.GENERATOR = "Generator";
|
||||
/**
|
||||
* An import/export type id for a Blockly Toolbox.
|
||||
*
|
||||
* @package
|
||||
* @const
|
||||
*/
|
||||
BlocklyDevTools.Analytics.TOOLBOX = "Toolbox";
|
||||
/**
|
||||
* An import/export type id for the serialized contents of a workspace.
|
||||
*
|
||||
* @package
|
||||
* @const
|
||||
*/
|
||||
BlocklyDevTools.Analytics.WORKSPACE_CONTENTS = "Workspace contents";
|
||||
|
||||
/**
|
||||
* Format id for imported/exported JavaScript resources.
|
||||
*
|
||||
* @package
|
||||
* @const
|
||||
*/
|
||||
BlocklyDevTools.Analytics.FORMAT_JS = "JavaScript";
|
||||
/**
|
||||
* Format id for imported/exported JSON resources.
|
||||
*
|
||||
* @package
|
||||
* @const
|
||||
*/
|
||||
BlocklyDevTools.Analytics.FORMAT_JSON = "JSON";
|
||||
/**
|
||||
* Format id for imported/exported XML resources.
|
||||
*
|
||||
* @package
|
||||
* @const
|
||||
*/
|
||||
BlocklyDevTools.Analytics.FORMAT_XML = "XML";
|
||||
|
||||
/**
|
||||
* Platform id for resources exported for use in Android projects.
|
||||
*
|
||||
* @package
|
||||
* @const
|
||||
*/
|
||||
BlocklyDevTools.Analytics.PLATFORM_ANDROID = "Android";
|
||||
/**
|
||||
* Platform id for resources exported for use in iOS projects.
|
||||
*
|
||||
* @package
|
||||
* @const
|
||||
*/
|
||||
BlocklyDevTools.Analytics.PLATFORM_IOS = "iOS";
|
||||
/**
|
||||
* Platform id for resources exported for use in web projects.
|
||||
*
|
||||
* @package
|
||||
* @const
|
||||
*/
|
||||
BlocklyDevTools.Analytics.PLATFORM_WEB = "web";
|
||||
|
||||
/**
|
||||
* Initializes the analytics framework, including noting that the page/app was
|
||||
* opened.
|
||||
* @package
|
||||
*/
|
||||
BlocklyDevTools.Analytics.init = function() {
|
||||
// stub
|
||||
this.LOG_TO_CONSOLE_ && console.log('Analytics.init');
|
||||
};
|
||||
|
||||
/**
|
||||
* Event noting the user navigated to a specific view.
|
||||
*
|
||||
* @package
|
||||
* @param viewId {string} An identifier for the view state.
|
||||
*/
|
||||
BlocklyDevTools.Analytics.onNavigateTo = function(viewId) {
|
||||
// stub
|
||||
this.LOG_TO_CONSOLE_ &&
|
||||
console.log('Analytics.onNavigateTo(' + viewId + ')');
|
||||
};
|
||||
|
||||
/**
|
||||
* Event noting a project resource was saved. In the web Block Factory, this
|
||||
* means saved to localStorage.
|
||||
*
|
||||
* @package
|
||||
* @param typeId {string} An identifying string for the saved type.
|
||||
*/
|
||||
BlocklyDevTools.Analytics.onSave = function(typeId) {
|
||||
// stub
|
||||
this.LOG_TO_CONSOLE_ && console.log('Analytics.onSave(' + typeId + ')');
|
||||
};
|
||||
|
||||
/**
|
||||
* Event noting the user attempted to import a resource file.
|
||||
*
|
||||
* @package
|
||||
* @param typeId {string} An identifying string for the imported type.
|
||||
* @param optMetadata {Object} Metadata about the import, such as format and
|
||||
* platform.
|
||||
*/
|
||||
BlocklyDevTools.Analytics.onImport = function(typeId, optMetadata) {
|
||||
// stub
|
||||
this.LOG_TO_CONSOLE_ && console.log('Analytics.onImport(' + typeId +
|
||||
(optMetadata ? '): ' + JSON.stringify(optMetadata) : ')'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Event noting a project resource was saved. In the web Block Factory, this
|
||||
* means downloaded to the user's system.
|
||||
*
|
||||
* @package
|
||||
* @param typeId {string} An identifying string for the exported object type.
|
||||
* @param optMetadata {Object} Metadata about the import, such as format and
|
||||
* platform.
|
||||
*/
|
||||
BlocklyDevTools.Analytics.onExport = function(typeId, optMetadata) {
|
||||
// stub
|
||||
this.LOG_TO_CONSOLE_ && console.log('Analytics.onExport(' + typeId +
|
||||
(optMetadata ? '): ' + JSON.stringify(optMetadata) : ')'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Event noting the system encountered an error. It should attempt to send
|
||||
* immediately.
|
||||
*
|
||||
* @package
|
||||
* @param e {!Object} A value representing or describing the error.
|
||||
*/
|
||||
BlocklyDevTools.Analytics.onError = function(e) {
|
||||
// stub
|
||||
this.LOG_TO_CONSOLE_ &&
|
||||
console.log('Analytics.onError("' + e.toString() + '")');
|
||||
};
|
||||
|
||||
/**
|
||||
* Event noting the user was notified with a warning.
|
||||
*
|
||||
* @package
|
||||
* @param msg {string} The warning message, or a description thereof.
|
||||
*/
|
||||
BlocklyDevTools.Analytics.onWarning = function(msg) {
|
||||
// stub
|
||||
this.LOG_TO_CONSOLE_ && console.log('Analytics.onWarning("' + msg + '")');
|
||||
};
|
||||
|
||||
/**
|
||||
* Request the analytics framework to send any queued events to the server.
|
||||
* @package
|
||||
*/
|
||||
BlocklyDevTools.Analytics.sendQueued = function() {
|
||||
// stub
|
||||
this.LOG_TO_CONSOLE_ && console.log('Analytics.sendQueued');
|
||||
};
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
goog.provide('AppController');
|
||||
|
||||
goog.require('BlockFactory');
|
||||
goog.require('BlocklyDevTools.Analytics');
|
||||
goog.require('FactoryUtils');
|
||||
goog.require('BlockLibraryController');
|
||||
goog.require('BlockExporterController');
|
||||
@@ -85,6 +86,10 @@ AppController.prototype.importBlockLibraryFromFile = function() {
|
||||
var files = document.getElementById('files');
|
||||
// If the file list is empty, the user likely canceled in the dialog.
|
||||
if (files.files.length > 0) {
|
||||
BlocklyDevTools.Analytics.onImport(
|
||||
BlocklyDevTools.Analytics.BLOCK_FACTORY_LIBRARY,
|
||||
{ format: BlocklyDevTools.Analytics.FORMAT_XML });
|
||||
|
||||
// The input tag doesn't have the "multiple" attribute
|
||||
// so the user can only choose 1 file.
|
||||
var file = files.files[0];
|
||||
@@ -137,9 +142,14 @@ AppController.prototype.exportBlockLibraryToFile = function() {
|
||||
// Download file if all necessary parameters are provided.
|
||||
if (filename) {
|
||||
FactoryUtils.createAndDownloadFile(blockLibText, filename, 'xml');
|
||||
BlocklyDevTools.Analytics.onExport(
|
||||
BlocklyDevTools.Analytics.BLOCK_FACTORY_LIBRARY,
|
||||
{ format: BlocklyDevTools.Analytics.FORMAT_XML });
|
||||
} else {
|
||||
alert('Could not export Block Library without file name under which to ' +
|
||||
'save library.');
|
||||
var msg = 'Could not export Block Library without file name under which ' +
|
||||
'to save library.';
|
||||
BlocklyDevTools.Analytics.onWarning(msg);
|
||||
alert(msg);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -201,7 +211,7 @@ AppController.prototype.formatBlockLibraryForImport_ = function(xmlText) {
|
||||
var blockType = this.getBlockTypeFromXml_(xmlText).toLowerCase();
|
||||
// Some names are invalid so fix them up.
|
||||
blockType = FactoryUtils.cleanBlockType(blockType);
|
||||
|
||||
|
||||
blockXmlTextMap[blockType] = xmlText;
|
||||
}
|
||||
|
||||
@@ -285,13 +295,17 @@ AppController.prototype.onTab = function() {
|
||||
|
||||
var hasUnsavedChanges =
|
||||
!FactoryUtils.savedBlockChanges(this.blockLibraryController);
|
||||
if (hasUnsavedChanges &&
|
||||
!confirm('You have unsaved changes in Block Factory.')) {
|
||||
// If the user doesn't want to switch tabs with unsaved changes,
|
||||
// stay on Block Factory Tab.
|
||||
this.setSelected_(AppController.BLOCK_FACTORY);
|
||||
this.lastSelectedTab = AppController.BLOCK_FACTORY;
|
||||
return;
|
||||
if (hasUnsavedChanges) {
|
||||
var msg = 'You have unsaved changes in Block Factory.';
|
||||
var continueAnyway = confirm(msg);
|
||||
BlocklyDevTools.Analytics.onWarning(msg);
|
||||
if (!continueAnyway) {
|
||||
// If the user doesn't want to switch tabs with unsaved changes,
|
||||
// stay on Block Factory Tab.
|
||||
this.setSelected_(AppController.BLOCK_FACTORY);
|
||||
this.lastSelectedTab = AppController.BLOCK_FACTORY;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,6 +318,8 @@ AppController.prototype.onTab = function() {
|
||||
this.styleTabs_();
|
||||
|
||||
if (this.selectedTab == AppController.EXPORTER) {
|
||||
BlocklyDevTools.Analytics.onNavigateTo('Exporter');
|
||||
|
||||
// Hide other tabs.
|
||||
FactoryUtils.hide('workspaceFactoryContent');
|
||||
FactoryUtils.hide('blockFactoryContent');
|
||||
@@ -325,6 +341,8 @@ AppController.prototype.onTab = function() {
|
||||
this.exporter.updatePreview();
|
||||
|
||||
} else if (this.selectedTab == AppController.BLOCK_FACTORY) {
|
||||
BlocklyDevTools.Analytics.onNavigateTo('BlockFactory');
|
||||
|
||||
// Hide other tabs.
|
||||
FactoryUtils.hide('blockLibraryExporter');
|
||||
FactoryUtils.hide('workspaceFactoryContent');
|
||||
@@ -332,6 +350,9 @@ AppController.prototype.onTab = function() {
|
||||
FactoryUtils.show('blockFactoryContent');
|
||||
|
||||
} else if (this.selectedTab == AppController.WORKSPACE_FACTORY) {
|
||||
// TODO: differentiate Workspace and Toolbox editor, based on the other tab state.
|
||||
BlocklyDevTools.Analytics.onNavigateTo('WorkspaceFactory');
|
||||
|
||||
// Hide other tabs.
|
||||
FactoryUtils.hide('blockLibraryExporter');
|
||||
FactoryUtils.hide('blockFactoryContent');
|
||||
@@ -624,12 +645,14 @@ AppController.prototype.onresize = function(event) {
|
||||
* @param {!Event} e beforeunload event.
|
||||
*/
|
||||
AppController.prototype.confirmLeavePage = function(e) {
|
||||
BlocklyDevTools.Analytics.sendQueued();
|
||||
if ((!BlockFactory.isStarterBlock() &&
|
||||
!FactoryUtils.savedBlockChanges(blocklyFactory.blockLibraryController)) ||
|
||||
blocklyFactory.workspaceFactoryController.hasUnsavedChanges()) {
|
||||
|
||||
var confirmationMessage = 'You will lose any unsaved changes. ' +
|
||||
'Are you sure you want to exit this page?';
|
||||
BlocklyDevTools.Analytics.onWarning(confirmationMessage);
|
||||
e.returnValue = confirmationMessage;
|
||||
return confirmationMessage;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
goog.provide('BlockExporterController');
|
||||
|
||||
goog.require('BlocklyDevTools.Analytics');
|
||||
goog.require('FactoryUtils');
|
||||
goog.require('StandardCategories');
|
||||
goog.require('BlockExporterView');
|
||||
@@ -103,7 +104,9 @@ BlockExporterController.prototype.export = function() {
|
||||
// User wants to export selected blocks' definitions.
|
||||
if (!blockDef_filename) {
|
||||
// User needs to enter filename.
|
||||
alert('Please enter a filename for your block definition(s) download.');
|
||||
var msg = 'Please enter a filename for your block definition(s) download.';
|
||||
BlocklyDevTools.Analytics.onWarning(msg);
|
||||
alert(msg);
|
||||
} else {
|
||||
// Get block definition code in the selected format for the blocks.
|
||||
var blockDefs = this.tools.getBlockDefinitions(blockXmlMap,
|
||||
@@ -111,6 +114,13 @@ BlockExporterController.prototype.export = function() {
|
||||
// Download the file, using .js file ending for JSON or Javascript.
|
||||
FactoryUtils.createAndDownloadFile(
|
||||
blockDefs, blockDef_filename, 'javascript');
|
||||
BlocklyDevTools.Analytics.onExport(
|
||||
BlocklyDevTools.Analytics.BLOCK_DEFINITIONS,
|
||||
{
|
||||
format: (definitionFormat == 'JSON' ?
|
||||
BlocklyDevTools.Analytics.FORMAT_JSON :
|
||||
BlocklyDevTools.Analytics.FORMAT_JS)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +128,9 @@ BlockExporterController.prototype.export = function() {
|
||||
// User wants to export selected blocks' generator stubs.
|
||||
if (!generatorStub_filename) {
|
||||
// User needs to enter filename.
|
||||
alert('Please enter a filename for your generator stub(s) download.');
|
||||
var msg = 'Please enter a filename for your generator stub(s) download.';
|
||||
BlocklyDevTools.Analytics.onWarning(msg);
|
||||
alert(msg);
|
||||
} else {
|
||||
// Get generator stub code in the selected language for the blocks.
|
||||
var genStubs = this.tools.getGeneratorCode(blockXmlMap,
|
||||
@@ -128,6 +140,10 @@ BlockExporterController.prototype.export = function() {
|
||||
// Download the file.
|
||||
FactoryUtils.createAndDownloadFile(
|
||||
genStubs, generatorStub_filename, fileType);
|
||||
BlocklyDevTools.Analytics.onExport(
|
||||
BlocklyDevTools.Analytics.GENERATOR,
|
||||
(fileType == 'javascript' ?
|
||||
{ format: BlocklyDevTools.Analytics.FORMAT_JS } : undefined));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
|
||||
goog.provide('BlockLibraryController');
|
||||
|
||||
goog.require('BlocklyDevTools.Analytics');
|
||||
goog.require('BlockLibraryStorage');
|
||||
goog.require('BlockLibraryView');
|
||||
goog.require('BlockFactory');
|
||||
@@ -110,8 +111,9 @@ BlockLibraryController.prototype.getSelectedBlockType = function() {
|
||||
* updating the dropdown and displaying the starter block (factory_base).
|
||||
*/
|
||||
BlockLibraryController.prototype.clearBlockLibrary = function() {
|
||||
var check = confirm('Delete all blocks from library?');
|
||||
if (check) {
|
||||
var msg = 'Delete all blocks from library?';
|
||||
BlocklyDevTools.Analytics.onWarning(msg);
|
||||
if (confirm(msg)) {
|
||||
// Clear Block Library Storage.
|
||||
this.storage.clear();
|
||||
this.storage.saveToLocalStorage();
|
||||
@@ -133,9 +135,11 @@ BlockLibraryController.prototype.saveToBlockLibrary = function() {
|
||||
// If user has not changed the name of the starter block.
|
||||
if (blockType == 'block_type') {
|
||||
// Do not save block if it has the default type, 'block_type'.
|
||||
alert('You cannot save a block under the name "block_type". Try changing ' +
|
||||
'the name before saving. Then, click on the "Block Library" button ' +
|
||||
'to view your saved blocks.');
|
||||
var msg = 'You cannot save a block under the name "block_type". Try ' +
|
||||
'changing the name before saving. Then, click on the "Block Library"' +
|
||||
' button to view your saved blocks.';
|
||||
alert(msg);
|
||||
BlocklyDevTools.Analytics.onWarning(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -159,6 +163,7 @@ BlockLibraryController.prototype.saveToBlockLibrary = function() {
|
||||
|
||||
// Add select handler to the new option.
|
||||
this.addOptionSelectHandler(blockType);
|
||||
BlocklyDevTools.Analytics.onSave('Block');
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -352,8 +352,8 @@ FactoryUtils.formatJavaScript_ = function(blockType, rootBlock, workspace) {
|
||||
|
||||
var tooltip = FactoryUtils.getTooltipFromRootBlock_(rootBlock);
|
||||
var helpUrl = FactoryUtils.getHelpUrlFromRootBlock_(rootBlock);
|
||||
code.push(" this.setTooltip('" + tooltip + "');");
|
||||
code.push(" this.setHelpUrl('" + helpUrl + "');");
|
||||
code.push(' this.setTooltip(' + JSON.stringify(tooltip) + ');');
|
||||
code.push(' this.setHelpUrl(' + JSON.stringify(helpUrl) + ');');
|
||||
code.push(' }');
|
||||
code.push('};');
|
||||
return code.join('\n');
|
||||
@@ -875,7 +875,7 @@ FactoryUtils.injectCode = function(code, id) {
|
||||
var pre = document.getElementById(id);
|
||||
pre.textContent = code;
|
||||
code = pre.textContent;
|
||||
code = prettyPrintOne(code, 'js');
|
||||
code = PR.prettyPrintOne(code, 'js');
|
||||
pre.innerHTML = code;
|
||||
};
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<script src="../../msg/js/en.js"></script>
|
||||
<script src="../../blocks_compressed.js"></script>
|
||||
<script src="../../../closure-library/closure/goog/base.js"></script>
|
||||
<script src="analytics.js"></script>
|
||||
<script src="factory_utils.js"></script>
|
||||
<script src="workspacefactory/wfactory_model.js"></script>
|
||||
<script src="standard_categories.js"></script>
|
||||
@@ -28,11 +29,12 @@
|
||||
<script src="app_controller.js"></script>
|
||||
<script src="/storage.js"></script>
|
||||
<link rel="stylesheet" href="factory.css">
|
||||
<link rel="stylesheet" href="../prettify.css">
|
||||
<script src="../prettify.js"></script>
|
||||
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></script>
|
||||
<script>
|
||||
var blocklyFactory;
|
||||
var init = function() {
|
||||
BlocklyDevTools.Analytics.init();
|
||||
|
||||
blocklyFactory = new AppController();
|
||||
blocklyFactory.init();
|
||||
window.addEventListener('beforeunload', blocklyFactory.confirmLeavePage);
|
||||
|
||||
@@ -34,8 +34,9 @@
|
||||
* @author Emma Dauterman (evd2014)
|
||||
*/
|
||||
|
||||
goog.require('FactoryUtils');
|
||||
goog.require('StandardCategories');
|
||||
goog.require('BlocklyDevTools.Analytics');
|
||||
goog.require('FactoryUtils');
|
||||
goog.require('StandardCategories');
|
||||
|
||||
|
||||
/**
|
||||
@@ -342,13 +343,25 @@ WorkspaceFactoryController.prototype.exportXmlFile = function(exportMode) {
|
||||
this.hasUnsavedPreloadChanges = false;
|
||||
} else {
|
||||
// Unknown mode. Throw error.
|
||||
throw new Error ("Unknown export mode: " + exportMode);
|
||||
var msg = "Unknown export mode: " + exportMode;
|
||||
BlocklyDevTools.Analytics.onError(msg);
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
// Download file.
|
||||
var data = new Blob([configXml], {type: 'text/xml'});
|
||||
this.view.createAndDownloadFile(fileName, data);
|
||||
};
|
||||
|
||||
if (exportMode == WorkspaceFactoryController.MODE_TOOLBOX) {
|
||||
BlocklyDevTools.Analytics.onExport(
|
||||
BlocklyDevTools.Analytics.TOOLBOX,
|
||||
{ format: BlocklyDevTools.Analytics.FORMAT_XML });
|
||||
} else if (exportMode == WorkspaceFactoryController.MODE_PRELOAD) {
|
||||
BlocklyDevTools.Analytics.onExport(
|
||||
BlocklyDevTools.Analytics.WORKSPACE_CONTENTS,
|
||||
{ format: BlocklyDevTools.Analytics.FORMAT_XML });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Export the options object to be used for the Blockly inject call. Gets a
|
||||
@@ -366,6 +379,13 @@ WorkspaceFactoryController.prototype.exportInjectFile = function() {
|
||||
var printableOptions = this.generator.generateInjectString()
|
||||
var data = new Blob([printableOptions], {type: 'text/javascript'});
|
||||
this.view.createAndDownloadFile(fileName, data);
|
||||
|
||||
BlocklyDevTools.Analytics.onExport(
|
||||
BlocklyDevTools.Analytics.STARTER_CODE,
|
||||
{
|
||||
format: BlocklyDevTools.Analytics.FORMAT_JS,
|
||||
platform: BlocklyDevTools.Analytics.PLATFORM_WEB
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -729,33 +749,45 @@ WorkspaceFactoryController.prototype.importFile = function(file, importMode) {
|
||||
// Confirm that the user wants to override their current toolbox.
|
||||
var hasToolboxElements = controller.model.hasElements() ||
|
||||
controller.toolboxWorkspace.getAllBlocks().length > 0;
|
||||
if (hasToolboxElements &&
|
||||
!confirm('Are you sure you want to import? You will lose your ' +
|
||||
'current toolbox.')) {
|
||||
return;
|
||||
if (hasToolboxElements) {
|
||||
var msg = 'Are you sure you want to import? You will lose your ' +
|
||||
'current toolbox.';
|
||||
BlocklyDevTools.Analytics.onWarning(msg);
|
||||
var continueAnyway = confirm();
|
||||
if (!continueAnyway) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Import toolbox XML.
|
||||
controller.importToolboxFromTree_(tree);
|
||||
BlocklyDevTools.Analytics.onImport('Toolbox.xml');
|
||||
|
||||
} else if (importMode == WorkspaceFactoryController.MODE_PRELOAD) {
|
||||
// Switch mode.
|
||||
controller.setMode(WorkspaceFactoryController.MODE_PRELOAD);
|
||||
|
||||
// Confirm that the user wants to override their current blocks.
|
||||
if (controller.toolboxWorkspace.getAllBlocks().length > 0 &&
|
||||
!confirm('Are you sure you want to import? You will lose your ' +
|
||||
'current workspace blocks.')) {
|
||||
if (controller.toolboxWorkspace.getAllBlocks().length > 0) {
|
||||
var msg = 'Are you sure you want to import? You will lose your ' +
|
||||
'current workspace blocks.';
|
||||
var continueAnyway = confirm(msg);
|
||||
BlocklyDevTools.Analytics.onWarning(msg);
|
||||
if (!continueAnyway) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Import pre-loaded workspace XML.
|
||||
controller.importPreloadFromTree_(tree);
|
||||
BlocklyDevTools.Analytics.onImport('WorkspaceContents.xml');
|
||||
} else {
|
||||
// Throw error if invalid mode.
|
||||
throw new Error("Unknown import mode: " + importMode);
|
||||
}
|
||||
} catch(e) {
|
||||
alert('Cannot load XML from file.');
|
||||
var msg = 'Cannot load XML from file.';
|
||||
alert(msg);
|
||||
BlocklyDevTools.Analytics.onError(msg);
|
||||
console.log(e);
|
||||
} finally {
|
||||
Blockly.Events.enable();
|
||||
@@ -888,8 +920,10 @@ WorkspaceFactoryController.prototype.importPreloadFromTree_ = function(tree) {
|
||||
* "Clear" button.
|
||||
*/
|
||||
WorkspaceFactoryController.prototype.clearAll = function() {
|
||||
if (!confirm('Are you sure you want to clear all of your work in Workspace' +
|
||||
' Factory?')) {
|
||||
var msg = 'Are you sure you want to clear all of your work in Workspace' +
|
||||
' Factory?';
|
||||
BlocklyDevTools.Analytics.onWarning(msg);
|
||||
if (!confirm(msg)) {
|
||||
return;
|
||||
}
|
||||
var hasCategories = this.model.hasElements();
|
||||
@@ -1213,11 +1247,15 @@ WorkspaceFactoryController.prototype.importBlocks = function(file, format) {
|
||||
|
||||
// If an imported block type is already defined, check if the user wants
|
||||
// to override the current block definition.
|
||||
if (controller.model.hasDefinedBlockTypes(blockTypes) &&
|
||||
!confirm('An imported block uses the same name as a block '
|
||||
if (controller.model.hasDefinedBlockTypes(blockTypes)) {
|
||||
var msg = 'An imported block uses the same name as a block '
|
||||
+ 'already in your toolbox. Are you sure you want to override the '
|
||||
+ 'currently defined block?')) {
|
||||
+ 'currently defined block?';
|
||||
var continueAnyway = confirm(msg);
|
||||
BlocklyDevTools.Analytics.onWarning(msg);
|
||||
if (!continueAnyway) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var blocks = controller.generator.getDefinedBlocks(blockTypes);
|
||||
@@ -1234,8 +1272,13 @@ WorkspaceFactoryController.prototype.importBlocks = function(file, format) {
|
||||
// Reload current category to possibly reflect any newly defined blocks.
|
||||
controller.clearAndLoadXml_
|
||||
(Blockly.Xml.workspaceToDom(controller.toolboxWorkspace));
|
||||
|
||||
BlocklyDevTools.Analytics.onImport('BlockDefinitions' +
|
||||
(format == 'JSON' ? '.json' : '.js'));
|
||||
} catch (e) {
|
||||
alert('Cannot read blocks from file.');
|
||||
msg = 'Cannot read blocks from file.';
|
||||
alert(msg);
|
||||
BlocklyDevTools.Analytics.onError(msg);
|
||||
window.console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,8 +396,9 @@ WorkspaceFactoryInit.addWorkspaceFactoryEventListeners_ = function(controller) {
|
||||
// Not listening for Blockly create events because causes the user to drop
|
||||
// blocks when dragging them into workspace. Could cause problems if ever
|
||||
// load blocks into workspace directly without calling updatePreview.
|
||||
if (e.type == Blockly.Events.MOVE || e.type == Blockly.Events.DELETE ||
|
||||
e.type == Blockly.Events.CHANGE) {
|
||||
if (e.type == Blockly.Events.BLOCK_MOVE ||
|
||||
e.type == Blockly.Events.BLOCK_DELETE ||
|
||||
e.type == Blockly.Events.BLOCK_CHANGE) {
|
||||
controller.saveStateFromWorkspace();
|
||||
controller.updatePreview();
|
||||
}
|
||||
@@ -406,7 +407,7 @@ WorkspaceFactoryInit.addWorkspaceFactoryEventListeners_ = function(controller) {
|
||||
// Only enable "Edit Block" when a block is selected and it has a
|
||||
// surrounding parent, meaning it is nested in another block (blocks that
|
||||
// are not nested in parents cannot be shadow blocks).
|
||||
if (e.type == Blockly.Events.MOVE || (e.type == Blockly.Events.UI &&
|
||||
if (e.type == Blockly.Events.BLOCK_MOVE || (e.type == Blockly.Events.UI &&
|
||||
e.element == 'selected')) {
|
||||
var selected = Blockly.selected;
|
||||
|
||||
@@ -480,7 +481,7 @@ WorkspaceFactoryInit.addWorkspaceFactoryEventListeners_ = function(controller) {
|
||||
|
||||
// Convert actual shadow blocks added from the toolbox to user-generated
|
||||
// shadow blocks.
|
||||
if (e.type == Blockly.Events.CREATE) {
|
||||
if (e.type == Blockly.Events.BLOCK_CREATE) {
|
||||
controller.convertShadowBlocks();
|
||||
|
||||
// Let the user create a Variables or Functions category if they use
|
||||
|
||||
@@ -749,7 +749,7 @@ function injectCode(code, id) {
|
||||
var pre = document.getElementById(id);
|
||||
pre.textContent = code;
|
||||
code = pre.textContent;
|
||||
code = prettyPrintOne(code, 'js');
|
||||
code = PR.prettyPrintOne(code, 'js');
|
||||
pre.innerHTML = code;
|
||||
}
|
||||
|
||||
|
||||
@@ -88,8 +88,7 @@
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="../prettify.css">
|
||||
<script src="../prettify.js"></script>
|
||||
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<table>
|
||||
|
||||
+26
-20
@@ -200,14 +200,8 @@ Code.bindClick = function(el, func) {
|
||||
* Load the Prettify CSS and JavaScript.
|
||||
*/
|
||||
Code.importPrettify = function() {
|
||||
//<link rel="stylesheet" href="../prettify.css">
|
||||
//<script src="../prettify.js"></script>
|
||||
var link = document.createElement('link');
|
||||
link.setAttribute('rel', 'stylesheet');
|
||||
link.setAttribute('href', '../prettify.css');
|
||||
document.head.appendChild(link);
|
||||
var script = document.createElement('script');
|
||||
script.setAttribute('src', '../prettify.js');
|
||||
script.setAttribute('src', 'https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js');
|
||||
document.head.appendChild(script);
|
||||
};
|
||||
|
||||
@@ -313,41 +307,41 @@ Code.renderContent = function() {
|
||||
} else if (content.id == 'content_javascript') {
|
||||
var code = Blockly.JavaScript.workspaceToCode(Code.workspace);
|
||||
content.textContent = code;
|
||||
if (typeof prettyPrintOne == 'function') {
|
||||
if (typeof PR.prettyPrintOne == 'function') {
|
||||
code = content.textContent;
|
||||
code = prettyPrintOne(code, 'js');
|
||||
code = PR.prettyPrintOne(code, 'js');
|
||||
content.innerHTML = code;
|
||||
}
|
||||
} else if (content.id == 'content_python') {
|
||||
code = Blockly.Python.workspaceToCode(Code.workspace);
|
||||
content.textContent = code;
|
||||
if (typeof prettyPrintOne == 'function') {
|
||||
if (typeof PR.prettyPrintOne == 'function') {
|
||||
code = content.textContent;
|
||||
code = prettyPrintOne(code, 'py');
|
||||
code = PR.prettyPrintOne(code, 'py');
|
||||
content.innerHTML = code;
|
||||
}
|
||||
} else if (content.id == 'content_php') {
|
||||
code = Blockly.PHP.workspaceToCode(Code.workspace);
|
||||
content.textContent = code;
|
||||
if (typeof prettyPrintOne == 'function') {
|
||||
if (typeof PR.prettyPrintOne == 'function') {
|
||||
code = content.textContent;
|
||||
code = prettyPrintOne(code, 'php');
|
||||
code = PR.prettyPrintOne(code, 'php');
|
||||
content.innerHTML = code;
|
||||
}
|
||||
} else if (content.id == 'content_dart') {
|
||||
code = Blockly.Dart.workspaceToCode(Code.workspace);
|
||||
content.textContent = code;
|
||||
if (typeof prettyPrintOne == 'function') {
|
||||
if (typeof PR.prettyPrintOne == 'function') {
|
||||
code = content.textContent;
|
||||
code = prettyPrintOne(code, 'dart');
|
||||
code = PR.prettyPrintOne(code, 'dart');
|
||||
content.innerHTML = code;
|
||||
}
|
||||
} else if (content.id == 'content_lua') {
|
||||
code = Blockly.Lua.workspaceToCode(Code.workspace);
|
||||
content.textContent = code;
|
||||
if (typeof prettyPrintOne == 'function') {
|
||||
if (typeof PR.prettyPrintOne == 'function') {
|
||||
code = content.textContent;
|
||||
code = prettyPrintOne(code, 'lua');
|
||||
code = PR.prettyPrintOne(code, 'lua');
|
||||
content.innerHTML = code;
|
||||
}
|
||||
}
|
||||
@@ -383,10 +377,22 @@ Code.init = function() {
|
||||
};
|
||||
window.addEventListener('resize', onresize, false);
|
||||
|
||||
// Interpolate translated messages into toolbox.
|
||||
// The toolbox XML specifies each category name using Blockly's messaging
|
||||
// format (eg. `<category name="%{BKY_CATLOGIC}">`).
|
||||
// These message keys need to be defined in `Blockly.Msg` in order to
|
||||
// be decoded by the library. Therefore, we'll use the `MSG` dictionary that's
|
||||
// been defined for each language to import each category name message
|
||||
// into `Blockly.Msg`.
|
||||
// TODO: Clean up the message files so this is done explicitly instead of
|
||||
// through this for-loop.
|
||||
for (var messageKey in MSG) {
|
||||
if (goog.string.startsWith(messageKey, 'cat')) {
|
||||
Blockly.Msg[messageKey.toUpperCase()] = MSG[messageKey];
|
||||
}
|
||||
}
|
||||
|
||||
// Construct the toolbox XML.
|
||||
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',
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
<textarea id="content_xml" class="content" wrap="off"></textarea>
|
||||
|
||||
<xml id="toolbox" style="display: none">
|
||||
<category name="{catLogic}" colour="210">
|
||||
<category name="%{BKY_CATLOGIC}" colour="%{BKY_LOGIC_HUE}">
|
||||
<block type="controls_if"></block>
|
||||
<block type="logic_compare"></block>
|
||||
<block type="logic_operation"></block>
|
||||
@@ -83,7 +83,7 @@
|
||||
<block type="logic_null"></block>
|
||||
<block type="logic_ternary"></block>
|
||||
</category>
|
||||
<category name="{catLoops}" colour="120">
|
||||
<category name="%{BKY_CATLOOPS}" colour="%{BKY_LOOPS_HUE}">
|
||||
<block type="controls_repeat_ext">
|
||||
<value name="TIMES">
|
||||
<shadow type="math_number">
|
||||
@@ -112,7 +112,7 @@
|
||||
<block type="controls_forEach"></block>
|
||||
<block type="controls_flow_statements"></block>
|
||||
</category>
|
||||
<category name="{catMath}" colour="230">
|
||||
<category name="%{BKY_CATMATH}" colour="%{BKY_MATH_HUE}">
|
||||
<block type="math_number"></block>
|
||||
<block type="math_arithmetic">
|
||||
<value name="A">
|
||||
@@ -199,7 +199,7 @@
|
||||
</block>
|
||||
<block type="math_random_float"></block>
|
||||
</category>
|
||||
<category name="{catText}" colour="160">
|
||||
<category name="%{BKY_CATTEXT}" colour="%{BKY_TEXTS_HUE}">
|
||||
<block type="text"></block>
|
||||
<block type="text_join"></block>
|
||||
<block type="text_append">
|
||||
@@ -276,7 +276,7 @@
|
||||
</value>
|
||||
</block>
|
||||
</category>
|
||||
<category name="{catLists}" colour="260">
|
||||
<category name="%{BKY_CATLISTS}" colour="%{BKY_LISTS_HUE}">
|
||||
<block type="lists_create_with">
|
||||
<mutation items="0"></mutation>
|
||||
</block>
|
||||
@@ -327,7 +327,7 @@
|
||||
</block>
|
||||
<block type="lists_sort"></block>
|
||||
</category>
|
||||
<category name="{catColour}" colour="20">
|
||||
<category name="%{BKY_CATCOLOUR}" colour="%{BKY_COLOUR_HUE}">
|
||||
<block type="colour_picker"></block>
|
||||
<block type="colour_random"></block>
|
||||
<block type="colour_rgb">
|
||||
@@ -366,8 +366,8 @@
|
||||
</block>
|
||||
</category>
|
||||
<sep></sep>
|
||||
<category name="{catVariables}" colour="330" custom="VARIABLE"></category>
|
||||
<category name="{catFunctions}" colour="290" custom="PROCEDURE"></category>
|
||||
<category name="%{BKY_CATVARIABLES}" colour="%{BKY_VARIABLES_HUE}" custom="VARIABLE"></category>
|
||||
<category name="%{BKY_CATFUNCTIONS}" colour="%{BKY_PROCEDURES_HUE}" custom="PROCEDURE"></category>
|
||||
</xml>
|
||||
|
||||
</body>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
var MSG = {
|
||||
title: "Tangalt",
|
||||
blocks: "Iḥedran",
|
||||
linkTooltip: "Sekles sakin cudd ar iḥedran.",
|
||||
runTooltip: "Senker ahil i d-sbadun iḥedran di tallunt n umahil.",
|
||||
badCode: "Tuccḍa deg ahil :\n%1",
|
||||
timeout: "Amḍan afellay n wallusen n uselkem iɛeddan.",
|
||||
trashTooltip: "Ḍeggeṛ akk iḥedran.",
|
||||
catLogic: "Tameẓla",
|
||||
catLoops: "Tineddicin",
|
||||
catMath: "Tusnakt",
|
||||
catText: "Aḍris",
|
||||
catLists: "Tibdarin",
|
||||
catColour: "Ini",
|
||||
catVariables: "Imuttiyen",
|
||||
catFunctions: "Tiwiriwin",
|
||||
listVariable: "tabdart",
|
||||
textVariable: "aḍris",
|
||||
httpRequestError: "yella ugur deg usuter.",
|
||||
linkAlert: "Bḍu iḥedran-ik s useqdec n useɣwen-agi:\n\n%1",
|
||||
hashError: "Suref-aɣ, '%1' ur imenṭaḍ ulad d yiwen n wahil yettwaskelsen.",
|
||||
xmlError: "Ur izmir ara ad d-isali afaylu n usekles. Ahatyettwarna s lqem-nniḍen n Blockly?",
|
||||
badXml: "Tuccḍa di tesleḍt n XML :\n%1\n\nFren 'IH' akken ad tzgleḍ asnifel-ik 'Sefsex' akken ad tkemmleḍ asnifel n XML."
|
||||
};
|
||||
Alguns arquivos não foram exibidos porque demasiados arquivos foram alterados neste diff Mostrar Mais
Referência em uma Nova Issue
Bloquear um usuário