diff --git a/.eslintrc b/.eslintrc index bba9e1ba2..9530b7666 100644 --- a/.eslintrc +++ b/.eslintrc @@ -2,13 +2,34 @@ "rules": { "curly": ["error", "multi-line"], "eol-last": ["error"], - "indent": ["error", 2, {"SwitchCase": 1}], # Blockly/Google use 2-space indents + "indent": [ + "error", 2, # Blockly/Google use 2-space indents + # Blockly/Google uses +4 space indents for line continuations. + { + "SwitchCase": 1, + "MemberExpression": 2, + "ObjectExpression": 1, + "FunctionDeclaration": { + "body": 1, + "parameters": 2 + }, + "FunctionExpression": { + "body": 1, + "parameters": 2 + }, + "CallExpression": { + "arguments": 2 + }, + # Ignore default rules for ternary expressions. + "ignoredNodes": ["ConditionalExpression"] + } + ], "linebreak-style": ["error", "unix"], "max-len": ["error", 120, 4], "no-trailing-spaces": ["error", { "skipBlankLines": true }], "no-unused-vars": ["error", {"args": "after-used", "varsIgnorePattern": "^_"}], "no-use-before-define": ["error"], - "quotes": ["off"], # Blockly mixes single and double quotes + "quotes": ["off"], # Blockly uses single quotes except for JSON blobs, which must use double quotes. "semi": ["error", "always"], "space-before-function-paren": ["error", "never"], # Blockly doesn't have space before function paren "strict": ["off"], # Blockly uses 'use strict' in files diff --git a/blockly_accessible_compressed.js b/blockly_accessible_compressed.js index fae52629e..135a8e64d 100644 --- a/blockly_accessible_compressed.js +++ b/blockly_accessible_compressed.js @@ -3,19 +3,19 @@ var $jscomp=$jscomp||{};$jscomp.scope={};var COMPILED=!0,goog=goog||{};goog.global=this;goog.isDef=function(a){return void 0!==a};goog.isString=function(a){return"string"==typeof a};goog.isBoolean=function(a){return"boolean"==typeof a};goog.isNumber=function(a){return"number"==typeof a}; goog.exportPath_=function(a,b,c){a=a.split(".");c=c||goog.global;a[0]in c||!c.execScript||c.execScript("var "+a[0]);for(var d;a.length&&(d=a.shift());)!a.length&&goog.isDef(b)?c[d]=b:c=c[d]&&c[d]!==Object.prototype[d]?c[d]:c[d]={}}; -goog.define=function(a,b){COMPILED||(goog.global.CLOSURE_UNCOMPILED_DEFINES&&void 0===goog.global.CLOSURE_UNCOMPILED_DEFINES.nodeType&&Object.prototype.hasOwnProperty.call(goog.global.CLOSURE_UNCOMPILED_DEFINES,a)?b=goog.global.CLOSURE_UNCOMPILED_DEFINES[a]:goog.global.CLOSURE_DEFINES&&void 0===goog.global.CLOSURE_DEFINES.nodeType&&Object.prototype.hasOwnProperty.call(goog.global.CLOSURE_DEFINES,a)&&(b=goog.global.CLOSURE_DEFINES[a]));goog.exportPath_(a,b)};goog.DEBUG=!1;goog.LOCALE="en"; -goog.TRUSTED_SITE=!0;goog.STRICT_MODE_COMPATIBLE=!1;goog.DISALLOW_TEST_ONLY_CODE=COMPILED&&!goog.DEBUG;goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING=!1;goog.provide=function(a){if(goog.isInModuleLoader_())throw Error("goog.provide can not be used within a goog.module.");if(!COMPILED&&goog.isProvided_(a))throw Error('Namespace "'+a+'" already declared.');goog.constructNamespace_(a)}; -goog.constructNamespace_=function(a,b){if(!COMPILED){delete goog.implicitNamespaces_[a];for(var c=a;(c=c.substring(0,c.lastIndexOf(".")))&&!goog.getObjectByName(c);)goog.implicitNamespaces_[c]=!0}goog.exportPath_(a,b)};goog.VALID_MODULE_RE_=/^[a-zA-Z_$][a-zA-Z0-9._$]*$/; -goog.module=function(a){if(!goog.isString(a)||!a||-1==a.search(goog.VALID_MODULE_RE_))throw Error("Invalid module identifier");if(!goog.isInModuleLoader_())throw Error("Module "+a+" has been loaded incorrectly. Note, modules cannot be loaded as normal scripts. They require some kind of pre-processing step. You're likely trying to load a module via a script tag or as a part of a concatenated bundle without rewriting the module. For more info see: https://github.com/google/closure-library/wiki/goog.module:-an-ES6-module-like-alternative-to-goog.provide.");if(goog.moduleLoaderState_.moduleName)throw Error("goog.module may only be called once per module."); -goog.moduleLoaderState_.moduleName=a;if(!COMPILED){if(goog.isProvided_(a))throw Error('Namespace "'+a+'" already declared.');delete goog.implicitNamespaces_[a]}};goog.module.get=function(a){return goog.module.getInternal_(a)};goog.module.getInternal_=function(a){if(!COMPILED){if(a in goog.loadedModules_)return goog.loadedModules_[a];if(!goog.implicitNamespaces_[a])return a=goog.getObjectByName(a),null!=a?a:null}return null};goog.moduleLoaderState_=null; -goog.isInModuleLoader_=function(){return null!=goog.moduleLoaderState_};goog.module.declareLegacyNamespace=function(){if(!COMPILED&&!goog.isInModuleLoader_())throw Error("goog.module.declareLegacyNamespace must be called from within a goog.module");if(!COMPILED&&!goog.moduleLoaderState_.moduleName)throw Error("goog.module must be called prior to goog.module.declareLegacyNamespace.");goog.moduleLoaderState_.declareLegacyNamespace=!0}; +goog.define=function(a,b){if(!COMPILED){var c=goog.global.CLOSURE_UNCOMPILED_DEFINES,d=goog.global.CLOSURE_DEFINES;c&&void 0===c.nodeType&&Object.prototype.hasOwnProperty.call(c,a)?b=c[a]:d&&void 0===d.nodeType&&Object.prototype.hasOwnProperty.call(d,a)&&(b=d[a])}goog.exportPath_(a,b)};goog.DEBUG=!1;goog.LOCALE="en";goog.TRUSTED_SITE=!0;goog.STRICT_MODE_COMPATIBLE=!1;goog.DISALLOW_TEST_ONLY_CODE=COMPILED&&!goog.DEBUG;goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING=!1; +goog.provide=function(a){if(goog.isInModuleLoader_())throw Error("goog.provide can not be used within a goog.module.");if(!COMPILED&&goog.isProvided_(a))throw Error('Namespace "'+a+'" already declared.');goog.constructNamespace_(a)};goog.constructNamespace_=function(a,b){if(!COMPILED){delete goog.implicitNamespaces_[a];for(var c=a;(c=c.substring(0,c.lastIndexOf(".")))&&!goog.getObjectByName(c);)goog.implicitNamespaces_[c]=!0}goog.exportPath_(a,b)};goog.VALID_MODULE_RE_=/^[a-zA-Z_$][a-zA-Z0-9._$]*$/; +goog.module=function(a){if(!goog.isString(a)||!a||-1==a.search(goog.VALID_MODULE_RE_))throw Error("Invalid module identifier");if(!goog.isInModuleLoader_())throw Error("Module "+a+" has been loaded incorrectly. Note, modules cannot be loaded as normal scripts. They require some kind of pre-processing step. You're likely trying to load a module via a script tag or as a part of a concatenated bundle without rewriting the module. For more info see: https://github.com/google/closure-library/wiki/goog.module:-an-ES6-module-like-alternative-to-goog.provide."); +if(goog.moduleLoaderState_.moduleName)throw Error("goog.module may only be called once per module.");goog.moduleLoaderState_.moduleName=a;if(!COMPILED){if(goog.isProvided_(a))throw Error('Namespace "'+a+'" already declared.');delete goog.implicitNamespaces_[a]}};goog.module.get=function(a){return goog.module.getInternal_(a)}; +goog.module.getInternal_=function(a){if(!COMPILED){if(a in goog.loadedModules_)return goog.loadedModules_[a];if(!goog.implicitNamespaces_[a])return a=goog.getObjectByName(a),null!=a?a:null}return null};goog.moduleLoaderState_=null;goog.isInModuleLoader_=function(){return null!=goog.moduleLoaderState_}; +goog.module.declareLegacyNamespace=function(){if(!COMPILED&&!goog.isInModuleLoader_())throw Error("goog.module.declareLegacyNamespace must be called from within a goog.module");if(!COMPILED&&!goog.moduleLoaderState_.moduleName)throw Error("goog.module must be called prior to goog.module.declareLegacyNamespace.");goog.moduleLoaderState_.declareLegacyNamespace=!0}; goog.setTestOnly=function(a){if(goog.DISALLOW_TEST_ONLY_CODE)throw a=a||"",Error("Importing test-only code into non-debug environment"+(a?": "+a:"."));};goog.forwardDeclare=function(a){};COMPILED||(goog.isProvided_=function(a){return a in goog.loadedModules_||!goog.implicitNamespaces_[a]&&goog.isDefAndNotNull(goog.getObjectByName(a))},goog.implicitNamespaces_={"goog.module":!0}); goog.getObjectByName=function(a,b){a=a.split(".");b=b||goog.global;for(var c=0;cNumber(a[1])?!1:b('(()=>{"use strict";class X{constructor(){if(new.target!=String)throw 1;this.x=42}}let q=Reflect.construct(X,[],String);if(q.x!=42||!(q instanceof String))throw 1;for(const a of[2,3]){if(a==2)continue;function f(z={a}){let a=0;return z.a}{function f(){return 0;}}return f()==3}})()')});a("es6-impl",function(){return!0});a("es7",function(){return b("2 ** 2 == 4")});a("es8",function(){return b("async () => 1, true")});return c},goog.Transpiler.prototype.needsTranspile= -function(a){if("always"==goog.TRANSPILE)return!0;if("never"==goog.TRANSPILE)return!1;this.requiresTranspilation_||(this.requiresTranspilation_=this.createRequiresTranspilation_());if(a in this.requiresTranspilation_)return this.requiresTranspilation_[a];throw Error("Unknown language mode: "+a);},goog.Transpiler.prototype.transpile=function(a,b){return goog.transpile_(a,b)},goog.transpiler_=new goog.Transpiler,goog.DebugLoader=function(){this.dependencies_={loadFlags:{},nameToPath:{},requires:{},visited:{}, -written:{},deferred:{}};this.oldIeWaiting_=!1;this.queuedModules_=[];this.lastNonModuleScriptIndex_=0},goog.DebugLoader.IS_OLD_IE_=!(goog.global.atob||!goog.global.document||!goog.global.document.all),goog.DebugLoader.prototype.earlyProcessLoad=function(a){goog.DebugLoader.IS_OLD_IE_&&this.maybeProcessDeferredDep_(a)},goog.DebugLoader.prototype.load=function(a){var b=this.getPathFromDeps_(a);if(b){var c=function(a){if(!(a in f.written||a in f.visited)){f.visited[a]=!0;if(a in f.requires)for(var b in f.requires[a])if(!g.isProvided(b))if(b in -f.nameToPath)c(f.nameToPath[b]);else throw Error("Undefined nameToPath for "+b);a in e||(e[a]=!0,d.push(a))}},d=[],e={},f=this.dependencies_,g=this;c(b);for(a=0;a\x3c/script>')},goog.DebugLoader.prototype.appendScriptSrcNode_=function(a){var b=goog.global.document,c=b.createElement("script");c.type="text/javascript";c.src=a;c.defer=!1;c.async=!1;b.head.appendChild(c)},goog.DebugLoader.prototype.writeScriptTag_=function(a,b){if(this.inHtmlDocument()){var c=goog.global.document;if(!goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING&& -"complete"==c.readyState){if(/\bdeps.js$/.test(a))return!1;throw Error('Cannot write "'+a+'" after document load');}void 0===b?goog.DebugLoader.IS_OLD_IE_?(this.oldIeWaiting_=!0,b=" onreadystatechange='goog.debugLoader_.onScriptLoad_(this, "+ ++this.lastNonModuleScriptIndex_+")' ",c.write(''); // Load fresh Closure Library. - document.write(''); - document.write(''); + document.write(''); } diff --git a/blockly_compressed.js b/blockly_compressed.js index 75586e3d7..d8878a006 100644 --- a/blockly_compressed.js +++ b/blockly_compressed.js @@ -3,19 +3,19 @@ var $jscomp=$jscomp||{};$jscomp.scope={};var COMPILED=!0,goog=goog||{};goog.global=this;goog.isDef=function(a){return void 0!==a};goog.isString=function(a){return"string"==typeof a};goog.isBoolean=function(a){return"boolean"==typeof a};goog.isNumber=function(a){return"number"==typeof a}; goog.exportPath_=function(a,b,c){a=a.split(".");c=c||goog.global;a[0]in c||!c.execScript||c.execScript("var "+a[0]);for(var d;a.length&&(d=a.shift());)!a.length&&goog.isDef(b)?c[d]=b:c=c[d]&&c[d]!==Object.prototype[d]?c[d]:c[d]={}}; -goog.define=function(a,b){var c=b;COMPILED||(goog.global.CLOSURE_UNCOMPILED_DEFINES&&void 0===goog.global.CLOSURE_UNCOMPILED_DEFINES.nodeType&&Object.prototype.hasOwnProperty.call(goog.global.CLOSURE_UNCOMPILED_DEFINES,a)?c=goog.global.CLOSURE_UNCOMPILED_DEFINES[a]:goog.global.CLOSURE_DEFINES&&void 0===goog.global.CLOSURE_DEFINES.nodeType&&Object.prototype.hasOwnProperty.call(goog.global.CLOSURE_DEFINES,a)&&(c=goog.global.CLOSURE_DEFINES[a]));goog.exportPath_(a,c)};goog.DEBUG=!1;goog.LOCALE="en"; -goog.TRUSTED_SITE=!0;goog.STRICT_MODE_COMPATIBLE=!1;goog.DISALLOW_TEST_ONLY_CODE=COMPILED&&!goog.DEBUG;goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING=!1;goog.provide=function(a){if(goog.isInModuleLoader_())throw Error("goog.provide can not be used within a goog.module.");if(!COMPILED&&goog.isProvided_(a))throw Error('Namespace "'+a+'" already declared.');goog.constructNamespace_(a)}; -goog.constructNamespace_=function(a,b){if(!COMPILED){delete goog.implicitNamespaces_[a];for(var c=a;(c=c.substring(0,c.lastIndexOf(".")))&&!goog.getObjectByName(c);)goog.implicitNamespaces_[c]=!0}goog.exportPath_(a,b)};goog.VALID_MODULE_RE_=/^[a-zA-Z_$][a-zA-Z0-9._$]*$/; -goog.module=function(a){if(!goog.isString(a)||!a||-1==a.search(goog.VALID_MODULE_RE_))throw Error("Invalid module identifier");if(!goog.isInModuleLoader_())throw Error("Module "+a+" has been loaded incorrectly. Note, modules cannot be loaded as normal scripts. They require some kind of pre-processing step. You're likely trying to load a module via a script tag or as a part of a concatenated bundle without rewriting the module. For more info see: https://github.com/google/closure-library/wiki/goog.module:-an-ES6-module-like-alternative-to-goog.provide.");if(goog.moduleLoaderState_.moduleName)throw Error("goog.module may only be called once per module."); -goog.moduleLoaderState_.moduleName=a;if(!COMPILED){if(goog.isProvided_(a))throw Error('Namespace "'+a+'" already declared.');delete goog.implicitNamespaces_[a]}};goog.module.get=function(a){return goog.module.getInternal_(a)};goog.module.getInternal_=function(a){if(!COMPILED){if(a in goog.loadedModules_)return goog.loadedModules_[a];if(!goog.implicitNamespaces_[a])return a=goog.getObjectByName(a),null!=a?a:null}return null};goog.moduleLoaderState_=null; -goog.isInModuleLoader_=function(){return null!=goog.moduleLoaderState_};goog.module.declareLegacyNamespace=function(){if(!COMPILED&&!goog.isInModuleLoader_())throw Error("goog.module.declareLegacyNamespace must be called from within a goog.module");if(!COMPILED&&!goog.moduleLoaderState_.moduleName)throw Error("goog.module must be called prior to goog.module.declareLegacyNamespace.");goog.moduleLoaderState_.declareLegacyNamespace=!0}; +goog.define=function(a,b){var c=b;if(!COMPILED){var d=goog.global.CLOSURE_UNCOMPILED_DEFINES,e=goog.global.CLOSURE_DEFINES;d&&void 0===d.nodeType&&Object.prototype.hasOwnProperty.call(d,a)?c=d[a]:e&&void 0===e.nodeType&&Object.prototype.hasOwnProperty.call(e,a)&&(c=e[a])}goog.exportPath_(a,c)};goog.DEBUG=!1;goog.LOCALE="en";goog.TRUSTED_SITE=!0;goog.STRICT_MODE_COMPATIBLE=!1;goog.DISALLOW_TEST_ONLY_CODE=COMPILED&&!goog.DEBUG;goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING=!1; +goog.provide=function(a){if(goog.isInModuleLoader_())throw Error("goog.provide can not be used within a goog.module.");if(!COMPILED&&goog.isProvided_(a))throw Error('Namespace "'+a+'" already declared.');goog.constructNamespace_(a)};goog.constructNamespace_=function(a,b){if(!COMPILED){delete goog.implicitNamespaces_[a];for(var c=a;(c=c.substring(0,c.lastIndexOf(".")))&&!goog.getObjectByName(c);)goog.implicitNamespaces_[c]=!0}goog.exportPath_(a,b)};goog.VALID_MODULE_RE_=/^[a-zA-Z_$][a-zA-Z0-9._$]*$/; +goog.module=function(a){if(!goog.isString(a)||!a||-1==a.search(goog.VALID_MODULE_RE_))throw Error("Invalid module identifier");if(!goog.isInModuleLoader_())throw Error("Module "+a+" has been loaded incorrectly. Note, modules cannot be loaded as normal scripts. They require some kind of pre-processing step. You're likely trying to load a module via a script tag or as a part of a concatenated bundle without rewriting the module. For more info see: https://github.com/google/closure-library/wiki/goog.module:-an-ES6-module-like-alternative-to-goog.provide."); +if(goog.moduleLoaderState_.moduleName)throw Error("goog.module may only be called once per module.");goog.moduleLoaderState_.moduleName=a;if(!COMPILED){if(goog.isProvided_(a))throw Error('Namespace "'+a+'" already declared.');delete goog.implicitNamespaces_[a]}};goog.module.get=function(a){return goog.module.getInternal_(a)}; +goog.module.getInternal_=function(a){if(!COMPILED){if(a in goog.loadedModules_)return goog.loadedModules_[a];if(!goog.implicitNamespaces_[a])return a=goog.getObjectByName(a),null!=a?a:null}return null};goog.moduleLoaderState_=null;goog.isInModuleLoader_=function(){return null!=goog.moduleLoaderState_}; +goog.module.declareLegacyNamespace=function(){if(!COMPILED&&!goog.isInModuleLoader_())throw Error("goog.module.declareLegacyNamespace must be called from within a goog.module");if(!COMPILED&&!goog.moduleLoaderState_.moduleName)throw Error("goog.module must be called prior to goog.module.declareLegacyNamespace.");goog.moduleLoaderState_.declareLegacyNamespace=!0}; goog.setTestOnly=function(a){if(goog.DISALLOW_TEST_ONLY_CODE)throw a=a||"",Error("Importing test-only code into non-debug environment"+(a?": "+a:"."));};goog.forwardDeclare=function(a){};COMPILED||(goog.isProvided_=function(a){return a in goog.loadedModules_||!goog.implicitNamespaces_[a]&&goog.isDefAndNotNull(goog.getObjectByName(a))},goog.implicitNamespaces_={"goog.module":!0}); goog.getObjectByName=function(a,b){for(var c=a.split("."),d=b||goog.global,e=0;eNumber(a[1])?!1:b('(()=>{"use strict";class X{constructor(){if(new.target!=String)throw 1;this.x=42}}let q=Reflect.construct(X,[],String);if(q.x!=42||!(q instanceof String))throw 1;for(const a of[2,3]){if(a==2)continue;function f(z={a}){let a=0;return z.a}{function f(){return 0;}}return f()==3}})()')});a("es6-impl",function(){return!0});a("es7",function(){return b("2 ** 2 == 4")});a("es8",function(){return b("async () => 1, true")});return c},goog.Transpiler.prototype.needsTranspile= -function(a){if("always"==goog.TRANSPILE)return!0;if("never"==goog.TRANSPILE)return!1;this.requiresTranspilation_||(this.requiresTranspilation_=this.createRequiresTranspilation_());if(a in this.requiresTranspilation_)return this.requiresTranspilation_[a];throw Error("Unknown language mode: "+a);},goog.Transpiler.prototype.transpile=function(a,b){return goog.transpile_(a,b)},goog.transpiler_=new goog.Transpiler,goog.DebugLoader=function(){this.dependencies_={loadFlags:{},nameToPath:{},requires:{},visited:{}, -written:{},deferred:{}};this.oldIeWaiting_=!1;this.queuedModules_=[];this.lastNonModuleScriptIndex_=0},goog.DebugLoader.IS_OLD_IE_=!(goog.global.atob||!goog.global.document||!goog.global.document.all),goog.DebugLoader.prototype.earlyProcessLoad=function(a){goog.DebugLoader.IS_OLD_IE_&&this.maybeProcessDeferredDep_(a)},goog.DebugLoader.prototype.load=function(a){var b=this.getPathFromDeps_(a);if(b){var c=function(a){if(!(a in f.written||a in f.visited)){f.visited[a]=!0;if(a in f.requires)for(var b in f.requires[a])if(!g.isProvided(b))if(b in -f.nameToPath)c(f.nameToPath[b]);else throw Error("Undefined nameToPath for "+b);a in e||(e[a]=!0,d.push(a))}},d=[],e={},f=this.dependencies_,g=this;c(b);for(a=0;a\x3c/script>')},goog.DebugLoader.prototype.appendScriptSrcNode_=function(a){var b=goog.global.document,c=b.createElement("script");c.type="text/javascript";c.src=a;c.defer=!1;c.async=!1;b.head.appendChild(c)},goog.DebugLoader.prototype.writeScriptTag_=function(a,b){if(this.inHtmlDocument()){var c=goog.global.document;if(!goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING&& -"complete"==c.readyState){if(/\bdeps.js$/.test(a))return!1;throw Error('Cannot write "'+a+'" after document load');}if(void 0===b)if(goog.DebugLoader.IS_OLD_IE_){this.oldIeWaiting_=!0;var d=" onreadystatechange='goog.debugLoader_.onScriptLoad_(this, "+ ++this.lastNonModuleScriptIndex_+")' ";c.write(''); // Load fresh Closure Library. - document.write(''); - document.write(''); + document.write(''); } diff --git a/blocks/lists.js b/blocks/lists.js index 6377bb5ff..1700cb593 100644 --- a/blocks/lists.js +++ b/blocks/lists.js @@ -293,8 +293,10 @@ Blockly.Blocks['lists_indexOf'] = { */ init: function() { var OPERATORS = - [[Blockly.Msg.LISTS_INDEX_OF_FIRST, 'FIRST'], - [Blockly.Msg.LISTS_INDEX_OF_LAST, 'LAST']]; + [ + [Blockly.Msg.LISTS_INDEX_OF_FIRST, 'FIRST'], + [Blockly.Msg.LISTS_INDEX_OF_LAST, 'LAST'] + ]; this.setHelpUrl(Blockly.Msg.LISTS_INDEX_OF_HELPURL); this.setColour(Blockly.Blocks.lists.HUE); this.setOutput(true, 'Number'); @@ -320,15 +322,19 @@ Blockly.Blocks['lists_getIndex'] = { */ init: function() { var MODE = - [[Blockly.Msg.LISTS_GET_INDEX_GET, 'GET'], - [Blockly.Msg.LISTS_GET_INDEX_GET_REMOVE, 'GET_REMOVE'], - [Blockly.Msg.LISTS_GET_INDEX_REMOVE, 'REMOVE']]; + [ + [Blockly.Msg.LISTS_GET_INDEX_GET, 'GET'], + [Blockly.Msg.LISTS_GET_INDEX_GET_REMOVE, 'GET_REMOVE'], + [Blockly.Msg.LISTS_GET_INDEX_REMOVE, 'REMOVE'] + ]; this.WHERE_OPTIONS = - [[Blockly.Msg.LISTS_GET_INDEX_FROM_START, 'FROM_START'], - [Blockly.Msg.LISTS_GET_INDEX_FROM_END, 'FROM_END'], - [Blockly.Msg.LISTS_GET_INDEX_FIRST, 'FIRST'], - [Blockly.Msg.LISTS_GET_INDEX_LAST, 'LAST'], - [Blockly.Msg.LISTS_GET_INDEX_RANDOM, 'RANDOM']]; + [ + [Blockly.Msg.LISTS_GET_INDEX_FROM_START, 'FROM_START'], + [Blockly.Msg.LISTS_GET_INDEX_FROM_END, 'FROM_END'], + [Blockly.Msg.LISTS_GET_INDEX_FIRST, 'FIRST'], + [Blockly.Msg.LISTS_GET_INDEX_LAST, 'LAST'], + [Blockly.Msg.LISTS_GET_INDEX_RANDOM, 'RANDOM'] + ]; this.setHelpUrl(Blockly.Msg.LISTS_GET_INDEX_HELPURL); this.setColour(Blockly.Blocks.lists.HUE); var modeMenu = new Blockly.FieldDropdown(MODE, function(value) { @@ -501,14 +507,18 @@ Blockly.Blocks['lists_setIndex'] = { */ init: function() { var MODE = - [[Blockly.Msg.LISTS_SET_INDEX_SET, 'SET'], - [Blockly.Msg.LISTS_SET_INDEX_INSERT, 'INSERT']]; + [ + [Blockly.Msg.LISTS_SET_INDEX_SET, 'SET'], + [Blockly.Msg.LISTS_SET_INDEX_INSERT, 'INSERT'] + ]; this.WHERE_OPTIONS = - [[Blockly.Msg.LISTS_GET_INDEX_FROM_START, 'FROM_START'], - [Blockly.Msg.LISTS_GET_INDEX_FROM_END, 'FROM_END'], - [Blockly.Msg.LISTS_GET_INDEX_FIRST, 'FIRST'], - [Blockly.Msg.LISTS_GET_INDEX_LAST, 'LAST'], - [Blockly.Msg.LISTS_GET_INDEX_RANDOM, 'RANDOM']]; + [ + [Blockly.Msg.LISTS_GET_INDEX_FROM_START, 'FROM_START'], + [Blockly.Msg.LISTS_GET_INDEX_FROM_END, 'FROM_END'], + [Blockly.Msg.LISTS_GET_INDEX_FIRST, 'FIRST'], + [Blockly.Msg.LISTS_GET_INDEX_LAST, 'LAST'], + [Blockly.Msg.LISTS_GET_INDEX_RANDOM, 'RANDOM'] + ]; this.setHelpUrl(Blockly.Msg.LISTS_SET_INDEX_HELPURL); this.setColour(Blockly.Blocks.lists.HUE); this.appendValueInput('LIST') @@ -637,13 +647,17 @@ 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']]; + [ + [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']]; + [ + [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') @@ -786,8 +800,10 @@ Blockly.Blocks['lists_split'] = { // Assign 'this' to a variable for use in the closures below. var thisBlock = this; var dropdown = new Blockly.FieldDropdown( - [[Blockly.Msg.LISTS_SPLIT_LIST_FROM_TEXT, 'SPLIT'], - [Blockly.Msg.LISTS_SPLIT_TEXT_FROM_LIST, 'JOIN']], + [ + [Blockly.Msg.LISTS_SPLIT_LIST_FROM_TEXT, 'SPLIT'], + [Blockly.Msg.LISTS_SPLIT_TEXT_FROM_LIST, 'JOIN'] + ], function(newMode) { thisBlock.updateType_(newMode); }); diff --git a/blocks/logic.js b/blocks/logic.js index 5899e04a5..a37387d8f 100644 --- a/blocks/logic.js +++ b/blocks/logic.js @@ -564,12 +564,12 @@ Blockly.Constants.Logic.LOGIC_COMPARE_ONCHANGE_MIXIN = { * @readonly */ Blockly.Constants.Logic.LOGIC_COMPARE_EXTENSION = function() { - // Fix operator labels in RTL + // Fix operator labels in RTL. if (this.RTL) { Blockly.Constants.Logic.fixLogicCompareRtlOpLabels.apply(this); } - // Add onchange handler to ensure types are compatable. + // Add onchange handler to ensure types are compatible. this.mixin(Blockly.Constants.Logic.LOGIC_COMPARE_ONCHANGE_MIXIN); }; diff --git a/blocks/loops.js b/blocks/loops.js index b93147c4c..ee80482d5 100644 --- a/blocks/loops.js +++ b/blocks/loops.js @@ -228,7 +228,7 @@ Blockly.Constants.Loops.WHILE_UNTIL_TOOLTIPS = { Blockly.Extensions.register('controls_whileUntil_tooltip', Blockly.Extensions.buildTooltipForDropdown( - 'MODE', Blockly.Constants.Loops.WHILE_UNTIL_TOOLTIPS)); + 'MODE', Blockly.Constants.Loops.WHILE_UNTIL_TOOLTIPS)); /** * Tooltips for the 'controls_flow_statements' block, keyed by FLOW value. @@ -243,7 +243,7 @@ Blockly.Constants.Loops.BREAK_CONTINUE_TOOLTIPS = { Blockly.Extensions.register('controls_flow_tooltip', Blockly.Extensions.buildTooltipForDropdown( - 'FLOW', Blockly.Constants.Loops.BREAK_CONTINUE_TOOLTIPS)); + 'FLOW', Blockly.Constants.Loops.BREAK_CONTINUE_TOOLTIPS)); /** * Mixin to add a context menu item to create a 'variables_get' block. diff --git a/blocks/procedures.js b/blocks/procedures.js index 194de664c..cb2cec226 100644 --- a/blocks/procedures.js +++ b/blocks/procedures.js @@ -59,6 +59,7 @@ Blockly.Blocks['procedures_defnoreturn'] = { this.setTooltip(Blockly.Msg.PROCEDURES_DEFNORETURN_TOOLTIP); this.setHelpUrl(Blockly.Msg.PROCEDURES_DEFNORETURN_HELPURL); this.arguments_ = []; + this.argumentVarModels_ = []; this.setStatements_(true); this.statementConnection_ = null; }, @@ -131,9 +132,11 @@ Blockly.Blocks['procedures_defnoreturn'] = { if (opt_paramIds) { container.setAttribute('name', this.getFieldValue('NAME')); } - for (var i = 0; i < this.arguments_.length; i++) { + for (var i = 0; i < this.argumentVarModels_.length; i++) { var parameter = document.createElement('arg'); - parameter.setAttribute('name', this.arguments_[i]); + var argModel = this.argumentVarModels_[i]; + parameter.setAttribute('name', argModel.name); + parameter.setAttribute('varId', argModel.getId()); if (opt_paramIds && this.paramIds_) { parameter.setAttribute('paramId', this.paramIds_[i]); } @@ -153,9 +156,15 @@ Blockly.Blocks['procedures_defnoreturn'] = { */ domToMutation: function(xmlElement) { this.arguments_ = []; + this.argumentVarModels_ = []; for (var i = 0, childNode; childNode = xmlElement.childNodes[i]; i++) { if (childNode.nodeName.toLowerCase() == 'arg') { - this.arguments_.push(childNode.getAttribute('name')); + var varName = childNode.getAttribute('name'); + var varId = childNode.getAttribute('varId'); + this.arguments_.push(varName); + var variable = Blockly.Variables.getOrCreateVariablePackage( + this.workspace, varId, varName, ''); + this.argumentVarModels_.push(variable); } } this.updateParams_(); @@ -176,8 +185,8 @@ Blockly.Blocks['procedures_defnoreturn'] = { // Check/uncheck the allow statement box. if (this.getInput('RETURN')) { - containerBlock.setFieldValue(this.hasStatements_ ? 'TRUE' : 'FALSE', - 'STATEMENTS'); + containerBlock.setFieldValue( + this.hasStatements_ ? 'TRUE' : 'FALSE', 'STATEMENTS'); } else { containerBlock.getInput('STATEMENT_INPUT').setVisible(false); } @@ -206,9 +215,13 @@ Blockly.Blocks['procedures_defnoreturn'] = { // Parameter list. this.arguments_ = []; this.paramIds_ = []; + this.argumentVarModels_ = []; var paramBlock = containerBlock.getInputTargetBlock('STACK'); while (paramBlock) { - this.arguments_.push(paramBlock.getFieldValue('NAME')); + var varName = paramBlock.getFieldValue('NAME'); + this.arguments_.push(varName); + var variable = this.workspace.getVariable(varName, ''); + this.argumentVarModels_.push(variable); this.paramIds_.push(paramBlock.id); paramBlock = paramBlock.nextConnection && paramBlock.nextConnection.targetBlock(); @@ -260,30 +273,78 @@ Blockly.Blocks['procedures_defnoreturn'] = { return this.arguments_; }, /** - * Notification that a variable is renaming. - * If the name matches one of this block's variables, rename it. - * @param {string} oldName Previous name of variable. - * @param {string} newName Renamed variable. + * Return all variables referenced by this block. + * @return {!Array.} List of variable models. * @this Blockly.Block */ - renameVar: function(oldName, newName) { + getVarModels: function() { + return this.argumentVarModels_; + }, + /** + * Notification that a variable is renaming. + * If the ID matches one of this block's variables, rename it. + * @param {string} oldId ID of variable to rename. + * @param {string} newId ID of new variable. May be the same as oldId, but + * with an updated name. Guaranteed to be the same type as the old + * variable. + * @this Blockly.Block + */ + renameVarById: function(oldId, newId) { + var oldVariable = this.workspace.getVariableById(oldId); + if (oldVariable.type != '') { + // Procedure arguments always have the empty type. + return; + } + var oldName = oldVariable.name; + var newVar = this.workspace.getVariableById(newId); + var change = false; - for (var i = 0; i < this.arguments_.length; i++) { - if (Blockly.Names.equals(oldName, this.arguments_[i])) { + for (var i = 0; i < this.argumentVarModels_.length; i++) { + if (this.argumentVarModels_[i].getId() == oldId) { + this.arguments_[i] = newVar.name; + this.argumentVarModels_[i] = newVar; + change = true; + } + } + if (change) { + this.displayRenamedVar_(oldName, newVar.name); + } + }, + /** + * Notification that a variable is renaming but keeping the same ID. If the + * variable is in use on this block, rerender to show the new name. + * @param {!Blockly.VariableModel} variable The variable being renamed. + * @package + */ + updateVarName: function(variable) { + var newName = variable.name; + var change = false; + for (var i = 0; i < this.argumentVarModels_.length; i++) { + if (this.argumentVarModels_[i].getId() == variable.getId()) { + var oldName = this.arguments_[i]; this.arguments_[i] = newName; change = true; } } if (change) { - this.updateParams_(); - // Update the mutator's variables if the mutator is open. - if (this.mutator.isVisible()) { - var blocks = this.mutator.workspace_.getAllBlocks(); - for (var i = 0, block; block = blocks[i]; i++) { - if (block.type == 'procedures_mutatorarg' && - Blockly.Names.equals(oldName, block.getFieldValue('NAME'))) { - block.setFieldValue(newName, 'NAME'); - } + this.displayRenamedVar_(oldName, newName); + } + }, + /** + * Update the display to reflect a newly renamed argument. + * @param {string} oldName The old display name of the argument. + * @param {string} newName The new display name of the argument. + * @private + */ + displayRenamedVar_: function(oldName, newName) { + this.updateParams_(); + // Update the mutator's variables if the mutator is open. + if (this.mutator.isVisible()) { + var blocks = this.mutator.workspace_.getAllBlocks(); + for (var i = 0, block; block = blocks[i]; i++) { + if (block.type == 'procedures_mutatorarg' && + Blockly.Names.equals(oldName, block.getFieldValue('NAME'))) { + block.setFieldValue(newName, 'NAME'); } } } @@ -355,6 +416,7 @@ Blockly.Blocks['procedures_defreturn'] = { this.setTooltip(Blockly.Msg.PROCEDURES_DEFRETURN_TOOLTIP); this.setHelpUrl(Blockly.Msg.PROCEDURES_DEFRETURN_HELPURL); this.arguments_ = []; + this.argumentVarModels_ = []; this.setStatements_(true); this.statementConnection_ = null; }, @@ -376,7 +438,10 @@ Blockly.Blocks['procedures_defreturn'] = { return [this.getFieldValue('NAME'), this.arguments_, true]; }, getVars: Blockly.Blocks['procedures_defnoreturn'].getVars, - renameVar: Blockly.Blocks['procedures_defnoreturn'].renameVar, + getVarModels: Blockly.Blocks['procedures_defnoreturn'].getVarModels, + renameVarById: Blockly.Blocks['procedures_defnoreturn'].renameVarById, + updateVarName: Blockly.Blocks['procedures_defnoreturn'].updateVarName, + displayRenamedVar_: Blockly.Blocks['procedures_defnoreturn'].displayRenamedVar_, customContextMenu: Blockly.Blocks['procedures_defnoreturn'].customContextMenu, callType_: 'procedures_callreturn' }; @@ -406,6 +471,15 @@ Blockly.Blocks['procedures_mutatorarg'] = { */ init: function() { var field = new Blockly.FieldTextInput('x', this.validator_); + // Hack: override showEditor to do just a little bit more work. + // We don't have a good place to hook into the start of a text edit. + field.oldShowEditorFn_ = field.showEditor_; + var newShowEditorFn = function() { + this.createdVariables_ = []; + this.oldShowEditorFn_(); + }; + field.showEditor_ = newShowEditorFn; + this.appendDummyInput() .appendField(Blockly.Msg.PROCEDURES_MUTATORARG_TITLE) .appendField(field, 'NAME'); @@ -417,40 +491,58 @@ Blockly.Blocks['procedures_mutatorarg'] = { // Create the default variable when we drag the block in from the flyout. // Have to do this after installing the field on the block. - field.onFinishEditing_ = this.createNewVar_; + field.onFinishEditing_ = this.deleteIntermediateVars_; + // Create an empty list so onFinishEditing_ has something to look at, even + // though the editor was never opened. + field.createdVariables_ = []; field.onFinishEditing_('x'); }, /** - * Obtain a valid name for the procedure. + * Obtain a valid name for the procedure argument. Create a variable if + * necessary. * Merge runs of whitespace. Strip leading and trailing whitespace. * Beyond this, all names are legal. - * @param {string} newVar User-supplied name. + * @param {string} varName User-supplied name. * @return {?string} Valid name, or null if a name was not specified. * @private - * @this Blockly.Block + * @this Blockly.FieldTextInput */ - validator_: function(newVar) { - newVar = newVar.replace(/[\s\xa0]+/g, ' ').replace(/^ | $/g, ''); - return newVar || null; + validator_: function(varName) { + var outerWs = Blockly.Mutator.findParentWs(this.sourceBlock_.workspace); + varName = varName.replace(/[\s\xa0]+/g, ' ').replace(/^ | $/g, ''); + if (!varName) { + return null; + } + var model = outerWs.getVariable(varName, ''); + if (model && model.name != varName) { + // Rename the variable (case change) + outerWs.renameVarById(model.getId(), varName); + } + if (!model) { + model = outerWs.createVariable(varName, ''); + if (model && this.createdVariables_) { + this.createdVariables_.push(model); + } + } + return varName; }, /** * Called when focusing away from the text field. - * Creates a new variable with this name. + * Deletes all variables that were created as the user typed their intended + * variable name. * @param {string} newText The new variable name. * @private * @this Blockly.FieldTextInput */ - createNewVar_: function(newText) { - var source = this.sourceBlock_; - if (source && source.workspace && source.workspace.options && - source.workspace.options.parentWorkspace) { - var workspace = source.workspace.options.parentWorkspace; - var variable = workspace.getVariable(newText); - // If there is a case change, rename the variable. - if (variable && variable.name !== newText) { - workspace.renameVariableById(variable.getId(), newText); - } else { - workspace.createVariable(newText); + deleteIntermediateVars_: function(newText) { + var outerWs = Blockly.Mutator.findParentWs(this.sourceBlock_.workspace); + if (!outerWs) { + return; + } + for (var i = 0; i < this.createdVariables_.length; i++) { + var model = this.createdVariables_[i]; + if (model.name != newText) { + outerWs.deleteVariableById(model.getId()); } } } @@ -470,6 +562,7 @@ Blockly.Blocks['procedures_callnoreturn'] = { // Tooltip is set in renameProcedure. this.setHelpUrl(Blockly.Msg.PROCEDURES_CALLNORETURN_HELPURL); this.arguments_ = []; + this.argumentVarModels_ = []; this.quarkConnections_ = {}; this.quarkIds_ = null; }, @@ -492,10 +585,10 @@ Blockly.Blocks['procedures_callnoreturn'] = { renameProcedure: function(oldName, newName) { if (Blockly.Names.equals(oldName, this.getProcedureCall())) { this.setFieldValue(newName, 'NAME'); - this.setTooltip( - (this.outputConnection ? Blockly.Msg.PROCEDURES_CALLRETURN_TOOLTIP : - Blockly.Msg.PROCEDURES_CALLNORETURN_TOOLTIP) - .replace('%1', newName)); + var baseMsg = this.outputConnection ? + Blockly.Msg.PROCEDURES_CALLRETURN_TOOLTIP : + Blockly.Msg.PROCEDURES_CALLNORETURN_TOOLTIP; + this.setTooltip(baseMsg.replace('%1', newName)); } }, /** @@ -568,6 +661,14 @@ Blockly.Blocks['procedures_callnoreturn'] = { } // Rebuild the block's arguments. this.arguments_ = [].concat(paramNames); + // And rebuild the argument model list. + this.argumentVarModels_ = []; + for (var i = 0; i < this.arguments_.length; i++) { + var variable = Blockly.Variables.getOrCreateVariablePackage( + this.workspace, null, this.arguments_[i], ''); + this.argumentVarModels_.push(variable); + } + this.updateShape_(); this.quarkIds_ = paramIds; // Reconnect any child blocks. @@ -670,19 +771,12 @@ Blockly.Blocks['procedures_callnoreturn'] = { this.setProcedureParameters_(args, paramIds); }, /** - * Notification that a variable is renaming. - * If the name matches one of this block's variables, rename it. - * @param {string} oldName Previous name of variable. - * @param {string} newName Renamed variable. + * Return all variables referenced by this block. + * @return {!Array.} List of variable models. * @this Blockly.Block */ - renameVar: function(oldName, newName) { - for (var i = 0; i < this.arguments_.length; i++) { - if (Blockly.Names.equals(oldName, this.arguments_[i])) { - this.arguments_[i] = newName; - this.getField('ARGNAME' + i).setValue(newName); - } - } + getVarModels: function() { + return this.argumentVarModels_; }, /** * Procedure calls cannot exist without the corresponding procedure @@ -793,7 +887,7 @@ Blockly.Blocks['procedures_callreturn'] = { updateShape_: Blockly.Blocks['procedures_callnoreturn'].updateShape_, mutationToDom: Blockly.Blocks['procedures_callnoreturn'].mutationToDom, domToMutation: Blockly.Blocks['procedures_callnoreturn'].domToMutation, - renameVar: Blockly.Blocks['procedures_callnoreturn'].renameVar, + getVarModels: Blockly.Blocks['procedures_callnoreturn'].getVarModels, onchange: Blockly.Blocks['procedures_callnoreturn'].onchange, customContextMenu: Blockly.Blocks['procedures_callnoreturn'].customContextMenu, @@ -840,7 +934,7 @@ Blockly.Blocks['procedures_ifreturn'] = { if (!this.hasReturnValue_) { this.removeInput('VALUE'); this.appendDummyInput('VALUE') - .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN); + .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN); } }, /** diff --git a/blocks/text.js b/blocks/text.js index 9723469e8..2252815a6 100644 --- a/blocks/text.js +++ b/blocks/text.js @@ -190,11 +190,13 @@ Blockly.defineBlocksWithJsonArray([ // BEGIN JSON EXTRACT { "type": "field_dropdown", "name": "WHERE", - "options": [["%{BKY_TEXT_CHARAT_FROM_START}", "FROM_START"], - ["%{BKY_TEXT_CHARAT_FROM_END}", "FROM_END"], - ["%{BKY_TEXT_CHARAT_FIRST}", "FIRST"], - ["%{BKY_TEXT_CHARAT_LAST}", "LAST"], - ["%{BKY_TEXT_CHARAT_RANDOM}", "RANDOM"]] + "options": [ + ["%{BKY_TEXT_CHARAT_FROM_START}", "FROM_START"], + ["%{BKY_TEXT_CHARAT_FROM_END}", "FROM_END"], + ["%{BKY_TEXT_CHARAT_FIRST}", "FIRST"], + ["%{BKY_TEXT_CHARAT_LAST}", "LAST"], + ["%{BKY_TEXT_CHARAT_RANDOM}", "RANDOM"] + ] } ], "output": "String", @@ -629,10 +631,10 @@ Blockly.Constants.Text.QUOTE_IMAGE_MIXIN = { this.QUOTE_IMAGE_LEFT_DATAURI : this.QUOTE_IMAGE_RIGHT_DATAURI; return new Blockly.FieldImage( - dataUri, - this.QUOTE_IMAGE_WIDTH, - this.QUOTE_IMAGE_HEIGHT, - isLeft ? '\u201C' : '\u201D'); + dataUri, + this.QUOTE_IMAGE_WIDTH, + this.QUOTE_IMAGE_HEIGHT, + isLeft ? '\u201C' : '\u201D'); } }; diff --git a/blocks/variables.js b/blocks/variables.js index 1ec3825a4..5cfa0d0b5 100644 --- a/blocks/variables.js +++ b/blocks/variables.js @@ -103,6 +103,9 @@ Blockly.Constants.Variables.CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = { * @this Blockly.Block */ customContextMenu: function(options) { + if(this.isInFlyout){ + return; + } // Getter blocks have the option to create a setter block, and vice versa. if (this.type == 'variables_get') { var opposite_type = 'variables_set'; @@ -125,4 +128,4 @@ Blockly.Constants.Variables.CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = { }; Blockly.Extensions.registerMixin('contextMenu_variableSetterGetter', - Blockly.Constants.Variables.CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN); + Blockly.Constants.Variables.CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN); diff --git a/blocks/variables_dynamic.js b/blocks/variables_dynamic.js new file mode 100644 index 000000000..5afa267ae --- /dev/null +++ b/blocks/variables_dynamic.js @@ -0,0 +1,138 @@ +/** + * @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 Variable blocks for Blockly. + + * This file is scraped to extract a .json file of block definitions. The array + * passed to defineBlocksWithJsonArray(..) must be strict JSON: double quotes + * only, no outside references, no functions, no trailing commas, etc. The one + * exception is end-of-line comments, which the scraper will remove. + * @author duzc2dtw@gmail.com (Du Tian Wei) + */ +'use strict'; + +goog.provide('Blockly.Constants.VariablesDynamic'); + +goog.require('Blockly.Blocks'); +goog.require('Blockly'); + + +/** + * Common HSV hue for all blocks in this category. + * Should be the same as Blockly.Msg.VARIABLES_DYNAMIC_HUE. + * @readonly + */ +Blockly.Constants.VariablesDynamic.HUE = 310; + +Blockly.defineBlocksWithJsonArray([ // BEGIN JSON EXTRACT + // Block for variable getter. + { + "type": "variables_get_dynamic", + "message0": "%1", + "args0": [{ + "type": "field_variable", + "name": "VAR", + "variable": "%{BKY_VARIABLES_DEFAULT_NAME}" + }], + "output": null, + "colour": "%{BKY_VARIABLES_DYNAMIC_HUE}", + "helpUrl": "%{BKY_VARIABLES_GET_HELPURL}", + "tooltip": "%{BKY_VARIABLES_GET_TOOLTIP}", + "extensions": ["contextMenu_variableDynamicSetterGetter"] + }, + // Block for variable setter. + { + "type": "variables_set_dynamic", + "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_DYNAMIC_HUE}", + "tooltip": "%{BKY_VARIABLES_SET_TOOLTIP}", + "helpUrl": "%{BKY_VARIABLES_SET_HELPURL}", + "extensions": ["contextMenu_variableDynamicSetterGetter"] + } +]); // END JSON EXTRACT (Do not delete this comment.) + +/** + * Mixin to add context menu items to create getter/setter blocks for this + * setter/getter. + * Used by blocks 'variables_set_dynamic' and 'variables_get_dynamic'. + * @mixin + * @augments Blockly.Block + * @package + * @readonly + */ +Blockly.Constants.VariablesDynamic.CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = { + /** + * Add menu option to create getter/setter block for this setter/getter. + * @param {!Array} options List of menu options to add to. + * @this Blockly.Block + */ + customContextMenu: function(options) { + // Getter blocks have the option to create a setter block, and vice versa. + if (this.isInFlyout) { + return; + } + var opposite_type; + var contextMenuMsg; + if (this.type == 'variables_get_dynamic') { + opposite_type = 'variables_set_dynamic'; + contextMenuMsg = Blockly.Msg.VARIABLES_GET_CREATE_SET; + } else { + opposite_type = 'variables_get_dynamic'; + contextMenuMsg = Blockly.Msg.VARIABLES_SET_CREATE_GET; + } + + var option = { enabled: this.workspace.remainingCapacity() > 0 }; + var name = this.getFieldValue('VAR'); + option.text = contextMenuMsg.replace('%1', name); + var xmlField = goog.dom.createDom('field', null, name); + xmlField.setAttribute('name', 'VAR'); + var variableModel = this.workspace.getVariable(name); + xmlField.setAttribute('variabletype', variableModel.type); + var xmlBlock = goog.dom.createDom('block', null, xmlField); + xmlBlock.setAttribute('type', opposite_type); + option.callback = Blockly.ContextMenu.callbackFactory(this, xmlBlock); + options.push(option); + }, + onchange: function() { + var id = this.getFieldValue('VAR'); + var variableModel = this.workspace.getVariableById(id); + if (this.type == 'variables_get_dynamic') { + this.outputConnection.setCheck(variableModel.type); + } else { + this.getInput('VALUE').connection.setCheck(variableModel.type); + } + } +}; + +Blockly.Extensions.registerMixin('contextMenu_variableDynamicSetterGetter', + Blockly.Constants.VariablesDynamic.CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN); diff --git a/blocks_compressed.js b/blocks_compressed.js index a3553a179..578206ac8 100644 --- a/blocks_compressed.js +++ b/blocks_compressed.js @@ -88,32 +88,35 @@ Blockly.Constants.Math.IS_DIVISIBLE_MUTATOR_EXTENSION=function(){this.getField(" Blockly.Extensions.register("math_change_tooltip",Blockly.Extensions.buildTooltipWithFieldValue("%{BKY_MATH_CHANGE_TOOLTIP}","VAR"));Blockly.Constants.Math.LIST_MODES_MUTATOR_MIXIN={updateType_:function(a){"MODE"==a?this.outputConnection.setCheck("Array"):this.outputConnection.setCheck("Number")},mutationToDom:function(){var a=document.createElement("mutation");a.setAttribute("op",this.getFieldValue("OP"));return a},domToMutation:function(a){this.updateType_(a.getAttribute("op"))}}; Blockly.Constants.Math.LIST_MODES_MUTATOR_EXTENSION=function(){this.getField("OP").setValidator(function(a){this.updateType_(a)}.bind(this))};Blockly.Extensions.registerMutator("math_modes_of_list_mutator",Blockly.Constants.Math.LIST_MODES_MUTATOR_MIXIN,Blockly.Constants.Math.LIST_MODES_MUTATOR_EXTENSION);Blockly.Blocks.procedures={};Blockly.Blocks.procedures.HUE=290; Blockly.Blocks.procedures_defnoreturn={init:function(){var a=new Blockly.FieldTextInput("",Blockly.Procedures.rename);a.setSpellcheck(!1);this.appendDummyInput().appendField(Blockly.Msg.PROCEDURES_DEFNORETURN_TITLE).appendField(a,"NAME").appendField("","PARAMS");this.setMutator(new Blockly.Mutator(["procedures_mutatorarg"]));(this.workspace.options.comments||this.workspace.options.parentWorkspace&&this.workspace.options.parentWorkspace.options.comments)&&Blockly.Msg.PROCEDURES_DEFNORETURN_COMMENT&& -this.setCommentText(Blockly.Msg.PROCEDURES_DEFNORETURN_COMMENT);this.setColour(Blockly.Blocks.procedures.HUE);this.setTooltip(Blockly.Msg.PROCEDURES_DEFNORETURN_TOOLTIP);this.setHelpUrl(Blockly.Msg.PROCEDURES_DEFNORETURN_HELPURL);this.arguments_=[];this.setStatements_(!0);this.statementConnection_=null},setStatements_:function(a){this.hasStatements_!==a&&(a?(this.appendStatementInput("STACK").appendField(Blockly.Msg.PROCEDURES_DEFNORETURN_DO),this.getInput("RETURN")&&this.moveInputBefore("STACK", +this.setCommentText(Blockly.Msg.PROCEDURES_DEFNORETURN_COMMENT);this.setColour(Blockly.Blocks.procedures.HUE);this.setTooltip(Blockly.Msg.PROCEDURES_DEFNORETURN_TOOLTIP);this.setHelpUrl(Blockly.Msg.PROCEDURES_DEFNORETURN_HELPURL);this.arguments_=[];this.argumentVarModels_=[];this.setStatements_(!0);this.statementConnection_=null},setStatements_:function(a){this.hasStatements_!==a&&(a?(this.appendStatementInput("STACK").appendField(Blockly.Msg.PROCEDURES_DEFNORETURN_DO),this.getInput("RETURN")&&this.moveInputBefore("STACK", "RETURN")):this.removeInput("STACK",!0),this.hasStatements_=a)},updateParams_:function(){for(var a=!1,b={},c=0;cvar goog = undefined;'); // Load fresh Closure Library. - document.write(''); - document.write(''); + document.write(''); } """) f.close() @@ -270,33 +267,6 @@ class Gen_compressed(threading.Thread): 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): # '../' - continue - f = open(filename) - params.append(("js_code", "".join(f.readlines()))) - f.close() - - self.do_compile(params, target_filename, filenames, "") - def gen_blocks(self): target_filename = "blocks_compressed.js" # Define the parameters for the POST request. diff --git a/core/block.js b/core/block.js index 11b97f1e6..23670666f 100644 --- a/core/block.js +++ b/core/block.js @@ -52,6 +52,14 @@ goog.require('goog.string'); * @constructor */ Blockly.Block = function(workspace, prototypeName, opt_id) { + if (typeof Blockly.Generator.prototype[prototypeName] !== 'undefined') { + console.warn('FUTURE ERROR: Block prototypeName "' + prototypeName + + '" conflicts with Blockly.Generator members. Registering Generators ' + + 'for this block type will incur errors.' + + '\nThis name will be DISALLOWED (throwing an error) in future ' + + 'versions of Blockly.'); + } + /** @type {string} */ this.id = (opt_id && !workspace.getBlockById(opt_id)) ? opt_id : Blockly.utils.genUid(); @@ -155,8 +163,21 @@ Blockly.Block = function(workspace, prototypeName, opt_id) { // Record initial inline state. /** @type {boolean|undefined} */ this.inputsInlineDefault = this.inputsInline; + + // Fire a create event. if (Blockly.Events.isEnabled()) { - Blockly.Events.fire(new Blockly.Events.BlockCreate(this)); + var existingGroup = Blockly.Events.getGroup(); + if (!existingGroup) { + Blockly.Events.setGroup(true); + } + try { + Blockly.Events.fire(new Blockly.Events.BlockCreate(this)); + } finally { + if (!existingGroup) { + Blockly.Events.setGroup(false); + } + } + } // Bind an onchange function, if it exists. if (goog.isFunction(this.onchange)) { @@ -258,6 +279,25 @@ Blockly.Block.prototype.dispose = function(healStack) { } }; +/** + * Call initModel on all fields on the block. + * May be called more than once. + * Either initModel or initSvg must be called after creating a block and before + * the first interaction with it. Interactions include UI actions + * (e.g. clicking and dragging) and firing events (e.g. create, delete, and + * change). + * @public + */ +Blockly.Block.prototype.initModel = function() { + for (var i = 0, input; input = this.inputList[i]; i++) { + for (var j = 0, field; field = input.fieldRow[j]; j++) { + if (field.initModel) { + field.initModel(); + } + } + } +}; + /** * Unplug this block from its superior block. If this block is a statement, * optionally reconnect the block underneath with the block on top. @@ -673,6 +713,7 @@ Blockly.Block.prototype.getField = function(name) { /** * Return all variables referenced by this block. * @return {!Array.} List of variable names. + * @package */ Blockly.Block.prototype.getVars = function() { var vars = []; @@ -687,17 +728,57 @@ Blockly.Block.prototype.getVars = function() { }; /** - * Notification that a variable is renaming. - * If the name matches one of this block's variables, rename it. - * @param {string} oldName Previous name of variable. - * @param {string} newName Renamed variable. + * Return all variables referenced by this block. + * @return {!Array.} List of variable models. + * @package */ -Blockly.Block.prototype.renameVar = function(oldName, newName) { +Blockly.Block.prototype.getVarModels = function() { + var vars = []; + for (var i = 0, input; input = this.inputList[i]; i++) { + for (var j = 0, field; field = input.fieldRow[j]; j++) { + if (field instanceof Blockly.FieldVariable) { + var model = this.workspace.getVariableById(field.getValue()); + // Check if the variable actually exists (and isn't just a potential + // variable). + if (model) { + vars.push(model); + } + } + } + } + return vars; +}; + +/** + * Notification that a variable is renaming but keeping the same ID. If the + * variable is in use on this block, rerender to show the new name. + * @param {!Blockly.VariableModel} variable The variable being renamed. + * @package + */ +Blockly.Block.prototype.updateVarName = function(variable) { for (var i = 0, input; input = this.inputList[i]; i++) { for (var j = 0, field; field = input.fieldRow[j]; j++) { if (field instanceof Blockly.FieldVariable && - Blockly.Names.equals(oldName, field.getValue())) { - field.setValue(newName); + variable.getId() == field.getValue()) { + field.setText(variable.name); + } + } + } +}; + +/** + * Notification that a variable is renaming. + * If the ID matches one of this block's variables, rename it. + * @param {string} oldId ID of variable to rename. + * @param {string} newId ID of new variable. May be the same as oldId, but with + * an updated name. + */ +Blockly.Block.prototype.renameVarById = function(oldId, newId) { + for (var i = 0, input; input = this.inputList[i]; i++) { + for (var j = 0, field; field = input.fieldRow[j]; j++) { + if (field instanceof Blockly.FieldVariable && + oldId == field.getValue()) { + field.setValue(newId); } } } @@ -975,8 +1056,8 @@ Blockly.Block.prototype.appendDummyInput = function(opt_name) { Blockly.Block.prototype.jsonInit = function(json) { // Validate inputs. - goog.asserts.assert(json['output'] == undefined || - json['previousStatement'] == undefined, + goog.asserts.assert( + json['output'] == undefined || json['previousStatement'] == undefined, 'Must not have both an output and a previousStatement.'); // Set basic properties of block. @@ -1089,11 +1170,11 @@ Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) { var token = tokens[i]; if (typeof token == 'number') { if (token <= 0 || token > args.length) { - throw new Error('Block \"' + this.type + '\": ' + + throw new Error('Block "' + this.type + '": ' + 'Message index %' + token + ' out of range.'); } if (indexDup[token]) { - throw new Error('Block \"' + this.type + '\": ' + + throw new Error('Block "' + this.type + '": ' + 'Message index %' + token + ' duplicated.'); } indexDup[token] = true; @@ -1107,13 +1188,13 @@ Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) { } } if(indexCount != args.length) { - throw new Error('Block \"' + this.type + '\": ' + + 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'], - 'field_'))) { + goog.string.startsWith( + elements[elements.length - 1]['type'], 'field_'))) { var dummyInput = {type: 'input_dummy'}; if (lastDummyAlign) { dummyInput['align'] = lastDummyAlign; @@ -1151,37 +1232,35 @@ Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) { input = this.appendDummyInput(element['name']); break; case 'field_label': - field = Blockly.Block.newFieldLabelFromJson_(element); + field = Blockly.FieldLabel.fromJson(element); break; case 'field_input': - field = Blockly.Block.newFieldTextInputFromJson_(element); + field = Blockly.FieldTextInput.fromJson(element); break; case 'field_angle': - field = new Blockly.FieldAngle(element['angle']); + field = Blockly.FieldAngle.fromJson(element); break; case 'field_checkbox': - field = new Blockly.FieldCheckbox( - element['checked'] ? 'TRUE' : 'FALSE'); + field = Blockly.FieldCheckbox.fromJson(element); break; case 'field_colour': - field = new Blockly.FieldColour(element['colour']); + field = Blockly.FieldColour.fromJson(element); break; case 'field_variable': - field = Blockly.Block.newFieldVariableFromJson_(element); + field = Blockly.FieldVariable.fromJson(element); break; case 'field_dropdown': - field = new Blockly.FieldDropdown(element['options']); + field = Blockly.FieldDropdown.fromJson(element); break; case 'field_image': - field = Blockly.Block.newFieldImageFromJson_(element); + field = Blockly.FieldImage.fromJson(element); break; case 'field_number': - field = new Blockly.FieldNumber(element['value'], - element['min'], element['max'], element['precision']); + field = Blockly.FieldNumber.fromJson(element); break; case 'field_date': if (Blockly.FieldDate) { - field = new Blockly.FieldDate(element['date']); + field = Blockly.FieldDate.fromJson(element); break; } // Fall through if FieldDate is not compiled in. @@ -1212,65 +1291,6 @@ Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) { } }; -/** - * Helper function to construct a FieldImage from a JSON arg object, - * dereferencing any string table references. - * @param {!Object} options A JSON object with options (src, width, height, and alt). - * @returns {!Blockly.FieldImage} The new image. - * @private - */ -Blockly.Block.newFieldImageFromJson_ = function(options) { - var src = Blockly.utils.replaceMessageReferences(options['src']); - var width = Number(Blockly.utils.replaceMessageReferences(options['width'])); - var height = - Number(Blockly.utils.replaceMessageReferences(options['height'])); - var alt = Blockly.utils.replaceMessageReferences(options['alt']); - return new Blockly.FieldImage(src, width, height, alt); -}; - -/** - * Helper function to construct a FieldLabel from a JSON arg object, - * dereferencing any string table references. - * @param {!Object} options A JSON object with options (text, and class). - * @returns {!Blockly.FieldLabel} The new label. - * @private - */ -Blockly.Block.newFieldLabelFromJson_ = function(options) { - var text = Blockly.utils.replaceMessageReferences(options['text']); - return new Blockly.FieldLabel(text, options['class']); -}; - -/** - * Helper function to construct a FieldTextInput from a JSON arg object, - * dereferencing any string table references. - * @param {!Object} options A JSON object with options (text, class, and - * spellcheck). - * @returns {!Blockly.FieldTextInput} The new text input. - * @private - */ -Blockly.Block.newFieldTextInputFromJson_ = function(options) { - var text = Blockly.utils.replaceMessageReferences(options['text']); - var field = new Blockly.FieldTextInput(text, options['class']); - if (typeof options['spellcheck'] == 'boolean') { - field.setSpellcheck(options['spellcheck']); - } - return field; -}; - -/** - * Helper function to construct a FieldVariable from a JSON arg object, - * dereferencing any string table references. - * @param {!Object} options A JSON object with options (variable). - * @returns {!Blockly.FieldVariable} The variable field. - * @private - */ -Blockly.Block.newFieldVariableFromJson_ = function(options) { - var varname = Blockly.utils.replaceMessageReferences(options['variable']); - var variableTypes = options['variableTypes']; - return new Blockly.FieldVariable(varname, null, variableTypes); -}; - - /** * Add a value input, statement input or local variable to this block. * @param {number} type Either Blockly.INPUT_VALUE or Blockly.NEXT_STATEMENT or @@ -1318,8 +1338,8 @@ Blockly.Block.prototype.moveInputBefore = function(name, refName) { } } goog.asserts.assert(inputIndex != -1, 'Named input "%s" not found.', name); - goog.asserts.assert(refIndex != -1, 'Reference input "%s" not found.', - refName); + goog.asserts.assert( + refIndex != -1, 'Reference input "%s" not found.', refName); this.moveNumberedInputBefore(inputIndex, refIndex); }; @@ -1333,9 +1353,9 @@ Blockly.Block.prototype.moveNumberedInputBefore = function( // Validate arguments. goog.asserts.assert(inputIndex != refIndex, 'Can\'t move input to itself.'); goog.asserts.assert(inputIndex < this.inputList.length, - 'Input index ' + inputIndex + ' out of bounds.'); + 'Input index ' + inputIndex + ' out of bounds.'); goog.asserts.assert(refIndex <= this.inputList.length, - 'Reference input ' + refIndex + ' out of bounds.'); + 'Reference input ' + refIndex + ' out of bounds.'); // Remove input. var input = this.inputList[inputIndex]; this.inputList.splice(inputIndex, 1); diff --git a/core/block_drag_surface.js b/core/block_drag_surface.js index d87809519..be1edb3b0 100644 --- a/core/block_drag_surface.js +++ b/core/block_drag_surface.js @@ -113,8 +113,8 @@ Blockly.BlockDragSurfaceSvg.prototype.createDom = function() { * surface. */ Blockly.BlockDragSurfaceSvg.prototype.setBlocksAndShow = function(blocks) { - goog.asserts.assert(this.dragGroup_.childNodes.length == 0, - 'Already dragging a block.'); + goog.asserts.assert( + this.dragGroup_.childNodes.length == 0, 'Already dragging a block.'); // appendChild removes the blocks from the previous parent this.dragGroup_.appendChild(blocks); this.SVG_.style.display = 'block'; @@ -214,7 +214,7 @@ Blockly.BlockDragSurfaceSvg.prototype.clearAndHide = function(opt_newSurface) { this.dragGroup_.removeChild(this.getCurrentBlock()); } this.SVG_.style.display = 'none'; - goog.asserts.assert(this.dragGroup_.childNodes.length == 0, - 'Drag group was not cleared.'); + goog.asserts.assert( + this.dragGroup_.childNodes.length == 0, 'Drag group was not cleared.'); this.surfaceXY_ = null; }; diff --git a/core/block_dragger.js b/core/block_dragger.js index 0ba1749f0..c6a0a3b1a 100644 --- a/core/block_dragger.js +++ b/core/block_dragger.js @@ -168,7 +168,9 @@ Blockly.BlockDragger.prototype.startBlockDrag = function(currentDragDeltaXY) { this.draggingBlock_.moveToDragSurface_(); if (this.workspace_.toolbox_) { - this.workspace_.toolbox_.addDeleteStyle(); + var style = this.draggingBlock_.isDeletable() ? 'blocklyToolboxDelete' : + 'blocklyToolboxGrab'; + this.workspace_.toolbox_.addStyle(style); } }; @@ -224,7 +226,9 @@ Blockly.BlockDragger.prototype.endBlockDrag = function(e, currentDragDeltaXY) { this.workspace_.setResizesEnabled(true); if (this.workspace_.toolbox_) { - this.workspace_.toolbox_.removeDeleteStyle(); + var style = this.draggingBlock_.isDeletable() ? 'blocklyToolboxDelete' : + 'blocklyToolboxGrab'; + this.workspace_.toolbox_.removeStyle(style); } Blockly.Events.setGroup(false); }; diff --git a/core/block_svg.js b/core/block_svg.js index 86993dd11..ae4087c53 100644 --- a/core/block_svg.js +++ b/core/block_svg.js @@ -149,8 +149,8 @@ Blockly.BlockSvg.prototype.initSvg = function() { this.updateColour(); this.updateMovable(); if (!this.workspace.options.readOnly && !this.eventsInit_) { - Blockly.bindEventWithChecks_(this.getSvgRoot(), 'mousedown', this, - this.onMouseDown_); + Blockly.bindEventWithChecks_( + this.getSvgRoot(), 'mousedown', this, this.onMouseDown_); } this.eventsInit_ = true; @@ -708,12 +708,12 @@ Blockly.BlockSvg.prototype.setDragging = function(adding) { group.skew_ = ''; Blockly.draggingConnections_ = Blockly.draggingConnections_.concat(this.getConnections_(true)); - Blockly.utils.addClass(/** @type {!Element} */ (this.svgGroup_), - 'blocklyDragging'); + Blockly.utils.addClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklyDragging'); } else { Blockly.draggingConnections_ = []; - Blockly.utils.removeClass(/** @type {!Element} */ (this.svgGroup_), - 'blocklyDragging'); + Blockly.utils.removeClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklyDragging'); } // Recurse through all blocks attached under this one. for (var i = 0; i < this.childBlocks_.length; i++) { @@ -726,11 +726,11 @@ Blockly.BlockSvg.prototype.setDragging = function(adding) { */ Blockly.BlockSvg.prototype.updateMovable = function() { if (this.isMovable()) { - Blockly.utils.addClass(/** @type {!Element} */ (this.svgGroup_), - 'blocklyDraggable'); + Blockly.utils.addClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklyDraggable'); } else { - Blockly.utils.removeClass(/** @type {!Element} */ (this.svgGroup_), - 'blocklyDraggable'); + Blockly.utils.removeClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklyDraggable'); } }; @@ -877,8 +877,8 @@ Blockly.BlockSvg.disposeUiStep_ = function(clone, rtl, start, workspaceScale) { var scale = (1 - percent) * workspaceScale; clone.setAttribute('transform', 'translate(' + x + ',' + y + ')' + ' scale(' + scale + ')'); - setTimeout(Blockly.BlockSvg.disposeUiStep_, 10, clone, rtl, start, - workspaceScale); + setTimeout( + Blockly.BlockSvg.disposeUiStep_, 10, clone, rtl, start, workspaceScale); } }; @@ -901,8 +901,14 @@ Blockly.BlockSvg.prototype.connectionUiEffect = function() { xy.y += 3 * this.workspace.scale; } var ripple = Blockly.utils.createSvgElement('circle', - {'cx': xy.x, 'cy': xy.y, 'r': 0, 'fill': 'none', - 'stroke': '#888', 'stroke-width': 10}, + { + 'cx': xy.x, + 'cy': xy.y, + 'r': 0, + 'fill': 'none', + 'stroke': '#888', + 'stroke-width': 10 + }, this.workspace.getParentSvg()); // Start the animation. Blockly.BlockSvg.connectionUiStep_(ripple, new Date, this.workspace.scale); @@ -965,13 +971,13 @@ Blockly.BlockSvg.disconnectUiStep_ = function(group, magnitude, start) { if (percent > 1) { group.skew_ = ''; } else { - var skew = Math.round(Math.sin(percent * Math.PI * WIGGLES) * - (1 - percent) * magnitude); + var skew = Math.round( + Math.sin(percent * Math.PI * WIGGLES) * (1 - percent) * magnitude); group.skew_ = 'skewX(' + skew + ')'; Blockly.BlockSvg.disconnectUiStop_.group = group; Blockly.BlockSvg.disconnectUiStop_.pid = - setTimeout(Blockly.BlockSvg.disconnectUiStep_, 10, group, magnitude, - start); + setTimeout( + Blockly.BlockSvg.disconnectUiStep_, 10, group, magnitude, start); } group.setAttribute('transform', group.translate_ + group.skew_); }; @@ -1032,9 +1038,10 @@ Blockly.BlockSvg.prototype.updateColour = function() { } // Bump every dropdown to change its colour. + // TODO (#1456) for (var x = 0, input; input = this.inputList[x]; x++) { for (var y = 0, field; field = input.fieldRow[y]; y++) { - field.setText(null); + field.forceRerender(); } } }; @@ -1044,14 +1051,16 @@ Blockly.BlockSvg.prototype.updateColour = function() { */ Blockly.BlockSvg.prototype.updateDisabled = function() { if (this.disabled || this.getInheritedDisabled()) { - if (Blockly.utils.addClass(/** @type {!Element} */ (this.svgGroup_), - 'blocklyDisabled')) { + var added = Blockly.utils.addClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklyDisabled'); + if (added) { this.svgPath_.setAttribute('fill', 'url(#' + this.workspace.options.disabledPatternId + ')'); } } else { - if (Blockly.utils.removeClass(/** @type {!Element} */ (this.svgGroup_), - 'blocklyDisabled')) { + var removed = Blockly.utils.removeClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklyDisabled'); + if (removed) { this.updateColour(); } } @@ -1232,16 +1241,16 @@ Blockly.BlockSvg.prototype.setHighlighted = function(highlighted) { * Select this block. Highlight it visually. */ Blockly.BlockSvg.prototype.addSelect = function() { - Blockly.utils.addClass(/** @type {!Element} */ (this.svgGroup_), - 'blocklySelected'); + Blockly.utils.addClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklySelected'); }; /** * Unselect this block. Remove its highlighting. */ Blockly.BlockSvg.prototype.removeSelect = function() { - Blockly.utils.removeClass(/** @type {!Element} */ (this.svgGroup_), - 'blocklySelected'); + Blockly.utils.removeClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklySelected'); }; /** diff --git a/core/blockly.js b/core/blockly.js index 973899635..427d45199 100644 --- a/core/blockly.js +++ b/core/blockly.js @@ -120,8 +120,10 @@ Blockly.hueToRgb = function(hue) { * @return {!Object} Contains width and height properties. */ Blockly.svgSize = function(svg) { - return {width: svg.cachedWidth_, - height: svg.cachedHeight_}; + return { + width: svg.cachedWidth_, + height: svg.cachedHeight_ + }; }; /** @@ -200,12 +202,16 @@ Blockly.onKeyDown_ = function(e) { } if (Blockly.selected && Blockly.selected.isDeletable() && Blockly.selected.isMovable()) { + // Don't allow copying immovable or undeletable blocks. The next step + // would be to paste, which would create additional undeletable/immovable + // blocks on the workspace. if (e.keyCode == 67) { // 'c' for copy. Blockly.hideChaff(); Blockly.copy_(Blockly.selected); - } else if (e.keyCode == 88) { - // 'x' for cut. + } else if (e.keyCode == 88 && !Blockly.selected.workspace.isFlyout) { + // 'x' for cut, but not in a flyout. + // Don't even copy the selected item in the flyout. Blockly.copy_(Blockly.selected); deleteBlock = true; } @@ -214,7 +220,13 @@ Blockly.onKeyDown_ = function(e) { // 'v' for paste. if (Blockly.clipboardXml_) { Blockly.Events.setGroup(true); - Blockly.clipboardSource_.paste(Blockly.clipboardXml_); + // Pasting always pastes to the main workspace, even if the copy started + // in a flyout workspace. + var workspace = Blockly.clipboardSource_; + if (workspace.isFlyout) { + workspace = workspace.targetWorkspace; + } + workspace.paste(Blockly.clipboardXml_); Blockly.Events.setGroup(false); } } else if (e.keyCode == 90) { @@ -223,8 +235,9 @@ Blockly.onKeyDown_ = function(e) { Blockly.mainWorkspace.undo(e.shiftKey); } } - if (deleteBlock) { - // Common code for delete and cut. + // Common code for delete and cut. + // Don't delete in the flyout. + if (deleteBlock && !Blockly.selected.workspace.isFlyout) { Blockly.Events.setGroup(true); Blockly.hideChaff(); Blockly.selected.dispose(/* heal */ true, true); @@ -306,8 +319,9 @@ Blockly.hideChaff = function(opt_allowToolbox) { */ Blockly.addChangeListener = function(func) { // Backwards compatibility from before there could be multiple workspaces. - console.warn('Deprecated call to Blockly.addChangeListener, ' + - 'use workspace.addChangeListener instead.'); + console.warn( + 'Deprecated call to Blockly.addChangeListener, ' + + 'use workspace.addChangeListener instead.'); return Blockly.getMainWorkspace().addChangeListener(func); }; @@ -380,12 +394,14 @@ Blockly.defineBlocksWithJsonArray = function(jsonArray) { for (var i = 0, elem; elem = jsonArray[i]; i++) { var typename = elem.type; if (typename == null || typename === '') { - console.warn('Block definition #' + i + - ' in JSON array is missing a type attribute. Skipping.'); + console.warn( + 'Block definition #' + i + + ' in JSON array is missing a type attribute. Skipping.'); } else { if (Blockly.Blocks[typename]) { - console.warn('Block definition #' + i + - ' in JSON array overwrites prior definition of "' + typename + '".'); + console.warn( + 'Block definition #' + i + ' in JSON array' + + ' overwrites prior definition of "' + typename + '".'); } Blockly.Blocks[typename] = { init: Blockly.jsonInitFactory_(elem) @@ -448,10 +464,9 @@ Blockly.bindEventWithChecks_ = function(node, name, thisObject, func, e.preventDefault(); } }; - for (var i = 0, eventName; - eventName = Blockly.Touch.TOUCH_MAP[name][i]; i++) { - node.addEventListener(eventName, touchWrapFunc, false); - bindData.push([node, eventName, touchWrapFunc]); + for (var i = 0, type; type = Blockly.Touch.TOUCH_MAP[name][i]; i++) { + node.addEventListener(type, touchWrapFunc, false); + bindData.push([node, type, touchWrapFunc]); } } return bindData; @@ -487,7 +502,7 @@ Blockly.bindEvent_ = function(node, name, thisObject, func) { if (name in Blockly.Touch.TOUCH_MAP) { var touchWrapFunc = function(e) { // Punt on multitouch events. - if (e.changedTouches.length == 1) { + if (e.changedTouches && e.changedTouches.length == 1) { // Map the touch event's properties to the event. var touchPoint = e.changedTouches[0]; e.clientX = touchPoint.clientX; @@ -498,10 +513,9 @@ Blockly.bindEvent_ = function(node, name, thisObject, func) { // Stop the browser from scrolling/zooming the page. e.preventDefault(); }; - for (var i = 0, eventName; - eventName = Blockly.Touch.TOUCH_MAP[name][i]; i++) { - node.addEventListener(eventName, touchWrapFunc, false); - bindData.push([node, eventName, touchWrapFunc]); + for (var i = 0, type; type = Blockly.Touch.TOUCH_MAP[name][i]; i++) { + node.addEventListener(type, touchWrapFunc, false); + bindData.push([node, type, touchWrapFunc]); } } return bindData; diff --git a/core/bubble.js b/core/bubble.js index 1cd9443fd..92dc28894 100644 --- a/core/bubble.js +++ b/core/bubble.js @@ -47,7 +47,7 @@ goog.require('goog.userAgent'); * @constructor */ Blockly.Bubble = function(workspace, content, shape, anchorXY, - bubbleWidth, bubbleHeight) { + bubbleWidth, bubbleHeight) { this.workspace_ = workspace; this.content_ = content; this.shape_ = shape; @@ -75,11 +75,11 @@ Blockly.Bubble = function(workspace, content, shape, anchorXY, this.rendered_ = true; if (!workspace.options.readOnly) { - Blockly.bindEventWithChecks_(this.bubbleBack_, 'mousedown', this, - this.bubbleMouseDown_); + Blockly.bindEventWithChecks_( + this.bubbleBack_, 'mousedown', this, this.bubbleMouseDown_); if (this.resizeGroup_) { - Blockly.bindEventWithChecks_(this.resizeGroup_, 'mousedown', this, - this.resizeMouseDown_); + Blockly.bindEventWithChecks_( + this.resizeGroup_, 'mousedown', this, this.resizeMouseDown_); } } }; @@ -162,14 +162,15 @@ Blockly.Bubble.bubbleMouseUp_ = function(/*e*/) { Blockly.Bubble.prototype.rendered_ = false; /** - * Absolute coordinate of anchor point. + * Absolute coordinate of anchor point, in workspace coordinates. * @type {goog.math.Coordinate} * @private */ Blockly.Bubble.prototype.anchorXY_ = null; /** - * Relative X coordinate of bubble with respect to the anchor's centre. + * Relative X coordinate of bubble with respect to the anchor's centre, + * in workspace units. * In RTL mode the initial value is negated. * @private */ @@ -235,8 +236,13 @@ Blockly.Bubble.prototype.createDom_ = function(content, hasResize) { filter, this.bubbleGroup_); this.bubbleArrow_ = Blockly.utils.createSvgElement('path', {}, bubbleEmboss); this.bubbleBack_ = Blockly.utils.createSvgElement('rect', - {'class': 'blocklyDraggable', 'x': 0, 'y': 0, - 'rx': Blockly.Bubble.BORDER_WIDTH, 'ry': Blockly.Bubble.BORDER_WIDTH}, + { + 'class': 'blocklyDraggable', + 'x': 0, + 'y': 0, + 'rx': Blockly.Bubble.BORDER_WIDTH, + 'ry': Blockly.Bubble.BORDER_WIDTH + }, bubbleEmboss); if (hasResize) { this.resizeGroup_ = Blockly.utils.createSvgElement('g', @@ -248,13 +254,19 @@ Blockly.Bubble.prototype.createDom_ = function(content, hasResize) { {'points': '0,x x,x x,0'.replace(/x/g, resizeSize.toString())}, this.resizeGroup_); Blockly.utils.createSvgElement('line', - {'class': 'blocklyResizeLine', - 'x1': resizeSize / 3, 'y1': resizeSize - 1, - 'x2': resizeSize - 1, 'y2': resizeSize / 3}, this.resizeGroup_); + { + 'class': 'blocklyResizeLine', + 'x1': resizeSize / 3, 'y1': resizeSize - 1, + 'x2': resizeSize - 1, 'y2': resizeSize / 3 + }, this.resizeGroup_); Blockly.utils.createSvgElement('line', - {'class': 'blocklyResizeLine', - 'x1': resizeSize * 2 / 3, 'y1': resizeSize - 1, - 'x2': resizeSize - 1, 'y2': resizeSize * 2 / 3}, this.resizeGroup_); + { + 'class': 'blocklyResizeLine', + 'x1': resizeSize * 2 / 3, + 'y1': resizeSize - 1, + 'x2': resizeSize - 1, + 'y2': resizeSize * 2 / 3 + }, this.resizeGroup_); } else { this.resizeGroup_ = null; } @@ -262,48 +274,24 @@ Blockly.Bubble.prototype.createDom_ = function(content, hasResize) { return this.bubbleGroup_; }; +/** + * Return the root node of the bubble's SVG group. + * @return {Element} The root SVG node of the bubble's group. + */ +Blockly.Bubble.prototype.getSvgRoot = function() { + return this.bubbleGroup_; +}; + /** * Handle a mouse-down on bubble's border. * @param {!Event} e Mouse down event. * @private */ Blockly.Bubble.prototype.bubbleMouseDown_ = function(e) { - this.promote_(); - Blockly.Bubble.unbindDragEvents_(); - if (Blockly.utils.isRightButton(e)) { - // No right-click. - e.stopPropagation(); - return; - } else if (Blockly.utils.isTargetInput(e)) { - // When focused on an HTML text input widget, don't trap any events. - return; + var gesture = this.workspace_.getGesture(e); + if (gesture) { + gesture.handleBubbleStart(e, this); } - // Left-click (or middle click) - this.workspace_.startDrag(e, new goog.math.Coordinate( - this.workspace_.RTL ? -this.relativeLeft_ : this.relativeLeft_, - this.relativeTop_)); - - Blockly.Bubble.onMouseUpWrapper_ = Blockly.bindEventWithChecks_(document, - 'mouseup', this, Blockly.Bubble.bubbleMouseUp_); - Blockly.Bubble.onMouseMoveWrapper_ = Blockly.bindEventWithChecks_(document, - 'mousemove', this, this.bubbleMouseMove_); - Blockly.hideChaff(); - // This event has been handled. No need to bubble up to the document. - e.stopPropagation(); -}; - -/** - * Drag this bubble to follow the mouse. - * @param {!Event} e Mouse move event. - * @private - */ -Blockly.Bubble.prototype.bubbleMouseMove_ = function(e) { - this.autoLayout_ = false; - var newXY = this.workspace_.moveDrag(e); - this.relativeLeft_ = this.workspace_.RTL ? -newXY.x : newXY.x; - this.relativeTop_ = newXY.y; - this.positionBubble_(); - this.renderArrow_(); }; /** @@ -434,8 +422,17 @@ Blockly.Bubble.prototype.positionBubble_ = function() { left += this.relativeLeft_; } var top = this.relativeTop_ + this.anchorXY_.y; - this.bubbleGroup_.setAttribute('transform', - 'translate(' + left + ',' + top + ')'); + this.moveTo(left, top); +}; + +/** + * Move the bubble group to the specified location in workspace coordinates. + * @param {number} x The x position to move to. + * @param {number} y The y position to move to. + * @package + */ +Blockly.Bubble.prototype.moveTo = function(x, y) { + this.bubbleGroup_.setAttribute('transform', 'translate(' + x + ',' + y + ')'); }; /** @@ -584,3 +581,50 @@ Blockly.Bubble.prototype.dispose = function() { this.content_ = null; this.shape_ = null; }; + +/** + * Move this bubble during a drag, taking into account whether or not there is + * a drag surface. + * @param {?Blockly.BlockDragSurfaceSvg} dragSurface The surface that carries + * rendered items during a drag, or null if no drag surface is in use. + * @param {!goog.math.Coordinate} newLoc The location to translate to, in + * workspace coordinates. + * @package + */ +Blockly.Bubble.prototype.moveDuringDrag = function(dragSurface, newLoc) { + if (dragSurface) { + dragSurface.translateSurface(newLoc.x, newLoc.y); + } else { + this.moveTo(newLoc.x, newLoc.y); + } + if (this.workspace_.RTL) { + this.relativeLeft_ = this.anchorXY_.x - newLoc.x - this.width_; + } else { + this.relativeLeft_ = newLoc.x - this.anchorXY_.x; + } + this.relativeTop_ = newLoc.y - this.anchorXY_.y; + this.renderArrow_(); +}; + +/** + * Return the coordinates of the top-left corner of this bubble's body relative + * to the drawing surface's origin (0,0), in workspace units. + * @return {!goog.math.Coordinate} Object with .x and .y properties. + */ +Blockly.Bubble.prototype.getRelativeToSurfaceXY = function() { + return new goog.math.Coordinate( + this.anchorXY_.x + this.relativeLeft_, + this.anchorXY_.y + this.relativeTop_); +}; + +/** + * Set whether auto-layout of this bubble is enabled. The first time a bubble + * is shown it positions itself to not cover any blocks. Once a user has + * dragged it to reposition, it renders where the user put it. + * @param {boolean} enable True if auto-layout should be enabled, false + * otherwise. + * @package + */ +Blockly.Bubble.prototype.setAutoLayout = function(enable) { + this.autoLayout_ = enable; +}; diff --git a/core/bubble_dragger.js b/core/bubble_dragger.js new file mode 100644 index 000000000..e0dc03eda --- /dev/null +++ b/core/bubble_dragger.js @@ -0,0 +1,188 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2018 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 bubble visually. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.BubbleDragger'); + +goog.require('goog.math.Coordinate'); +goog.require('goog.asserts'); + + +/** + * Class for a bubble dragger. It moves bubbles around the workspace when they + * are being dragged by a mouse or touch. + * @param {!Blockly.Bubble} bubble The bubble to drag. + * @param {!Blockly.WorkspaceSvg} workspace The workspace to drag on. + * @constructor + */ +Blockly.BubbleDragger = function(bubble, workspace) { + /** + * The bubble that is being dragged. + * @type {!Blockly.Bubble} + * @private + */ + this.draggingBubble_ = bubble; + + /** + * The workspace on which the bubble is being dragged. + * @type {!Blockly.WorkspaceSvg} + * @private + */ + this.workspace_ = workspace; + + /** + * The location of the top left corner of the dragging bubble's body at the + * beginning of the drag, in workspace coordinates. + * @type {!goog.math.Coordinate} + * @private + */ + this.startXY_ = this.draggingBubble_.getRelativeToSurfaceXY(); + + /** + * The drag surface to move bubbles to during a drag, or null if none should + * be used. Block dragging and bubble dragging use the same surface. + * @type {?Blockly.BlockDragSurfaceSvg} + * @private + */ + this.dragSurface_ = + Blockly.utils.is3dSupported() && !!workspace.getBlockDragSurface() ? + workspace.getBlockDragSurface() : null; +}; + +/** + * Sever all links from this object. + * @package + */ +Blockly.BubbleDragger.prototype.dispose = function() { + this.draggingBubble_ = null; + this.workspace_ = null; + this.dragSurface_ = null; +}; + +/** + * Start dragging a bubble. This includes moving it to the drag surface. + * @package + */ +Blockly.BubbleDragger.prototype.startBubbleDrag = function() { + if (!Blockly.Events.getGroup()) { + Blockly.Events.setGroup(true); + } + + this.workspace_.setResizesEnabled(false); + this.draggingBubble_.setAutoLayout(false); + if (this.dragSurface_) { + this.moveToDragSurface_(); + } +}; + +/** + * Execute a step of bubble 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.BubbleDragger.prototype.dragBubble = function(e, currentDragDeltaXY) { + var delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY); + var newLoc = goog.math.Coordinate.sum(this.startXY_, delta); + + this.draggingBubble_.moveDuringDrag(this.dragSurface_, newLoc); + // TODO (fenichel): Possibly update the cursor if dragging to the trash can + // is allowed. +}; + +/** + * Finish a bubble drag and put the bubble 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.BubbleDragger.prototype.endBubbleDrag = function( + e, currentDragDeltaXY) { + // Make sure internal state is fresh. + this.dragBubble(e, currentDragDeltaXY); + + var delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY); + var newLoc = goog.math.Coordinate.sum(this.startXY_, delta); + + // Move the bubble to its final location. + this.draggingBubble_.moveTo(newLoc.x, newLoc.y); + // Put everything back onto the bubble canvas. + if (this.dragSurface_) { + this.dragSurface_.clearAndHide(this.workspace_.getBubbleCanvas()); + } + + this.fireMoveEvent_(); + this.workspace_.setResizesEnabled(true); + + Blockly.Events.setGroup(false); +}; + +/** + * Fire a move event at the end of a bubble drag. + * @private + */ +Blockly.BubbleDragger.prototype.fireMoveEvent_ = function() { + // TODO (fenichel): move events for comments. + return; +}; + +/** + * 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.BubbleDragger.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 the bubble onto the drag surface at the beginning of a drag. Move the + * drag surface to preserve the apparent location of the bubble. + * @private + */ +Blockly.BubbleDragger.prototype.moveToDragSurface_ = function() { + this.draggingBubble_.moveTo(0, 0); + this.dragSurface_.translateSurface(this.startXY_.x, this.startXY_.y); + // Execute the move on the top-level SVG component. + this.dragSurface_.setBlocksAndShow(this.draggingBubble_.getSvgRoot()); +}; diff --git a/core/comment.js b/core/comment.js index ff56598f4..2372e2bbb 100644 --- a/core/comment.js +++ b/core/comment.js @@ -75,15 +75,21 @@ Blockly.Comment.prototype.drawIcon_ = function(group) { // systems render it differently. // Body of question mark. Blockly.utils.createSvgElement('path', - {'class': 'blocklyIconSymbol', - 'd': 'm6.8,10h2c0.003,-0.617 0.271,-0.962 0.633,-1.266 2.875,-2.405' + - '0.607,-5.534 -3.765,-3.874v1.7c3.12,-1.657 3.698,0.118 2.336,1.25' + - '-1.201,0.998 -1.201,1.528 -1.204,2.19z'}, + { + 'class': 'blocklyIconSymbol', + 'd': 'm6.8,10h2c0.003,-0.617 0.271,-0.962 0.633,-1.266 2.875,-2.405' + + '0.607,-5.534 -3.765,-3.874v1.7c3.12,-1.657 3.698,0.118 2.336,1.25' + + '-1.201,0.998 -1.201,1.528 -1.204,2.19z'}, group); // Dot of question mark. Blockly.utils.createSvgElement('rect', - {'class': 'blocklyIconSymbol', - 'x': '6.8', 'y': '10.78', 'height': '2', 'width': '2'}, + { + 'class': 'blocklyIconSymbol', + 'x': '6.8', + 'y': '10.78', + 'height': '2', + 'width': '2' + }, group); }; @@ -124,7 +130,7 @@ Blockly.Comment.prototype.createEditor_ = function() { /* eslint-enable no-unused-vars */) { if (this.text_ != textarea.value) { Blockly.Events.fire(new Blockly.Events.BlockChange( - this.block_, 'comment', null, this.text_, textarea.value)); + this.block_, 'comment', null, this.text_, textarea.value)); this.text_ = textarea.value; } }); @@ -212,7 +218,7 @@ Blockly.Comment.prototype.setVisible = function(visible) { * @private */ Blockly.Comment.prototype.textareaFocus_ = function( - /* eslint-disable no-unused-vars */ e /* eslint-enable no-unused-vars */) { + /* eslint-disable no-unused-vars */ e /* eslint-enable no-unused-vars */) { // Ideally this would be hooked to the focus event for the comment. // However doing so in Firefox swallows the cursor for unknown reasons. // So this is hooked to mouseup instead. No big deal. @@ -263,7 +269,7 @@ Blockly.Comment.prototype.getText = function() { Blockly.Comment.prototype.setText = function(text) { if (this.text_ != text) { Blockly.Events.fire(new Blockly.Events.BlockChange( - this.block_, 'comment', null, this.text_, text)); + this.block_, 'comment', null, this.text_, text)); this.text_ = text; } if (this.textarea_) { diff --git a/core/connection_db.js b/core/connection_db.js index 8b3c3008e..acb044cf6 100644 --- a/core/connection_db.js +++ b/core/connection_db.js @@ -109,9 +109,8 @@ Blockly.ConnectionDB.prototype.findConnection = function(conn) { * @return {number} The candidate index. * @private */ -Blockly.ConnectionDB.prototype.findPositionForConnection_ = - function(connection) { - /* eslint-disable indent */ +Blockly.ConnectionDB.prototype.findPositionForConnection_ = function( + connection) { if (!this.length) { return 0; } @@ -129,7 +128,7 @@ Blockly.ConnectionDB.prototype.findPositionForConnection_ = } } return pointerMin; -}; /* eslint-enable indent */ +}; /** * Remove a connection from the database. Must already exist in DB. diff --git a/core/constants.js b/core/constants.js index f5428ff74..54ed1fb49 100644 --- a/core/constants.js +++ b/core/constants.js @@ -241,6 +241,13 @@ Blockly.DELETE_AREA_TOOLBOX = 2; * @const {string} */ Blockly.VARIABLE_CATEGORY_NAME = 'VARIABLE'; +/** + * String for use in the "custom" attribute of a category in toolbox xml. + * This string indicates that the category should be dynamically populated with + * variable blocks. + * @const {string} + */ +Blockly.VARIABLE_DYNAMIC_CATEGORY_NAME = 'VARIABLE_DYNAMIC'; /** * String for use in the "custom" attribute of a category in toolbox xml. diff --git a/core/contextmenu.js b/core/contextmenu.js index 3cbc9a831..ba25701ea 100644 --- a/core/contextmenu.js +++ b/core/contextmenu.js @@ -67,8 +67,8 @@ Blockly.ContextMenu.show = function(e, options, rtl) { } var menu = Blockly.ContextMenu.populate_(options, rtl); - goog.events.listen(menu, goog.ui.Component.EventType.ACTION, - Blockly.ContextMenu.hide); + 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 @@ -98,8 +98,8 @@ Blockly.ContextMenu.populate_ = function(options, rtl) { menu.addChild(menuItem, true); menuItem.setEnabled(option.enabled); if (option.enabled) { - goog.events.listen(menuItem, goog.ui.Component.EventType.ACTION, - option.callback); + goog.events.listen( + menuItem, goog.ui.Component.EventType.ACTION, option.callback); menuItem.handleContextMenu = function(/* e */) { // Right-clicking on menu option should count as a click. goog.events.dispatchEvent(this, goog.ui.Component.EventType.ACTION); @@ -154,8 +154,8 @@ Blockly.ContextMenu.createWidget_ = function(menu) { var menuDom = menu.getElement(); Blockly.utils.addClass(menuDom, 'blocklyContextMenu'); // Prevent system context menu when right-clicking a Blockly context menu. - Blockly.bindEventWithChecks_(menuDom, 'contextmenu', null, - Blockly.utils.noEvent); + Blockly.bindEventWithChecks_( + menuDom, 'contextmenu', null, Blockly.utils.noEvent); // Enable autofocus after the initial render to avoid issue #1329. menu.setAllowAutoFocus(true); }; diff --git a/core/css.js b/core/css.js index f81fd4dd5..57c244d3c 100644 --- a/core/css.js +++ b/core/css.js @@ -136,8 +136,8 @@ Blockly.Css.CONTENT = [ '.blocklyNonSelectable {', 'user-select: none;', '-moz-user-select: none;', - '-webkit-user-select: none;', '-ms-user-select: none;', + '-webkit-user-select: none;', '}', '.blocklyWsDragSurface {', @@ -172,7 +172,7 @@ Blockly.Css.CONTENT = [ 'display: none;', 'font-family: sans-serif;', 'font-size: 9pt;', - 'opacity: 0.9;', + 'opacity: .9;', 'padding: 2px;', 'position: absolute;', 'z-index: 100000;', /* big value for bootstrap3 compatibility */ @@ -219,7 +219,6 @@ Blockly.Css.CONTENT = [ 'cursor: url("<<>>/handopen.cur"), auto;', 'cursor: grab;', 'cursor: -webkit-grab;', - 'cursor: -moz-grab;', '}', '.blocklyDragging {', @@ -227,7 +226,6 @@ Blockly.Css.CONTENT = [ 'cursor: url("<<>>/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 */ @@ -236,7 +234,6 @@ Blockly.Css.CONTENT = [ 'cursor: url("<<>>/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. @@ -246,7 +243,6 @@ Blockly.Css.CONTENT = [ 'cursor: url("<<>>/handclosed.cur"), auto;', 'cursor: grabbing;', 'cursor: -webkit-grabbing;', - 'cursor: -moz-grabbing;', '}', '.blocklyDragging.blocklyDraggingDelete {', @@ -257,6 +253,12 @@ Blockly.Css.CONTENT = [ 'cursor: url("<<>>/handdelete.cur"), auto;', '}', + '.blocklyToolboxGrab {', + 'cursor: url("<<>>/handclosed.cur"), auto;', + 'cursor: grabbing;', + 'cursor: -webkit-grabbing;', + '}', + '.blocklyDragging>.blocklyPath,', '.blocklyDragging>.blocklyPathLight {', 'fill-opacity: .8;', @@ -344,6 +346,7 @@ Blockly.Css.CONTENT = [ '.blocklySvg text, .blocklyBlockDragSurface text {', 'user-select: none;', '-moz-user-select: none;', + '-ms-user-select: none;', '-webkit-user-select: none;', 'cursor: inherit;', '}', @@ -502,6 +505,9 @@ Blockly.Css.CONTENT = [ '.blocklyDropdownMenu {', 'padding: 0 !important;', + /* max-height value is same as the constant + * Blockly.FieldDropdown.MAX_MENU_HEIGHT defined in field_dropdown.js. */ + 'max-height: 300px !important;', '}', /* Override the default Closure URL. */ @@ -516,6 +522,10 @@ Blockly.Css.CONTENT = [ 'overflow-x: visible;', 'overflow-y: auto;', 'position: absolute;', + 'user-select: none;', + '-moz-user-select: none;', + '-ms-user-select: none;', + '-webkit-user-select: none;', 'z-index: 70;', /* so blocks go under toolbox when dragging */ '-webkit-tap-highlight-color: transparent;', /* issue #1345 */ '}', @@ -580,7 +590,7 @@ Blockly.Css.CONTENT = [ '}', '.blocklyTreeIconClosedRtl {', - 'background-position: 0px -1px;', + 'background-position: 0 -1px;', '}', '.blocklyTreeIconOpen {', @@ -592,7 +602,7 @@ Blockly.Css.CONTENT = [ '}', '.blocklyTreeSelected>.blocklyTreeIconClosedRtl {', - 'background-position: 0px -17px;', + 'background-position: 0 -17px;', '}', '.blocklyTreeSelected>.blocklyTreeIconOpen {', @@ -776,7 +786,6 @@ Blockly.Css.CONTENT = [ '.blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-icon {', 'opacity: 0.3;', - '-moz-opacity: 0.3;', 'filter: alpha(opacity=30);', '}', diff --git a/core/dragged_connection_manager.js b/core/dragged_connection_manager.js index 059d22fab..c51d31eab 100644 --- a/core/dragged_connection_manager.js +++ b/core/dragged_connection_manager.js @@ -163,8 +163,8 @@ Blockly.DraggedConnectionManager.prototype.update = function(dxy, deleteArea) { oldClosestConnection.unhighlight(); } - // Prefer connecting over dropping into the trash can, but prefer dragging to - // the toolbox over connecting to other blocks. + // 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() && diff --git a/core/events.js b/core/events.js index e1ffad6f0..6cf774477 100644 --- a/core/events.js +++ b/core/events.js @@ -1126,7 +1126,6 @@ Blockly.Events.VarRename.prototype.run = function(forward) { Blockly.Events.disableOrphans = function(event) { if (event.type == Blockly.Events.MOVE || event.type == Blockly.Events.CREATE) { - Blockly.Events.disable(); var workspace = Blockly.Workspace.getById(event.workspaceId); var block = workspace.getBlockById(event.blockId); if (block) { @@ -1143,6 +1142,5 @@ Blockly.Events.disableOrphans = function(event) { } while (block); } } - Blockly.Events.enable(); } }; diff --git a/core/extensions.js b/core/extensions.js index b6f4a25c2..016fc4ac5 100644 --- a/core/extensions.js +++ b/core/extensions.js @@ -33,6 +33,9 @@ **/ goog.provide('Blockly.Extensions'); +goog.require('Blockly.Mutator'); +goog.require('Blockly.utils'); +goog.require('goog.string'); /** * The set of all registered extensions, keyed by extension name/id. @@ -71,6 +74,9 @@ Blockly.Extensions.register = function(name, initFn) { * registered. */ Blockly.Extensions.registerMixin = function(name, mixinObj) { + if (!goog.isObject(mixinObj)){ + throw new Error('Error: Mixin "' + name + '" must be a object'); + } Blockly.Extensions.register(name, function() { this.mixin(mixinObj); }); @@ -94,10 +100,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, - 'domToMutation'); - Blockly.Extensions.checkHasFunction_(errorPrefix, mixinObj.mutationToDom, - 'mutationToDom'); + Blockly.Extensions.checkHasFunction_( + errorPrefix, mixinObj.domToMutation, 'domToMutation'); + Blockly.Extensions.checkHasFunction_( + errorPrefix, mixinObj.mutationToDom, 'mutationToDom'); var hasMutatorDialog = Blockly.Extensions.checkMutatorDialog_(mixinObj, errorPrefix); @@ -322,7 +328,7 @@ Blockly.Extensions.buildTooltipForDropdown = function(dropdownName, // Wait for load, in case Blockly.Msg is not yet populated. // runAfterPageLoad() does not run in a Node.js environment due to lack of // document object, in which case skip the validation. - if (document) { // Relies on document.readyState + if (typeof document == 'object') { // Relies on document.readyState Blockly.utils.runAfterPageLoad(function() { for (var key in lookupTable) { // Will print warnings if reference is missing. @@ -403,7 +409,7 @@ Blockly.Extensions.buildTooltipWithFieldValue = function(msgTemplate, // Wait for load, in case Blockly.Msg is not yet populated. // runAfterPageLoad() does not run in a Node.js environment due to lack of // document object, in which case skip the validation. - if (document) { // Relies on document.readyState + if (typeof document == 'object') { // Relies on document.readyState Blockly.utils.runAfterPageLoad(function() { // Will print warnings if reference is missing. Blockly.utils.checkMessageReferences(msgTemplate); diff --git a/core/field.js b/core/field.js index 2dbb16919..66f457a3a 100644 --- a/core/field.js +++ b/core/field.js @@ -142,11 +142,13 @@ Blockly.Field.prototype.init = function() { this.fieldGroup_.style.display = 'none'; } this.borderRect_ = Blockly.utils.createSvgElement('rect', - {'rx': 4, - 'ry': 4, - 'x': -Blockly.BlockSvg.SEP_SPACE_X / 2, - 'y': 0, - 'height': 16}, this.fieldGroup_); + { + 'rx': 4, + 'ry': 4, + 'x': -Blockly.BlockSvg.SEP_SPACE_X / 2, + 'y': 0, + 'height': 16 + }, this.fieldGroup_); /** @type {!Element} */ this.textElement_ = Blockly.utils.createSvgElement('text', {'class': 'blocklyText', 'y': this.size_.height - 12.5}, @@ -155,8 +157,8 @@ Blockly.Field.prototype.init = function() { this.updateEditable(); this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_); this.mouseDownWrapper_ = - Blockly.bindEventWithChecks_(this.fieldGroup_, 'mousedown', this, - this.onMouseDown_); + Blockly.bindEventWithChecks_( + this.fieldGroup_, 'mousedown', this, this.onMouseDown_); // Force a render. this.render_(); }; diff --git a/core/field_angle.js b/core/field_angle.js index 5e66e0a68..59384d08b 100644 --- a/core/field_angle.js +++ b/core/field_angle.js @@ -53,6 +53,16 @@ Blockly.FieldAngle = function(opt_value, opt_validator) { }; goog.inherits(Blockly.FieldAngle, Blockly.FieldTextInput); +/** + * Construct a FieldAngle from a JSON arg object. + * @param {!Object} options A JSON object with options (angle). + * @returns {!Blockly.FieldAngle} The new field instance. + * @package + */ +Blockly.FieldAngle.fromJson = function(options) { + return new Blockly.FieldAngle(options['angle']); +}; + /** * Round angles to the nearest 15 degrees when using mouse. * Set to 0 to disable rounding. @@ -203,8 +213,7 @@ Blockly.FieldAngle.prototype.showEditor_ = function() { this.moveWrapper1_ = Blockly.bindEvent_(circle, 'mousemove', this, this.onMouseMove); this.moveWrapper2_ = - Blockly.bindEvent_(this.gauge_, 'mousemove', this, - this.onMouseMove); + Blockly.bindEvent_(this.gauge_, 'mousemove', this, this.onMouseMove); this.updateGraph_(); }; diff --git a/core/field_checkbox.js b/core/field_checkbox.js index 0f2c78c6a..d723bab1f 100644 --- a/core/field_checkbox.js +++ b/core/field_checkbox.js @@ -46,6 +46,16 @@ Blockly.FieldCheckbox = function(state, opt_validator) { }; goog.inherits(Blockly.FieldCheckbox, Blockly.Field); +/** + * Construct a FieldCheckbox from a JSON arg object. + * @param {!Object} options A JSON object with options (checked). + * @returns {!Blockly.FieldCheckbox} The new field instance. + * @package + */ +Blockly.FieldCheckbox.fromJson = function(options) { + return new Blockly.FieldCheckbox(options['checked'] ? 'TRUE' : 'FALSE'); +}; + /** * Character for the checkmark. */ diff --git a/core/field_colour.js b/core/field_colour.js index c7d26d0a9..77632c184 100644 --- a/core/field_colour.js +++ b/core/field_colour.js @@ -52,6 +52,16 @@ Blockly.FieldColour = function(colour, opt_validator) { }; goog.inherits(Blockly.FieldColour, Blockly.Field); +/** + * Construct a FieldColour from a JSON arg object. + * @param {!Object} options A JSON object with options (colour). + * @returns {!Blockly.FieldColour} The new field instance. + * @package + */ +Blockly.FieldColour.fromJson = function(options) { + return new Blockly.FieldColour(options['colour']); +}; + /** * By default use the global constants for colours. * @type {Array.} diff --git a/core/field_date.js b/core/field_date.js index 8d3c601b8..53ced0e14 100644 --- a/core/field_date.js +++ b/core/field_date.js @@ -30,6 +30,7 @@ goog.require('Blockly.Field'); goog.require('Blockly.utils'); goog.require('goog.date'); +goog.require('goog.date.DateTime'); goog.require('goog.dom'); goog.require('goog.events'); goog.require('goog.i18n.DateTimeSymbols'); @@ -58,6 +59,16 @@ Blockly.FieldDate = function(date, opt_validator) { }; goog.inherits(Blockly.FieldDate, Blockly.Field); +/** + * Construct a FieldDate from a JSON arg object. + * @param {!Object} options A JSON object with options (date). + * @returns {!Blockly.FieldDate} The new field instance. + * @package + */ +Blockly.FieldDate.fromJson = function(options) { + return new Blockly.FieldDate(options['date']); +}; + /** * Mouse cursor style when over the hotspot that initiates the editor. */ @@ -144,7 +155,7 @@ Blockly.FieldDate.prototype.createWidget_ = function() { picker.setShowWeekNum(false); var div = Blockly.WidgetDiv.DIV; picker.render(div); - picker.setDate(goog.date.fromIsoString(this.getValue())); + picker.setDate(goog.date.DateTime.fromIsoString(this.getValue())); return picker; }; diff --git a/core/field_dropdown.js b/core/field_dropdown.js index 75a6adb94..3ecca22ba 100644 --- a/core/field_dropdown.js +++ b/core/field_dropdown.js @@ -63,11 +63,27 @@ Blockly.FieldDropdown = function(menuGenerator, opt_validator) { }; goog.inherits(Blockly.FieldDropdown, Blockly.Field); +/** + * Construct a FieldDropdown from a JSON arg object. + * @param {!Object} options A JSON object with options (options). + * @returns {!Blockly.FieldDropdown} The new field instance. + * @package + */ +Blockly.FieldDropdown.fromJson = function(options) { + return new Blockly.FieldDropdown(options['options']); +}; + /** * Horizontal distance that a checkmark overhangs the dropdown. */ Blockly.FieldDropdown.CHECKMARK_OVERHANG = 25; +/** + * Maximum height of the dropdown menu,it's also referenced in css.js as + * part of .blocklyDropdownMenu. + */ +Blockly.FieldDropdown.MAX_MENU_HEIGHT = 300; + /** * Android can't (in 2014) display "▾", so use "▼" instead. */ @@ -115,10 +131,6 @@ Blockly.FieldDropdown.prototype.init = function() { ' ' + Blockly.FieldDropdown.ARROW_CHAR)); Blockly.FieldDropdown.superClass_.init.call(this); - // Force a reset of the text to add the arrow. - var text = this.text_; - this.text_ = null; - this.setText(text); }; /** @@ -176,8 +188,8 @@ Blockly.FieldDropdown.prototype.addTouchStartListener_ = function(menu) { // Highlight the menu item. control.handleMouseDown(e); } - menu.getHandler().listen(menu.getElement(), goog.events.EventType.TOUCHSTART, - callback); + menu.getHandler().listen( + menu.getElement(), goog.events.EventType.TOUCHSTART, callback); }; /** @@ -192,8 +204,8 @@ Blockly.FieldDropdown.prototype.addTouchEndListener_ = function(menu) { // Activate the menu item. control.performActionInternal(e); } - menu.getHandler().listen(menu.getElement(), goog.events.EventType.TOUCHEND, - callbackTouchEnd); + menu.getHandler().listen( + menu.getElement(), goog.events.EventType.TOUCHEND, callbackTouchEnd); }; /** @@ -241,6 +253,10 @@ Blockly.FieldDropdown.prototype.positionMenu_ = function(menu) { this.createWidget_(menu); var menuSize = Blockly.utils.uiMenu.getSize(menu); + if (menuSize.height > Blockly.FieldDropdown.MAX_MENU_HEIGHT) { + menuSize.height = Blockly.FieldDropdown.MAX_MENU_HEIGHT; + } + if (this.sourceBlock_.RTL) { Blockly.utils.uiMenu.adjustBBoxesForRTL(viewportBBox, anchorBBox, menuSize); } @@ -477,11 +493,13 @@ Blockly.FieldDropdown.prototype.render_ = function() { Blockly.FieldDropdown.prototype.renderSelectedImage_ = function() { // Image option is selected. this.imageElement_ = Blockly.utils.createSvgElement('image', - {'y': 5, - 'height': this.imageJson_.height + 'px', - 'width': this.imageJson_.width + 'px'}, this.fieldGroup_); - this.imageElement_.setAttributeNS('http://www.w3.org/1999/xlink', - 'xlink:href', this.imageJson_.src); + { + 'y': 5, + 'height': this.imageJson_.height + 'px', + 'width': this.imageJson_.width + 'px' + }, this.fieldGroup_); + this.imageElement_.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', this.imageJson_.src); // Insert dropdown arrow. this.textElement_.appendChild(this.arrow_); var arrowWidth = Blockly.Field.getCachedWidth(this.arrow_); diff --git a/core/field_image.js b/core/field_image.js index 8ee57bc6a..eaa8e9995 100644 --- a/core/field_image.js +++ b/core/field_image.js @@ -60,6 +60,23 @@ Blockly.FieldImage = function(src, width, height, opt_alt, opt_onClick) { }; goog.inherits(Blockly.FieldImage, Blockly.Field); +/** + * Construct a FieldImage from a JSON arg object, + * dereferencing any string table references. + * @param {!Object} options A JSON object with options (src, width, height, and + * alt). + * @returns {!Blockly.FieldImage} The new field instance. + * @package + */ +Blockly.FieldImage.fromJson = function(options) { + var src = Blockly.utils.replaceMessageReferences(options['src']); + var width = Number(Blockly.utils.replaceMessageReferences(options['width'])); + var height = + Number(Blockly.utils.replaceMessageReferences(options['height'])); + var alt = Blockly.utils.replaceMessageReferences(options['alt']); + return new Blockly.FieldImage(src, width, height, alt); +}; + /** * Editable fields are saved by the XML renderer, non-editable fields are not. */ @@ -81,12 +98,12 @@ Blockly.FieldImage.prototype.init = function() { } /** @type {SVGElement} */ this.imageElement_ = Blockly.utils.createSvgElement( - 'image', - { - 'height': this.height_ + 'px', - 'width': this.width_ + 'px' - }, - this.fieldGroup_); + 'image', + { + 'height': this.height_ + 'px', + 'width': this.width_ + 'px' + }, + this.fieldGroup_); this.setValue(this.src_); this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_); @@ -114,8 +131,8 @@ Blockly.FieldImage.prototype.dispose = function() { Blockly.FieldImage.prototype.maybeAddClickHandler_ = function() { if (this.clickHandler_) { this.mouseDownWrapper_ = - Blockly.bindEventWithChecks_(this.fieldGroup_, 'mousedown', this, - this.onMouseDown_); + Blockly.bindEventWithChecks_( + this.fieldGroup_, 'mousedown', this, this.onMouseDown_); } }; @@ -175,12 +192,19 @@ Blockly.FieldImage.prototype.render_ = function() { // NOP }; +/** + * Images are fixed width, no need to render even if forced. + */ +Blockly.FieldImage.prototype.forceRerender = function() { + // NOP +}; + /** * Images are fixed width, no need to update. * @private */ Blockly.FieldImage.prototype.updateWidth = function() { - // NOP + // NOP }; /** diff --git a/core/field_label.js b/core/field_label.js index f4bca6a24..039f73a75 100644 --- a/core/field_label.js +++ b/core/field_label.js @@ -46,6 +46,18 @@ Blockly.FieldLabel = function(text, opt_class) { }; goog.inherits(Blockly.FieldLabel, Blockly.Field); +/** + * Construct a FieldLabel from a JSON arg object, + * dereferencing any string table references. + * @param {!Object} options A JSON object with options (text, and class). + * @returns {!Blockly.FieldLabel} The new field instance. + * @package + */ +Blockly.FieldLabel.fromJson = function(options) { + var text = Blockly.utils.replaceMessageReferences(options['text']); + return new Blockly.FieldLabel(text, options['class']); +}; + /** * Editable fields are saved by the XML renderer, non-editable fields are not. */ diff --git a/core/field_number.js b/core/field_number.js index d3575fde3..ca90d4d2c 100644 --- a/core/field_number.js +++ b/core/field_number.js @@ -53,6 +53,18 @@ Blockly.FieldNumber = function(opt_value, opt_min, opt_max, opt_precision, }; goog.inherits(Blockly.FieldNumber, Blockly.FieldTextInput); +/** + * Construct a FieldNumber from a JSON arg object. + * @param {!Object} options A JSON object with options (value, min, max, and + * precision). + * @returns {!Blockly.FieldNumber} The new field instance. + * @package + */ +Blockly.FieldNumber.fromJson = function(options) { + return new Blockly.FieldNumber(options['value'], + options['min'], options['max'], options['precision']); +}; + /** * Set the maximum, minimum and precision constraints on this field. * Any of these properties may be undefiend or NaN to be disabled. diff --git a/core/field_textinput.js b/core/field_textinput.js index 82a0c3158..a8490ab18 100644 --- a/core/field_textinput.js +++ b/core/field_textinput.js @@ -50,6 +50,23 @@ Blockly.FieldTextInput = function(text, opt_validator) { }; goog.inherits(Blockly.FieldTextInput, Blockly.Field); +/** + * Construct a FieldTextInput from a JSON arg object, + * dereferencing any string table references. + * @param {!Object} options A JSON object with options (text, class, and + * spellcheck). + * @returns {!Blockly.FieldTextInput} The new field instance. + * @package + */ +Blockly.FieldTextInput.fromJson = function(options) { + var text = Blockly.utils.replaceMessageReferences(options['text']); + var field = new Blockly.FieldTextInput(text, options['class']); + if (typeof options['spellcheck'] === 'boolean') { + field.setSpellcheck(options['spellcheck']); + } + return field; +}; + /** * Point size of text. Should match blocklyText's font-size in CSS. */ @@ -156,12 +173,12 @@ Blockly.FieldTextInput.prototype.showEditor_ = function(opt_quietInput) { Blockly.FieldTextInput.prototype.showPromptEditor_ = function() { var fieldText = this; Blockly.prompt(Blockly.Msg.CHANGE_VALUE_TITLE, this.text_, - function(newValue) { - if (fieldText.sourceBlock_) { - newValue = fieldText.callValidator(newValue); - } - fieldText.setValue(newValue); - }); + function(newValue) { + if (fieldText.sourceBlock_) { + newValue = fieldText.callValidator(newValue); + } + fieldText.setValue(newValue); + }); }; /** @@ -206,16 +223,16 @@ Blockly.FieldTextInput.prototype.showInlineEditor_ = function(quietInput) { Blockly.FieldTextInput.prototype.bindEvents_ = function(htmlInput) { // Bind to keydown -- trap Enter without IME and Esc to hide. htmlInput.onKeyDownWrapper_ = - Blockly.bindEventWithChecks_(htmlInput, 'keydown', this, - this.onHtmlInputKeyDown_); + Blockly.bindEventWithChecks_( + htmlInput, 'keydown', this, this.onHtmlInputKeyDown_); // Bind to keyup -- trap Enter; resize after every keystroke. htmlInput.onKeyUpWrapper_ = - Blockly.bindEventWithChecks_(htmlInput, 'keyup', this, - this.onHtmlInputChange_); + Blockly.bindEventWithChecks_( + htmlInput, 'keyup', this, this.onHtmlInputChange_); // Bind to keyPress -- repeatedly resize when holding down a key. htmlInput.onKeyPressWrapper_ = - Blockly.bindEventWithChecks_(htmlInput, 'keypress', this, - this.onHtmlInputChange_); + Blockly.bindEventWithChecks_( + htmlInput, 'keypress', this, this.onHtmlInputChange_); htmlInput.onWorkspaceChangeWrapper_ = this.resizeEditor_.bind(this); this.workspace_.addChangeListener(htmlInput.onWorkspaceChangeWrapper_); }; diff --git a/core/field_variable.js b/core/field_variable.js index 3fa227c39..333fee2d6 100644 --- a/core/field_variable.js +++ b/core/field_variable.js @@ -30,7 +30,6 @@ 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'); @@ -43,19 +42,44 @@ goog.require('goog.string'); * option is selected. Its sole argument is the new option value. * @param {Array.=} opt_variableTypes A list of the types of variables * to include in the dropdown. + * @param {string=} opt_defaultType The type of variable to create if this + * field's value is not explicitly set. Defaults to ''. * @extends {Blockly.FieldDropdown} * @constructor */ -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; +Blockly.FieldVariable = function(varname, opt_validator, opt_variableTypes, + opt_defaultType) { + // The FieldDropdown constructor would call setValue, which might create a + // spurious variable. Just do the relevant parts of the constructor. + this.menuGenerator_ = Blockly.FieldVariable.dropdownCreate; + this.size_ = new goog.math.Size(0, Blockly.BlockSvg.MIN_BLOCK_Y); + this.setValidator(opt_validator); + this.defaultVariableName = (varname || ''); + + this.setTypes_(opt_variableTypes, opt_defaultType); + this.value_ = null; }; goog.inherits(Blockly.FieldVariable, Blockly.FieldDropdown); /** - * Install this dropdown on a block. + * Construct a FieldVariable from a JSON arg object, + * dereferencing any string table references. + * @param {!Object} options A JSON object with options (variable, + * variableTypes, and defaultType). + * @returns {!Blockly.FieldVariable} The new field instance. + * @package + */ +Blockly.FieldVariable.fromJson = function(options) { + var varname = Blockly.utils.replaceMessageReferences(options['variable']); + var variableTypes = options['variableTypes']; + var defaultType = options['defaultType']; + return new Blockly.FieldVariable(varname, null, variableTypes, defaultType); +}; + +/** + * Initialize everything needed to render this field. This includes making sure + * that the field's value is valid. + * @public */ Blockly.FieldVariable.prototype.init = function() { if (this.fieldGroup_) { @@ -68,23 +92,40 @@ Blockly.FieldVariable.prototype.init = function() { this.initModel(); }; +/** + * Initialize the model for this field if it has not already been initialized. + * If the value has not been set to a variable by the first render, we make up a + * variable rather than let the value be invalid. + * @package + */ Blockly.FieldVariable.prototype.initModel = function() { - if (!this.getValue()) { - // Variables without names get uniquely named for this workspace. - var workspace = - this.sourceBlock_.isInFlyout ? - this.sourceBlock_.workspace.targetWorkspace : - this.sourceBlock_.workspace; - this.setValue(Blockly.Variables.generateUniqueName(workspace)); + if (this.variable_) { + return; // Initialization already happened. } - // If the selected variable doesn't exist yet, create it. - // For instance, some blocks in the toolbox have variable dropdowns filled - // in by default. - if (!this.sourceBlock_.isInFlyout) { - this.sourceBlock_.workspace.createVariable(this.getValue()); + this.workspace_ = this.sourceBlock_.workspace; + var variable = Blockly.Variables.getOrCreateVariablePackage( + this.workspace_, null, this.defaultVariableName, this.defaultType_); + + // Don't fire a change event for this setValue. It would have null as the + // old value, which is not valid. + Blockly.Events.disable(); + try { + this.setValue(variable.getId()); + } finally { + Blockly.Events.enable(); } }; +/** + * Dispose of this field. + * @public + */ +Blockly.FieldVariable.dispose = function() { + Blockly.FieldVariable.superClass_.dispose.call(this); + this.workspace_ = null; + this.variableMap_ = null; +}; + /** * Attach this field to a block. * @param {!Blockly.Block} block The block containing this field. @@ -96,39 +137,80 @@ Blockly.FieldVariable.prototype.setSourceBlock = function(block) { }; /** - * Get the variable's name (use a variableDB to convert into a real name). - * Unline a regular dropdown, variables are literal and have no neutral value. - * @return {string} Current text. + * Get the variable's ID. + * @return {string} Current variable's ID. */ Blockly.FieldVariable.prototype.getValue = function() { - return this.getText(); + return this.variable_ ? this.variable_.getId() : null; }; /** - * Set the variable name. - * @param {string} value New text. + * Get the text from this field, which is the selected variable's name. + * @return {string} The selected variable's name, or the empty string if no + * variable is selected. */ -Blockly.FieldVariable.prototype.setValue = function(value) { - var newValue = value; - var newText = value; +Blockly.FieldVariable.prototype.getText = function() { + return this.variable_ ? this.variable_.name : ''; +}; - 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)); +/** + * Get the variable model for the selected variable. + * Not guaranteed to be in the variable map on the workspace (e.g. if accessed + * after the variable has been deleted). + * @return {?Blockly.VariableModel} the selected variable, or null if none was + * selected. + * @package + */ +Blockly.FieldVariable.prototype.getVariable = function() { + return this.variable_; +}; + +/** + * Set the variable ID. + * @param {string} id New variable ID, which must reference an existing + * variable. + */ +Blockly.FieldVariable.prototype.setValue = function(id) { + var workspace = this.sourceBlock_.workspace; + var variable = Blockly.Variables.getVariable(workspace, id); + + if (!variable) { + throw new Error('Variable id doesn\'t point to a real variable! ID was ' + + id); + } + // Type checks! + var type = variable.type; + if (!this.typeIsAllowed_(type)) { + throw new Error('Variable type doesn\'t match this field! Type was ' + + type); + } + if (this.sourceBlock_ && Blockly.Events.isEnabled()) { + var oldValue = this.variable_ ? this.variable_.getId() : null; + Blockly.Events.fire(new Blockly.Events.BlockChange( + this.sourceBlock_, 'field', this.name, oldValue, id)); + } + this.variable_ = variable; + this.value_ = id; + this.setText(variable.name); +}; + +/** + * Check whether the given variable type is allowed on this field. + * @param {string} type The type to check. + * @return {boolean} True if the type is in the list of allowed types. + * @private + */ +Blockly.FieldVariable.prototype.typeIsAllowed_ = function(type) { + var typeList = this.getVariableTypes_(); + if (!typeList) { + return true; // If it's null, all types are valid. + } + for (var i = 0; i < typeList.length; i++) { + if (type == typeList[i]) { + return true; } } - this.value_ = newValue; - this.setText(newText); + return false; }; /** @@ -138,6 +220,7 @@ Blockly.FieldVariable.prototype.setValue = function(value) { * @private */ Blockly.FieldVariable.prototype.getVariableTypes_ = function() { + // TODO (#1513): Try to avoid calling this every time the field is edited. var variableTypes = this.variableTypes; if (variableTypes === null) { // If variableTypes is null, return all variable types. @@ -156,6 +239,46 @@ Blockly.FieldVariable.prototype.getVariableTypes_ = function() { return variableTypes; }; +/** + * Parse the optional arguments representing the allowed variable types and the + * default variable type. + * @param {Array.=} opt_variableTypes A list of the types of variables + * to include in the dropdown. If null or undefined, variables of all types + * will be displayed in the dropdown. + * @param {string=} opt_defaultType The type of the variable to create if this + * field's value is not explicitly set. Defaults to ''. + * @private + */ +Blockly.FieldVariable.prototype.setTypes_ = function(opt_variableTypes, + opt_defaultType) { + // If you expected that the default type would be the same as the only entry + // in the variable types array, tell the Blockly team by commenting on #1499. + var defaultType = opt_defaultType || ''; + // Set the allowable variable types. Null means all types on the workspace. + if (opt_variableTypes == null || opt_variableTypes == undefined) { + var variableTypes = null; + } else if (Array.isArray(opt_variableTypes)) { + var variableTypes = opt_variableTypes; + // Make sure the default type is valid. + var isInArray = false; + for (var i = 0; i < variableTypes.length; i++) { + if (variableTypes[i] == defaultType) { + isInArray = true; + } + } + if (!isInArray) { + throw new Error('Invalid default type \'' + defaultType + '\' in ' + + 'the definition of a FieldVariable'); + } + } else { + throw new Error('\'variableTypes\' was not an array in the definition of ' + + 'a FieldVariable'); + } + // Only update the field once all checks pass. + this.defaultType_ = defaultType; + this.variableTypes = variableTypes; +}; + /** * Return a sorted list of variable names for variable dropdown menus. * Include a special option at the end for creating a new variable name. @@ -163,10 +286,12 @@ Blockly.FieldVariable.prototype.getVariableTypes_ = function() { * @this {Blockly.FieldVariable} */ Blockly.FieldVariable.dropdownCreate = function() { + if (!this.variable_) { + throw new Error('Tried to call dropdownCreate on a variable field with no' + + ' variable selected.'); + } 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; @@ -181,20 +306,9 @@ Blockly.FieldVariable.dropdownCreate = function() { 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. - if (createSelectedVariable && workspace) { - var newVar = workspace.createVariable(name); - variableModelList.push(newVar); } variableModelList.sort(Blockly.VariableModel.compareByName); + var options = []; for (var i = 0; i < variableModelList.length; i++) { // Set the UUID as the internal representation of the variable. @@ -202,9 +316,14 @@ Blockly.FieldVariable.dropdownCreate = function() { } 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]); + options.push( + [ + Blockly.Msg.DELETE_VARIABLE.replace('%1', name), + Blockly.DELETE_VARIABLE_ID + ] + ); } + return options; }; @@ -217,31 +336,19 @@ Blockly.FieldVariable.dropdownCreate = function() { */ Blockly.FieldVariable.prototype.onItemSelected = function(menu, menuItem) { 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; - 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) { + if (id == Blockly.RENAME_VARIABLE_ID) { // Rename variable. - var currentName = this.getText(); - variable = workspace.getVariable(currentName); - Blockly.Variables.renameVariable(workspace, variable); + Blockly.Variables.renameVariable(workspace, this.variable_); return; } else if (id == Blockly.DELETE_VARIABLE_ID) { // Delete variable. - workspace.deleteVariable(this.getText()); + workspace.deleteVariableById(this.variable_.getId()); return; } - // Call any validation function, and allow it to override. - itemText = this.callValidator(itemText); - } - if (itemText !== null) { - this.setValue(itemText); + // TODO (#1529): Call any validation function, and allow it to override. } + this.setValue(id); }; diff --git a/core/flyout_base.js b/core/flyout_base.js index 1ec79c31b..d845ae8a6 100644 --- a/core/flyout_base.js +++ b/core/flyout_base.js @@ -241,34 +241,17 @@ Blockly.Flyout.prototype.init = function(targetWorkspace) { // Dragging the flyout up and down. Array.prototype.push.apply(this.eventWrappers_, - Blockly.bindEventWithChecks_(this.svgBackground_, 'mousedown', this, - this.onMouseDown_)); + 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_.variableMap_ = this.targetWorkspace_.getVariableMap(); - 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_); + this.workspace_.createPotentialVariableMap(); }; /** @@ -332,7 +315,7 @@ 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. @@ -542,6 +525,9 @@ Blockly.Flyout.prototype.clearOldBlocks_ = function() { button.dispose(); } this.buttons_.length = 0; + + // Clear potential variables from the previous showing. + this.workspace_.getPotentialVariableMap().clear(); }; /** @@ -606,6 +592,7 @@ Blockly.Flyout.prototype.onMouseDown_ = function(e) { Blockly.Flyout.prototype.createBlock = function(originalBlock) { var newBlock = null; Blockly.Events.disable(); + var variablesBeforeCreation = this.targetWorkspace_.getAllVariables(); this.targetWorkspace_.setResizesEnabled(false); try { newBlock = this.placeNewBlock_(originalBlock); @@ -615,9 +602,17 @@ Blockly.Flyout.prototype.createBlock = function(originalBlock) { Blockly.Events.enable(); } + var newVariables = Blockly.Variables.getAddedVariables(this.targetWorkspace_, + variablesBeforeCreation); + if (Blockly.Events.isEnabled()) { Blockly.Events.setGroup(true); Blockly.Events.fire(new Blockly.Events.Create(newBlock)); + // Fire a VarCreate event for each (if any) new variable created. + for(var i = 0; i < newVariables.length; i++) { + var thisVariable = newVariables[i]; + Blockly.Events.fire(new Blockly.Events.VarCreate(thisVariable)); + } } if (this.autoClose) { this.hide(); @@ -641,8 +636,9 @@ Blockly.Flyout.prototype.initFlyoutButton_ = function(button, 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.listeners_.push( + Blockly.bindEventWithChecks_( + buttonSvg, 'mousedown', this, this.onMouseDown_)); this.buttons_.push(button); }; @@ -664,13 +660,13 @@ 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); + { + '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. diff --git a/core/flyout_button.js b/core/flyout_button.js index bcb1343aa..13ce2b1a6 100644 --- a/core/flyout_button.js +++ b/core/flyout_button.js @@ -138,20 +138,28 @@ Blockly.FlyoutButton.prototype.createDom = function() { if (!this.isLabel_) { // Shadow rectangle (light source does not mirror in RTL). var shadow = Blockly.utils.createSvgElement('rect', - {'class': 'blocklyFlyoutButtonShadow', - 'rx': 4, 'ry': 4, 'x': 1, 'y': 1}, - this.svgGroup_); + { + 'class': 'blocklyFlyoutButtonShadow', + 'rx': 4, 'ry': 4, 'x': 1, 'y': 1 + }, + this.svgGroup_); } // Background rectangle. var rect = Blockly.utils.createSvgElement('rect', - {'class': this.isLabel_ ? - 'blocklyFlyoutLabelBackground' : 'blocklyFlyoutButtonBackground', - 'rx': 4, 'ry': 4}, + { + 'class': this.isLabel_ ? + 'blocklyFlyoutLabelBackground' : 'blocklyFlyoutButtonBackground', + 'rx': 4, 'ry': 4 + }, this.svgGroup_); var svgText = Blockly.utils.createSvgElement('text', - {'class': this.isLabel_ ? 'blocklyFlyoutLabelText' : 'blocklyText', - 'x': 0, 'y': 0, 'text-anchor': 'middle'}, + { + 'class': this.isLabel_ ? 'blocklyFlyoutLabelText' : 'blocklyText', + 'x': 0, + 'y': 0, + 'text-anchor': 'middle' + }, this.svgGroup_); svgText.textContent = this.text_; diff --git a/core/flyout_horizontal.js b/core/flyout_horizontal.js index cee108295..7268e9bee 100644 --- a/core/flyout_horizontal.js +++ b/core/flyout_horizontal.js @@ -193,7 +193,7 @@ Blockly.HorizontalFlyout.prototype.setBackgroundPath_ = function(width, path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1, this.CORNER_RADIUS, -this.CORNER_RADIUS); path.push('h', width); - // Right. + // Right. path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1, this.CORNER_RADIUS, this.CORNER_RADIUS); path.push('v', height); diff --git a/core/flyout_vertical.js b/core/flyout_vertical.js index 32dc7969f..5559c43d9 100644 --- a/core/flyout_vertical.js +++ b/core/flyout_vertical.js @@ -365,7 +365,7 @@ Blockly.VerticalFlyout.prototype.reflowInternal_ = function() { // With the flyoutWidth known, right-align the buttons. for (var i = 0, button; button = this.buttons_[i]; i++) { var y = button.getPosition().y; - var x = flyoutWidth - button.width - this.MARGIN - + var x = flyoutWidth / this.workspace_.scale - button.width - this.MARGIN - Blockly.BlockSvg.TAB_WIDTH; button.moveTo(x, y); } diff --git a/core/generator.js b/core/generator.js index 81feb8f78..eadde6530 100644 --- a/core/generator.js +++ b/core/generator.js @@ -105,7 +105,7 @@ Blockly.Generator.prototype.workspaceToCode = function(workspace) { line = line[0]; } if (line) { - if (block.outputConnection && this.scrubNakedValue) { + if (block.outputConnection) { // This block is a naked value. Ask the language's code generator if // it wants to append a semicolon, or something. line = this.scrubNakedValue(line); @@ -380,7 +380,11 @@ Blockly.Generator.prototype.provideFunction_ = function(desiredName, code) { * names. * @param {!Blockly.Workspace} workspace Workspace to generate code from. */ -Blockly.Generator.prototype.init = undefined; +Blockly.Generator.prototype.init = function( + /* eslint-disable no-unused-vars */ workspace + /* eslint-enable no-unused-vars */) { + // Optionally override +}; /** * Common tasks for generating code from blocks. This is called from @@ -393,7 +397,12 @@ Blockly.Generator.prototype.init = undefined; * @return {string} JavaScript code with comments and subsequent blocks added. * @private */ -Blockly.Generator.prototype.scrub_ = undefined; +Blockly.Generator.prototype.scrub_ = function( + /* eslint-disable no-unused-vars */ block, code + /* eslint-enable no-unused-vars */) { + // Optionally override + return code; +}; /** * Hook for code to run at end of code generation. @@ -402,7 +411,12 @@ Blockly.Generator.prototype.scrub_ = undefined; * @param {string} code Generated code. * @return {string} Completed code. */ -Blockly.Generator.prototype.finish = undefined; +Blockly.Generator.prototype.finish = function( + /* eslint-disable no-unused-vars */ code + /* eslint-enable no-unused-vars */) { + // Optionally override + return code; +}; /** * Naked values are top-level blocks with outputs that aren't plugged into @@ -412,4 +426,9 @@ Blockly.Generator.prototype.finish = undefined; * @param {string} line Line of generated code. * @return {string} Legal line of code. */ -Blockly.Generator.prototype.scrubNakedValue = undefined; +Blockly.Generator.prototype.scrubNakedValue = function( + /* eslint-disable no-unused-vars */ line + /* eslint-enable no-unused-vars */) { + // Optionally override + return line; +}; diff --git a/core/gesture.js b/core/gesture.js index 524923951..6c222ddfb 100644 --- a/core/gesture.js +++ b/core/gesture.js @@ -28,6 +28,7 @@ goog.provide('Blockly.Gesture'); goog.require('Blockly.BlockDragger'); +goog.require('Blockly.BubbleDragger'); goog.require('Blockly.constants'); goog.require('Blockly.FlyoutDragger'); goog.require('Blockly.Tooltip'); @@ -68,6 +69,14 @@ Blockly.Gesture = function(e, creatorWorkspace) { */ this.currentDragDeltaXY_ = 0; + /** + * The bubble that the gesture started on, or null if it did not start on a + * bubble. + * @type {Blockly.Bubble} + * @private + */ + this.startBubble_ = null; + /** * The field that the gesture started on, or null if it did not start on a * field. @@ -136,6 +145,13 @@ Blockly.Gesture = function(e, creatorWorkspace) { */ this.isDraggingBlock_ = false; + /** + * Whether the bubble is currently being dragged. + * @type {boolean} + * @private + */ + this.isDraggingBubble_ = false; + /** * The event that most recently updated this gesture. * @type {!Event} @@ -159,6 +175,13 @@ Blockly.Gesture = function(e, creatorWorkspace) { */ this.onUpWrapper_ = null; + /** + * The object tracking a bubble drag, or null if none is in progress. + * @type {Blockly.BubbleDragger} + * @private + */ + this.bubbleDragger_ = null; + /** * The object tracking a block drag, or null if none is in progress. * @type {Blockly.BlockDragger} @@ -235,6 +258,10 @@ Blockly.Gesture.prototype.dispose = function() { this.workspaceDragger_.dispose(); this.workspaceDragger_ = null; } + if (this.bubbleDragger_) { + this.bubbleDragger_.dispose(); + this.bubbleDragger_ = null; + } }; /** @@ -312,6 +339,25 @@ Blockly.Gesture.prototype.updateIsDraggingFromFlyout_ = function() { return false; }; +/** + * Update this gesture to record whether a bubble 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 bubble should be dragged this function creates the necessary + * BubbleDragger and starts the drag. + * @return {boolean} true if a bubble is being dragged. + * @private + */ +Blockly.Gesture.prototype.updateIsDraggingBubble_ = function() { + if (!this.startBubble_) { + return false; + } + + this.isDraggingBubble_ = true; + this.startDraggingBubble_(); + return true; +}; + /** * 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 @@ -377,7 +423,11 @@ Blockly.Gesture.prototype.updateIsDragging_ = function() { 'updateIsDragging_ should only be called once per gesture.'); this.calledUpdateIsDragging_ = true; - // First check if it was a block drag. + // First check if it was a bubble drag. Bubbles always sit on top of blocks. + if (this.updateIsDraggingBubble_()) { + return; + } + // Then check if it was a block drag. if (this.updateIsDraggingBlock_()) { return; } @@ -397,6 +447,19 @@ Blockly.Gesture.prototype.startDraggingBlock_ = function() { this.currentDragDeltaXY_); }; +/** + * Create a bubble dragger and start dragging the selected bubble. + * TODO (fenichel): Possibly combine this and startDraggingBlock_. + * @private + */ +Blockly.Gesture.prototype.startDraggingBubble_ = function() { + this.bubbleDragger_ = new Blockly.BubbleDragger(this.startBubble_, + this.startWorkspace_); + this.bubbleDragger_.startBubbleDrag(); + this.bubbleDragger_.dragBubble(this.mostRecentEvent_, + this.currentDragDeltaXY_); +}; + /** * Start a gesture: update the workspace to indicate that a gesture is in * progress and bind mousemove and mouseup handlers. @@ -433,12 +496,22 @@ Blockly.Gesture.prototype.doStart = function(e) { return; } - if (goog.string.caseInsensitiveEquals(e.type, 'touchstart')) { + if (goog.string.caseInsensitiveEquals(e.type, 'touchstart') || + goog.string.caseInsensitiveEquals(e.type, 'pointerdown')) { Blockly.longStart_(e, this); } this.mouseDownXY_ = new goog.math.Coordinate(e.clientX, e.clientY); + this.bindMouseEvents(e); +}; + +/** + * Bind gesture events. + * @param {!Event} e A mouse down or touch start event. + * @package + */ +Blockly.Gesture.prototype.bindMouseEvents = function(e) { this.onMoveWrapper_ = Blockly.bindEventWithChecks_( document, 'mousemove', null, this.handleMove.bind(this)); this.onUpWrapper_ = Blockly.bindEventWithChecks_( @@ -460,6 +533,9 @@ Blockly.Gesture.prototype.handleMove = function(e) { } else if (this.isDraggingBlock_) { this.blockDragger_.dragBlock(this.mostRecentEvent_, this.currentDragDeltaXY_); + } else if (this.isDraggingBubble_) { + this.bubbleDragger_.dragBubble(this.mostRecentEvent_, + this.currentDragDeltaXY_); } e.preventDefault(); e.stopPropagation(); @@ -482,10 +558,17 @@ Blockly.Gesture.prototype.handleUp = function(e) { // 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_) { + // The ordering within drags does not matter, because the three types of + // dragging are exclusive. + if (this.isDraggingBubble_) { + this.bubbleDragger_.endBubbleDrag(e, this.currentDragDeltaXY_); + } else if (this.isDraggingBlock_) { this.blockDragger_.endBlockDrag(e, this.currentDragDeltaXY_); } else if (this.isDraggingWorkspace_) { this.workspaceDragger_.endDrag(this.currentDragDeltaXY_); + } else if (this.isBubbleClick_()) { + // Bubbles are in front of all fields and blocks. + this.doBubbleClick_(); } else if (this.isFieldClick_()) { this.doFieldClick_(); } else if (this.isBlockClick_()) { @@ -512,7 +595,10 @@ Blockly.Gesture.prototype.cancel = function() { return; } Blockly.longStop_(); - if (this.isDraggingBlock_) { + if (this.isDraggingBubble_) { + this.bubbleDragger_.endBubbleDrag(this.mostRecentEvent_, + this.currentDragDeltaXY_); + } else if (this.isDraggingBlock_) { this.blockDragger_.endBlockDrag(this.mostRecentEvent_, this.currentDragDeltaXY_); } else if (this.isDraggingWorkspace_) { @@ -536,6 +622,7 @@ Blockly.Gesture.prototype.handleRightClick = function(e) { this.startWorkspace_.showContextMenu_(e); } + // TODO: Handle right-click on a bubble. e.preventDefault(); e.stopPropagation(); @@ -550,8 +637,8 @@ Blockly.Gesture.prototype.handleRightClick = function(e) { */ Blockly.Gesture.prototype.handleWsStart = function(e, ws) { goog.asserts.assert(!this.hasStarted_, - 'Tried to call gesture.handleWsStart, but the gesture had already been ' + - 'started.'); + 'Tried to call gesture.handleWsStart, but the gesture had already been ' + + 'started.'); this.setStartWorkspace_(ws); this.mostRecentEvent_ = e; this.doStart(e); @@ -565,8 +652,8 @@ Blockly.Gesture.prototype.handleWsStart = function(e, ws) { */ Blockly.Gesture.prototype.handleFlyoutStart = function(e, flyout) { goog.asserts.assert(!this.hasStarted_, - 'Tried to call gesture.handleFlyoutStart, but the gesture had already been ' + - 'started.'); + 'Tried to call gesture.handleFlyoutStart, but the gesture had already ' + + 'been started.'); this.setStartFlyout_(flyout); this.handleWsStart(e, flyout.getWorkspace()); }; @@ -579,16 +666,39 @@ Blockly.Gesture.prototype.handleFlyoutStart = function(e, flyout) { */ Blockly.Gesture.prototype.handleBlockStart = function(e, block) { goog.asserts.assert(!this.hasStarted_, - 'Tried to call gesture.handleBlockStart, but the gesture had already been ' + - 'started.'); + 'Tried to call gesture.handleBlockStart, but the gesture had already ' + + 'been started.'); this.setStartBlock(block); this.mostRecentEvent_ = e; }; +/** + * Handle a mousedown/touchstart event on a bubble. + * @param {!Event} e A mouse down or touch start event. + * @param {!Blockly.Bubble} bubble The bubble the event hit. + * @package + */ +Blockly.Gesture.prototype.handleBubbleStart = function(e, bubble) { + goog.asserts.assert(!this.hasStarted_, + 'Tried to call gesture.handleBubbleStart, but the gesture had already ' + + 'been started.'); + this.setStartBubble(bubble); + 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 bubble click. + * @private + */ +Blockly.Gesture.prototype.doBubbleClick_ = function() { + // TODO: This isn't really enough, is it. + this.startBubble_.promote_(); +}; + /** * Execute a field click. * @private @@ -634,6 +744,7 @@ Blockly.Gesture.prototype.doWorkspaceClick_ = function() { /* End functions defining what actions to take to execute clicks on each type * of target. */ +// TODO (fenichel): Move bubbles to the front. /** * Move the dragged/clicked block to the front of the workspace so that it is * not occluded by other blocks. @@ -655,13 +766,24 @@ Blockly.Gesture.prototype.bringBlockToFront_ = function() { */ Blockly.Gesture.prototype.setStartField = function(field) { goog.asserts.assert(!this.hasStarted_, - 'Tried to call gesture.setStartField, but the gesture had already been ' + - 'started.'); + 'Tried to call gesture.setStartField, but the gesture had already been ' + + 'started.'); if (!this.startField_) { this.startField_ = field; } }; +/** + * Record the bubble that a gesture started on + * @param {Blockly.Bubble} bubble The bubble the gesture started on. + * @package + */ +Blockly.Gesture.prototype.setStartBubble = function(bubble) { + if (!this.startBubble_) { + this.startBubble_ = bubble; + } +}; + /** * Record the block that a gesture started on, and set the target block * appropriately. @@ -721,6 +843,18 @@ Blockly.Gesture.prototype.setStartFlyout_ = function(flyout) { /* 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 bubble. This should only be called when + * ending a gesture (mouse up, touch end). + * @return {boolean} whether this gesture was a click on a bubble. + * @private + */ +Blockly.Gesture.prototype.isBubbleClick_ = function() { + // A bubble click starts on a bubble and never escapes the drag radius. + var hasStartBubble = !!this.startBubble_; + return hasStartBubble && !this.hasExceededDragRadius_; +}; + /** * Whether this gesture is a click on a block. This should only be called when * ending a gesture (mouse up, touch end). @@ -768,7 +902,8 @@ Blockly.Gesture.prototype.isWorkspaceClick_ = function() { * @package */ Blockly.Gesture.prototype.isDragging = function() { - return this.isDraggingWorkspace_ || this.isDraggingBlock_; + return this.isDraggingWorkspace_ || this.isDraggingBlock_ || + this.isDraggingBubble_; }; /** diff --git a/core/grid.js b/core/grid.js index b0eeb3fc2..c8b2e254e 100644 --- a/core/grid.js +++ b/core/grid.js @@ -207,8 +207,10 @@ Blockly.Grid.createDom = function(rnd, gridOptions, defs) { */ var gridPattern = Blockly.utils.createSvgElement('pattern', - {'id': 'blocklyGridPattern' + rnd, - 'patternUnits': 'userSpaceOnUse'}, defs); + { + 'id': 'blocklyGridPattern' + rnd, + 'patternUnits': 'userSpaceOnUse' + }, defs); if (gridOptions['length'] > 0 && gridOptions['spacing'] > 0) { Blockly.utils.createSvgElement('line', {'stroke': gridOptions['colour']}, gridPattern); diff --git a/core/icon.js b/core/icon.js index 37dd6768d..88b7fb689 100644 --- a/core/icon.js +++ b/core/icon.js @@ -79,14 +79,14 @@ Blockly.Icon.prototype.createIcon = function() { this.iconGroup_ = Blockly.utils.createSvgElement('g', {'class': 'blocklyIconGroup'}, null); if (this.block_.isInFlyout) { - Blockly.utils.addClass(/** @type {!Element} */ (this.iconGroup_), - 'blocklyIconGroupReadonly'); + Blockly.utils.addClass( + /** @type {!Element} */ (this.iconGroup_), 'blocklyIconGroupReadonly'); } this.drawIcon_(this.iconGroup_); this.block_.getSvgRoot().appendChild(this.iconGroup_); - Blockly.bindEventWithChecks_(this.iconGroup_, 'mouseup', this, - this.iconClick_); + Blockly.bindEventWithChecks_( + this.iconGroup_, 'mouseup', this, this.iconClick_); this.updateEditable(); }; diff --git a/core/inject.js b/core/inject.js index 2b1c2ee3d..11f3efad4 100644 --- a/core/inject.js +++ b/core/inject.js @@ -137,17 +137,34 @@ Blockly.createDom_ = function(container, options) { Blockly.utils.createSvgElement('feGaussianBlur', {'in': 'SourceAlpha', 'stdDeviation': 1, 'result': 'blur'}, embossFilter); var feSpecularLighting = Blockly.utils.createSvgElement('feSpecularLighting', - {'in': 'blur', 'surfaceScale': 1, 'specularConstant': 0.5, - 'specularExponent': 10, 'lighting-color': 'white', 'result': 'specOut'}, + { + 'in': 'blur', + 'surfaceScale': 1, + 'specularConstant': 0.5, + 'specularExponent': 10, + 'lighting-color': 'white', + 'result': 'specOut' + }, embossFilter); Blockly.utils.createSvgElement('fePointLight', {'x': -5000, 'y': -10000, 'z': 20000}, feSpecularLighting); Blockly.utils.createSvgElement('feComposite', - {'in': 'specOut', 'in2': 'SourceAlpha', 'operator': 'in', - 'result': 'specOut'}, embossFilter); + { + 'in': 'specOut', + 'in2': 'SourceAlpha', + 'operator': 'in', + 'result': 'specOut' + }, embossFilter); Blockly.utils.createSvgElement('feComposite', - {'in': 'SourceGraphic', 'in2': 'specOut', 'operator': 'arithmetic', - 'k1': 0, 'k2': 1, 'k3': 1, 'k4': 0}, embossFilter); + { + 'in': 'SourceGraphic', + 'in2': 'specOut', + 'operator': 'arithmetic', + 'k1': 0, + 'k2': 1, + 'k3': 1, + 'k4': 0 + }, embossFilter); options.embossFilterId = embossFilter.id; /* */ var disabledPattern = Blockly.utils.createSvgElement('pattern', - {'id': 'blocklyDisabledPattern' + rnd, - 'patternUnits': 'userSpaceOnUse', - 'width': 10, 'height': 10}, defs); + { + 'id': 'blocklyDisabledPattern' + rnd, + 'patternUnits': 'userSpaceOnUse', + 'width': 10, + 'height': 10 + }, defs); Blockly.utils.createSvgElement('rect', {'width': 10, 'height': 10, 'fill': '#aaa'}, disabledPattern); Blockly.utils.createSvgElement('path', @@ -346,17 +366,23 @@ Blockly.inject.bindDocumentEvents_ = function() { Blockly.inject.loadSounds_ = function(pathToMedia, workspace) { var audioMgr = workspace.getAudioManager(); audioMgr.load( - [pathToMedia + 'click.mp3', - pathToMedia + 'click.wav', - pathToMedia + 'click.ogg'], 'click'); + [ + pathToMedia + 'click.mp3', + pathToMedia + 'click.wav', + pathToMedia + 'click.ogg' + ], 'click'); audioMgr.load( - [pathToMedia + 'disconnect.wav', - pathToMedia + 'disconnect.mp3', - pathToMedia + 'disconnect.ogg'], 'disconnect'); + [ + pathToMedia + 'disconnect.wav', + pathToMedia + 'disconnect.mp3', + pathToMedia + 'disconnect.ogg' + ], 'disconnect'); audioMgr.load( - [pathToMedia + 'delete.mp3', - pathToMedia + 'delete.ogg', - pathToMedia + 'delete.wav'], 'delete'); + [ + pathToMedia + 'delete.mp3', + pathToMedia + 'delete.ogg', + pathToMedia + 'delete.wav' + ], 'delete'); // Bind temporary hooks that preload the sounds. var soundBinds = []; diff --git a/core/mutator.js b/core/mutator.js index 7261fe382..a57727ae2 100644 --- a/core/mutator.js +++ b/core/mutator.js @@ -66,25 +66,37 @@ Blockly.Mutator.prototype.workspaceHeight_ = 0; Blockly.Mutator.prototype.drawIcon_ = function(group) { // Square with rounded corners. Blockly.utils.createSvgElement('rect', - {'class': 'blocklyIconShape', - 'rx': '4', 'ry': '4', - 'height': '16', 'width': '16'}, - group); + { + 'class': 'blocklyIconShape', + 'rx': '4', + 'ry': '4', + 'height': '16', + 'width': '16' + }, + group); // Gear teeth. Blockly.utils.createSvgElement('path', - {'class': 'blocklyIconSymbol', - 'd': 'm4.203,7.296 0,1.368 -0.92,0.677 -0.11,0.41 0.9,1.559 0.41,' + - '0.11 1.043,-0.457 1.187,0.683 0.127,1.134 0.3,0.3 1.8,0 0.3,' + - '-0.299 0.127,-1.138 1.185,-0.682 1.046,0.458 0.409,-0.11 0.9,' + - '-1.559 -0.11,-0.41 -0.92,-0.677 0,-1.366 0.92,-0.677 0.11,' + - '-0.41 -0.9,-1.559 -0.409,-0.109 -1.046,0.458 -1.185,-0.682 ' + - '-0.127,-1.138 -0.3,-0.299 -1.8,0 -0.3,0.3 -0.126,1.135 -1.187,' + - '0.682 -1.043,-0.457 -0.41,0.11 -0.899,1.559 0.108,0.409z'}, - group); + { + 'class': 'blocklyIconSymbol', + 'd': 'm4.203,7.296 0,1.368 -0.92,0.677 -0.11,0.41 0.9,1.559 0.41,' + + '0.11 1.043,-0.457 1.187,0.683 0.127,1.134 0.3,0.3 1.8,0 0.3,' + + '-0.299 0.127,-1.138 1.185,-0.682 1.046,0.458 0.409,-0.11 0.9,' + + '-1.559 -0.11,-0.41 -0.92,-0.677 0,-1.366 0.92,-0.677 0.11,' + + '-0.41 -0.9,-1.559 -0.409,-0.109 -1.046,0.458 -1.185,-0.682 ' + + '-0.127,-1.138 -0.3,-0.299 -1.8,0 -0.3,0.3 -0.126,1.135 -1.187,' + + '0.682 -1.043,-0.457 -0.41,0.11 -0.899,1.559 0.108,0.409z' + }, + group); // Axle hole. - Blockly.utils.createSvgElement('circle', - {'class': 'blocklyIconShape', 'r': '2.7', 'cx': '8', 'cy': '8'}, - group); + Blockly.utils.createSvgElement( + 'circle', + { + 'class': 'blocklyIconShape', + 'r': '2.7', + 'cx': '8', + 'cy': '8' + }, + group); }; /** @@ -160,15 +172,17 @@ Blockly.Mutator.prototype.updateEditable = function() { if (!this.block_.isInFlyout) { if (this.block_.isEditable()) { if (this.iconGroup_) { - Blockly.utils.removeClass(/** @type {!Element} */ (this.iconGroup_), - 'blocklyIconGroupReadonly'); + Blockly.utils.removeClass( + /** @type {!Element} */ (this.iconGroup_), + 'blocklyIconGroupReadonly'); } } else { // Close any mutator bubble. Icon is not clickable. this.setVisible(false); if (this.iconGroup_) { - Blockly.utils.addClass(/** @type {!Element} */ (this.iconGroup_), - 'blocklyIconGroupReadonly'); + Blockly.utils.addClass( + /** @type {!Element} */ (this.iconGroup_), + 'blocklyIconGroupReadonly'); } } } @@ -203,8 +217,8 @@ Blockly.Mutator.prototype.resizeBubble_ = function() { this.workspaceWidth_ = width; this.workspaceHeight_ = height; // Resize the bubble. - this.bubble_.setBubbleSize(width + doubleBorderWidth, - height + doubleBorderWidth); + this.bubble_.setBubbleSize( + width + doubleBorderWidth, height + doubleBorderWidth); this.svgDialog_.setAttribute('width', this.workspaceWidth_); this.svgDialog_.setAttribute('height', this.workspaceHeight_); } @@ -401,6 +415,30 @@ Blockly.Mutator.reconnect = function(connectionChild, block, inputName) { return false; }; +/** + * Get the parent workspace of a workspace that is inside a mutator, taking into + * account whether it is a flyout. + * @param {?Blockly.Workspace} workspace The workspace that is inside a mutator. + * @return {?Blockly.Workspace} The mutator's parent workspace or null. + * @package + */ +Blockly.Mutator.findParentWs = function(workspace) { + var outerWs = null; + if (workspace && workspace.options) { + var parent = workspace.options.parentWorkspace; + // If we were in a flyout in a mutator, need to go up two levels to find + // the actual parent. + if (workspace.isFlyout) { + if (parent && parent.options) { + outerWs = parent.options.parentWorkspace; + } + } else if (parent) { + outerWs = parent; + } + } + return outerWs; +}; + // Export symbols that would otherwise be renamed by Closure compiler. if (!goog.global['Blockly']) { goog.global['Blockly'] = {}; diff --git a/core/names.js b/core/names.js index bfe942ab9..7b7977290 100644 --- a/core/names.js +++ b/core/names.js @@ -47,6 +47,15 @@ Blockly.Names = function(reservedWords, opt_variablePrefix) { this.reset(); }; +/** + * Constant to separate developer variable names from user-defined variable + * names when running generators. + * A developer variable will be declared as a global in the generated code, but + * will never be shown to the user in the workspace or stored in the variable + * map. + */ +Blockly.Names.DEVELOPER_VARIABLE_TYPE = 'DEVELOPER_VARIABLE'; + /** * When JavaScript (or most other languages) is generated, variable 'foo' and * procedure 'foo' would collide. However, Blockly has no such problems since @@ -62,6 +71,41 @@ Blockly.Names = function(reservedWords, opt_variablePrefix) { Blockly.Names.prototype.reset = function() { this.db_ = Object.create(null); this.dbReverse_ = Object.create(null); + this.variableMap_ = null; +}; + +/** + * Set the variable map that maps from variable name to variable object. + * @param {!Blockly.VariableMap} map The map to track. + * @package + */ +Blockly.Names.prototype.setVariableMap = function(map) { + this.variableMap_ = map; +}; + +/** + * Get the name for a user-defined variable, based on its ID. + * This should only be used for variables of type Blockly.Variables.NAME_TYPE. + * @param {string} id The ID to look up in the variable map. + * @return {?string} The name of the referenced variable, or null if there was + * no variable map or the variable was not found in the map. + * @private + */ +Blockly.Names.prototype.getNameForUserVariable_ = function(id) { + if (!this.variableMap_) { + console.log('Deprecated call to Blockly.Names.prototype.getName without ' + + 'defining a variable map. To fix, add the folowing code in your ' + + 'generator\'s init() function:\n' + + 'Blockly.YourGeneratorName.variableDB_.setVariableMap(' + + 'workspace.getVariableMap());'); + return null; + } + var variable = this.variableMap_.getVariableById(id); + if (variable) { + return variable.name; + } else { + return null; + } }; /** @@ -69,12 +113,21 @@ Blockly.Names.prototype.reset = function() { * @param {string} name The Blockly entity name (no constraints). * @param {string} type The type of entity in Blockly * ('VARIABLE', 'PROCEDURE', 'BUILTIN', etc...). - * @return {string} An entity name legal for the exported language. + * @return {string} An entity name that is legal in the exported language. */ Blockly.Names.prototype.getName = function(name, type) { + if (type == Blockly.Variables.NAME_TYPE) { + var varName = this.getNameForUserVariable_(name); + if (varName) { + name = varName; + } + } var normalized = name.toLowerCase() + '_' + type; - var prefix = (type == Blockly.Variables.NAME_TYPE) ? - this.variablePrefix_ : ''; + + var isVarType = type == Blockly.Variables.NAME_TYPE || + type == Blockly.Names.DEVELOPER_VARIABLE_TYPE; + + var prefix = isVarType ? this.variablePrefix_ : ''; if (normalized in this.db_) { return prefix + this.db_[normalized]; } @@ -91,7 +144,7 @@ Blockly.Names.prototype.getName = function(name, type) { * @param {string} name The Blockly entity name (no constraints). * @param {string} type The type of entity in Blockly * ('VARIABLE', 'PROCEDURE', 'BUILTIN', etc...). - * @return {string} An entity name legal for the exported language. + * @return {string} An entity name that is legal in the exported language. */ Blockly.Names.prototype.getDistinctName = function(name, type) { var safeName = this.safeName_(name); @@ -103,8 +156,9 @@ Blockly.Names.prototype.getDistinctName = function(name, type) { } safeName += i; this.dbReverse_[safeName] = true; - var prefix = (type == Blockly.Variables.NAME_TYPE) ? - this.variablePrefix_ : ''; + var isVarType = type == Blockly.Variables.NAME_TYPE || + type == Blockly.Names.DEVELOPER_VARIABLE_TYPE; + var prefix = isVarType ? this.variablePrefix_ : ''; return prefix + safeName; }; diff --git a/core/rendered_connection.js b/core/rendered_connection.js index ea3fcc1d5..cfd0473d1 100644 --- a/core/rendered_connection.js +++ b/core/rendered_connection.js @@ -205,11 +205,14 @@ Blockly.RenderedConnection.prototype.highlight = function() { var xy = this.sourceBlock_.getRelativeToSurfaceXY(); var x = this.x_ - xy.x; var y = this.y_ - xy.y; - Blockly.Connection.highlightedPath_ = Blockly.utils.createSvgElement('path', - {'class': 'blocklyHighlightedConnectionPath', - 'd': steps, - transform: 'translate(' + x + ',' + y + ')' + - (this.sourceBlock_.RTL ? ' scale(-1 1)' : '')}, + Blockly.Connection.highlightedPath_ = Blockly.utils.createSvgElement( + 'path', + { + 'class': 'blocklyHighlightedConnectionPath', + 'd': steps, + transform: 'translate(' + x + ',' + y + ')' + + (this.sourceBlock_.RTL ? ' scale(-1 1)' : '') + }, this.sourceBlock_.getSvgRoot()); }; diff --git a/core/scrollbar.js b/core/scrollbar.js index 3849a6385..ec0458e31 100644 --- a/core/scrollbar.js +++ b/core/scrollbar.js @@ -43,14 +43,18 @@ goog.require('goog.events'); */ Blockly.ScrollbarPair = function(workspace) { this.workspace_ = workspace; - this.hScroll = new Blockly.Scrollbar(workspace, true, true, - 'blocklyMainWorkspaceScrollbar'); - this.vScroll = new Blockly.Scrollbar(workspace, false, true, - 'blocklyMainWorkspaceScrollbar'); - this.corner_ = Blockly.utils.createSvgElement('rect', - {'height': Blockly.Scrollbar.scrollbarThickness, - 'width': Blockly.Scrollbar.scrollbarThickness, - 'class': 'blocklyScrollbarBackground'}, null); + this.hScroll = new Blockly.Scrollbar( + workspace, true, true, 'blocklyMainWorkspaceScrollbar'); + this.vScroll = new Blockly.Scrollbar( + workspace, false, true, 'blocklyMainWorkspaceScrollbar'); + this.corner_ = Blockly.utils.createSvgElement( + 'rect', + { + 'height': Blockly.Scrollbar.scrollbarThickness, + 'width': Blockly.Scrollbar.scrollbarThickness, + 'class': 'blocklyScrollbarBackground' + }, + null); Blockly.utils.insertAfter_(this.corner_, workspace.getBubbleCanvas()); }; @@ -210,24 +214,20 @@ Blockly.Scrollbar = function(workspace, horizontal, opt_pair, opt_class) { */ this.position_ = new goog.math.Coordinate(0, 0); + // Store the thickness in a temp variable for readability. + var scrollbarThickness = Blockly.Scrollbar.scrollbarThickness; if (horizontal) { - this.svgBackground_.setAttribute('height', - Blockly.Scrollbar.scrollbarThickness); - this.outerSvg_.setAttribute('height', - Blockly.Scrollbar.scrollbarThickness); - this.svgHandle_.setAttribute('height', - Blockly.Scrollbar.scrollbarThickness - 5); + this.svgBackground_.setAttribute('height', scrollbarThickness); + this.outerSvg_.setAttribute('height', scrollbarThickness); + this.svgHandle_.setAttribute('height', scrollbarThickness - 5); this.svgHandle_.setAttribute('y', 2.5); this.lengthAttribute_ = 'width'; this.positionAttribute_ = 'x'; } else { - this.svgBackground_.setAttribute('width', - Blockly.Scrollbar.scrollbarThickness); - this.outerSvg_.setAttribute('width', - Blockly.Scrollbar.scrollbarThickness); - this.svgHandle_.setAttribute('width', - Blockly.Scrollbar.scrollbarThickness - 5); + this.svgBackground_.setAttribute('width', scrollbarThickness); + this.outerSvg_.setAttribute('width', scrollbarThickness); + this.svgHandle_.setAttribute('width', scrollbarThickness - 5); this.svgHandle_.setAttribute('x', 2.5); this.lengthAttribute_ = 'height'; @@ -609,17 +609,21 @@ Blockly.Scrollbar.prototype.createDom_ = function(opt_class) { if (opt_class) { className += ' ' + opt_class; } - this.outerSvg_ = Blockly.utils.createSvgElement('svg', {'class': className}, - null); + this.outerSvg_ = Blockly.utils.createSvgElement( + 'svg', {'class': className}, null); this.svgGroup_ = Blockly.utils.createSvgElement('g', {}, this.outerSvg_); - this.svgBackground_ = Blockly.utils.createSvgElement('rect', - {'class': 'blocklyScrollbarBackground'}, this.svgGroup_); + this.svgBackground_ = Blockly.utils.createSvgElement( + 'rect', {'class': 'blocklyScrollbarBackground'}, this.svgGroup_); var radius = Math.floor((Blockly.Scrollbar.scrollbarThickness - 5) / 2); - this.svgHandle_ = Blockly.utils.createSvgElement('rect', - {'class': 'blocklyScrollbarHandle', 'rx': radius, 'ry': radius}, + this.svgHandle_ = Blockly.utils.createSvgElement( + 'rect', + { + 'class': 'blocklyScrollbarHandle', + 'rx': radius, + 'ry': radius + }, this.svgGroup_); - Blockly.utils.insertAfter_(this.outerSvg_, - this.workspace_.getParentSvg()); + Blockly.utils.insertAfter_(this.outerSvg_, this.workspace_.getParentSvg()); }; /** diff --git a/core/toolbox.js b/core/toolbox.js index 5531f1c4a..429720246 100644 --- a/core/toolbox.js +++ b/core/toolbox.js @@ -189,8 +189,8 @@ Blockly.Toolbox.prototype.init = function() { } else { this.flyout_ = new Blockly.VerticalFlyout(workspaceOptions); } - goog.dom.insertSiblingAfter(this.flyout_.createDom('svg'), - this.workspace_.getParentSvg()); + goog.dom.insertSiblingAfter( + this.flyout_.createDom('svg'), this.workspace_.getParentSvg()); this.flyout_.init(workspace); this.config_['cleardotPath'] = workspace.options.pathToMedia + '1x1.gif'; @@ -315,7 +315,7 @@ Blockly.Toolbox.prototype.syncTrees_ = function(treeIn, treeOut, pathToMedia) { // Decode the category name for any potential message references // (eg. `%{BKY_CATEGORY_NAME_LOGIC}`). var categoryName = Blockly.utils.replaceMessageReferences( - childIn.getAttribute('name')); + childIn.getAttribute('name')); var childOut = this.tree_.createNode(categoryName); childOut.blocks = []; treeOut.add(childOut); @@ -422,21 +422,21 @@ Blockly.Toolbox.prototype.clearSelection = function() { }; /** - * Adds styles on the toolbox indicating blocks will be deleted. + * Adds a style on the toolbox. Usually used to change the cursor. + * @param {string} style The name of the class to add. * @package */ -Blockly.Toolbox.prototype.addDeleteStyle = function() { - Blockly.utils.addClass(/** @type {!Element} */ (this.HtmlDiv), - 'blocklyToolboxDelete'); +Blockly.Toolbox.prototype.addStyle = function(style) { + Blockly.utils.addClass(/** @type {!Element} */ (this.HtmlDiv), style); }; /** - * Remove styles from the toolbox that indicate blocks will be deleted. + * Removes a style from the toolbox. Usually used to change the cursor. + * @param {string} style The name of the class to remove. * @package */ -Blockly.Toolbox.prototype.removeDeleteStyle = function() { - Blockly.utils.removeClass(/** @type {!Element} */ (this.HtmlDiv), - 'blocklyToolboxDelete'); +Blockly.Toolbox.prototype.removeStyle = function(style) { + Blockly.utils.removeClass(/** @type {!Element} */ (this.HtmlDiv), style); }; /** @@ -540,8 +540,9 @@ Blockly.Toolbox.TreeControl.prototype.handleTouchEvent_ = function(e) { * @override */ Blockly.Toolbox.TreeControl.prototype.createNode = function(opt_html) { - return new Blockly.Toolbox.TreeNode(this.toolbox_, opt_html ? - goog.html.SafeHtml.htmlEscape(opt_html) : goog.html.SafeHtml.EMPTY, + var html = opt_html ? + goog.html.SafeHtml.htmlEscape(opt_html) : goog.html.SafeHtml.EMPTY; + return new Blockly.Toolbox.TreeNode(this.toolbox_, html, this.getConfig(), this.getDomHelper()); }; diff --git a/core/touch.js b/core/touch.js index 4a494aa17..816e8d618 100644 --- a/core/touch.js +++ b/core/touch.js @@ -75,14 +75,18 @@ Blockly.longPid_ = 0; Blockly.longStart_ = function(e, gesture) { Blockly.longStop_(); // Punt on multitouch events. - if (e.changedTouches.length != 1) { + if (e.changedTouches && e.changedTouches.length != 1) { return; } Blockly.longPid_ = setTimeout(function() { - e.button = 2; // Simulate a right button click. - // 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; + // Additional check to distinguish between touch events and pointer events + if (e.changedTouches) { + // TouchEvent + e.button = 2; // Simulate a right button click. + // 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; + } // Let the gesture route the right-click correctly. if (gesture) { @@ -134,7 +138,8 @@ Blockly.Touch.shouldHandleEvent = function(e) { * defined. Otherwise 'mouse'. */ Blockly.Touch.getTouchIdentifierFromEvent = function(e) { - return (e.changedTouches && e.changedTouches[0] && + return e.pointerId != undefined ? e.pointerId : + (e.changedTouches && e.changedTouches[0] && e.changedTouches[0].identifier != undefined && e.changedTouches[0].identifier != null) ? e.changedTouches[0].identifier : 'mouse'; @@ -163,7 +168,7 @@ Blockly.Touch.checkTouchIdentifier = function(e) { // source? return Blockly.Touch.touchIdentifier_ == identifier; } - if (e.type == 'mousedown' || e.type == 'touchstart') { + if (e.type == 'mousedown' || e.type == 'touchstart' || e.type == 'pointerdown') { // No identifier set yet, and this is the start of a drag. Set it and // return. Blockly.Touch.touchIdentifier_ = identifier; @@ -196,7 +201,18 @@ Blockly.Touch.setClientFromTouch = function(e) { */ Blockly.Touch.isMouseOrTouchEvent = function(e) { return goog.string.startsWith(e.type, 'touch') || - goog.string.startsWith(e.type, 'mouse'); + goog.string.startsWith(e.type, 'mouse') || + goog.string.startsWith(e.type, 'pointer'); +}; + +/** + * Check whether a given event is a touch event or a pointer event. + * @param {!Event} e An event. + * @return {boolean} true if it is a touch event; false otherwise. + */ +Blockly.Touch.isTouchEvent = function(e) { + return goog.string.startsWith(e.type, 'touch') || + goog.string.startsWith(e.type, 'pointer'); }; /** diff --git a/core/touch_gesture.js b/core/touch_gesture.js new file mode 100644 index 000000000..26adc0fcc --- /dev/null +++ b/core/touch_gesture.js @@ -0,0 +1,309 @@ +/** + * @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 extends Blockly.Gesture to support pinch to zoom + * for both pointer and touch events. + * @author samelh@microsoft.com (Sam El-Husseini) + */ +'use strict'; + +goog.provide('Blockly.TouchGesture'); + +goog.require('Blockly.Gesture'); + +goog.require('goog.asserts'); +goog.require('goog.math.Coordinate'); + + +/* + * Note: In this file "start" refers to touchstart, mousedown, and pointerstart + * events. "End" refers to touchend, mouseup, and pointerend events. + */ + +/** + * 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. + * @extends {Blockly.Gesture} + * @constructor + */ +Blockly.TouchGesture = function(e, creatorWorkspace) { + Blockly.TouchGesture.superClass_.constructor.call(this, e, creatorWorkspace); + + /** + * Boolean for whether or not this gesture is a multi-touch gesture. + * @type {boolean} + * @private + */ + this.isMultiTouch_ = false; + + /** + * A map of cached points used for tracking multi-touch gestures. + * @type {Object} + * @private + */ + this.cachedPoints_ = {}; + + /** + * This is the ratio between the starting distance between the touch points + * and the most recent distance between the touch points. + * Scales between 0 and 1 mean the most recent zoom was a zoom out. + * Scales above 1.0 mean the most recent zoom was a zoom in. + * @type {number} + * @private + */ + this.previousScale_ = 0; + + /** + * The starting distance between two touch points. + * @type {number} + * @private + */ + this.startDistance_ = 0; + + /** + * A handle to use to unbind the second touch start or pointer down listener + * at the end of a drag. Opaque data returned from Blockly.bindEventWithChecks_. + * @type {Array.} + * @private + */ + this.onStartWrapper_ = null; +}; +goog.inherits(Blockly.TouchGesture, Blockly.Gesture); + +/** + * A multiplier used to convert the gesture scale to a zoom in delta. + * @const + */ +Blockly.TouchGesture.ZOOM_IN_MULTIPLIER = 5; + +/** + * A multiplier used to convert the gesture scale to a zoom out delta. + * @const + */ +Blockly.TouchGesture.ZOOM_OUT_MULTIPLIER = 6; + +/** + * 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, touch start or pointer down event. + * @package + */ +Blockly.TouchGesture.prototype.doStart = function(e) { + Blockly.TouchGesture.superClass_.doStart.call(this, e); + if (Blockly.Touch.isTouchEvent(e)) { + this.handleTouchStart(e); + } +}; + +/** + * Bind gesture events. + * Overriding the gesture definition of this function, binding the same + * functions for onMoveWrapper_ and onUpWrapper_ but passing opt_noCaptureIdentifier. + * In addition, binding a second mouse down event to detect multi-touch events. + * @param {!Event} e A mouse down or touch start event. + * @package + */ +Blockly.TouchGesture.prototype.bindMouseEvents = function(e) { + this.onStartWrapper_ = Blockly.bindEventWithChecks_( + document, 'mousedown', null, this.handleStart.bind(this), + /*opt_noCaptureIdentifier*/ true); + this.onMoveWrapper_ = Blockly.bindEventWithChecks_( + document, 'mousemove', null, this.handleMove.bind(this), + /*opt_noCaptureIdentifier*/ true); + this.onUpWrapper_ = Blockly.bindEventWithChecks_( + document, 'mouseup', null, this.handleUp.bind(this), + /*opt_noCaptureIdentifier*/ true); + + e.preventDefault(); +}; + +/** + * Handle a mouse down, touch start, or pointer down event. + * @param {!Event} e A mouse down, touch start, or pointer down event. + * @package + */ +Blockly.TouchGesture.prototype.handleStart = function(e) { + if (!this.isDragging) { + // A drag has already started, so this can no longer be a pinch-zoom. + return; + } + if (Blockly.Touch.isTouchEvent(e)) { + this.handleTouchStart(e); + + if (this.isMultiTouch()) { + Blockly.longStop_(); + } + } +}; + +/** + * Handle a mouse move, touch move, or pointer move event. + * @param {!Event} e A mouse move, touch move, or pointer move event. + * @package + */ +Blockly.TouchGesture.prototype.handleMove = function(e) { + if (this.isDragging()) { + // We are in the middle of a drag, only handle the relevant events + if (Blockly.Touch.shouldHandleEvent(e)) { + Blockly.TouchGesture.superClass_.handleMove.call(this, e); + } + return; + } + if (this.isMultiTouch()) { + if (Blockly.Touch.isTouchEvent(e)) { + this.handleTouchMove(e); + } + Blockly.longStop_(); + } else { + Blockly.TouchGesture.superClass_.handleMove.call(this, e); + } +}; + +/** + * Handle a mouse up, touch end, or pointer up event. + * @param {!Event} e A mouse up, touch end, or pointer up event. + * @package + */ +Blockly.TouchGesture.prototype.handleUp = function(e) { + if (Blockly.Touch.isTouchEvent(e) && !this.isDragging()) { + this.handleTouchEnd(e); + } + if (!this.isMultiTouch() || this.isDragging()) { + if (!Blockly.Touch.shouldHandleEvent(e)) { + return; + } + Blockly.TouchGesture.superClass_.handleUp.call(this, e); + } else { + e.preventDefault(); + e.stopPropagation(); + + this.dispose(); + } +}; + +/** + * Whether this gesture is part of a multi-touch gesture. + * @return {boolean} whether this gesture is part of a multi-touch gesture. + * @package + */ +Blockly.TouchGesture.prototype.isMultiTouch = function() { + return this.isMultiTouch_; +}; + +/** + * Sever all links from this object. + * @package + */ +Blockly.TouchGesture.prototype.dispose = function() { + Blockly.TouchGesture.superClass_.dispose.call(this); + + if (this.onStartWrapper_) { + Blockly.unbindEvent_(this.onStartWrapper_); + } +}; + +/** + * Handle a touch start or pointer down event and keep track of current pointers. + * @param {!Event} e A touch start, or pointer down event. + * @package + */ +Blockly.TouchGesture.prototype.handleTouchStart = function(e) { + var pointerId = Blockly.Touch.getTouchIdentifierFromEvent(e); + // store the pointerId in the current list of pointers + this.cachedPoints_[pointerId] = this.getTouchPoint(e); + var pointers = Object.keys(this.cachedPoints_); + // If two pointers are down, check for pinch gestures + if (pointers.length == 2) { + var point0 = this.cachedPoints_[pointers[0]]; + var point1 = this.cachedPoints_[pointers[1]]; + this.startDistance_ = goog.math.Coordinate.distance(point0, point1); + this.isMultiTouch_ = true; + } + e.preventDefault(); +}; + +/** + * Handle a touch move or pointer move event and zoom in/out if two pointers are on the screen. + * @param {!Event} e A touch move, or pointer move event. + * @package + */ +Blockly.TouchGesture.prototype.handleTouchMove = function(e) { + var pointerId = Blockly.Touch.getTouchIdentifierFromEvent(e); + // Update the cache + this.cachedPoints_[pointerId] = this.getTouchPoint(e); + + var pointers = Object.keys(this.cachedPoints_); + // If two pointers are down, check for pinch gestures + if (pointers.length == 2) { + // Calculate the distance between the two pointers + var point0 = this.cachedPoints_[pointers[0]]; + var point1 = this.cachedPoints_[pointers[1]]; + var moveDistance = goog.math.Coordinate.distance(point0, point1); + var startDistance = this.startDistance_; + var scale = this.touchScale_ = moveDistance / startDistance; + + if (this.previousScale_ > 0 && this.previousScale_ < Infinity) { + var gestureScale = scale - this.previousScale_; + var delta = gestureScale > 0 ? + gestureScale * Blockly.TouchGesture.ZOOM_IN_MULTIPLIER : + gestureScale * Blockly.TouchGesture.ZOOM_OUT_MULTIPLIER; + var workspace = this.startWorkspace_; + var position = Blockly.utils.mouseToSvg(e, workspace.getParentSvg(), workspace.getInverseScreenCTM()); + workspace.zoom(position.x, position.y, delta); + } + this.previousScale_ = scale; + } + e.preventDefault(); +}; + +/** + * Handle a touch end or pointer end event and end the gesture. + * @param {!Event} e A touch end, or pointer end event. + * @package + */ +Blockly.TouchGesture.prototype.handleTouchEnd = function(e) { + var pointerId = Blockly.Touch.getTouchIdentifierFromEvent(e); + if (this.cachedPoints_[pointerId]) { + delete this.cachedPoints_[pointerId]; + } + if (Object.keys(this.cachedPoints_).length < 2) { + this.cachedPoints_ = {}; + this.previousScale_ = 0; + } +}; + +/** + * Helper function returning the current touch point coordinate. + * @param {!Event} e A touch or pointer event. + * @return {goog.math.Coordinate} the current touch point coordinate + * @package + */ +Blockly.TouchGesture.prototype.getTouchPoint = function(e) { + if (!this.startWorkspace_) { + return null; + } + return new goog.math.Coordinate( + (e.pageX ? e.pageX : e.changedTouches[0].pageX), + (e.pageY ? e.pageY : e.changedTouches[0].pageY) + ); +}; diff --git a/core/trashcan.js b/core/trashcan.js index 7dfb600a1..031daf7b3 100644 --- a/core/trashcan.js +++ b/core/trashcan.js @@ -172,13 +172,20 @@ Blockly.Trashcan.prototype.createDom = function() { {'id': 'blocklyTrashBodyClipPath' + rnd}, this.svgGroup_); Blockly.utils.createSvgElement('rect', - {'width': this.WIDTH_, 'height': this.BODY_HEIGHT_, - 'y': this.LID_HEIGHT_}, + { + 'width': this.WIDTH_, + 'height': this.BODY_HEIGHT_, + 'y': this.LID_HEIGHT_ + }, clip); var body = Blockly.utils.createSvgElement('image', - {'width': Blockly.SPRITE.width, 'x': -this.SPRITE_LEFT_, - 'height': Blockly.SPRITE.height, 'y': -this.SPRITE_TOP_, - 'clip-path': 'url(#blocklyTrashBodyClipPath' + rnd + ')'}, + { + 'width': Blockly.SPRITE.width, + 'x': -this.SPRITE_LEFT_, + 'height': Blockly.SPRITE.height, + 'y': -this.SPRITE_TOP_, + 'clip-path': 'url(#blocklyTrashBodyClipPath' + rnd + ')' + }, this.svgGroup_); body.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', this.workspace_.options.pathToMedia + Blockly.SPRITE.url); @@ -189,9 +196,13 @@ Blockly.Trashcan.prototype.createDom = function() { Blockly.utils.createSvgElement('rect', {'width': this.WIDTH_, 'height': this.LID_HEIGHT_}, clip); this.svgLid_ = Blockly.utils.createSvgElement('image', - {'width': Blockly.SPRITE.width, 'x': -this.SPRITE_LEFT_, - 'height': Blockly.SPRITE.height, 'y': -this.SPRITE_TOP_, - 'clip-path': 'url(#blocklyTrashLidClipPath' + rnd + ')'}, + { + 'width': Blockly.SPRITE.width, + 'x': -this.SPRITE_LEFT_, + 'height': Blockly.SPRITE.height, + 'y': -this.SPRITE_TOP_, + 'clip-path': 'url(#blocklyTrashLidClipPath' + rnd + ')' + }, this.svgGroup_); this.svgLid_.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', this.workspace_.options.pathToMedia + Blockly.SPRITE.url); diff --git a/core/utils.js b/core/utils.js index 8f2179e5c..030079e9b 100644 --- a/core/utils.js +++ b/core/utils.js @@ -269,7 +269,7 @@ Blockly.utils.getScale_REGEXP_ = /scale\(\s*([-+\d.e]+)\s*\)/; * @private */ Blockly.utils.getRelativeXY.XY_3D_REGEX_ = - /transform:\s*translate3d\(\s*([-+\d.e]+)px([ ,]\s*([-+\d.e]+)\s*)px([ ,]\s*([-+\d.e]+)\s*)px\)?/; + /transform:\s*translate3d\(\s*([-+\d.e]+)px([ ,]\s*([-+\d.e]+)\s*)px([ ,]\s*([-+\d.e]+)\s*)px\)?/; /** * Static regex to pull the x,y,z values out of a translate3d() style property. @@ -278,7 +278,7 @@ Blockly.utils.getRelativeXY.XY_3D_REGEX_ = * @private */ Blockly.utils.getRelativeXY.XY_2D_REGEX_ = - /transform:\s*translate\(\s*([-+\d.e]+)px([ ,]\s*([-+\d.e]+)\s*)px\)?/; + /transform:\s*translate\(\s*([-+\d.e]+)px([ ,]\s*([-+\d.e]+)\s*)px\)?/; /** * Helper method for creating SVG elements. @@ -288,8 +288,8 @@ Blockly.utils.getRelativeXY.XY_2D_REGEX_ = * @return {!SVGElement} Newly created SVG element. */ Blockly.utils.createSvgElement = function(name, attrs, parent) { - var e = /** @type {!SVGElement} */ ( - document.createElementNS(Blockly.SVG_NS, name)); + var e = /** @type {!SVGElement} */ + (document.createElementNS(Blockly.SVG_NS, name)); for (var key in attrs) { e.setAttribute(key, attrs[key]); } @@ -896,7 +896,7 @@ Blockly.utils.insertAfter_ = function(newNode, refNode) { * @throws Error Will throw if no global document can be found (e.g., Node.js). */ Blockly.utils.runAfterPageLoad = function(fn) { - if (!document) { + if (typeof document != 'object') { throw new Error('Blockly.utils.runAfterPageLoad() requires browser document.'); } if (document.readyState === 'complete') { diff --git a/core/variable_map.js b/core/variable_map.js index fbf71151b..a7044898c 100644 --- a/core/variable_map.js +++ b/core/variable_map.js @@ -18,7 +18,7 @@ * limitations under the License. */ - /** +/** * @fileoverview Object representing a map of variables and their types. * @author marisaleung@google.com (Marisa Leung) */ @@ -35,7 +35,7 @@ goog.provide('Blockly.VariableMap'); * @constructor */ Blockly.VariableMap = function(workspace) { - /** + /** * 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. @@ -58,51 +58,104 @@ Blockly.VariableMap.prototype.clear = function() { this.variableMap_ = new Object(null); }; +/* Begin functions for renaming variables. */ + /** * Rename the given variable by updating its name in the variable map. - * @param {Blockly.VariableModel} variable Variable to rename. + * @param {!Blockly.VariableModel} variable Variable to rename. * @param {string} newName New variable name. + * @package */ 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); + var type = variable.type; + var conflictVar = this.getVariable(newName, type); + var blocks = this.workspace.getAllBlocks(); + Blockly.Events.setGroup(true); + try { + // The IDs may match if the rename is a simple case change (name1 -> Name1). + if (!conflictVar || conflictVar.getId() == variable.getId()) { + this.renameVariableAndUses_(variable, newName, blocks); + } else { + this.renameVariableWithConflict_(variable, newName, conflictVar, blocks); + } + } finally { + Blockly.Events.setGroup(false); } }; +/** + * 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.VariableMap.prototype.renameVariableById = function(id, newName) { + var variable = this.getVariableById(id); + if (!variable) { + throw new Error('Tried to rename a variable that didn\'t exist. ID: ' + id); + } + + this.renameVariable(variable, newName); +}; + +/** + * Update the name of the given variable and refresh all references to it. + * The new name must not conflict with any existing variable names. + * @param {!Blockly.VariableModel} variable Variable to rename. + * @param {string} newName New variable name. + * @param {!Array.} blocks The list of all blocks in the + * workspace. + * @private + */ +Blockly.VariableMap.prototype.renameVariableAndUses_ = function(variable, + newName, blocks) { + Blockly.Events.fire(new Blockly.Events.VarRename(variable, newName)); + variable.name = newName; + for (var i = 0; i < blocks.length; i++) { + blocks[i].updateVarName(variable); + } +}; + +/** + * Update the name of the given variable to the same name as an existing + * variable. The two variables are coalesced into a single variable with the ID + * of the existing variable that was already using newName. + * Refresh all references to the variable. + * @param {!Blockly.VariableModel} variable Variable to rename. + * @param {string} newName New variable name. + * @param {!Blockly.VariableModel} conflictVar The variable that was already + * using newName. + * @param {!Array.} blocks The list of all blocks in the + * workspace. + * @private + */ +Blockly.VariableMap.prototype.renameVariableWithConflict_ = function(variable, + newName, conflictVar, blocks) { + var type = variable.type; + var oldCase = conflictVar.name; + + if (newName != oldCase) { + // Simple rename to change the case and update references. + this.renameVariableAndUses_(conflictVar, newName, blocks); + } + + // These blocks now refer to a different variable. + // These will fire change events. + for (var i = 0; i < blocks.length; i++) { + blocks[i].renameVarById(variable.getId(), conflictVar.getId()); + } + + // Finally delete the original variable, which is now unreferenced. + Blockly.Events.fire(new Blockly.Events.VarDelete(variable)); + // And remove it from the list. + var variableList = this.getVariablesOfType(type); + var variableIndex = variableList.indexOf(variable); + this.variableMap_[type].splice(variableIndex, 1); + +}; + +/* End functions for renaming variabless. */ + /** * 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 @@ -116,19 +169,14 @@ Blockly.VariableMap.prototype.renameVariable = function(variable, newName) { */ Blockly.VariableMap.prototype.createVariable = function(name, opt_type, opt_id) { - var variable = this.getVariable(name); + var variable = this.getVariable(name, opt_type); 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. + // The variable already exists and has the same ID. return variable; } if (opt_id && this.getVariableById(opt_id)) { @@ -148,6 +196,8 @@ Blockly.VariableMap.prototype.createVariable = function(name, return variable; }; +/* Begin functions for variable deletion. */ + /** * Delete a variable. * @param {!Blockly.VariableModel} variable Variable to delete. @@ -164,17 +214,90 @@ Blockly.VariableMap.prototype.deleteVariable = function(variable) { }; /** - * Find the variable by the given name and return it. Return null if it is not - * found. + * 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.VariableMap.prototype.deleteVariableById = function(id) { + var variable = this.getVariableById(id); + if (variable) { + // Check whether this variable is a function parameter before deleting. + var variableName = variable.name; + var uses = this.getVariableUsesById(id); + for (var i = 0, block; block = uses[i]; i++) { + if (block.type == 'procedures_defnoreturn' || + block.type == 'procedures_defreturn') { + var procedureName = block.getFieldValue('NAME'); + var deleteText = Blockly.Msg.CANNOT_DELETE_VARIABLE_PROCEDURE. + replace('%1', variableName). + replace('%2', procedureName); + Blockly.alert(deleteText); + return; + } + } + + var map = this; + if (uses.length > 1) { + // Confirm before deleting multiple blocks. + var confirmText = Blockly.Msg.DELETE_VARIABLE_CONFIRMATION. + replace('%1', String(uses.length)). + replace('%2', variableName); + Blockly.confirm(confirmText, + function(ok) { + if (ok) { + map.deleteVariableInternal_(variable, uses); + } + }); + } else { + // No confirmation necessary for a single block. + map.deleteVariableInternal_(variable, uses); + } + } else { + console.warn("Can't delete non-existent 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. + * @param {!Array.} uses An array of uses of the variable. + * @private + */ +Blockly.VariableMap.prototype.deleteVariableInternal_ = function(variable, + uses) { + var existingGroup = Blockly.Events.getGroup(); + if (!existingGroup) { + Blockly.Events.setGroup(true); + } + try { + for (var i = 0; i < uses.length; i++) { + uses[i].dispose(true, false); + } + this.deleteVariable(variable); + } finally { + if (!existingGroup) { + Blockly.Events.setGroup(false); + } + } +}; + +/* End functions for variable deletion. */ + +/** + * Find the variable by the given name and type and return it. Return null if + * it is not found. * @param {string} name The name to check for. + * @param {string=} opt_type The type of the variable. If not provided it + * defaults to the empty string, which is a specific type. * @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++) { +Blockly.VariableMap.prototype.getVariable = function(name, opt_type) { + var type = opt_type || ''; + var list = this.variableMap_[type]; + if (list) { + for (var j = 0, variable; variable = list[j]; j++) { if (Blockly.Names.equals(variable.name, name)) { return variable; } @@ -219,11 +342,22 @@ Blockly.VariableMap.prototype.getVariablesOfType = function(type) { }; /** - * Return all variable types. + * Return all variable types. This list always contains the empty string. * @return {!Array.} List of variable types. + * @package */ Blockly.VariableMap.prototype.getVariableTypes = function() { - return Object.keys(this.variableMap_); + var types = Object.keys(this.variableMap_); + var hasEmpty = false; + for (var i = 0; i < types.length; i++) { + if (types[i] == '') { + hasEmpty = true; + } + } + if (!hasEmpty) { + types.push(''); + } + return types; }; /** @@ -238,3 +372,25 @@ Blockly.VariableMap.prototype.getAllVariables = function() { } return all_variables; }; + +/** + * Find all the uses of a named variable. + * @param {string} id ID of the variable to find. + * @return {!Array.} Array of block usages. + */ +Blockly.VariableMap.prototype.getVariableUsesById = function(id) { + var uses = []; + var blocks = this.workspace.getAllBlocks(); + // Iterate through every block and check the name. + for (var i = 0; i < blocks.length; i++) { + var blockVariables = blocks[i].getVarModels(); + if (blockVariables) { + for (var j = 0; j < blockVariables.length; j++) { + if (blockVariables[j].getId() == id) { + uses.push(blocks[i]); + } + } + } + } + return uses; +}; diff --git a/core/variables.js b/core/variables.js index be9dcb681..75209df92 100644 --- a/core/variables.js +++ b/core/variables.js @@ -47,57 +47,77 @@ Blockly.Variables.NAME_TYPE = Blockly.VARIABLE_CATEGORY_NAME; /** * Find all user-created variables that are in use in the workspace. * For use by generators. - * @param {!Blockly.Block|!Blockly.Workspace} root Root block or workspace. - * @return {!Array.} Array of variable names. + * To get a list of all variables on a workspace, including unused variables, + * call Workspace.getAllVariables. + * @param {!Blockly.Workspace} ws The workspace to search for variables. + * @return {!Array.} Array of variable models. */ -Blockly.Variables.allUsedVariables = function(root) { - var blocks; - if (root instanceof Blockly.Block) { - // Root is Block. - blocks = root.getDescendants(); - } else if (root.getAllBlocks) { - // Root is Workspace. - blocks = root.getAllBlocks(); - } else { - throw 'Not Block or Workspace: ' + root; - } +Blockly.Variables.allUsedVarModels = function(ws) { + var blocks = ws.getAllBlocks(); var variableHash = Object.create(null); // Iterate through every block and add each variable to the hash. for (var x = 0; x < blocks.length; x++) { - var blockVariables = blocks[x].getVars(); + var blockVariables = blocks[x].getVarModels(); if (blockVariables) { for (var y = 0; y < blockVariables.length; y++) { - var varName = blockVariables[y]; - // Variable name may be null if the block is only half-built. - if (varName) { - variableHash[varName.toLowerCase()] = varName; + var variable = blockVariables[y]; + if (variable.getId()) { + variableHash[variable.getId()] = variable; } } } } // Flatten the hash into a list. var variableList = []; - for (var name in variableHash) { - variableList.push(variableHash[name]); + for (var id in variableHash) { + variableList.push(variableHash[id]); } return variableList; }; /** - * 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.} Array of variable models. + * Find all user-created variables that are in use in the workspace and return + * only their names. + * For use by generators. + * To get a list of all variables on a workspace, including unused variables, + * call Workspace.getAllVariables. + * @deprecated January 2018 */ -Blockly.Variables.allVariables = function(root) { - if (root instanceof Blockly.Block) { - // Root is Block. - console.warn('Deprecated call to Blockly.Variables.allVariables ' + - 'with a block instead of a workspace. You may want ' + - 'Blockly.Variables.allUsedVariables'); - return {}; +Blockly.Variables.allUsedVariables = function() { + console.warn('Deprecated call to Blockly.Variables.allUsedVariables. ' + + 'Use Blockly.Variables.allUsedVarModels instead.\nIf this is a major ' + + 'issue please file a bug on GitHub.'); +}; + +/** + * Find all developer variables used by blocks in the workspace. + * Developer variables are never shown to the user, but are declared as global + * variables in the generated code. + * To declare developer variables, define the getDeveloperVariables function on + * your block and return a list of variable names. + * For use by generators. + * @param {!Blockly.Workspace} workspace The workspace to search. + * @return {!Array.} A list of non-duplicated variable names. + */ +Blockly.Variables.allDeveloperVariables = function(workspace) { + var blocks = workspace.getAllBlocks(); + var hash = {}; + for (var i = 0; i < blocks.length; i++) { + var block = blocks[i]; + if (block.getDeveloperVars) { + var devVars = block.getDeveloperVars(); + for (var j = 0; j < devVars.length; j++) { + hash[devVars[j]] = devVars[j]; + } + } } - return root.getAllVariables(); + + // Flatten the hash into a list. + var list = []; + for (var name in hash) { + list.push(hash[name]); + } + return list; }; /** @@ -113,7 +133,7 @@ Blockly.Variables.flyoutCategory = function(workspace) { button.setAttribute('callbackKey', 'CREATE_VARIABLE'); workspace.registerButtonCallback('CREATE_VARIABLE', function(button) { - Blockly.Variables.createVariable(button.getTargetWorkspace()); + Blockly.Variables.createVariableButtonHandler(button.getTargetWorkspace()); }); xmlList.push(button); @@ -226,81 +246,123 @@ Blockly.Variables.generateUniqueName = function(workspace) { }; /** - * Create a new variable on the given workspace. + * Handles "Create Variable" button in the default variables toolbox category. + * It will prompt the user for a varibale name, including re-prompts if a name + * is already in use among the workspace's variables. + * + * Custom button handlers can delegate to this function, allowing variables + * types and after-creation processing. More complex customization (e.g., + * prompting for variable type) is beyond the scope of this function. + * * @param {!Blockly.Workspace} workspace The workspace on which to create the * variable. - * @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 {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, opt_type) { +Blockly.Variables.createVariableButtonHandler = function( + workspace, opt_callback, opt_type) { + var type = 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.getVariable(text)) { - Blockly.alert(Blockly.Msg.VARIABLE_ALREADY_EXISTS.replace('%1', - text.toLowerCase()), - function() { - promptAndCheckWithAlert(text); // Recurse - }); + function(text) { + if (text) { + var existing = + Blockly.Variables.nameUsedWithAnyType_(text, workspace); + if (existing) { + var lowerCase = text.toLowerCase(); + if (existing.type == type) { + var msg = Blockly.Msg.VARIABLE_ALREADY_EXISTS.replace( + '%1', lowerCase); + } else { + var msg = Blockly.Msg.VARIABLE_ALREADY_EXISTS_FOR_ANOTHER_TYPE; + msg = msg.replace('%1', lowerCase).replace('%2', existing.type); + } + Blockly.alert(msg, + function() { + promptAndCheckWithAlert(text); // Recurse + }); + } else { + // No conflict + workspace.createVariable(text, type); + if (opt_callback) { + opt_callback(text); + } + } } else { - workspace.createVariable(text, opt_type); + // User canceled prompt. if (opt_callback) { - opt_callback(text); + opt_callback(null); } } - } else { - // User canceled prompt without a value. - if (opt_callback) { - opt_callback(null); - } - } - }); + }); }; promptAndCheckWithAlert(''); }; +goog.exportSymbol('Blockly.Variables.createVariableButtonHandler', + Blockly.Variables.createVariableButtonHandler); + +/** + * Original name of Blockly.Variables.createVariableButtonHandler(..). + * @deprecated Use Blockly.Variables.createVariableButtonHandler(..). + * + * @param {!Blockly.Workspace} workspace The workspace on which to create the + * variable. + * @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 = + Blockly.Variables.createVariableButtonHandler; +goog.exportSymbol('Blockly.Variables.createVariable', + Blockly.Variables.createVariable); /** * 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 {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) { + 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 - }); + var promptText = + Blockly.Msg.RENAME_VARIABLE_TITLE.replace('%1', variable.name); + Blockly.Variables.promptName(promptText, defaultName, + function(newName) { + if (newName) { + var existing = Blockly.Variables.nameUsedWithOtherType_(newName, + variable.type, workspace); + if (existing) { + var msg = Blockly.Msg.VARIABLE_ALREADY_EXISTS_FOR_ANOTHER_TYPE + .replace('%1', newName.toLowerCase()) + .replace('%2', existing.type); + Blockly.alert(msg, + function() { + promptAndCheckWithAlert(newName); // Recurse + }); + } else { + workspace.renameVariableById(variable.getId(), newName); + if (opt_callback) { + opt_callback(newName); + } + } } else { - workspace.renameVariable(variable.name, newName); + // User canceled prompt. if (opt_callback) { - opt_callback(newName); + opt_callback(null); } } - } else { - // User canceled prompt without a value. - if (opt_callback) { - opt_callback(null); - } - } - }); + }); }; promptAndCheckWithAlert(''); }; @@ -328,6 +390,50 @@ Blockly.Variables.promptName = function(promptText, defaultText, callback) { }); }; +/** + * Check whether there exists a variable with the given name but a different + * type. + * @param {string} name The name to search for. + * @param {string} type The type to exclude from the search. + * @param {!Blockly.Workspace} workspace The workspace to search for the + * variable. + * @return {?Blockly.VariableModel} The variable with the given name and a + * different type, or null if none was found. + * @private + */ +Blockly.Variables.nameUsedWithOtherType_ = function(name, type, workspace) { + var allVariables = workspace.getVariableMap().getAllVariables(); + + name = name.toLowerCase(); + for (var i = 0, variable; variable = allVariables[i]; i++) { + if (variable.name.toLowerCase() == name && variable.type != type) { + return variable; + } + } + return null; +}; + +/** + * Check whether there exists a variable with the given name of any type. + * @param {string} name The name to search for. + * @param {!Blockly.Workspace} workspace The workspace to search for the + * variable. + * @return {?Blockly.VariableModel} The variable with the given name, or null if + * none was found. + * @private + */ +Blockly.Variables.nameUsedWithAnyType_ = function(name, workspace) { + var allVariables = workspace.getVariableMap().getAllVariables(); + + name = name.toLowerCase(); + for (var i = 0, variable; variable = allVariables[i]; i++) { + if (variable.name.toLowerCase() == name) { + return variable; + } + } + return null; +}; + /** * Generate XML string for variable field. * @param {!Blockly.VariableModel} variableModel The variable model to generate @@ -338,12 +444,129 @@ Blockly.Variables.promptName = function(promptText, defaultText, callback) { Blockly.Variables.generateVariableFieldXml_ = function(variableModel) { // The variable name may be user input, so it may contain characters that need // to be escaped to create valid XML. - var element = goog.dom.createDom('field'); - element.setAttribute('name', 'VAR'); - element.setAttribute('variabletype', variableModel.type); - element.setAttribute('id', variableModel.getId()); - element.textContent = variableModel.name; - - var xmlString = Blockly.Xml.domToText(element); - return xmlString; + var typeString = variableModel.type; + if (typeString == '') { + typeString = '\'\''; + } + var text = '' + goog.string.htmlEscape(variableModel.name) + ''; + return text; +}; + +/** + * Helper function to look up or create a variable on the given workspace. + * If no variable exists, creates and returns it. + * @param {!Blockly.Workspace} workspace The workspace to search for the + * variable. It may be a flyout workspace or main workspace. + * @param {string} id The ID to use to look up or create the variable, or null. + * @param {string=} opt_name The string to use to look up or create the + * variable. + * @param {string=} opt_type The type to use to look up or create the variable. + * @return {!Blockly.VariableModel} The variable corresponding to the given ID + * or name + type combination. + */ +Blockly.Variables.getOrCreateVariablePackage = function(workspace, id, opt_name, + opt_type) { + var variable = Blockly.Variables.getVariable(workspace, id, opt_name, + opt_type); + if (!variable) { + variable = Blockly.Variables.createVariable_(workspace, id, opt_name, + opt_type); + } + return variable; +}; + +/** + * Look up a variable on the given workspace. + * Always looks in the main workspace before looking in the flyout workspace. + * Always prefers lookup by ID to lookup by name + type. + * @param {!Blockly.Workspace} workspace The workspace to search for the + * variable. It may be a flyout workspace or main workspace. + * @param {string} id The ID to use to look up the variable, or null. + * @param {string=} opt_name The string to use to look up the variable. Only + * used if lookup by ID fails. + * @param {string=} opt_type The type to use to look up the variable. Only used + * if lookup by ID fails. + * @return {?Blockly.VariableModel} The variable corresponding to the given ID + * or name + type combination, or null if not found. + * @package + */ +Blockly.Variables.getVariable = function(workspace, id, opt_name, opt_type) { + var potentialVariableMap = workspace.getPotentialVariableMap(); + // Try to just get the variable, by ID if possible. + if (id) { + // Look in the real variable map before checking the potential variable map. + var variable = workspace.getVariableById(id); + if (!variable && potentialVariableMap) { + variable = potentialVariableMap.getVariableById(id); + } + } else if (opt_name) { + if (opt_type == undefined) { + throw new Error('Tried to look up a variable by name without a type'); + } + // Otherwise look up by name and type. + var variable = workspace.getVariable(opt_name, opt_type); + if (!variable && potentialVariableMap) { + variable = potentialVariableMap.getVariable(opt_name, opt_type); + } + } + return variable; +}; + +/** + * Helper function to create a variable on the given workspace. + * @param {!Blockly.Workspace} workspace The workspace in which to create the + * variable. It may be a flyout workspace or main workspace. + * @param {string} id The ID to use to create the variable, or null. + * @param {string=} opt_name The string to use to create the variable. + * @param {string=} opt_type The type to use to create the variable. + * @return {!Blockly.VariableModel} The variable corresponding to the given ID + * or name + type combination. + * @private + */ +Blockly.Variables.createVariable_ = function(workspace, id, opt_name, + opt_type) { + var potentialVariableMap = workspace.getPotentialVariableMap(); + // Variables without names get uniquely named for this workspace. + if (!opt_name) { + var ws = workspace.isFlyout ? workspace.targetWorkspace : workspace; + opt_name = Blockly.Variables.generateUniqueName(ws); + } + + // Create a potential variable if in the flyout. + if (potentialVariableMap) { + var variable = potentialVariableMap.createVariable(opt_name, opt_type, id); + } else { // In the main workspace, create a real variable. + var variable = workspace.createVariable(opt_name, opt_type, id); + } + return variable; +}; + +/** + * Helper function to get the list of variables that have been added to the + * workspace after adding a new block, using the given list of variables that + * were in the workspace before the new block was added. + * @param {!Blockly.Workspace} workspace The workspace to inspect. + * @param {!Array.} originalVariables The array of + * variables that existed in the workspace before adding the new block. + * @return {!Array.} The new array of variables that were + * freshly added to the workspace after creating the new block, or [] if no + * new variables were added to the workspace. + * @package + */ +Blockly.Variables.getAddedVariables = function(workspace, originalVariables) { + var allCurrentVariables = workspace.getAllVariables(); + var addedVariables = []; + if (originalVariables.length != allCurrentVariables.length) { + for (var i = 0; i < allCurrentVariables.length; i++) { + var variable = allCurrentVariables[i]; + // For any variable that is present in allCurrentVariables but not + // present in originalVariables, add the variable to addedVariables. + if (!originalVariables.includes(variable)) { + addedVariables.push(variable); + } + } + } + return addedVariables; }; diff --git a/core/variables_dynamic.js b/core/variables_dynamic.js new file mode 100644 index 000000000..1a88e5e34 --- /dev/null +++ b/core/variables_dynamic.js @@ -0,0 +1,116 @@ +/** + * @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 Utility functions for handling variables dynamic. + * + * @author duzc2dtw@gmail.com (Du Tian Wei) + */ +'use strict'; + +goog.provide('Blockly.VariablesDynamic'); + +goog.require('Blockly.Variables'); +goog.require('Blockly.Blocks'); +goog.require('Blockly.constants'); +goog.require('Blockly.VariableModel'); +// TODO Fix circular dependencies +// goog.require('Blockly.Workspace'); +goog.require('goog.string'); + + +Blockly.VariablesDynamic.onCreateVariableButtonClick_String = function(button) { + Blockly.Variables.createVariableButtonHandler(button.getTargetWorkspace(), null, 'String'); +}; +Blockly.VariablesDynamic.onCreateVariableButtonClick_Number = function(button) { + Blockly.Variables.createVariableButtonHandler(button.getTargetWorkspace(), null, 'Number'); +}; +Blockly.VariablesDynamic.onCreateVariableButtonClick_Colour = function(button) { + Blockly.Variables.createVariableButtonHandler(button.getTargetWorkspace(), null, 'Colour'); +}; +/** + * Construct the elements (blocks and button) required by the flyout for the + * variable category. + * @param {!Blockly.Workspace} workspace The workspace containing variables. + * @return {!Array.} Array of XML elements. + */ +Blockly.VariablesDynamic.flyoutCategory = function(workspace) { + var xmlList = []; + var button = goog.dom.createDom('button'); + button.setAttribute('text', Blockly.Msg.NEW_STRING_VARIABLE); + button.setAttribute('callbackKey', 'CREATE_VARIABLE_STRING'); + xmlList.push(button); + button = goog.dom.createDom('button'); + button.setAttribute('text', Blockly.Msg.NEW_NUMBER_VARIABLE); + button.setAttribute('callbackKey', 'CREATE_VARIABLE_NUMBER'); + xmlList.push(button);button = goog.dom.createDom('button'); + button.setAttribute('text', Blockly.Msg.NEW_COLOUR_VARIABLE); + button.setAttribute('callbackKey', 'CREATE_VARIABLE_COLOUR'); + xmlList.push(button); + + workspace.registerButtonCallback('CREATE_VARIABLE_STRING', + Blockly.VariablesDynamic.onCreateVariableButtonClick_String); + workspace.registerButtonCallback('CREATE_VARIABLE_NUMBER', + Blockly.VariablesDynamic.onCreateVariableButtonClick_Number); + workspace.registerButtonCallback('CREATE_VARIABLE_COLOUR', + Blockly.VariablesDynamic.onCreateVariableButtonClick_Colour); + + + var blockList = Blockly.VariablesDynamic.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 containing variables. + * @return {!Array.} Array of XML block elements. + */ +Blockly.VariablesDynamic.flyoutCategoryBlocks = function(workspace) { + var variableModelList = workspace.getAllVariables(); + variableModelList.sort(Blockly.VariableModel.compareByName); + + var xmlList = []; + if (variableModelList.length > 0) { + if (Blockly.Blocks['variables_set_dynamic']) { + var firstVariable = variableModelList[0]; + var gap = 24; + var blockText = '' + + '' + + Blockly.Variables.generateVariableFieldXml_(firstVariable) + + '' + + ''; + var block = Blockly.Xml.textToDom(blockText).firstChild; + xmlList.push(block); + } + if (Blockly.Blocks['variables_get_dynamic']) { + for (var i = 0, variable; variable = variableModelList[i]; i++) { + var blockText = '' + + '' + + Blockly.Variables.generateVariableFieldXml_(variable) + + '' + + ''; + var block = Blockly.Xml.textToDom(blockText).firstChild; + xmlList.push(block); + } + } + } + return xmlList; +}; diff --git a/core/warning.js b/core/warning.js index 1c9d3481d..6805d42c2 100644 --- a/core/warning.js +++ b/core/warning.js @@ -57,21 +57,27 @@ Blockly.Warning.prototype.collapseHidden = false; Blockly.Warning.prototype.drawIcon_ = function(group) { // Triangle with rounded corners. Blockly.utils.createSvgElement('path', - {'class': 'blocklyIconShape', - 'd': 'M2,15Q-1,15 0.5,12L6.5,1.7Q8,-1 9.5,1.7L15.5,12Q17,15 14,15z'}, - group); + { + 'class': 'blocklyIconShape', + 'd': 'M2,15Q-1,15 0.5,12L6.5,1.7Q8,-1 9.5,1.7L15.5,12Q17,15 14,15z' + }, + group); // Can't use a real '!' text character since different browsers and operating // systems render it differently. // Body of exclamation point. Blockly.utils.createSvgElement('path', - {'class': 'blocklyIconSymbol', - 'd': 'm7,4.8v3.16l0.27,2.27h1.46l0.27,-2.27v-3.16z'}, - group); + { + 'class': 'blocklyIconSymbol', + 'd': 'm7,4.8v3.16l0.27,2.27h1.46l0.27,-2.27v-3.16z' + }, + group); // Dot of exclamation point. Blockly.utils.createSvgElement('rect', - {'class': 'blocklyIconSymbol', - 'x': '7', 'y': '11', 'height': '2', 'width': '2'}, - group); + { + 'class': 'blocklyIconSymbol', + 'x': '7', 'y': '11', 'height': '2', 'width': '2' + }, + group); }; /** @@ -81,11 +87,15 @@ Blockly.Warning.prototype.drawIcon_ = function(group) { * @private */ Blockly.Warning.textToDom_ = function(text) { - var paragraph = /** @type {!SVGTextElement} */ ( - Blockly.utils.createSvgElement('text', - {'class': 'blocklyText blocklyBubbleText', - 'y': Blockly.Bubble.BORDER_WIDTH}, - null)); + var paragraph = /** @type {!SVGTextElement} */ + (Blockly.utils.createSvgElement( + 'text', + { + 'class': 'blocklyText blocklyBubbleText', + 'y': Blockly.Bubble.BORDER_WIDTH + }, + null) + ); var lines = text.split('\n'); for (var i = 0; i < lines.length; i++) { var tspanElement = Blockly.utils.createSvgElement('tspan', diff --git a/core/widgetdiv.js b/core/widgetdiv.js index 005690a0c..be8a0e14e 100644 --- a/core/widgetdiv.js +++ b/core/widgetdiv.js @@ -134,7 +134,7 @@ Blockly.WidgetDiv.hideIfOwner = function(oldOwner) { * @param {boolean} rtl True if RTL, false if LTR. */ Blockly.WidgetDiv.position = function(anchorX, anchorY, windowSize, - scrollOffset, rtl) { + scrollOffset, rtl) { // Don't let the widget go above the top edge of the window. if (anchorY < scrollOffset.y) { anchorY = scrollOffset.y; diff --git a/core/workspace.js b/core/workspace.js index f3c6ca4a4..6f7b06231 100644 --- a/core/workspace.js +++ b/core/workspace.js @@ -84,6 +84,18 @@ Blockly.Workspace = function(opt_options) { * @private */ this.variableMap_ = new Blockly.VariableMap(this); + + /** + * Blocks in the flyout can refer to variables that don't exist in the main + * workspace. For instance, the "get item in list" block refers to an "item" + * variable regardless of whether the variable has been created yet. + * A FieldVariable must always refer to a Blockly.VariableModel. We reconcile + * these by tracking "potential" variables in the flyout. These variables + * become real when references to them are dragged into the main workspace. + * @type {!Blockly.VariableMap} + * @private + */ + this.potentialVariableMap_ = null; }; /** @@ -123,18 +135,6 @@ Blockly.Workspace.SCAN_ANGLE = 3; */ Blockly.Workspace.prototype.addTopBlock = function(block) { this.topBlocks_.push(block); - 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); - } - } }; /** @@ -197,92 +197,12 @@ Blockly.Workspace.prototype.clear = function() { Blockly.Events.setGroup(false); } this.variableMap_.clear(); -}; - -/** - * 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} clear True if the old variable map should be cleared. - */ -Blockly.Workspace.prototype.updateVariableStore = function(clear) { - // TODO: Sort - 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()}); - } 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); - } + if (this.potentialVariableMap_) { + this.potentialVariableMap_.clear(); } }; -/** - * 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.renameVariableInternal_ = function( - variable, newName) { - var newVariable = this.getVariable(newName); - var oldCase; - - // 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 and update name. - for (var i = 0; i < blocks.length; i++) { - blocks[i].renameVar(variable.name, newName); - if (oldCase) { - blocks[i].renameVar(oldCase, newName); - } - } - this.variableMap_.renameVariable(variable, newName); - Blockly.Events.setGroup(false); -}; - - -/** - * 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); -}; - +/* Begin functions that are just pass-throughs to the variable map. */ /** * Rename a variable by updating its name in the variable map. Identify the * variable to rename with the given ID. @@ -290,8 +210,7 @@ Blockly.Workspace.prototype.renameVariable = function(oldName, newName) { * @param {string} newName New variable name. */ Blockly.Workspace.prototype.renameVariableById = function(id, newName) { - var variable = this.getVariableById(id); - this.renameVariableInternal_(variable, newName); + this.variableMap_.renameVariableById(id, newName); }; /** @@ -310,66 +229,12 @@ Blockly.Workspace.prototype.createVariable = function(name, opt_type, opt_id) { }; /** - * Find all the uses of a named variable. - * @param {string} name Name of variable. + * Find all the uses of the given variable, which is identified by ID. + * @param {string} id ID of the variable to find. * @return {!Array.} Array of block usages. */ -Blockly.Workspace.prototype.getVariableUses = function(name) { - var uses = []; - var blocks = this.getAllBlocks(); - // Iterate through every block and check the name. - for (var i = 0; i < blocks.length; i++) { - var blockVariables = blocks[i].getVars(); - if (blockVariables) { - 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 && name && Blockly.Names.equals(varName, name)) { - uses.push(blocks[i]); - } - } - } - } - return uses; -}; - -/** - * 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) { - // Check whether this variable is a function parameter before deleting. - var uses = this.getVariableUses(name); - for (var i = 0, block; block = uses[i]; i++) { - if (block.type == 'procedures_defnoreturn' || - block.type == 'procedures_defreturn') { - var procedureName = block.getFieldValue('NAME'); - Blockly.alert( - Blockly.Msg.CANNOT_DELETE_VARIABLE_PROCEDURE. - replace('%1', name). - replace('%2', procedureName)); - return; - } - } - - var workspace = this; - var variable = workspace.getVariable(name); - if (uses.length > 1) { - // Confirm before deleting multiple blocks. - Blockly.confirm( - Blockly.Msg.DELETE_VARIABLE_CONFIRMATION.replace('%1', - String(uses.length)). - replace('%2', name), - function(ok) { - if (ok) { - workspace.deleteVariableInternal_(variable); - } - }); - } else { - // No confirmation necessary for a single block. - this.deleteVariableInternal_(variable); - } +Blockly.Workspace.prototype.getVariableUsesById = function(id) { + return this.variableMap_.getVariableUsesById(id); }; /** @@ -378,28 +243,18 @@ Blockly.Workspace.prototype.deleteVariable = function(name) { * @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-existent variable: " + id); - } + this.variableMap_.deleteVariableById(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. + * @param {!Array.} uses An array of uses of the variable. * @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); +Blockly.Workspace.prototype.deleteVariableInternal_ = function(variable, uses) { + this.variableMap_.deleteVariableInternal_(variable, uses); }; /** @@ -422,11 +277,14 @@ Blockly.Workspace.prototype.variableIndexOf = function( /** * Find the variable by the given name and return it. Return null if it is not * found. + * TODO (#1199): Possibly delete this function. * @param {!string} name The name to check for. + * @param {string=} opt_type The type of the variable. If not provided it + * defaults to the empty string, which is a specific type. * @return {?Blockly.VariableModel} the variable with the given name. */ -Blockly.Workspace.prototype.getVariable = function(name) { - return this.variableMap_.getVariable(name); +Blockly.Workspace.prototype.getVariable = function(name, opt_type) { + return this.variableMap_.getVariable(name, opt_type); }; /** @@ -439,6 +297,36 @@ Blockly.Workspace.prototype.getVariableById = function(id) { return this.variableMap_.getVariableById(id); }; +/** + * 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.} 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.} List of variable types. + * @package + */ +Blockly.Workspace.prototype.getVariableTypes = function() { + return this.variableMap_.getVariableTypes(); +}; + +/** + * Return all variables of all types. + * @return {!Array.} List of variable models. + */ +Blockly.Workspace.prototype.getAllVariables = function() { + return this.variableMap_.getAllVariables(); +}; + +/* End functions that are just pass-throughs to the variable map. */ + /** * Returns the horizontal offset of the workspace. * Intended for LTR/RTL compatibility in XML. @@ -578,30 +466,29 @@ Blockly.Workspace.prototype.allInputsFilled = function(opt_shadowBlocksAreFilled }; /** - * 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.} The sought after variables of the - * passed in type. An empty array if none are found. + * Return the variable map that contains "potential" variables. These exist in + * the flyout but not in the workspace. + * @return {?Blockly.VariableMap} The potential variable map. + * @package */ -Blockly.Workspace.prototype.getVariablesOfType = function(type) { - return this.variableMap_.getVariablesOfType(type); +Blockly.Workspace.prototype.getPotentialVariableMap = function() { + return this.potentialVariableMap_; }; /** - * Return all variable types. - * @return {!Array.} List of variable types. + * Create and store the potential variable map for this workspace. + * @package */ -Blockly.Workspace.prototype.getVariableTypes = function() { - return this.variableMap_.getVariableTypes(); +Blockly.Workspace.prototype.createPotentialVariableMap = function() { + this.potentialVariableMap_ = new Blockly.VariableMap(this); }; /** - * Return all variables of all types. - * @return {!Array.} List of variable models. + * Return the map of all variables on the workspace. + * @return {?Blockly.VariableMap} The variable map. */ -Blockly.Workspace.prototype.getAllVariables = function() { - return this.variableMap_.getAllVariables(); +Blockly.Workspace.prototype.getVariableMap = function() { + return this.variableMap_; }; /** diff --git a/core/workspace_drag_surface_svg.js b/core/workspace_drag_surface_svg.js index e24886acc..3227d9300 100644 --- a/core/workspace_drag_surface_svg.js +++ b/core/workspace_drag_surface_svg.js @@ -88,13 +88,14 @@ Blockly.WorkspaceDragSurfaceSvg.prototype.createDom = function() { * /g> * */ - this.SVG_ = Blockly.utils.createSvgElement('svg', { - 'xmlns': Blockly.SVG_NS, - 'xmlns:html': Blockly.HTML_NS, - 'xmlns:xlink': 'http://www.w3.org/1999/xlink', - 'version': '1.1', - 'class': 'blocklyWsDragSurface blocklyOverflowVisible' - }, null); + this.SVG_ = Blockly.utils.createSvgElement('svg', + { + 'xmlns': Blockly.SVG_NS, + 'xmlns:html': Blockly.HTML_NS, + 'xmlns:xlink': 'http://www.w3.org/1999/xlink', + 'version': '1.1', + 'class': 'blocklyWsDragSurface blocklyOverflowVisible' + }, null); this.container_.appendChild(this.SVG_); }; @@ -114,8 +115,8 @@ Blockly.WorkspaceDragSurfaceSvg.prototype.translateSurface = function(x, y) { y = y.toFixed(0); this.SVG_.style.display = 'block'; - Blockly.utils.setCssTransform(this.SVG_, - 'translate3d(' + x + 'px, ' + y + 'px, 0px)'); + Blockly.utils.setCssTransform( + this.SVG_, 'translate3d(' + x + 'px, ' + y + 'px, 0px)'); }; /** @@ -156,8 +157,8 @@ Blockly.WorkspaceDragSurfaceSvg.prototype.clearAndHide = function(newSurface) { Blockly.utils.insertAfter_(bubbleCanvas, blockCanvas); // Hide the drag surface. this.SVG_.style.display = 'none'; - goog.asserts.assert(this.SVG_.childNodes.length == 0, - 'Drag surface was not cleared.'); + goog.asserts.assert( + this.SVG_.childNodes.length == 0, 'Drag surface was not cleared.'); Blockly.utils.setCssTransform(this.SVG_, ''); this.previousSibling_ = null; }; @@ -176,13 +177,13 @@ Blockly.WorkspaceDragSurfaceSvg.prototype.clearAndHide = function(newSurface) { */ Blockly.WorkspaceDragSurfaceSvg.prototype.setContentsAndShow = function( blockCanvas, bubbleCanvas, previousSibling, width, height, scale) { - goog.asserts.assert(this.SVG_.childNodes.length == 0, - 'Already dragging a block.'); + goog.asserts.assert( + this.SVG_.childNodes.length == 0, 'Already dragging a block.'); this.previousSibling_ = previousSibling; // Make sure the blocks and bubble canvas are scaled appropriately. blockCanvas.setAttribute('transform', 'translate(0, 0) scale(' + scale + ')'); - bubbleCanvas.setAttribute('transform', - 'translate(0, 0) scale(' + scale + ')'); + bubbleCanvas.setAttribute( + 'transform', 'translate(0, 0) scale(' + scale + ')'); this.SVG_.setAttribute('width', width); this.SVG_.setAttribute('height', height); this.SVG_.appendChild(blockCanvas); diff --git a/core/workspace_dragger.js b/core/workspace_dragger.js index 5c45aab3f..09cb9b818 100644 --- a/core/workspace_dragger.js +++ b/core/workspace_dragger.js @@ -61,8 +61,8 @@ Blockly.WorkspaceDragger = function(workspace) { * @type {!goog.math.Coordinate} * @private */ - this.startScrollXY_ = new goog.math.Coordinate(workspace.scrollX, - workspace.scrollY); + this.startScrollXY_ = new goog.math.Coordinate( + workspace.scrollX, workspace.scrollY); }; /** diff --git a/core/workspace_svg.js b/core/workspace_svg.js index b5954b1f1..167549359 100644 --- a/core/workspace_svg.js +++ b/core/workspace_svg.js @@ -36,6 +36,7 @@ goog.require('Blockly.Options'); goog.require('Blockly.ScrollbarPair'); goog.require('Blockly.Touch'); goog.require('Blockly.Trashcan'); +goog.require('Blockly.VariablesDynamic'); goog.require('Blockly.Workspace'); goog.require('Blockly.WorkspaceAudio'); goog.require('Blockly.WorkspaceDragSurfaceSvg'); @@ -102,10 +103,18 @@ Blockly.WorkspaceSvg = function(options, opt_blockDragSurface, opt_wsDragSurface 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, - Blockly.Procedures.flyoutCategory); + if (Blockly.Variables && Blockly.Variables.flyoutCategory) { + this.registerToolboxCategoryCallback(Blockly.VARIABLE_CATEGORY_NAME, + Blockly.Variables.flyoutCategory); + } + if (Blockly.VariablesDynamic && Blockly.VariablesDynamic.flyoutCategory) { + this.registerToolboxCategoryCallback(Blockly.VARIABLE_DYNAMIC_CATEGORY_NAME, + Blockly.VariablesDynamic.flyoutCategory); + } + if (Blockly.Procedures && Blockly.Procedures.flyoutCategory) { + this.registerToolboxCategoryCallback(Blockly.PROCEDURE_CATEGORY_NAME, + Blockly.Procedures.flyoutCategory); + } }; goog.inherits(Blockly.WorkspaceSvg, Blockly.Workspace); @@ -258,6 +267,14 @@ Blockly.WorkspaceSvg.prototype.flyoutButtonCallbacks_ = {}; */ Blockly.WorkspaceSvg.prototype.toolboxCategoryCallbacks_ = {}; +/** + * In a flyout, the target workspace where blocks should be placed after a drag. + * Otherwise null. + * @type {?Blockly.WorkspaceSvg} + * @package + */ +Blockly.WorkspaceSvg.prototype.targetWorkspace = null; + /** * Inverted screen CTM, for use in mouseToSvg. * @type {SVGMatrix} @@ -379,7 +396,7 @@ Blockly.WorkspaceSvg.prototype.createDom = function(opt_backgroundClass) { bottom = this.addTrashcan_(bottom); } if (this.options.zoomOptions && this.options.zoomOptions.controls) { - bottom = this.addZoomControls_(bottom); + this.addZoomControls_(bottom); } if (!this.isFlyout) { @@ -464,7 +481,7 @@ Blockly.WorkspaceSvg.prototype.dispose = function() { } if (!this.options.parentWorkspace) { // Top-most workspace. Dispose of the div that the - // svg is injected into (i.e. injectionDiv). + // SVG is injected into (i.e. injectionDiv). goog.dom.removeNode(this.getParentSvg().parentNode); } if (this.resizeHandlerWrapper_) { @@ -541,7 +558,7 @@ Blockly.WorkspaceSvg.prototype.addFlyout_ = function(tagName) { this.flyout_.autoClose = false; // Return the element so that callers can place it in their desired - // spot in the dom. For exmaple, mutator flyouts do not go in the same place + // spot in the DOM. For example, mutator flyouts do not go in the same place // as main workspace flyouts. return this.flyout_.createDom(tagName); }; @@ -625,7 +642,7 @@ Blockly.WorkspaceSvg.prototype.resize = function() { */ Blockly.WorkspaceSvg.prototype.updateScreenCalculationsIfScrolled = function() { - /* eslint-disable indent */ + /* eslint-disable indent */ var currScroll = goog.dom.getDocumentScroll(); if (!goog.math.Coordinate.equals(this.lastRecordedPageScroll_, currScroll)) { @@ -745,6 +762,15 @@ Blockly.WorkspaceSvg.prototype.setupDragSurface = function() { this.workspaceDragSurface_.translateSurface(coord.x, coord.y); }; +/** + * @return {?Blockly.BlockDragSurfaceSvg} This workspace's block drag surface, + * if one is in use. + * @package + */ +Blockly.WorkspaceSvg.prototype.getBlockDragSurface = function() { + return this.blockDragSurface_; +}; + /** * Returns the horizontal offset of the workspace. * Intended for LTR/RTL compatibility in XML. @@ -911,23 +937,13 @@ Blockly.WorkspaceSvg.prototype.paste = function(xmlBlock) { /** * Refresh the toolbox unless there's a drag in progress. - * @private - */ -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. - * @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_(); +Blockly.WorkspaceSvg.prototype.refreshToolboxSelection = function() { + var ws = this.isFlyout ? this.targetWorkspace : this; + if (ws && !ws.currentGesture_ && ws.toolbox_ && ws.toolbox_.flyout_) { + ws.toolbox_.refreshSelection(); + } }; /** @@ -939,18 +955,7 @@ Blockly.WorkspaceSvg.prototype.renameVariable = function(oldName, newName) { */ 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_(); + this.refreshToolboxSelection(); }; /** @@ -961,7 +966,7 @@ Blockly.WorkspaceSvg.prototype.deleteVariable = function(name) { */ Blockly.WorkspaceSvg.prototype.deleteVariableById = function(id) { Blockly.WorkspaceSvg.superClass_.deleteVariableById.call(this, id); - this.refreshToolboxSelection_(); + this.refreshToolboxSelection(); }; /** @@ -977,9 +982,9 @@ Blockly.WorkspaceSvg.prototype.deleteVariableById = function(id) { * @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_(); + var newVar = Blockly.WorkspaceSvg.superClass_.createVariable.call( + this, name, opt_type, opt_id); + this.refreshToolboxSelection(); return newVar; }; @@ -1283,8 +1288,8 @@ Blockly.WorkspaceSvg.prototype.showContextMenu_ = function(e) { if (deleteList.length < 2 ) { deleteNext(); } else { - Blockly.confirm(Blockly.Msg.DELETE_ALL_BLOCKS. - replace('%1', deleteList.length), + Blockly.confirm( + Blockly.Msg.DELETE_ALL_BLOCKS.replace('%1', deleteList.length), function(ok) { if (ok) { deleteNext(); @@ -1586,7 +1591,7 @@ Blockly.WorkspaceSvg.getContentDimensionsExact_ = function(ws) { Blockly.WorkspaceSvg.getContentDimensionsBounded_ = function(ws, svgSize) { var content = Blockly.WorkspaceSvg.getContentDimensionsExact_(ws); - // View height and width are both in pixels, and are the same as the svg size. + // View height and width are both in pixels, and are the same as the SVG size. var viewWidth = svgSize.width; var viewHeight = svgSize.height; var halfWidth = viewWidth / 2; @@ -1819,13 +1824,13 @@ Blockly.WorkspaceSvg.prototype.removeToolboxCategoryCallback = function(key) { /** * 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 + * @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 isStart = (e.type == 'mousedown' || e.type == 'touchstart' || e.type == 'pointerdown'); var gesture = this.currentGesture_; if (gesture) { diff --git a/core/xml.js b/core/xml.js index 82e4040b4..e99a95a58 100644 --- a/core/xml.js +++ b/core/xml.js @@ -42,7 +42,8 @@ goog.require('goog.dom'); */ Blockly.Xml.workspaceToDom = function(workspace, opt_noId) { var xml = goog.dom.createDom('xml'); - xml.appendChild(Blockly.Xml.variablesToDom(workspace.getAllVariables())); + xml.appendChild(Blockly.Xml.variablesToDom( + Blockly.Variables.allUsedVarModels(workspace))); var blocks = workspace.getTopBlocks(true); for (var i = 0, block; block = blocks[i]; i++) { xml.appendChild(Blockly.Xml.blockToDomWithXY(block, opt_noId)); @@ -86,6 +87,80 @@ Blockly.Xml.blockToDomWithXY = function(block, opt_noId) { return element; }; +/** + * Encode a variable field as XML. + * @param {!Blockly.FieldVariable} field The field to encode. + * @return {?Element} XML element, or null if the field did not need to be + * serialized. + * @private + */ +Blockly.Xml.fieldToDomVariable_ = function(field) { + var id = field.getValue(); + // The field had not been initialized fully before being serialized. + // This can happen if a block is created directly through a call to + // workspace.newBlock instead of from XML. + // The new block will be serialized for the first time when firing a block + // creation event. + if (id == null) { + field.initModel(); + id = field.getValue(); + } + // Get the variable directly from the field, instead of doing a lookup. This + // will work even if the variable has already been deleted. This can happen + // because the flyout defers deleting blocks until the next time the flyout is + // opened. + var variable = field.getVariable(); + + if (!variable) { + throw Error('Tried to serialize a variable field with no variable.'); + } + var container = goog.dom.createDom('field', null, variable.name); + container.setAttribute('name', field.name); + container.setAttribute('id', variable.getId()); + container.setAttribute('variabletype', variable.type); + return container; +}; + +/** + * Encode a field as XML. + * @param {!Blockly.Field} field The field to encode. + * @param {!Blockly.Workspace} workspace The workspace that the field is in. + * @return {?Element} XML element, or null if the field did not need to be + * serialized. + * @private + */ +Blockly.Xml.fieldToDom_ = function(field) { + if (field.name && field.EDITABLE) { + if (field instanceof Blockly.FieldVariable) { + return Blockly.Xml.fieldToDomVariable_(field); + } else { + var container = goog.dom.createDom('field', null, field.getValue()); + container.setAttribute('name', field.name); + return container; + } + } + return null; +}; + +/** + * Encode all of a block's fields as XML and attach them to the given tree of + * XML elements. + * @param {!Blockly.Block} block A block with fields to be encoded. + * @param {!Element} element The XML element to which the field DOM should be + * attached. + * @private + */ +Blockly.Xml.allFieldsToDom_ = function(block, element) { + for (var i = 0, input; input = block.inputList[i]; i++) { + for (var j = 0, field; field = input.fieldRow[j]; j++) { + var fieldDom = Blockly.Xml.fieldToDom_(field); + if (fieldDom) { + element.appendChild(fieldDom); + } + } + } +}; + /** * Encode a block subtree as XML. * @param {!Blockly.Block} block The root block to encode. @@ -105,25 +180,8 @@ Blockly.Xml.blockToDom = function(block, opt_noId) { element.appendChild(mutation); } } - function fieldToDom(field) { - 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); - } - } - for (var i = 0, input; input = block.inputList[i]; i++) { - for (var j = 0, field; field = input.fieldRow[j]; j++) { - fieldToDom(field); - } - } + + Blockly.Xml.allFieldsToDom_(block, element); var commentText = block.getCommentText(); if (commentText) { @@ -370,7 +428,6 @@ Blockly.Xml.domToWorkspace = function(xml, workspace) { } Blockly.Field.stopCache(); } - workspace.updateVariableStore(false); // Re-enable workspace resizing. if (workspace.setResizesEnabled) { workspace.setResizesEnabled(true); @@ -449,13 +506,14 @@ Blockly.Xml.domToBlock = function(xmlBlock, workspace) { } // Create top-level block. Blockly.Events.disable(); + var variablesBeforeCreation = workspace.getAllVariables(); try { var topBlock = Blockly.Xml.domToBlockHeadless_(xmlBlock, workspace); + // Generate list of all blocks. + var blocks = topBlock.getDescendants(); if (workspace.rendered) { // Hide connections to speed up assembly. topBlock.setConnectionsHidden(true); - // Generate list of all blocks. - var blocks = topBlock.getDescendants(); // Render each block. for (var i = blocks.length - 1; i >= 0; i--) { blocks[i].initSvg(); @@ -474,11 +532,24 @@ Blockly.Xml.domToBlock = function(xmlBlock, workspace) { // Allow the scrollbars to resize and move based on the new contents. // TODO(@picklesrus): #387. Remove when domToBlock avoids resizing. workspace.resizeContents(); + } else { + for (var i = blocks.length - 1; i >= 0; i--) { + blocks[i].initModel(); + } } } finally { Blockly.Events.enable(); } if (Blockly.Events.isEnabled()) { + var newVariables = Blockly.Variables.getAddedVariables(workspace, + variablesBeforeCreation); + // Fire a VarCreate event for each (if any) new variable created. + for(var i = 0; i < newVariables.length; i++) { + var thisVariable = newVariables[i]; + Blockly.Events.fire(new Blockly.Events.VarCreate(thisVariable)); + } + // Block events come after var events, in case they refer to newly created + // variables. Blockly.Events.fire(new Blockly.Events.BlockCreate(topBlock)); } return topBlock; @@ -514,8 +585,8 @@ Blockly.Xml.domToVariables = function(xmlVariables, workspace) { Blockly.Xml.domToBlockHeadless_ = function(xmlBlock, workspace) { var block = null; var prototypeName = xmlBlock.getAttribute('type'); - goog.asserts.assert(prototypeName, 'Block type unspecified: %s', - xmlBlock.outerHTML); + goog.asserts.assert( + prototypeName, 'Block type unspecified: %s', xmlBlock.outerHTML); var id = xmlBlock.getAttribute('id'); block = workspace.newBlock(prototypeName, id); @@ -530,13 +601,12 @@ Blockly.Xml.domToBlockHeadless_ = function(xmlBlock, workspace) { // Find any enclosed blocks or shadows in this tag. var childBlockElement = null; var childShadowElement = null; - for (var j = 0, grandchildNode; grandchildNode = xmlChild.childNodes[j]; - j++) { - if (grandchildNode.nodeType == 1) { - if (grandchildNode.nodeName.toLowerCase() == 'block') { - childBlockElement = /** @type {!Element} */ (grandchildNode); - } else if (grandchildNode.nodeName.toLowerCase() == 'shadow') { - childShadowElement = /** @type {!Element} */ (grandchildNode); + for (var j = 0, grandchild; grandchild = xmlChild.childNodes[j]; j++) { + if (grandchild.nodeType == 1) { + if (grandchild.nodeName.toLowerCase() == 'block') { + childBlockElement = /** @type {!Element} */ (grandchild); + } else if (grandchild.nodeName.toLowerCase() == 'shadow') { + childShadowElement = /** @type {!Element} */ (grandchild); } } } @@ -583,31 +653,7 @@ Blockly.Xml.domToBlockHeadless_ = function(xmlBlock, workspace) { // Titles were renamed to field in December 2013. // 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 IDs 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 (type != null && 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(text); + Blockly.Xml.domToField_(block, name, xmlChild); break; case 'value': case 'statement': @@ -684,17 +730,72 @@ Blockly.Xml.domToBlockHeadless_ = function(xmlBlock, workspace) { // Ensure all children are also shadows. var children = block.getChildren(); for (var i = 0, child; child = children[i]; i++) { - goog.asserts.assert(child.isShadow(), - 'Shadow block not allowed non-shadow child.'); + goog.asserts.assert( + child.isShadow(), 'Shadow block not allowed non-shadow child.'); } // Ensure this block doesn't have any variable inputs. - goog.asserts.assert(block.getVars().length == 0, - 'Shadow blocks cannot have variable fields.'); + goog.asserts.assert(block.getVarModels().length == 0, + 'Shadow blocks cannot have variable references.'); block.setShadow(true); } return block; }; +/** + * Decode an XML variable field tag and set the value of that field. + * @param {!Blockly.Workspace} workspace The workspace that is currently being + * deserialized. + * @param {!Element} xml The field tag to decode. + * @param {string} text The text content of the XML tag. + * @param {!Blockly.FieldVariable} field The field on which the value will be + * set. + * @private + */ +Blockly.Xml.domToFieldVariable_ = function(workspace, xml, text, field) { + var type = xml.getAttribute('variabletype') || ''; + // TODO (fenichel): Does this need to be explicit or not? + if (type == '\'\'') { + type = ''; + } + + var variable = Blockly.Variables.getOrCreateVariablePackage(workspace, xml.id, + text, type); + + // This should never happen :) + if (type != null && 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(xml) + '.'); + } + + field.setValue(variable.getId()); +}; + +/** + * Decode an XML field tag and set the value of that field on the given block. + * @param {!Blockly.Block} block The block that is currently being deserialized. + * @param {string} fieldName The name of the field on the block. + * @param {!Element} xml The field tag to decode. + * @private + */ +Blockly.Xml.domToField_ = function(block, fieldName, xml) { + var field = block.getField(fieldName); + if (!field) { + console.warn('Ignoring non-existent field ' + fieldName + ' in block ' + + block.type); + return; + } + + var workspace = block.workspace; + var text = xml.textContent; + if (field instanceof Blockly.FieldVariable) { + Blockly.Xml.domToFieldVariable_(workspace, xml, text, field); + } else { + field.setValue(text); + } +}; + /** * Remove any 'next' block (statements in a stack). * @param {!Element} xmlBlock XML block element. diff --git a/core/zoom_controls.js b/core/zoom_controls.js index 143680658..8d8b085d4 100644 --- a/core/zoom_controls.js +++ b/core/zoom_controls.js @@ -125,10 +125,12 @@ Blockly.ZoomControls.prototype.createDom = function() { {'width': 32, 'height': 32, 'y': 77}, clip); var zoomoutSvg = Blockly.utils.createSvgElement('image', - {'width': Blockly.SPRITE.width, - 'height': Blockly.SPRITE.height, 'x': -64, - 'y': -15, - 'clip-path': 'url(#blocklyZoomoutClipPath' + rnd + ')'}, + { + 'width': Blockly.SPRITE.width, + 'height': Blockly.SPRITE.height, 'x': -64, + 'y': -15, + 'clip-path': 'url(#blocklyZoomoutClipPath' + rnd + ')' + }, this.svgGroup_); zoomoutSvg.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', workspace.options.pathToMedia + Blockly.SPRITE.url); @@ -140,11 +142,13 @@ Blockly.ZoomControls.prototype.createDom = function() { {'width': 32, 'height': 32, 'y': 43}, clip); var zoominSvg = Blockly.utils.createSvgElement('image', - {'width': Blockly.SPRITE.width, - 'height': Blockly.SPRITE.height, - 'x': -32, - 'y': -49, - 'clip-path': 'url(#blocklyZoominClipPath' + rnd + ')'}, + { + 'width': Blockly.SPRITE.width, + 'height': Blockly.SPRITE.height, + 'x': -32, + 'y': -49, + 'clip-path': 'url(#blocklyZoominClipPath' + rnd + ')' + }, this.svgGroup_); zoominSvg.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', workspace.options.pathToMedia + Blockly.SPRITE.url); @@ -156,9 +160,11 @@ Blockly.ZoomControls.prototype.createDom = function() { {'width': 32, 'height': 32}, clip); var zoomresetSvg = Blockly.utils.createSvgElement('image', - {'width': Blockly.SPRITE.width, - 'height': Blockly.SPRITE.height, 'y': -92, - 'clip-path': 'url(#blocklyZoomresetClipPath' + rnd + ')'}, + { + 'width': Blockly.SPRITE.width, + 'height': Blockly.SPRITE.height, 'y': -92, + 'clip-path': 'url(#blocklyZoomresetClipPath' + rnd + ')' + }, this.svgGroup_); zoomresetSvg.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', workspace.options.pathToMedia + Blockly.SPRITE.url); diff --git a/dart_compressed.js b/dart_compressed.js index 8a788c2f0..e41e1cc0f 100644 --- a/dart_compressed.js +++ b/dart_compressed.js @@ -5,7 +5,8 @@ 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.getAllVariables();if(a.length){for(var c=0;c>= ... +Blockly.JavaScript.ORDER_ASSIGNMENT = 16; // = += -= **= *= /= %= <<= >>= ... +Blockly.JavaScript.ORDER_YIELD = 16.5; // yield Blockly.JavaScript.ORDER_COMMA = 17; // , Blockly.JavaScript.ORDER_NONE = 99; // (...) @@ -152,13 +155,25 @@ Blockly.JavaScript.init = function(workspace) { Blockly.JavaScript.variableDB_.reset(); } + Blockly.JavaScript.variableDB_.setVariableMap(workspace.getVariableMap()); + var defvars = []; - var variables = workspace.getAllVariables(); - if (variables.length) { - for (var i = 0; i < variables.length; i++) { - defvars[i] = Blockly.JavaScript.variableDB_.getName(variables[i].name, - Blockly.Variables.NAME_TYPE); - } + // Add developer variables (not created or named by the user). + var devVarList = Blockly.Variables.allDeveloperVariables(workspace); + for (var i = 0; i < devVarList.length; i++) { + defvars.push(Blockly.JavaScript.variableDB_.getName(devVarList[i], + Blockly.Names.DEVELOPER_VARIABLE_TYPE)); + } + + // Add user variables, but only ones that are being used. + var variables = Blockly.Variables.allUsedVarModels(workspace); + for (var i = 0; i < variables.length; i++) { + defvars.push(Blockly.JavaScript.variableDB_.getName(variables[i].getId(), + Blockly.Variables.NAME_TYPE)); + } + + // Declare all of the variables. + if (defvars.length) { Blockly.JavaScript.definitions_['variables'] = 'var ' + defvars.join(', ') + ';'; } diff --git a/generators/javascript/math.js b/generators/javascript/math.js index a31b94392..0bd265af2 100644 --- a/generators/javascript/math.js +++ b/generators/javascript/math.js @@ -32,7 +32,9 @@ goog.require('Blockly.JavaScript'); Blockly.JavaScript['math_number'] = function(block) { // Numeric value. var code = parseFloat(block.getFieldValue('NUM')); - return [code, Blockly.JavaScript.ORDER_ATOMIC]; + var order = code >= 0 ? Blockly.JavaScript.ORDER_ATOMIC : + Blockly.JavaScript.ORDER_UNARY_NEGATION; + return [code, order]; }; Blockly.JavaScript['math_arithmetic'] = function(block) { diff --git a/generators/javascript/variables_dynamic.js b/generators/javascript/variables_dynamic.js new file mode 100644 index 000000000..fd3297b8d --- /dev/null +++ b/generators/javascript/variables_dynamic.js @@ -0,0 +1,37 @@ +/** + * @license + * Visual Blocks Language + * + * Copyright 2018 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 Generating JavaScript for dynamic variable blocks. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.JavaScript.variablesDynamic'); + +goog.require('Blockly.JavaScript'); +goog.require('Blockly.JavaScript.variables'); + + +// JavaScript is dynamically typed. +Blockly.JavaScript['variables_get_dynamic'] = + Blockly.JavaScript['variables_get']; +Blockly.JavaScript['variables_set_dynamic'] = + Blockly.JavaScript['variables_set']; diff --git a/generators/lua.js b/generators/lua.js index 1118a31e4..b7f8d5c90 100644 --- a/generators/lua.js +++ b/generators/lua.js @@ -109,6 +109,7 @@ Blockly.Lua.init = function(workspace) { } else { Blockly.Lua.variableDB_.reset(); } + Blockly.Lua.variableDB_.setVariableMap(workspace.getVariableMap()); }; /** diff --git a/generators/lua/variables_dynamic.js b/generators/lua/variables_dynamic.js new file mode 100644 index 000000000..60b07ef5e --- /dev/null +++ b/generators/lua/variables_dynamic.js @@ -0,0 +1,35 @@ +/** + * @license + * Visual Blocks Language + * + * Copyright 2018 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 Generating Lua for dynamic variable blocks. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.Lua.variablesDynamic'); + +goog.require('Blockly.Lua'); +goog.require('Blockly.Lua.variables'); + + +// Lua is dynamically typed. +Blockly.Lua['variables_get_dynamic'] = Blockly.Lua['variables_get']; +Blockly.Lua['variables_set_dynamic'] = Blockly.Lua['variables_set']; diff --git a/generators/php.js b/generators/php.js index 2c65a6809..cd53662dc 100644 --- a/generators/php.js +++ b/generators/php.js @@ -149,14 +149,24 @@ Blockly.PHP.init = function(workspace) { Blockly.PHP.variableDB_.reset(); } + Blockly.PHP.variableDB_.setVariableMap(workspace.getVariableMap()); + var defvars = []; - var varName; - var variables = Blockly.Variables.allVariables(workspace); - for (var i = 0, variable; variable = variables[i]; i++) { - varName = variable.name; - defvars[i] = Blockly.PHP.variableDB_.getName(varName, - Blockly.Variables.NAME_TYPE) + ';'; + // Add developer variables (not created or named by the user). + var devVarList = Blockly.Variables.allDeveloperVariables(workspace); + for (var i = 0; i < devVarList.length; i++) { + defvars.push(Blockly.PHP.variableDB_.getName(devVarList[i], + Blockly.Names.DEVELOPER_VARIABLE_TYPE) + ';'); } + + // Add user variables, but only ones that are being used. + var variables = Blockly.Variables.allUsedVarModels(workspace); + for (var i = 0, variable; variable = variables[i]; i++) { + defvars.push(Blockly.PHP.variableDB_.getName(variable.getId(), + Blockly.Variables.NAME_TYPE) + ';'); + } + + // Declare all of the variables. Blockly.PHP.definitions_['variables'] = defvars.join('\n'); }; diff --git a/generators/php/math.js b/generators/php/math.js index 7789ba8fe..ef9419603 100644 --- a/generators/php/math.js +++ b/generators/php/math.js @@ -32,12 +32,14 @@ goog.require('Blockly.PHP'); Blockly.PHP['math_number'] = function(block) { // Numeric value. var code = parseFloat(block.getFieldValue('NUM')); + var order = code >= 0 ? Blockly.PHP.ORDER_ATOMIC : + Blockly.PHP.ORDER_UNARY_NEGATION; if (code == Infinity) { code = 'INF'; } else if (code == -Infinity) { code = '-INF'; } - return [code, Blockly.PHP.ORDER_ATOMIC]; + return [code, order]; }; Blockly.PHP['math_arithmetic'] = function(block) { diff --git a/generators/php/procedures.js b/generators/php/procedures.js index d2aafc3d8..c81eab344 100644 --- a/generators/php/procedures.js +++ b/generators/php/procedures.js @@ -35,7 +35,7 @@ Blockly.PHP['procedures_defreturn'] = function(block) { var globals = []; var varName; var workspace = block.workspace; - var variables = workspace.getAllVariables() || []; + var variables = Blockly.Variables.allUsedVarModels(workspace) || []; for (var i = 0, variable; variable = variables[i]; i++) { varName = variable.name; if (block.arguments_.indexOf(varName) == -1) { @@ -43,7 +43,14 @@ Blockly.PHP['procedures_defreturn'] = function(block) { Blockly.Variables.NAME_TYPE)); } } - globals = globals.length ? Blockly.PHP.INDENT + 'global ' + globals.join(', ') + ';\n' : ''; + // Add developer variables. + var devVarList = Blockly.Variables.allDeveloperVariables(workspace); + for (var i = 0; i < devVarList.length; i++) { + globals.push(Blockly.PHP.variableDB_.getName(devVarList[i], + Blockly.Names.DEVELOPER_VARIABLE_TYPE)); + } + globals = globals.length ? + Blockly.PHP.INDENT + 'global ' + globals.join(', ') + ';\n' : ''; var funcName = Blockly.PHP.variableDB_.getName( block.getFieldValue('NAME'), Blockly.Procedures.NAME_TYPE); @@ -51,8 +58,8 @@ Blockly.PHP['procedures_defreturn'] = function(block) { if (Blockly.PHP.STATEMENT_PREFIX) { var id = block.id.replace(/\$/g, '$$$$'); // Issue 251. branch = Blockly.PHP.prefixLines( - Blockly.PHP.STATEMENT_PREFIX.replace(/%1/g, - '\'' + id + '\''), Blockly.PHP.INDENT) + branch; + Blockly.PHP.STATEMENT_PREFIX.replace( + /%1/g, '\'' + id + '\''), Blockly.PHP.INDENT) + branch; } if (Blockly.PHP.INFINITE_LOOP_TRAP) { branch = Blockly.PHP.INFINITE_LOOP_TRAP.replace(/%1/g, diff --git a/generators/php/variables_dynamic.js b/generators/php/variables_dynamic.js new file mode 100644 index 000000000..9ccc41465 --- /dev/null +++ b/generators/php/variables_dynamic.js @@ -0,0 +1,35 @@ +/** + * @license + * Visual Blocks Language + * + * Copyright 2018 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 Generating PHP for dynamic variable blocks. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.PHP.variablesDynamic'); + +goog.require('Blockly.PHP'); +goog.require('Blockly.PHP.variables'); + + +// PHP is dynamically typed. +Blockly.PHP['variables_get_dynamic'] = Blockly.PHP['variables_get']; +Blockly.PHP['variables_set_dynamic'] = Blockly.PHP['variables_set']; diff --git a/generators/python.js b/generators/python.js index e81a04234..347a41c2f 100644 --- a/generators/python.js +++ b/generators/python.js @@ -160,12 +160,23 @@ Blockly.Python.init = function(workspace) { Blockly.Python.variableDB_.reset(); } + Blockly.Python.variableDB_.setVariableMap(workspace.getVariableMap()); + var defvars = []; - var variables = workspace.getAllVariables(); - for (var i = 0; i < variables.length; i++) { - defvars[i] = Blockly.Python.variableDB_.getName(variables[i].name, - Blockly.Variables.NAME_TYPE) + ' = None'; + // Add developer variables (not created or named by the user). + var devVarList = Blockly.Variables.allDeveloperVariables(workspace); + for (var i = 0; i < devVarList.length; i++) { + defvars.push(Blockly.Python.variableDB_.getName(devVarList[i], + Blockly.Names.DEVELOPER_VARIABLE_TYPE) + ' = None'); } + + // Add user variables, but only ones that are being used. + var variables = Blockly.Variables.allUsedVarModels(workspace); + for (var i = 0; i < variables.length; i++) { + defvars.push(Blockly.Python.variableDB_.getName(variables[i].getId(), + Blockly.Variables.NAME_TYPE) + ' = None'); + } + Blockly.Python.definitions_['variables'] = defvars.join('\n'); }; diff --git a/generators/python/procedures.js b/generators/python/procedures.js index 0670f9d64..520b3f589 100644 --- a/generators/python/procedures.js +++ b/generators/python/procedures.js @@ -36,7 +36,7 @@ Blockly.Python['procedures_defreturn'] = function(block) { var globals = []; var varName; var workspace = block.workspace; - var variables = workspace.getAllVariables() || []; + var variables = Blockly.Variables.allUsedVarModels(workspace) || []; for (var i = 0, variable; variable = variables[i]; i++) { varName = variable.name; if (block.arguments_.indexOf(varName) == -1) { @@ -44,15 +44,23 @@ Blockly.Python['procedures_defreturn'] = function(block) { Blockly.Variables.NAME_TYPE)); } } - globals = globals.length ? Blockly.Python.INDENT + 'global ' + globals.join(', ') + '\n' : ''; - var funcName = Blockly.Python.variableDB_.getName(block.getFieldValue('NAME'), - Blockly.Procedures.NAME_TYPE); + // Add developer variables. + var devVarList = Blockly.Variables.allDeveloperVariables(workspace); + for (var i = 0; i < devVarList.length; i++) { + globals.push(Blockly.Python.variableDB_.getName(devVarList[i], + Blockly.Names.DEVELOPER_VARIABLE_TYPE)); + } + + globals = globals.length ? + Blockly.Python.INDENT + 'global ' + globals.join(', ') + '\n' : ''; + var funcName = Blockly.Python.variableDB_.getName( + block.getFieldValue('NAME'), Blockly.Procedures.NAME_TYPE); var branch = Blockly.Python.statementToCode(block, 'STACK'); if (Blockly.Python.STATEMENT_PREFIX) { var id = block.id.replace(/\$/g, '$$$$'); // Issue 251. branch = Blockly.Python.prefixLines( - Blockly.Python.STATEMENT_PREFIX.replace(/%1/g, - '\'' + id + '\''), Blockly.Python.INDENT) + branch; + Blockly.Python.STATEMENT_PREFIX.replace( + /%1/g, '\'' + id + '\''), Blockly.Python.INDENT) + branch; } if (Blockly.Python.INFINITE_LOOP_TRAP) { branch = Blockly.Python.INFINITE_LOOP_TRAP.replace(/%1/g, diff --git a/generators/python/variables_dynamic.js b/generators/python/variables_dynamic.js new file mode 100644 index 000000000..928421aee --- /dev/null +++ b/generators/python/variables_dynamic.js @@ -0,0 +1,35 @@ +/** + * @license + * Visual Blocks Language + * + * Copyright 2018 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 Generating Python for dynamic variable blocks. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.Python.variablesDynamic'); + +goog.require('Blockly.Python'); +goog.require('Blockly.Python.variables'); + + +// Python is dynamically typed. +Blockly.Python['variables_get_dynamic'] = Blockly.Python['variables_get']; +Blockly.Python['variables_set_dynamic'] = Blockly.Python['variables_set']; diff --git a/javascript_compressed.js b/javascript_compressed.js index 027f2df19..dc5c32694 100644 --- a/javascript_compressed.js +++ b/javascript_compressed.js @@ -4,12 +4,13 @@ Blockly.JavaScript=new Blockly.Generator("JavaScript");Blockly.JavaScript.addReservedWords("Blockly,break,case,catch,continue,debugger,default,delete,do,else,finally,for,function,if,in,instanceof,new,return,switch,this,throw,try,typeof,var,void,while,with,class,enum,export,extends,import,super,implements,interface,let,package,private,protected,public,static,yield,const,null,true,false,Array,ArrayBuffer,Boolean,Date,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,Error,eval,EvalError,Float32Array,Float64Array,Function,Infinity,Int16Array,Int32Array,Int8Array,isFinite,isNaN,Iterator,JSON,Math,NaN,Number,Object,parseFloat,parseInt,RangeError,ReferenceError,RegExp,StopIteration,String,SyntaxError,TypeError,Uint16Array,Uint32Array,Uint8Array,Uint8ClampedArray,undefined,uneval,URIError,applicationCache,closed,Components,content,_content,controllers,crypto,defaultStatus,dialogArguments,directories,document,frameElement,frames,fullScreen,globalStorage,history,innerHeight,innerWidth,length,location,locationbar,localStorage,menubar,messageManager,mozAnimationStartTime,mozInnerScreenX,mozInnerScreenY,mozPaintCount,name,navigator,opener,outerHeight,outerWidth,pageXOffset,pageYOffset,parent,performance,personalbar,pkcs11,returnValue,screen,screenX,screenY,scrollbars,scrollMaxX,scrollMaxY,scrollX,scrollY,self,sessionStorage,sidebar,status,statusbar,toolbar,top,URL,window,addEventListener,alert,atob,back,blur,btoa,captureEvents,clearImmediate,clearInterval,clearTimeout,close,confirm,disableExternalCapture,dispatchEvent,dump,enableExternalCapture,escape,find,focus,forward,GeckoActiveXObject,getAttention,getAttentionWithCycleCount,getComputedStyle,getSelection,home,matchMedia,maximize,minimize,moveBy,moveTo,mozRequestAnimationFrame,open,openDialog,postMessage,print,prompt,QueryInterface,releaseEvents,removeEventListener,resizeBy,resizeTo,restore,routeEvent,scroll,scrollBy,scrollByLines,scrollByPages,scrollTo,setCursor,setImmediate,setInterval,setResizable,setTimeout,showModalDialog,sizeToContent,stop,unescape,updateCommands,XPCNativeWrapper,XPCSafeJSObjectWrapper,onabort,onbeforeunload,onblur,onchange,onclick,onclose,oncontextmenu,ondevicemotion,ondeviceorientation,ondragdrop,onerror,onfocus,onhashchange,onkeydown,onkeypress,onkeyup,onload,onmousedown,onmousemove,onmouseout,onmouseover,onmouseup,onmozbeforepaint,onpaint,onpopstate,onreset,onresize,onscroll,onselect,onsubmit,onunload,onpageshow,onpagehide,Image,Option,Worker,Event,Range,File,FileReader,Blob,BlobBuilder,Attr,CDATASection,CharacterData,Comment,console,DocumentFragment,DocumentType,DomConfiguration,DOMError,DOMErrorHandler,DOMException,DOMImplementation,DOMImplementationList,DOMImplementationRegistry,DOMImplementationSource,DOMLocator,DOMObject,DOMString,DOMStringList,DOMTimeStamp,DOMUserData,Entity,EntityReference,MediaQueryList,MediaQueryListListener,NameList,NamedNodeMap,Node,NodeFilter,NodeIterator,NodeList,Notation,Plugin,PluginArray,ProcessingInstruction,SharedWorker,Text,TimeRanges,Treewalker,TypeInfo,UserDataHandler,Worker,WorkerGlobalScope,HTMLDocument,HTMLElement,HTMLAnchorElement,HTMLAppletElement,HTMLAudioElement,HTMLAreaElement,HTMLBaseElement,HTMLBaseFontElement,HTMLBodyElement,HTMLBRElement,HTMLButtonElement,HTMLCanvasElement,HTMLDirectoryElement,HTMLDivElement,HTMLDListElement,HTMLEmbedElement,HTMLFieldSetElement,HTMLFontElement,HTMLFormElement,HTMLFrameElement,HTMLFrameSetElement,HTMLHeadElement,HTMLHeadingElement,HTMLHtmlElement,HTMLHRElement,HTMLIFrameElement,HTMLImageElement,HTMLInputElement,HTMLKeygenElement,HTMLLabelElement,HTMLLIElement,HTMLLinkElement,HTMLMapElement,HTMLMenuElement,HTMLMetaElement,HTMLModElement,HTMLObjectElement,HTMLOListElement,HTMLOptGroupElement,HTMLOptionElement,HTMLOutputElement,HTMLParagraphElement,HTMLParamElement,HTMLPreElement,HTMLQuoteElement,HTMLScriptElement,HTMLSelectElement,HTMLSourceElement,HTMLSpanElement,HTMLStyleElement,HTMLTableElement,HTMLTableCaptionElement,HTMLTableCellElement,HTMLTableDataCellElement,HTMLTableHeaderCellElement,HTMLTableColElement,HTMLTableRowElement,HTMLTableSectionElement,HTMLTextAreaElement,HTMLTimeElement,HTMLTitleElement,HTMLTrackElement,HTMLUListElement,HTMLUnknownElement,HTMLVideoElement,HTMLCanvasElement,CanvasRenderingContext2D,CanvasGradient,CanvasPattern,TextMetrics,ImageData,CanvasPixelArray,HTMLAudioElement,HTMLVideoElement,NotifyAudioAvailableEvent,HTMLCollection,HTMLAllCollection,HTMLFormControlsCollection,HTMLOptionsCollection,HTMLPropertiesCollection,DOMTokenList,DOMSettableTokenList,DOMStringMap,RadioNodeList,SVGDocument,SVGElement,SVGAElement,SVGAltGlyphElement,SVGAltGlyphDefElement,SVGAltGlyphItemElement,SVGAnimationElement,SVGAnimateElement,SVGAnimateColorElement,SVGAnimateMotionElement,SVGAnimateTransformElement,SVGSetElement,SVGCircleElement,SVGClipPathElement,SVGColorProfileElement,SVGCursorElement,SVGDefsElement,SVGDescElement,SVGEllipseElement,SVGFilterElement,SVGFilterPrimitiveStandardAttributes,SVGFEBlendElement,SVGFEColorMatrixElement,SVGFEComponentTransferElement,SVGFECompositeElement,SVGFEConvolveMatrixElement,SVGFEDiffuseLightingElement,SVGFEDisplacementMapElement,SVGFEDistantLightElement,SVGFEFloodElement,SVGFEGaussianBlurElement,SVGFEImageElement,SVGFEMergeElement,SVGFEMergeNodeElement,SVGFEMorphologyElement,SVGFEOffsetElement,SVGFEPointLightElement,SVGFESpecularLightingElement,SVGFESpotLightElement,SVGFETileElement,SVGFETurbulenceElement,SVGComponentTransferFunctionElement,SVGFEFuncRElement,SVGFEFuncGElement,SVGFEFuncBElement,SVGFEFuncAElement,SVGFontElement,SVGFontFaceElement,SVGFontFaceFormatElement,SVGFontFaceNameElement,SVGFontFaceSrcElement,SVGFontFaceUriElement,SVGForeignObjectElement,SVGGElement,SVGGlyphElement,SVGGlyphRefElement,SVGGradientElement,SVGLinearGradientElement,SVGRadialGradientElement,SVGHKernElement,SVGImageElement,SVGLineElement,SVGMarkerElement,SVGMaskElement,SVGMetadataElement,SVGMissingGlyphElement,SVGMPathElement,SVGPathElement,SVGPatternElement,SVGPolylineElement,SVGPolygonElement,SVGRectElement,SVGScriptElement,SVGStopElement,SVGStyleElement,SVGSVGElement,SVGSwitchElement,SVGSymbolElement,SVGTextElement,SVGTextPathElement,SVGTitleElement,SVGTRefElement,SVGTSpanElement,SVGUseElement,SVGViewElement,SVGVKernElement,SVGAngle,SVGColor,SVGICCColor,SVGElementInstance,SVGElementInstanceList,SVGLength,SVGLengthList,SVGMatrix,SVGNumber,SVGNumberList,SVGPaint,SVGPoint,SVGPointList,SVGPreserveAspectRatio,SVGRect,SVGStringList,SVGTransform,SVGTransformList,SVGAnimatedAngle,SVGAnimatedBoolean,SVGAnimatedEnumeration,SVGAnimatedInteger,SVGAnimatedLength,SVGAnimatedLengthList,SVGAnimatedNumber,SVGAnimatedNumberList,SVGAnimatedPreserveAspectRatio,SVGAnimatedRect,SVGAnimatedString,SVGAnimatedTransformList,SVGPathSegList,SVGPathSeg,SVGPathSegArcAbs,SVGPathSegArcRel,SVGPathSegClosePath,SVGPathSegCurvetoCubicAbs,SVGPathSegCurvetoCubicRel,SVGPathSegCurvetoCubicSmoothAbs,SVGPathSegCurvetoCubicSmoothRel,SVGPathSegCurvetoQuadraticAbs,SVGPathSegCurvetoQuadraticRel,SVGPathSegCurvetoQuadraticSmoothAbs,SVGPathSegCurvetoQuadraticSmoothRel,SVGPathSegLinetoAbs,SVGPathSegLinetoHorizontalAbs,SVGPathSegLinetoHorizontalRel,SVGPathSegLinetoRel,SVGPathSegLinetoVerticalAbs,SVGPathSegLinetoVerticalRel,SVGPathSegMovetoAbs,SVGPathSegMovetoRel,ElementTimeControl,TimeEvent,SVGAnimatedPathData,SVGAnimatedPoints,SVGColorProfileRule,SVGCSSRule,SVGExternalResourcesRequired,SVGFitToViewBox,SVGLangSpace,SVGLocatable,SVGRenderingIntent,SVGStylable,SVGTests,SVGTextContentElement,SVGTextPositioningElement,SVGTransformable,SVGUnitTypes,SVGURIReference,SVGViewSpec,SVGZoomAndPan"); Blockly.JavaScript.ORDER_ATOMIC=0;Blockly.JavaScript.ORDER_NEW=1.1;Blockly.JavaScript.ORDER_MEMBER=1.2;Blockly.JavaScript.ORDER_FUNCTION_CALL=2;Blockly.JavaScript.ORDER_INCREMENT=3;Blockly.JavaScript.ORDER_DECREMENT=3;Blockly.JavaScript.ORDER_BITWISE_NOT=4.1;Blockly.JavaScript.ORDER_UNARY_PLUS=4.2;Blockly.JavaScript.ORDER_UNARY_NEGATION=4.3;Blockly.JavaScript.ORDER_LOGICAL_NOT=4.4;Blockly.JavaScript.ORDER_TYPEOF=4.5;Blockly.JavaScript.ORDER_VOID=4.6;Blockly.JavaScript.ORDER_DELETE=4.7; -Blockly.JavaScript.ORDER_DIVISION=5.1;Blockly.JavaScript.ORDER_MULTIPLICATION=5.2;Blockly.JavaScript.ORDER_MODULUS=5.3;Blockly.JavaScript.ORDER_SUBTRACTION=6.1;Blockly.JavaScript.ORDER_ADDITION=6.2;Blockly.JavaScript.ORDER_BITWISE_SHIFT=7;Blockly.JavaScript.ORDER_RELATIONAL=8;Blockly.JavaScript.ORDER_IN=8;Blockly.JavaScript.ORDER_INSTANCEOF=8;Blockly.JavaScript.ORDER_EQUALITY=9;Blockly.JavaScript.ORDER_BITWISE_AND=10;Blockly.JavaScript.ORDER_BITWISE_XOR=11;Blockly.JavaScript.ORDER_BITWISE_OR=12; -Blockly.JavaScript.ORDER_LOGICAL_AND=13;Blockly.JavaScript.ORDER_LOGICAL_OR=14;Blockly.JavaScript.ORDER_CONDITIONAL=15;Blockly.JavaScript.ORDER_ASSIGNMENT=16;Blockly.JavaScript.ORDER_COMMA=17;Blockly.JavaScript.ORDER_NONE=99; +Blockly.JavaScript.ORDER_AWAIT=4.8;Blockly.JavaScript.ORDER_EXPONENTIATION=5;Blockly.JavaScript.ORDER_MULTIPLICATION=5.1;Blockly.JavaScript.ORDER_DIVISION=5.2;Blockly.JavaScript.ORDER_MODULUS=5.3;Blockly.JavaScript.ORDER_SUBTRACTION=6.1;Blockly.JavaScript.ORDER_ADDITION=6.2;Blockly.JavaScript.ORDER_BITWISE_SHIFT=7;Blockly.JavaScript.ORDER_RELATIONAL=8;Blockly.JavaScript.ORDER_IN=8;Blockly.JavaScript.ORDER_INSTANCEOF=8;Blockly.JavaScript.ORDER_EQUALITY=9;Blockly.JavaScript.ORDER_BITWISE_AND=10; +Blockly.JavaScript.ORDER_BITWISE_XOR=11;Blockly.JavaScript.ORDER_BITWISE_OR=12;Blockly.JavaScript.ORDER_LOGICAL_AND=13;Blockly.JavaScript.ORDER_LOGICAL_OR=14;Blockly.JavaScript.ORDER_CONDITIONAL=15;Blockly.JavaScript.ORDER_ASSIGNMENT=16;Blockly.JavaScript.ORDER_YIELD=16.5;Blockly.JavaScript.ORDER_COMMA=17;Blockly.JavaScript.ORDER_NONE=99; Blockly.JavaScript.ORDER_OVERRIDES=[[Blockly.JavaScript.ORDER_FUNCTION_CALL,Blockly.JavaScript.ORDER_MEMBER],[Blockly.JavaScript.ORDER_FUNCTION_CALL,Blockly.JavaScript.ORDER_FUNCTION_CALL],[Blockly.JavaScript.ORDER_MEMBER,Blockly.JavaScript.ORDER_MEMBER],[Blockly.JavaScript.ORDER_MEMBER,Blockly.JavaScript.ORDER_FUNCTION_CALL],[Blockly.JavaScript.ORDER_LOGICAL_NOT,Blockly.JavaScript.ORDER_LOGICAL_NOT],[Blockly.JavaScript.ORDER_MULTIPLICATION,Blockly.JavaScript.ORDER_MULTIPLICATION],[Blockly.JavaScript.ORDER_ADDITION, Blockly.JavaScript.ORDER_ADDITION],[Blockly.JavaScript.ORDER_LOGICAL_AND,Blockly.JavaScript.ORDER_LOGICAL_AND],[Blockly.JavaScript.ORDER_LOGICAL_OR,Blockly.JavaScript.ORDER_LOGICAL_OR]]; -Blockly.JavaScript.init=function(a){Blockly.JavaScript.definitions_=Object.create(null);Blockly.JavaScript.functionNames_=Object.create(null);Blockly.JavaScript.variableDB_?Blockly.JavaScript.variableDB_.reset():Blockly.JavaScript.variableDB_=new Blockly.Names(Blockly.JavaScript.RESERVED_WORDS_);var b=[];a=a.getAllVariables();if(a.length){for(var c=0;cc?Blockly.JavaScript.valueToCode(a,b,Blockly.JavaScript.ORDER_SUBTRACTION)||f:d?Blockly.JavaScript.valueToCode(a,b,Blockly.JavaScript.ORDER_UNARY_NEGATION)||f:Blockly.JavaScript.valueToCode(a,b,e)||f;if(Blockly.isNumber(a))a=parseFloat(a)+c, @@ -49,7 +50,7 @@ Blockly.JavaScript.controls_for=function(a){var b=Blockly.JavaScript.variableDB_ 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.JavaScript.variableDB_.getDistinctName(b+"_start",Blockly.Variables.NAME_TYPE),a+="var "+g+" = "+c+";\n"),c=d,d.match(/^\w+$/)||Blockly.isNumber(d)||(c=Blockly.JavaScript.variableDB_.getDistinctName(b+"_end",Blockly.Variables.NAME_TYPE), a+="var "+c+" = "+d+";\n"),d=Blockly.JavaScript.variableDB_.getDistinctName(b+"_inc",Blockly.Variables.NAME_TYPE),a+="var "+d+" = ",a=Blockly.isNumber(e)?a+(Math.abs(e)+";\n"):a+("Math.abs("+e+");\n"),a=a+("if ("+g+" > "+c+") {\n")+(Blockly.JavaScript.INDENT+d+" = -"+d+";\n"),a+="}\n",a+="for ("+b+" = "+g+"; "+d+" >= 0 ? "+b+" <= "+c+" : "+b+" >= "+c+"; "+b+" += "+d+") {\n"+f+"}\n";return a}; Blockly.JavaScript.controls_forEach=function(a){var b=Blockly.JavaScript.variableDB_.getName(a.getFieldValue("VAR"),Blockly.Variables.NAME_TYPE),c=Blockly.JavaScript.valueToCode(a,"LIST",Blockly.JavaScript.ORDER_ASSIGNMENT)||"[]",d=Blockly.JavaScript.statementToCode(a,"DO");d=Blockly.JavaScript.addLoopTrap(d,a.id);a="";var e=c;c.match(/^\w+$/)||(e=Blockly.JavaScript.variableDB_.getDistinctName(b+"_list",Blockly.Variables.NAME_TYPE),a+="var "+e+" = "+c+";\n");c=Blockly.JavaScript.variableDB_.getDistinctName(b+ -"_index",Blockly.Variables.NAME_TYPE);d=Blockly.JavaScript.INDENT+b+" = "+e+"["+c+"];\n"+d;return a+("for (var "+c+" in "+e+") {\n"+d+"}\n")};Blockly.JavaScript.controls_flow_statements=function(a){switch(a.getFieldValue("FLOW")){case "BREAK":return"break;\n";case "CONTINUE":return"continue;\n"}throw"Unknown flow statement.";};Blockly.JavaScript.math={};Blockly.JavaScript.math_number=function(a){return[parseFloat(a.getFieldValue("NUM")),Blockly.JavaScript.ORDER_ATOMIC]}; +"_index",Blockly.Variables.NAME_TYPE);d=Blockly.JavaScript.INDENT+b+" = "+e+"["+c+"];\n"+d;return a+("for (var "+c+" in "+e+") {\n"+d+"}\n")};Blockly.JavaScript.controls_flow_statements=function(a){switch(a.getFieldValue("FLOW")){case "BREAK":return"break;\n";case "CONTINUE":return"continue;\n"}throw"Unknown flow statement.";};Blockly.JavaScript.math={};Blockly.JavaScript.math_number=function(a){a=parseFloat(a.getFieldValue("NUM"));return[a,0<=a?Blockly.JavaScript.ORDER_ATOMIC:Blockly.JavaScript.ORDER_UNARY_NEGATION]}; Blockly.JavaScript.math_arithmetic=function(a){var b={ADD:[" + ",Blockly.JavaScript.ORDER_ADDITION],MINUS:[" - ",Blockly.JavaScript.ORDER_SUBTRACTION],MULTIPLY:[" * ",Blockly.JavaScript.ORDER_MULTIPLICATION],DIVIDE:[" / ",Blockly.JavaScript.ORDER_DIVISION],POWER:[null,Blockly.JavaScript.ORDER_COMMA]}[a.getFieldValue("OP")],c=b[0];b=b[1];var d=Blockly.JavaScript.valueToCode(a,"A",b)||"0";a=Blockly.JavaScript.valueToCode(a,"B",b)||"0";return c?[d+c+a,b]:["Math.pow("+d+", "+a+")",Blockly.JavaScript.ORDER_FUNCTION_CALL]}; Blockly.JavaScript.math_single=function(a){var b=a.getFieldValue("OP");if("NEG"==b)return a=Blockly.JavaScript.valueToCode(a,"NUM",Blockly.JavaScript.ORDER_UNARY_NEGATION)||"0","-"==a[0]&&(a=" "+a),["-"+a,Blockly.JavaScript.ORDER_UNARY_NEGATION];a="SIN"==b||"COS"==b||"TAN"==b?Blockly.JavaScript.valueToCode(a,"NUM",Blockly.JavaScript.ORDER_DIVISION)||"0":Blockly.JavaScript.valueToCode(a,"NUM",Blockly.JavaScript.ORDER_NONE)||"0";switch(b){case "ABS":var c="Math.abs("+a+")";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="Math.round("+a+")";break;case "ROUNDUP":c="Math.ceil("+a+")";break;case "ROUNDDOWN":c="Math.floor("+a+")";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.JavaScript.ORDER_FUNCTION_CALL];switch(b){case "LOG10":c="Math.log("+a+ @@ -90,4 +91,5 @@ Blockly.JavaScript.text_changeCase=function(a){var b={UPPERCASE:".toUpperCase()" Blockly.JavaScript.text_prompt_ext=function(a){var b="window.prompt("+(a.getField("TEXT")?Blockly.JavaScript.quote_(a.getFieldValue("TEXT")):Blockly.JavaScript.valueToCode(a,"TEXT",Blockly.JavaScript.ORDER_NONE)||"''")+")";"NUMBER"==a.getFieldValue("TYPE")&&(b="parseFloat("+b+")");return[b,Blockly.JavaScript.ORDER_FUNCTION_CALL]};Blockly.JavaScript.text_prompt=Blockly.JavaScript.text_prompt_ext; Blockly.JavaScript.text_count=function(a){var b=Blockly.JavaScript.valueToCode(a,"TEXT",Blockly.JavaScript.ORDER_MEMBER)||"''";a=Blockly.JavaScript.valueToCode(a,"SUB",Blockly.JavaScript.ORDER_NONE)||"''";return[Blockly.JavaScript.provideFunction_("textCount",["function "+Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_+"(haystack, needle) {"," if (needle.length === 0) {"," return haystack.length + 1;"," } else {"," return haystack.split(needle).length - 1;"," }","}"])+"("+b+", "+a+")",Blockly.JavaScript.ORDER_SUBTRACTION]}; Blockly.JavaScript.text_replace=function(a){var b=Blockly.JavaScript.valueToCode(a,"TEXT",Blockly.JavaScript.ORDER_MEMBER)||"''",c=Blockly.JavaScript.valueToCode(a,"FROM",Blockly.JavaScript.ORDER_NONE)||"''";a=Blockly.JavaScript.valueToCode(a,"TO",Blockly.JavaScript.ORDER_NONE)||"''";return[Blockly.JavaScript.provideFunction_("textReplace",["function "+Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_+"(haystack, needle, replacement) {",' needle = needle.replace(/([-()\\[\\]{}+?*.$\\^|,:#", - "lastupdated": "2017-11-13 14:55:14.026613", + "lastupdated": "2018-02-16 11:14:06.797856", "locale": "en", "messagedocumentation" : "qqq" }, @@ -29,9 +29,13 @@ "RENAME_VARIABLE": "Rename variable...", "RENAME_VARIABLE_TITLE": "Rename all '%1' variables to:", "NEW_VARIABLE": "Create variable...", + "NEW_STRING_VARIABLE": "Create string variable...", + "NEW_NUMBER_VARIABLE": "Create number variable...", + "NEW_COLOUR_VARIABLE": "Create colour variable...", + "NEW_VARIABLE_TYPE_TITLE": "New variable type:", "NEW_VARIABLE_TITLE": "New variable name:", "VARIABLE_ALREADY_EXISTS": "A variable named '%1' already exists.", - "VARIABLE_ALREADY_EXISTS_FOR_ANOTHER_TYPE": "A variable named '%1' already exists for another variable of type '%2'.", + "VARIABLE_ALREADY_EXISTS_FOR_ANOTHER_TYPE": "A variable named '%1' already exists for another type: '%2'.", "DELETE_VARIABLE_CONFIRMATION": "Delete %1 uses of the '%2' variable?", "CANNOT_DELETE_VARIABLE_PROCEDURE": "Can't delete the variable '%1' because it's part of the definition of the function '%2'", "DELETE_VARIABLE": "Delete the '%1' variable", diff --git a/msg/json/qqq.json b/msg/json/qqq.json index 71e70e37a..27627c19e 100644 --- a/msg/json/qqq.json +++ b/msg/json/qqq.json @@ -1,13 +1,4 @@ { - "@metadata": { - "authors": [ - "Espertus", - "Liuxinyu970226", - "Metalhead64", - "Robby", - "Shirayuki" - ] - }, "VARIABLES_DEFAULT_NAME": "default name - A simple, general default name for a variable, preferably short. For more context, see [[Translating:Blockly#infrequent_message_types]].\n{{Identical|Item}}", "TODAY": "button text - Button that sets a calendar to today's date.\n{{Identical|Today}}", "DUPLICATE_BLOCK": "context menu - Make a copy of the selected block (and any blocks it contains).\n{{Identical|Duplicate}}", @@ -32,51 +23,55 @@ "RENAME_VARIABLE": "dropdown choice - When the user clicks on a variable block, this is one of the dropdown menu choices. It is used to rename the current variable. See [https://github.com/google/blockly/wiki/Variables#dropdown-menu https://github.com/google/blockly/wiki/Variables#dropdown-menu].", "RENAME_VARIABLE_TITLE": "prompt - Prompts the user to enter the new name for the selected variable. See [https://github.com/google/blockly/wiki/Variables#dropdown-menu https://github.com/google/blockly/wiki/Variables#dropdown-menu].\n\nParameters:\n* %1 - the name of the variable to be renamed.", "NEW_VARIABLE": "button text - Text on the button used to launch the variable creation dialogue.", + "NEW_STRING_VARIABLE": "button text - Text on the button used to launch the variable creation dialogue.", + "NEW_NUMBER_VARIABLE": "button text - Text on the button used to launch the variable creation dialogue.", + "NEW_COLOUR_VARIABLE": "button text - Text on the button used to launch the variable creation dialogue.", + "NEW_VARIABLE_TYPE_TITLE": "prompt - Prompts the user to enter the type for a variable.", "NEW_VARIABLE_TITLE": "prompt - Prompts the user to enter the name for a new variable. See [https://github.com/google/blockly/wiki/Variables#dropdown-menu https://github.com/google/blockly/wiki/Variables#dropdown-menu].", "VARIABLE_ALREADY_EXISTS": "alert - Tells the user that the name they entered is already in use.", "VARIABLE_ALREADY_EXISTS_FOR_ANOTHER_TYPE": "alert - Tells the user that the name they entered is already in use for another type.", "DELETE_VARIABLE_CONFIRMATION": "confirm - Ask the user to confirm their deletion of multiple uses of a variable.", "CANNOT_DELETE_VARIABLE_PROCEDURE": "alert - Tell the user that they can't delete a variable because it's part of the definition of a function.", "DELETE_VARIABLE": "dropdown choice - Delete the currently selected variable.", - "COLOUR_PICKER_HELPURL": "url - Information about colour.", + "COLOUR_PICKER_HELPURL": "{{Optional}} url - Information about colour.", "COLOUR_PICKER_TOOLTIP": "tooltip - See [https://github.com/google/blockly/wiki/Colour#picking-a-colour-from-a-palette https://github.com/google/blockly/wiki/Colour#picking-a-colour-from-a-palette].", - "COLOUR_RANDOM_HELPURL": "url - A link that displays a random colour each time you visit it.", + "COLOUR_RANDOM_HELPURL": "{{Optional}} url - A link that displays a random colour each time you visit it.", "COLOUR_RANDOM_TITLE": "block text - Title of block that generates a colour at random.", "COLOUR_RANDOM_TOOLTIP": "tooltip - See [https://github.com/google/blockly/wiki/Colour#generating-a-random-colour https://github.com/google/blockly/wiki/Colour#generating-a-random-colour].", - "COLOUR_RGB_HELPURL": "url - A link for color codes with percentages (0-100%) for each component, instead of the more common 0-255, which may be more difficult for beginners.", + "COLOUR_RGB_HELPURL": "{{Optional}} url - A link for color codes with percentages (0-100%) for each component, instead of the more common 0-255, which may be more difficult for beginners.", "COLOUR_RGB_TITLE": "block text - Title of block for [https://github.com/google/blockly/wiki/Colour#creating-a-colour-from-red-green-and-blue-components https://github.com/google/blockly/wiki/Colour#creating-a-colour-from-red-green-and-blue-components].", "COLOUR_RGB_RED": "block input text - The amount of red (from 0 to 100) to use when [https://github.com/google/blockly/wiki/Colour#creating-a-colour-from-red-green-and-blue-components https://github.com/google/blockly/wiki/Colour#creating-a-colour-from-red-green-and-blue-components].\n{{Identical|Red}}", "COLOUR_RGB_GREEN": "block input text - The amount of green (from 0 to 100) to use when [https://github.com/google/blockly/wiki/Colour#creating-a-colour-from-red-green-and-blue-components https://github.com/google/blockly/wiki/Colour#creating-a-colour-from-red-green-and-blue-components].", "COLOUR_RGB_BLUE": "block input text - The amount of blue (from 0 to 100) to use when [https://github.com/google/blockly/wiki/Colour#creating-a-colour-from-red-green-and-blue-components https://github.com/google/blockly/wiki/Colour#creating-a-colour-from-red-green-and-blue-components].\n{{Identical|Blue}}", "COLOUR_RGB_TOOLTIP": "tooltip - See [https://github.com/google/blockly/wiki/Colour#creating-a-colour-from-red-green-and-blue-components https://github.com/google/blockly/wiki/Colour#creating-a-colour-from-red-green-and-blue-components].", - "COLOUR_BLEND_HELPURL": "url - A useful link that displays blending of two colors.", + "COLOUR_BLEND_HELPURL": "{{Optional}} url - A useful link that displays blending of two colors.", "COLOUR_BLEND_TITLE": "block text - A verb for blending two shades of paint.", "COLOUR_BLEND_COLOUR1": "block input text - The first of two colours to [https://github.com/google/blockly/wiki/Colour#blending-colours blend].", "COLOUR_BLEND_COLOUR2": "block input text - The second of two colours to [https://github.com/google/blockly/wiki/Colour#blending-colours blend].", "COLOUR_BLEND_RATIO": "block input text - The proportion of the [https://github.com/google/blockly/wiki/Colour#blending-colours blend] containing the first color; the remaining proportion is of the second colour. For example, if the first colour is red and the second color blue, a ratio of 1 would yield pure red, a ratio of .5 would yield purple (equal amounts of red and blue), and a ratio of 0 would yield pure blue.\n{{Identical|Ratio}}", "COLOUR_BLEND_TOOLTIP": "tooltip - See [https://github.com/google/blockly/wiki/Colour#blending-colours https://github.com/google/blockly/wiki/Colour#blending-colours].", - "CONTROLS_REPEAT_HELPURL": "url - Describes 'repeat loops' in computer programs; consider using the translation of the page [https://en.wikipedia.org/wiki/Control_flow http://en.wikipedia.org/wiki/Control_flow].", + "CONTROLS_REPEAT_HELPURL": "{{Optional}} url - Describes 'repeat loops' in computer programs; consider using the translation of the page [https://en.wikipedia.org/wiki/Control_flow https://en.wikipedia.org/wiki/Control_flow].", "CONTROLS_REPEAT_TITLE": "block input text - Title of [https://github.com/google/blockly/wiki/Loops#repeat repeat block].\n\nParameters:\n* %1 - the number of times the body of the loop should be repeated.", "CONTROLS_REPEAT_INPUT_DO": "block text - Preceding the blocks in the body of the loop. See [https://github.com/google/blockly/wiki/Loops https://github.com/google/blockly/wiki/Loops].\n{{Identical|Do}}", "CONTROLS_REPEAT_TOOLTIP": "tooltip - See [https://github.com/google/blockly/wiki/Loops#repeat https://github.com/google/blockly/wiki/Loops#repeat].", - "CONTROLS_WHILEUNTIL_HELPURL": "url - Describes 'while loops' in computer programs; consider using the translation of [https://en.wikipedia.org/wiki/While_loop https://en.wikipedia.org/wiki/While_loop], if present, or [https://en.wikipedia.org/wiki/Control_flow https://en.wikipedia.org/wiki/Control_flow].", + "CONTROLS_WHILEUNTIL_HELPURL": "{{Optional}} url - Describes 'while loops' in computer programs; consider using the translation of [https://en.wikipedia.org/wiki/While_loop https://en.wikipedia.org/wiki/While_loop], if present, or [https://en.wikipedia.org/wiki/Control_flow https://en.wikipedia.org/wiki/Control_flow].", "CONTROLS_WHILEUNTIL_OPERATOR_WHILE": "dropdown - Specifies that a loop should [https://github.com/google/blockly/wiki/Loops#repeat-while repeat while] the following condition is true.", "CONTROLS_WHILEUNTIL_OPERATOR_UNTIL": "dropdown - Specifies that a loop should [https://github.com/google/blockly/wiki/Loops#repeat-until repeat until] the following condition becomes true.", "CONTROLS_WHILEUNTIL_TOOLTIP_WHILE": "tooltip - See [https://github.com/google/blockly/wiki/Loops#repeat-while Loops#repeat-while https://github.com/google/blockly/wiki/Loops#repeat-while Loops#repeat-while].", "CONTROLS_WHILEUNTIL_TOOLTIP_UNTIL": "tooltip - See [https://github.com/google/blockly/wiki/Loops#repeat-until https://github.com/google/blockly/wiki/Loops#repeat-until].", - "CONTROLS_FOR_HELPURL": "url - Describes 'for loops' in computer programs. Consider using your language's translation of [https://en.wikipedia.org/wiki/For_loop https://en.wikipedia.org/wiki/For_loop], if present.", + "CONTROLS_FOR_HELPURL": "{{Optional}} url - Describes 'for loops' in computer programs. Consider using your language's translation of [https://en.wikipedia.org/wiki/For_loop https://en.wikipedia.org/wiki/For_loop], if present.", "CONTROLS_FOR_TOOLTIP": "tooltip - See [https://github.com/google/blockly/wiki/Loops#count-with https://github.com/google/blockly/wiki/Loops#count-with].\n\nParameters:\n* %1 - the name of the loop variable.", "CONTROLS_FOR_TITLE": "block text - Repeatedly counts a variable (%1) starting with a (usually lower) number in a range (%2), ending with a (usually higher) number in a range (%3), and counting the iterations by a number of steps (%4). As in [https://github.com/google/blockly/wiki/Loops#count-with https://github.com/google/blockly/wiki/Loops#count-with]. [[File:Blockly-count-with.png]]", - "CONTROLS_FOREACH_HELPURL": "url - Describes 'for-each loops' in computer programs. Consider using your language's translation of [https://en.wikipedia.org/wiki/Foreach https://en.wikipedia.org/wiki/Foreach] if present.", + "CONTROLS_FOREACH_HELPURL": "{{Optional}} url - Describes 'for-each loops' in computer programs. Consider using your language's translation of [https://en.wikipedia.org/wiki/Foreach https://en.wikipedia.org/wiki/Foreach] if present.", "CONTROLS_FOREACH_TITLE": "block text - Title of [https://github.com/google/blockly/wiki/Loops#for-each for each block]. Sequentially assigns every item in array %2 to the valiable %1.", "CONTROLS_FOREACH_TOOLTIP": "block text - Description of [https://github.com/google/blockly/wiki/Loops#for-each for each blocks].\n\nParameters:\n* %1 - the name of the loop variable.", - "CONTROLS_FLOW_STATEMENTS_HELPURL": "url - Describes control flow in computer programs. Consider using your language's translation of [https://en.wikipedia.org/wiki/Control_flow https://en.wikipedia.org/wiki/Control_flow], if it exists.", + "CONTROLS_FLOW_STATEMENTS_HELPURL": "{{Optional}} url - Describes control flow in computer programs. Consider using your language's translation of [https://en.wikipedia.org/wiki/Control_flow https://en.wikipedia.org/wiki/Control_flow], if it exists.", "CONTROLS_FLOW_STATEMENTS_OPERATOR_BREAK": "dropdown - The current loop should be exited. See [https://github.com/google/blockly/wiki/Loops#break https://github.com/google/blockly/wiki/Loops#break].", "CONTROLS_FLOW_STATEMENTS_OPERATOR_CONTINUE": "dropdown - The current iteration of the loop should be ended and the next should begin. See [https://github.com/google/blockly/wiki/Loops#continue-with-next-iteration https://github.com/google/blockly/wiki/Loops#continue-with-next-iteration].", "CONTROLS_FLOW_STATEMENTS_TOOLTIP_BREAK": "tooltip - See [https://github.com/google/blockly/wiki/Loops#break-out-of-loop https://github.com/google/blockly/wiki/Loops#break-out-of-loop].", "CONTROLS_FLOW_STATEMENTS_TOOLTIP_CONTINUE": "tooltip - See [https://github.com/google/blockly/wiki/Loops#continue-with-next-iteration https://github.com/google/blockly/wiki/Loops#continue-with-next-iteration].", "CONTROLS_FLOW_STATEMENTS_WARNING": "warning - The user has tried placing a block outside of a loop (for each, while, repeat, etc.), but this type of block may only be used within a loop. See [https://github.com/google/blockly/wiki/Loops#loop-termination-blocks https://github.com/google/blockly/wiki/Loops#loop-termination-blocks].", - "CONTROLS_IF_HELPURL": "url - Describes conditional statements (if-then-else) in computer programs. Consider using your language's translation of [https://en.wikipedia.org/wiki/If_else https://en.wikipedia.org/wiki/If_else], if present.", + "CONTROLS_IF_HELPURL": "{{Optional}} url - Describes conditional statements (if-then-else) in computer programs. Consider using your language's translation of [https://en.wikipedia.org/wiki/If_else https://en.wikipedia.org/wiki/If_else], if present.", "CONTROLS_IF_TOOLTIP_1": "tooltip - Describes [https://github.com/google/blockly/wiki/IfElse#if-blocks 'if' blocks]. Consider using your language's translation of [https://en.wikipedia.org/wiki/If_statement https://en.wikipedia.org/wiki/If_statement], if present.", "CONTROLS_IF_TOOLTIP_2": "tooltip - Describes [https://github.com/google/blockly/wiki/IfElse#if-else-blocks if-else blocks]. Consider using your language's translation of [https://en.wikipedia.org/wiki/If_statement https://en.wikipedia.org/wiki/If_statement], if present.", "CONTROLS_IF_TOOLTIP_3": "tooltip - Describes [https://github.com/google/blockly/wiki/IfElse#if-else-if-blocks if-else-if blocks]. Consider using your language's translation of [https://en.wikipedia.org/wiki/If_statement https://en.wikipedia.org/wiki/If_statement], if present.", @@ -100,53 +95,53 @@ "IOS_VARIABLES_DELETE_BUTTON": "button text - Text on a button inside a variable deletion dialogue, which will delete a variable when pressed.\n{{Identical|Delete}}", "IOS_VARIABLES_VARIABLE_NAME": "placeholder text - Placeholder text used inside a text input, where a variable name should be entered.", "IOS_VARIABLES_EMPTY_NAME_ERROR": "alert - Error message that is displayed when the user attempts to create a variable without a name.", - "LOGIC_COMPARE_HELPURL": "url - Information about comparisons.", + "LOGIC_COMPARE_HELPURL": "{{Optional}} url - Information about comparisons.", "LOGIC_COMPARE_TOOLTIP_EQ": "tooltip - Describes the equals (=) block.", "LOGIC_COMPARE_TOOLTIP_NEQ": "tooltip - Describes the not equals (≠) block.", "LOGIC_COMPARE_TOOLTIP_LT": "tooltip - Describes the less than (<) block.", "LOGIC_COMPARE_TOOLTIP_LTE": "tooltip - Describes the less than or equals (≤) block.", "LOGIC_COMPARE_TOOLTIP_GT": "tooltip - Describes the greater than (>) block.", "LOGIC_COMPARE_TOOLTIP_GTE": "tooltip - Describes the greater than or equals (≥) block.", - "LOGIC_OPERATION_HELPURL": "url - Information about the Boolean conjunction ('and') and disjunction ('or') operators. Consider using the translation of [https://en.wikipedia.org/wiki/Boolean_logic https://en.wikipedia.org/wiki/Boolean_logic], if it exists in your language.", + "LOGIC_OPERATION_HELPURL": "{{Optional}} url - Information about the Boolean conjunction ('and') and disjunction ('or') operators. Consider using the translation of [https://en.wikipedia.org/wiki/Boolean_logic https://en.wikipedia.org/wiki/Boolean_logic], if it exists in your language.", "LOGIC_OPERATION_TOOLTIP_AND": "tooltip - See [https://en.wikipedia.org/wiki/Logical_conjunction https://en.wikipedia.org/wiki/Logical_conjunction].", "LOGIC_OPERATION_AND": "block text - See [https://en.wikipedia.org/wiki/Logical_conjunction https://en.wikipedia.org/wiki/Logical_conjunction].\n{{Identical|And}}", "LOGIC_OPERATION_TOOLTIP_OR": "block text - See [https://en.wikipedia.org/wiki/Disjunction https://en.wikipedia.org/wiki/Disjunction].", "LOGIC_OPERATION_OR": "block text - See [https://en.wikipedia.org/wiki/Disjunction https://en.wikipedia.org/wiki/Disjunction].\n{{Identical|Or}}", - "LOGIC_NEGATE_HELPURL": "url - Information about logical negation. The translation of [https://en.wikipedia.org/wiki/Logical_negation https://en.wikipedia.org/wiki/Logical_negation] is recommended if it exists in the target language.", + "LOGIC_NEGATE_HELPURL": "{{Optional}} url - Information about logical negation. The translation of [https://en.wikipedia.org/wiki/Logical_negation https://en.wikipedia.org/wiki/Logical_negation] is recommended if it exists in the target language.", "LOGIC_NEGATE_TITLE": "block text - This is a unary operator that returns ''false'' when the input is ''true'', and ''true'' when the input is ''false''. \n\nParameters:\n* %1 - the input (which should be either the value 'true' or 'false')", "LOGIC_NEGATE_TOOLTIP": "tooltip - See [https://en.wikipedia.org/wiki/Logical_negation https://en.wikipedia.org/wiki/Logical_negation].", - "LOGIC_BOOLEAN_HELPURL": "url - Information about the logic values ''true'' and ''false''. Consider using the translation of [https://en.wikipedia.org/wiki/Truth_value https://en.wikipedia.org/wiki/Truth_value] if it exists in your language.", + "LOGIC_BOOLEAN_HELPURL": "{{Optional}} url - Information about the logic values ''true'' and ''false''. Consider using the translation of [https://en.wikipedia.org/wiki/Truth_value https://en.wikipedia.org/wiki/Truth_value] if it exists in your language.", "LOGIC_BOOLEAN_TRUE": "block text - The word for the [https://en.wikipedia.org/wiki/Truth_value logical value] ''true''.\n{{Identical|True}}", "LOGIC_BOOLEAN_FALSE": "block text - The word for the [https://en.wikipedia.org/wiki/Truth_value logical value] ''false''.\n{{Identical|False}}", "LOGIC_BOOLEAN_TOOLTIP": "tooltip - Indicates that the block returns either of the two possible [https://en.wikipedia.org/wiki/Truth_value logical values].", - "LOGIC_NULL_HELPURL": "url - Provide a link to the translation of [https://en.wikipedia.org/wiki/Nullable_type https://en.wikipedia.org/wiki/Nullable_type], if it exists in your language; otherwise, do not worry about translating this advanced concept.", + "LOGIC_NULL_HELPURL": "{{Optional}} url - Provide a link to the translation of [https://en.wikipedia.org/wiki/Nullable_type https://en.wikipedia.org/wiki/Nullable_type], if it exists in your language; otherwise, do not worry about translating this advanced concept.", "LOGIC_NULL": "block text - In computer languages, ''null'' is a special value that indicates that no value has been set. You may use your language's word for 'nothing' or 'invalid'.\n{{Identical|Null}}", "LOGIC_NULL_TOOLTIP": "tooltip - This should use the word from the previous message.", - "LOGIC_TERNARY_HELPURL": "url - Describes the programming language operator known as the ''ternary'' or ''conditional'' operator. It is recommended that you use the translation of [https://en.wikipedia.org/wiki/%3F: https://en.wikipedia.org/wiki/%3F:] if it exists.", + "LOGIC_TERNARY_HELPURL": "{{Optional}} url - Describes the programming language operator known as the ''ternary'' or ''conditional'' operator. It is recommended that you use the translation of [https://en.wikipedia.org/wiki/%3F: https://en.wikipedia.org/wiki/%3F:] if it exists.", "LOGIC_TERNARY_CONDITION": "block input text - Label for the input whose value determines which of the other two inputs is returned. In some programming languages, this is called a ''''predicate''''.", "LOGIC_TERNARY_IF_TRUE": "block input text - Indicates that the following input should be returned (used as output) if the test input is true. Remember to try to keep block text terse (short).", "LOGIC_TERNARY_IF_FALSE": "block input text - Indicates that the following input should be returned (used as output) if the test input is false.", "LOGIC_TERNARY_TOOLTIP": "tooltip - See [https://en.wikipedia.org/wiki/%3F: https://en.wikipedia.org/wiki/%3F:].", - "MATH_NUMBER_HELPURL": "url - Information about (real) numbers.", + "MATH_NUMBER_HELPURL": "{{Optional}} url - Information about (real) numbers.", "MATH_NUMBER_TOOLTIP": "tooltip - Any positive or negative number, not necessarily an integer.", - "MATH_ADDITION_SYMBOL": "{{optional}}\nmath - The symbol for the binary operation addition.", - "MATH_SUBTRACTION_SYMBOL": "{{optional}}\nmath - The symbol for the binary operation indicating that the right operand should be subtracted from the left operand.", - "MATH_DIVISION_SYMBOL": "{{optional}}\nmath - The binary operation indicating that the left operand should be divided by the right operand.", - "MATH_MULTIPLICATION_SYMBOL": "{{optional}}\nmath - The symbol for the binary operation multiplication.", - "MATH_POWER_SYMBOL": "{{optional}}\nmath - The symbol for the binary operation exponentiation. Specifically, if the value of the left operand is L and the value of the right operand (the exponent) is R, multiply L by itself R times. (Fractional and negative exponents are also legal.)", + "MATH_ADDITION_SYMBOL": "{{Optional}} math - The symbol for the binary operation addition.", + "MATH_SUBTRACTION_SYMBOL": "{{Optional}} math - The symbol for the binary operation indicating that the right operand should be subtracted from the left operand.", + "MATH_DIVISION_SYMBOL": "{{Optional}} math - The binary operation indicating that the left operand should be divided by the right operand.", + "MATH_MULTIPLICATION_SYMBOL": "{{Optional}} math - The symbol for the binary operation multiplication.", + "MATH_POWER_SYMBOL": "{{Optional}} math - The symbol for the binary operation exponentiation. Specifically, if the value of the left operand is L and the value of the right operand (the exponent) is R, multiply L by itself R times. (Fractional and negative exponents are also legal.)", "MATH_TRIG_SIN": "math - The short name of the trigonometric function [https://en.wikipedia.org/wiki/Trigonometric_functions#Sine.2C_cosine_and_tangent sine].", "MATH_TRIG_COS": "math - The short name of the trigonometric function [https://en.wikipedia.org/wiki/Trigonometric_functions#Sine.2C_cosine_and_tangent cosine].", "MATH_TRIG_TAN": "math - The short name of the trigonometric function [https://en.wikipedia.org/wiki/Trigonometric_functions#Sine.2C_cosine_and_tangent tangent].", "MATH_TRIG_ASIN": "math - The short name of the ''inverse of'' the trigonometric function [https://en.wikipedia.org/wiki/Trigonometric_functions#Sine.2C_cosine_and_tangent sine].", "MATH_TRIG_ACOS": "math - The short name of the ''inverse of'' the trigonometric function [https://en.wikipedia.org/wiki/Trigonometric_functions#Sine.2C_cosine_and_tangent cosine].", "MATH_TRIG_ATAN": "math - The short name of the ''inverse of'' the trigonometric function [https://en.wikipedia.org/wiki/Trigonometric_functions#Sine.2C_cosine_and_tangent tangent].", - "MATH_ARITHMETIC_HELPURL": "url - Information about addition, subtraction, multiplication, division, and exponentiation.", + "MATH_ARITHMETIC_HELPURL": "{{Optional}} url - Information about addition, subtraction, multiplication, division, and exponentiation.", "MATH_ARITHMETIC_TOOLTIP_ADD": "tooltip - See [https://en.wikipedia.org/wiki/Addition https://en.wikipedia.org/wiki/Addition].", "MATH_ARITHMETIC_TOOLTIP_MINUS": "tooltip - See [https://en.wikipedia.org/wiki/Subtraction https://en.wikipedia.org/wiki/Subtraction].", "MATH_ARITHMETIC_TOOLTIP_MULTIPLY": "tooltip - See [https://en.wikipedia.org/wiki/Multiplication https://en.wikipedia.org/wiki/Multiplication].", "MATH_ARITHMETIC_TOOLTIP_DIVIDE": "tooltip - See [https://en.wikipedia.org/wiki/Division_(mathematics) https://en.wikipedia.org/wiki/Division_(mathematics)].", "MATH_ARITHMETIC_TOOLTIP_POWER": "tooltip - See [https://en.wikipedia.org/wiki/Exponentiation https://en.wikipedia.org/wiki/Exponentiation].", - "MATH_SINGLE_HELPURL": "url - Information about the square root operation.", + "MATH_SINGLE_HELPURL": "{{Optional}} url - Information about the square root operation.", "MATH_SINGLE_OP_ROOT": "dropdown - This computes the positive [https://en.wikipedia.org/wiki/Square_root square root] of its input. For example, the square root of 16 is 4.", "MATH_SINGLE_TOOLTIP_ROOT": "tooltip - Please use the same term as in the previous message.", "MATH_SINGLE_OP_ABSOLUTE": "dropdown - This leaves positive numeric inputs changed and inverts negative inputs. For example, the absolute value of 5 is 5; the absolute value of -5 is also 5. For more information, see [https://en.wikipedia.org/wiki/Absolute_value https://en.wikipedia.org/wiki/Absolute_value].", @@ -156,14 +151,14 @@ "MATH_SINGLE_TOOLTIP_LOG10": "tooltip - Calculates the [https://en.wikipedia.org/wiki/Common_logarithm common logarithm] of its single numeric input.", "MATH_SINGLE_TOOLTIP_EXP": "tooltip - Multiplies [https://en.wikipedia.org/wiki/E_(mathematical_constant) e] by itself n times, where n is the single numeric input.", "MATH_SINGLE_TOOLTIP_POW10": "tooltip - Multiplies 10 by itself n times, where n is the single numeric input.", - "MATH_TRIG_HELPURL": "url - Information about the trigonometric functions sine, cosine, tangent, and their inverses (ideally using degrees, not radians).", + "MATH_TRIG_HELPURL": "{{Optional}} url - Information about the trigonometric functions sine, cosine, tangent, and their inverses (ideally using degrees, not radians).", "MATH_TRIG_TOOLTIP_SIN": "tooltip - Return the [https://en.wikipedia.org/wiki/Trigonometric_functions#Sine.2C_cosine_and_tangent sine] of an [https://en.wikipedia.org/wiki/Degree_(angle) angle in degrees], not radians.", "MATH_TRIG_TOOLTIP_COS": "tooltip - Return the [https://en.wikipedia.org/wiki/Trigonometric_functions#Sine.2C_cosine_and_tangent cosine] of an [https://en.wikipedia.org/wiki/Degree_(angle) angle in degrees], not radians.", "MATH_TRIG_TOOLTIP_TAN": "tooltip - Return the [https://en.wikipedia.org/wiki/Trigonometric_functions#Sine.2C_cosine_and_tangent tangent] of an [https://en.wikipedia.org/wiki/Degree_(angle) angle in degrees], not radians.", "MATH_TRIG_TOOLTIP_ASIN": "tooltip - The [https://en.wikipedia.org/wiki/Inverse_trigonometric_functions inverse] of the [https://en.wikipedia.org/wiki/Cosine#Sine.2C_cosine_and_tangent sine function], using [https://en.wikipedia.org/wiki/Degree_(angle) degrees], not radians.", "MATH_TRIG_TOOLTIP_ACOS": "tooltip - The [https://en.wikipedia.org/wiki/Inverse_trigonometric_functions inverse] of the [https://en.wikipedia.org/wiki/Cosine#Sine.2C_cosine_and_tangent cosine] function, using [https://en.wikipedia.org/wiki/Degree_(angle) degrees], not radians.", "MATH_TRIG_TOOLTIP_ATAN": "tooltip - The [https://en.wikipedia.org/wiki/Inverse_trigonometric_functions inverse] of the [https://en.wikipedia.org/wiki/Cosine#Sine.2C_cosine_and_tangent tangent] function, using [https://en.wikipedia.org/wiki/Degree_(angle) degrees], not radians.", - "MATH_CONSTANT_HELPURL": "url - Information about the mathematical constants Pi (π), e, the golden ratio (φ), √ 2, √ 1/2, and infinity (∞).", + "MATH_CONSTANT_HELPURL": "{{Optional}} url - Information about the mathematical constants Pi (π), e, the golden ratio (φ), √ 2, √ 1/2, and infinity (∞).", "MATH_CONSTANT_TOOLTIP": "tooltip - Provides the specified [https://en.wikipedia.org/wiki/Mathematical_constant mathematical constant].", "MATH_IS_EVEN": "dropdown - A number is '''even''' if it is a multiple of 2. For example, 4 is even (yielding true), but 3 is not (false).", "MATH_IS_ODD": "dropdown - A number is '''odd''' if it is not a multiple of 2. For example, 3 is odd (yielding true), but 4 is not (false). The opposite of 'odd' is 'even'.", @@ -173,15 +168,15 @@ "MATH_IS_NEGATIVE": "dropdown - A number is '''negative''' if it is less than 0. (0 is neither negative nor positive.)", "MATH_IS_DIVISIBLE_BY": "dropdown - A number x is divisible by y if y goes into x evenly. For example, 10 is divisible by 5, but 10 is not divisible by 3.", "MATH_IS_TOOLTIP": "tooltip - This block lets the user specify via a dropdown menu whether to check if the numeric input is even, odd, prime, whole, positive, negative, or divisible by a given value.", - "MATH_CHANGE_HELPURL": "url - Information about incrementing (increasing the value of) a variable. For other languages, just use the translation of the Wikipedia page about addition ([https://en.wikipedia.org/wiki/Addition https://en.wikipedia.org/wiki/Addition]).", + "MATH_CHANGE_HELPURL": "{{Optional}} url - Information about incrementing (increasing the value of) a variable. For other languages, just use the translation of the Wikipedia page about addition ([https://en.wikipedia.org/wiki/Addition https://en.wikipedia.org/wiki/Addition]).", "MATH_CHANGE_TITLE": "- As in: ''change'' [the value of variable] ''item'' ''by'' 1 (e.g., if the variable named 'item' had the value 5, change it to 6). %1 is a variable name. %2 is the amount of change.", "MATH_CHANGE_TOOLTIP": "tooltip - This updates the value of the variable by adding to it the following numeric input.\n\nParameters:\n* %1 - the name of the variable whose value should be increased.", - "MATH_ROUND_HELPURL": "url - Information about how numbers are rounded to the nearest integer", + "MATH_ROUND_HELPURL": "{{Optional}} url - Information about how numbers are rounded to the nearest integer", "MATH_ROUND_TOOLTIP": "tooltip - See [https://en.wikipedia.org/wiki/Rounding https://en.wikipedia.org/wiki/Rounding].", "MATH_ROUND_OPERATOR_ROUND": "dropdown - This rounds its input to the nearest whole number. For example, 3.4 is rounded to 3.", "MATH_ROUND_OPERATOR_ROUNDUP": "dropdown - This rounds its input up to the nearest whole number. For example, if the input was 2.2, the result would be 3.", "MATH_ROUND_OPERATOR_ROUNDDOWN": "dropdown - This rounds its input down to the nearest whole number. For example, if the input was 3.8, the result would be 3.", - "MATH_ONLIST_HELPURL": "url - Information about applying a function to a list of numbers. (We were unable to find such information in English. Feel free to skip this and any other URLs that are difficult.)", + "MATH_ONLIST_HELPURL": "{{Optional}} url - Information about applying a function to a list of numbers. (We were unable to find such information in English. Feel free to skip this and any other URLs that are difficult.)", "MATH_ONLIST_OPERATOR_SUM": "dropdown - This computes the sum of the numeric elements in the list. For example, the sum of the list {1, 4} is 5.", "MATH_ONLIST_TOOLTIP_SUM": "tooltip - Please use the same term for 'sum' as in the previous message.", "MATH_ONLIST_OPERATOR_MIN": "dropdown - This finds the smallest (minimum) number in a list. For example, the smallest number in the list [-5, 0, 3] is -5.", @@ -198,41 +193,41 @@ "MATH_ONLIST_TOOLTIP_STD_DEV": "tooltip - See [https://en.wikipedia.org/wiki/Standard_deviation https://en.wikipedia.org/wiki/Standard_deviation] for more information.", "MATH_ONLIST_OPERATOR_RANDOM": "dropdown - This choose an element at random from a list. Each element is chosen with equal probability.", "MATH_ONLIST_TOOLTIP_RANDOM": "tooltip - Please use same term for 'random' as in previous entry.", - "MATH_MODULO_HELPURL": "url - information about the modulo (remainder) operation.", + "MATH_MODULO_HELPURL": "{{Optional}} url - information about the modulo (remainder) operation.", "MATH_MODULO_TITLE": "block text - Title of block providing the remainder when dividing the first numerical input by the second. For example, the remainder of 10 divided by 3 is 1.\n\nParameters:\n* %1 - the dividend (10, in our example)\n* %2 - the divisor (3 in our example).", "MATH_MODULO_TOOLTIP": "tooltip - For example, the remainder of 10 divided by 3 is 1.", - "MATH_CONSTRAIN_HELPURL": "url - Information about constraining a numeric value to be in a specific range. (The English URL is not ideal. Recall that translating URLs is the lowest priority.)", + "MATH_CONSTRAIN_HELPURL": "{{Optional}} url - Information about constraining a numeric value to be in a specific range. (The English URL is not ideal. Recall that translating URLs is the lowest priority.)", "MATH_CONSTRAIN_TITLE": "block text - The title of the block that '''constrain'''s (forces) a number to be in a given range. For example, if the number 150 is constrained to be between 5 and 100, the result will be 100. \n\nParameters:\n* %1 - the value to constrain (e.g., 150)\n* %2 - the minimum value (e.g., 5)\n* %3 - the maximum value (e.g., 100).", "MATH_CONSTRAIN_TOOLTIP": "tooltip - This compares a number ''x'' to a low value ''L'' and a high value ''H''. If ''x'' is less then ''L'', the result is ''L''. If ''x'' is greater than ''H'', the result is ''H''. Otherwise, the result is ''x''.", - "MATH_RANDOM_INT_HELPURL": "url - Information about how computers generate random numbers.", + "MATH_RANDOM_INT_HELPURL": "{{Optional}} url - Information about how computers generate random numbers.", "MATH_RANDOM_INT_TITLE": "block text - The title of the block that generates a random integer (whole number) in the specified range. For example, if the range is from 5 to 7, this returns 5, 6, or 7 with equal likelihood. %1 is a placeholder for the lower number, %2 is the placeholder for the larger number.", "MATH_RANDOM_INT_TOOLTIP": "tooltip - Return a random integer between two values specified as inputs. For example, if one input was 7 and another 9, any of the numbers 7, 8, or 9 could be produced.", - "MATH_RANDOM_FLOAT_HELPURL": "url - Information about how computers generate random numbers (specifically, numbers in the range from 0 to just below 1).", + "MATH_RANDOM_FLOAT_HELPURL": "{{Optional}} url - Information about how computers generate random numbers (specifically, numbers in the range from 0 to just below 1).", "MATH_RANDOM_FLOAT_TITLE_RANDOM": "block text - The title of the block that generates a random number greater than or equal to 0 and less than 1.", "MATH_RANDOM_FLOAT_TOOLTIP": "tooltip - Return a random fraction between 0 and 1. The value may be equal to 0 but must be less than 1.", - "TEXT_TEXT_HELPURL": "url - Information about how computers represent text (sometimes referred to as ''string''s).", + "TEXT_TEXT_HELPURL": "{{Optional}} url - Information about how computers represent text (sometimes referred to as ''string''s).", "TEXT_TEXT_TOOLTIP": "tooltip - See [https://github.com/google/blockly/wiki/Text https://github.com/google/blockly/wiki/Text].", - "TEXT_JOIN_HELPURL": "url - Information on concatenating/appending pieces of text.", + "TEXT_JOIN_HELPURL": "{{Optional}} url - Information on concatenating/appending pieces of text.", "TEXT_JOIN_TITLE_CREATEWITH": "block text - See [https://github.com/google/blockly/wiki/Text#text-creation https://github.com/google/blockly/wiki/Text#text-creation].", "TEXT_JOIN_TOOLTIP": "tooltip - See [https://github.com/google/blockly/wiki/Text#text-creation create text with] for more information.", "TEXT_CREATE_JOIN_TITLE_JOIN": "block text - This is shown when the programmer wants to change the number of pieces of text being joined together. See [https://github.com/google/blockly/wiki/Text#text-creation https://github.com/google/blockly/wiki/Text#text-creation], specifically the last picture in the 'Text creation' section.\n{{Identical|Join}}", "TEXT_CREATE_JOIN_TOOLTIP": "tooltip - See [https://github.com/google/blockly/wiki/Text#text-creation https://github.com/google/blockly/wiki/Text#text-creation], specifically the last picture in the 'Text creation' section.", "TEXT_CREATE_JOIN_ITEM_TOOLTIP": "block text - See [https://github.com/google/blockly/wiki/Text#text-creation https://github.com/google/blockly/wiki/Text#text-creation], specifically the last picture in the 'Text creation' section.", - "TEXT_APPEND_HELPURL": "url - This and the other text-related URLs are going to be hard to translate. As always, it is okay to leave untranslated or paste in the English-language URL. For these URLs, you might also consider a general URL about how computers represent text (such as the translation of [https://en.wikipedia.org/wiki/String_(computer_science) this Wikipedia page]).", + "TEXT_APPEND_HELPURL": "{{Optional}} url - This and the other text-related URLs are going to be hard to translate. As always, it is okay to leave untranslated or paste in the English-language URL. For these URLs, you might also consider a general URL about how computers represent text (such as the translation of [https://en.wikipedia.org/wiki/String_(computer_science) this Wikipedia page]).", "TEXT_APPEND_TITLE": "block input text - Message that the variable name at %1 will have the item at %2 appended to it. [[File:blockly-append-text.png]]", "TEXT_APPEND_TOOLTIP": "tooltip - See [https://github.com/google/blockly/wiki/Text#text-modification https://github.com/google/blockly/wiki/Text#text-modification] for more information.\n\nParameters:\n* %1 - the name of the variable to which text should be appended", - "TEXT_LENGTH_HELPURL": "url - Information about text on computers (usually referred to as 'strings').", + "TEXT_LENGTH_HELPURL": "{{Optional}} url - Information about text on computers (usually referred to as 'strings').", "TEXT_LENGTH_TITLE": "block text - See [https://github.com/google/blockly/wiki/Text#text-length https://github.com/google/blockly/wiki/Text#text-length]. \n\nParameters:\n* %1 - the piece of text to take the length of", "TEXT_LENGTH_TOOLTIP": "tooltip - See [https://github.com/google/blockly/wiki/Text#text-length https://github.com/google/blockly/wiki/Text#text-length].", - "TEXT_ISEMPTY_HELPURL": "url - Information about empty pieces of text on computers (usually referred to as 'empty strings').", + "TEXT_ISEMPTY_HELPURL": "{{Optional}} url - Information about empty pieces of text on computers (usually referred to as 'empty strings').", "TEXT_ISEMPTY_TITLE": "block text - See [https://github.com/google/blockly/wiki/Text#checking-for-empty-text https://github.com/google/blockly/wiki/Text#checking-for-empty-text]. \n\nParameters:\n* %1 - the piece of text to test for emptiness", "TEXT_ISEMPTY_TOOLTIP": "tooltip - See [https://github.com/google/blockly/wiki/Text#checking-for-empty-text https://github.com/google/blockly/wiki/Text#checking-for-empty-text].", - "TEXT_INDEXOF_HELPURL": "url - Information about finding a character in a piece of text.", + "TEXT_INDEXOF_HELPURL": "{{Optional}} url - Information about finding a character in a piece of text.", "TEXT_INDEXOF_TOOLTIP": "tooltip - %1 will be replaced by either the number 0 or -1 depending on the indexing mode. See [https://github.com/google/blockly/wiki/Text#finding-text https://github.com/google/blockly/wiki/Text#finding-text].", "TEXT_INDEXOF_TITLE": "block text - Title of blocks allowing users to find text. See [https://github.com/google/blockly/wiki/Text#finding-text https://github.com/google/blockly/wiki/Text#finding-text]. [[File:Blockly-find-text.png]]. In English the expanded message is 'in text %1 find (first|last) occurance of text %3' where %1 and %3 are added by the user. See TEXT_INDEXOF_OPERATOR_FIRST and TEXT_INDEXOF_OPERATOR_LAST for the dropdown text that replaces %2.", "TEXT_INDEXOF_OPERATOR_FIRST": "dropdown - See [https://github.com/google/blockly/wiki/Text#finding-text https://github.com/google/blockly/wiki/Text#finding-text]. [[File:Blockly-find-text.png]].", "TEXT_INDEXOF_OPERATOR_LAST": "dropdown - See [https://github.com/google/blockly/wiki/Text#finding-text https://github.com/google/blockly/wiki/Text#finding-text]. This would replace 'find first occurrence of text' below. (For more information on how common text is factored out of dropdown menus, see [https://translatewiki.net/wiki/Translating:Blockly#Drop-Down_Menus https://translatewiki.net/wiki/Translating:Blockly#Drop-Down_Menus)].) [[File:Blockly-find-text.png]].", - "TEXT_CHARAT_HELPURL": "url - Information about extracting characters (letters, number, symbols, etc.) from text.", + "TEXT_CHARAT_HELPURL": "{{Optional}} url - Information about extracting characters (letters, number, symbols, etc.) from text.", "TEXT_CHARAT_TITLE": "block text - Text for a block to extract a letter (or number, punctuation character, etc.) from a string, as shown below. %1 is added by the user and %2 is replaced by a dropdown of options, possibly followed by another user supplied string. TEXT_CHARAT_TAIL is then added to the end. See [https://github.com/google/blockly/wiki/Text#extracting-a-single-character https://github.com/google/blockly/wiki/Text#extracting-a-single-character]. [[File:Blockly-text-get.png]]", "TEXT_CHARAT_FROM_START": "dropdown - Indicates that the letter (or number, punctuation character, etc.) with the specified index should be obtained from the preceding piece of text. See [https://github.com/google/blockly/wiki/Text#extracting-a-single-character https://github.com/google/blockly/wiki/Text#extracting-a-single-character]. [[File:Blockly-text-get.png]]", "TEXT_CHARAT_FROM_END": "block text - Indicates that the letter (or number, punctuation character, etc.) with the specified index from the end of a given piece of text should be obtained. See [https://github.com/google/blockly/wiki/Text#extracting-a-single-character https://github.com/google/blockly/wiki/Text#extracting-a-single-character]. [[File:Blockly-text-get.png]]", @@ -242,7 +237,7 @@ "TEXT_CHARAT_TAIL": "block text - Text that goes after the rightmost block/dropdown when getting a single letter from a piece of text, as in [https://blockly-demo.appspot.com/static/apps/code/index.html#3m23km these blocks] or shown below. For most languages, this will be blank. [[File:Blockly-text-get.png]]", "TEXT_CHARAT_TOOLTIP": "tooltip - See [https://github.com/google/blockly/wiki/Text#extracting-a-single-character https://github.com/google/blockly/wiki/Text#extracting-a-single-character]. [[File:Blockly-text-get.png]]", "TEXT_GET_SUBSTRING_TOOLTIP": "See [https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text].", - "TEXT_GET_SUBSTRING_HELPURL": "url - Information about extracting characters from text. Reminder: urls are the lowest priority translations. Feel free to skip.", + "TEXT_GET_SUBSTRING_HELPURL": "{{Optional}} url - Information about extracting characters from text. Reminder: urls are the lowest priority translations. Feel free to skip.", "TEXT_GET_SUBSTRING_INPUT_IN_TEXT": "block text - Precedes a piece of text from which a portion should be extracted. [[File:Blockly-get-substring.png]]", "TEXT_GET_SUBSTRING_START_FROM_START": "dropdown - Indicates that the following number specifies the position (relative to the start position) of the beginning of the region of text that should be obtained from the preceding piece of text. See [https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text]. [[File:Blockly-get-substring.png]]", "TEXT_GET_SUBSTRING_START_FROM_END": "dropdown - Indicates that the following number specifies the position (relative to the end position) of the beginning of the region of text that should be obtained from the preceding piece of text. See [https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text]. Note: If {{msg-blockly|ORDINAL_NUMBER_SUFFIX}} is defined, it will automatically appear ''after'' this and any other [https://translatewiki.net/wiki/Translating:Blockly#Ordinal_numbers ordinal numbers] on this block. [[File:Blockly-get-substring.png]]", @@ -251,53 +246,53 @@ "TEXT_GET_SUBSTRING_END_FROM_END": "dropdown - Indicates that the following number specifies the position (relative to the end position) of the end of the region of text that should be obtained from the preceding piece of text. See [https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text]. [[File:Blockly-get-substring.png]]", "TEXT_GET_SUBSTRING_END_LAST": "block text - Indicates that a region ending with the last letter of the preceding piece of text should be extracted. See [https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text]. [[File:Blockly-get-substring.png]]", "TEXT_GET_SUBSTRING_TAIL": "block text - Text that should go after the rightmost block/dropdown when [https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text extracting a region of text]. In most languages, this will be the empty string. [[File:Blockly-get-substring.png]]", - "TEXT_CHANGECASE_HELPURL": "url - Information about the case of letters (upper-case and lower-case).", + "TEXT_CHANGECASE_HELPURL": "{{Optional}} url - Information about the case of letters (upper-case and lower-case).", "TEXT_CHANGECASE_TOOLTIP": "tooltip - Describes a block to adjust the case of letters. For more information on this block, see [https://github.com/google/blockly/wiki/Text#adjusting-text-case https://github.com/google/blockly/wiki/Text#adjusting-text-case].", "TEXT_CHANGECASE_OPERATOR_UPPERCASE": "block text - Indicates that all of the letters in the following piece of text should be capitalized. If your language does not use case, you may indicate that this is not applicable to your language. For more information on this block, see [https://github.com/google/blockly/wiki/Text#adjusting-text-case https://github.com/google/blockly/wiki/Text#adjusting-text-case].", "TEXT_CHANGECASE_OPERATOR_LOWERCASE": "block text - Indicates that all of the letters in the following piece of text should be converted to lower-case. If your language does not use case, you may indicate that this is not applicable to your language. For more information on this block, see [https://github.com/google/blockly/wiki/Text#adjusting-text-case https://github.com/google/blockly/wiki/Text#adjusting-text-case].", "TEXT_CHANGECASE_OPERATOR_TITLECASE": "block text - Indicates that the first letter of each of the following words should be capitalized and the rest converted to lower-case. If your language does not use case, you may indicate that this is not applicable to your language. For more information on this block, see [https://github.com/google/blockly/wiki/Text#adjusting-text-case https://github.com/google/blockly/wiki/Text#adjusting-text-case].", - "TEXT_TRIM_HELPURL": "url - Information about trimming (removing) text off the beginning and ends of pieces of text.", + "TEXT_TRIM_HELPURL": "{{Optional}} url - Information about trimming (removing) text off the beginning and ends of pieces of text.", "TEXT_TRIM_TOOLTIP": "tooltip - See [https://github.com/google/blockly/wiki/Text#trimming-removing-spaces https://github.com/google/blockly/wiki/Text#trimming-removing-spaces].", "TEXT_TRIM_OPERATOR_BOTH": "dropdown - Removes spaces from the beginning and end of a piece of text. See [https://github.com/google/blockly/wiki/Text#trimming-removing-spaces https://github.com/google/blockly/wiki/Text#trimming-removing-spaces]. Note that neither this nor the other options modify the original piece of text (that follows); the block just returns a version of the text without the specified spaces.", "TEXT_TRIM_OPERATOR_LEFT": "dropdown - Removes spaces from the beginning of a piece of text. See [https://github.com/google/blockly/wiki/Text#trimming-removing-spaces https://github.com/google/blockly/wiki/Text#trimming-removing-spaces]. Note that in right-to-left scripts, this will remove spaces from the right side.", "TEXT_TRIM_OPERATOR_RIGHT": "dropdown - Removes spaces from the end of a piece of text. See [https://github.com/google/blockly/wiki/Text#trimming-removing-spaces https://github.com/google/blockly/wiki/Text#trimming-removing-spaces]. Note that in right-to-left scripts, this will remove spaces from the left side.", - "TEXT_PRINT_HELPURL": "url - Information about displaying text on computers.", + "TEXT_PRINT_HELPURL": "{{Optional}} url - Information about displaying text on computers.", "TEXT_PRINT_TITLE": "block text - Display the input on the screen. See [https://github.com/google/blockly/wiki/Text#printing-text https://github.com/google/blockly/wiki/Text#printing-text]. \n\nParameters:\n* %1 - the value to print", "TEXT_PRINT_TOOLTIP": "tooltip - See [https://github.com/google/blockly/wiki/Text#printing-text https://github.com/google/blockly/wiki/Text#printing-text].", - "TEXT_PROMPT_HELPURL": "url - Information about getting text from users.", + "TEXT_PROMPT_HELPURL": "{{Optional}} url - Information about getting text from users.", "TEXT_PROMPT_TYPE_TEXT": "dropdown - Specifies that a piece of text should be requested from the user with the following message. See [https://github.com/google/blockly/wiki/Text#printing-text https://github.com/google/blockly/wiki/Text#printing-text].", "TEXT_PROMPT_TYPE_NUMBER": "dropdown - Specifies that a number should be requested from the user with the following message. See [https://github.com/google/blockly/wiki/Text#printing-text https://github.com/google/blockly/wiki/Text#printing-text].", "TEXT_PROMPT_TOOLTIP_NUMBER": "dropdown - Precedes the message with which the user should be prompted for a number. See [https://github.com/google/blockly/wiki/Text#printing-text https://github.com/google/blockly/wiki/Text#printing-text].", "TEXT_PROMPT_TOOLTIP_TEXT": "dropdown - Precedes the message with which the user should be prompted for some text. See [https://github.com/google/blockly/wiki/Text#printing-text https://github.com/google/blockly/wiki/Text#printing-text].", "TEXT_COUNT_MESSAGE0": "block text - Title of a block that counts the number of instances of a smaller pattern (%1) inside a longer string (%2).", - "TEXT_COUNT_HELPURL": "url - Information about counting how many times a string appears in another string.", + "TEXT_COUNT_HELPURL": "{{Optional}} url - Information about counting how many times a string appears in another string.", "TEXT_COUNT_TOOLTIP": "tooltip - Short description of a block that counts how many times some text occurs within some other text.", "TEXT_REPLACE_MESSAGE0": "block text - Title of a block that returns a copy of text (%3) with all instances of some smaller text (%1) replaced with other text (%2).", - "TEXT_REPLACE_HELPURL": "url - Information about replacing each copy text (or string, in computer lingo) with other text.", + "TEXT_REPLACE_HELPURL": "{{Optional}} url - Information about replacing each copy text (or string, in computer lingo) with other text.", "TEXT_REPLACE_TOOLTIP": "tooltip - Short description of a block that replaces copies of text in a large text with other text.", "TEXT_REVERSE_MESSAGE0": "block text - Title of block that returns a copy of text (%1) with the order of letters and characters reversed.", - "TEXT_REVERSE_HELPURL": "url - Information about reversing a letters/characters in text.", + "TEXT_REVERSE_HELPURL": "{{Optional}} url - Information about reversing a letters/characters in text.", "TEXT_REVERSE_TOOLTIP": "tooltip - See [https://github.com/google/blockly/wiki/Text].", - "LISTS_CREATE_EMPTY_HELPURL": "url - Information on empty lists.", + "LISTS_CREATE_EMPTY_HELPURL": "{{Optional}} url - Information on empty lists.", "LISTS_CREATE_EMPTY_TITLE": "block text - See [https://github.com/google/blockly/wiki/Lists#create-empty-list https://github.com/google/blockly/wiki/Lists#create-empty-list].", "LISTS_CREATE_EMPTY_TOOLTIP": "block text - See [https://github.com/google/blockly/wiki/Lists#create-empty-list https://github.com/google/blockly/wiki/Lists#create-empty-list].", - "LISTS_CREATE_WITH_HELPURL": "url - Information on building lists.", + "LISTS_CREATE_WITH_HELPURL": "{{Optional}} url - Information on building lists.", "LISTS_CREATE_WITH_TOOLTIP": "tooltip - See [https://github.com/google/blockly/wiki/Lists#create-list-with https://github.com/google/blockly/wiki/Lists#create-list-with].", "LISTS_CREATE_WITH_INPUT_WITH": "block text - See [https://github.com/google/blockly/wiki/Lists#create-list-with https://github.com/google/blockly/wiki/Lists#create-list-with].", "LISTS_CREATE_WITH_CONTAINER_TITLE_ADD": "block text - This appears in a sub-block when [https://github.com/google/blockly/wiki/Lists#changing-number-of-inputs changing the number of inputs in a ''''create list with'''' block].\n{{Identical|List}}", "LISTS_CREATE_WITH_CONTAINER_TOOLTIP": "tooltip - See [https://github.com/google/blockly/wiki/Lists#changing-number-of-inputs https://github.com/google/blockly/wiki/Lists#changing-number-of-inputs].", "LISTS_CREATE_WITH_ITEM_TOOLTIP": "tooltip - See [https://github.com/google/blockly/wiki/Lists#changing-number-of-inputs https://github.com/google/blockly/wiki/Lists#changing-number-of-inputs].", - "LISTS_REPEAT_HELPURL": "url - Information about [https://github.com/google/blockly/wiki/Lists#create-list-with creating a list with multiple copies of a single item].", - "LISTS_REPEAT_TOOLTIP": "url - See [https://github.com/google/blockly/wiki/Lists#create-list-with creating a list with multiple copies of a single item].", + "LISTS_REPEAT_HELPURL": "{{Optional}} url - Information about [https://github.com/google/blockly/wiki/Lists#create-list-with creating a list with multiple copies of a single item].", + "LISTS_REPEAT_TOOLTIP": "{{Optional}} url - See [https://github.com/google/blockly/wiki/Lists#create-list-with creating a list with multiple copies of a single item].", "LISTS_REPEAT_TITLE": "block text - See [https://github.com/google/blockly/wiki/Lists#create-list-with https://github.com/google/blockly/wiki/Lists#create-list-with]. \n\nParameters:\n* %1 - the item (text) to be repeated\n* %2 - the number of times to repeat it", - "LISTS_LENGTH_HELPURL": "url - Information about how the length of a list is computed (i.e., by the total number of elements, not the number of different elements).", + "LISTS_LENGTH_HELPURL": "{{Optional}} url - Information about how the length of a list is computed (i.e., by the total number of elements, not the number of different elements).", "LISTS_LENGTH_TITLE": "block text - See [https://github.com/google/blockly/wiki/Lists#length-of https://github.com/google/blockly/wiki/Lists#length-of]. \n\nParameters:\n* %1 - the list whose length is desired", "LISTS_LENGTH_TOOLTIP": "tooltip - See [https://github.com/google/blockly/wiki/Lists#length-of https://github.com/google/blockly/wiki/Lists#length-of Blockly:Lists:length of].", - "LISTS_ISEMPTY_HELPURL": "url - See [https://github.com/google/blockly/wiki/Lists#is-empty https://github.com/google/blockly/wiki/Lists#is-empty].", + "LISTS_ISEMPTY_HELPURL": "{{Optional}} url - See [https://github.com/google/blockly/wiki/Lists#is-empty https://github.com/google/blockly/wiki/Lists#is-empty].", "LISTS_ISEMPTY_TITLE": "block text - See [https://github.com/google/blockly/wiki/Lists#is-empty https://github.com/google/blockly/wiki/Lists#is-empty]. \n\nParameters:\n* %1 - the list to test", "LISTS_ISEMPTY_TOOLTIP": "block tooltip - See [https://github.com/google/blockly/wiki/Lists#is-empty https://github.com/google/blockly/wiki/Lists#is-empty].", "LISTS_INLIST": "block text - Title of blocks operating on [https://github.com/google/blockly/wiki/Lists lists].", - "LISTS_INDEX_OF_HELPURL": "url - See [https://github.com/google/blockly/wiki/Lists#getting-items-from-a-list https://github.com/google/blockly/wiki/Lists#getting-items-from-a-list].", + "LISTS_INDEX_OF_HELPURL": "{{Optional}} url - See [https://github.com/google/blockly/wiki/Lists#getting-items-from-a-list https://github.com/google/blockly/wiki/Lists#getting-items-from-a-list].", "LISTS_INDEX_OF_FIRST": "dropdown - See [https://github.com/google/blockly/wiki/Lists#finding-items-in-a-list Lists#finding-items-in-a-list]. [[File:Blockly-list-find.png]]", "LISTS_INDEX_OF_LAST": "dropdown - See [https://github.com/google/blockly/wiki/Lists#finding-items-in-a-list https://github.com/google/blockly/wiki/Lists#finding-items-in-a-list]. [[File:Blockly-list-find.png]]", "LISTS_INDEX_OF_TOOLTIP": "tooltip - %1 will be replaced by either the number 0 or -1 depending on the indexing mode. See [https://github.com/google/blockly/wiki/Lists#finding-items-in-a-list https://github.com/google/blockly/wiki/Lists#finding-items-in-a-list]. [[File:Blockly-list-find.png]]", @@ -324,7 +319,7 @@ "LISTS_GET_INDEX_TOOLTIP_REMOVE_FIRST": "tooltip - See [https://github.com/google/blockly/wiki/Lists#getting-and-removing-an-item] (for remove and return) and [https://github.com/google/blockly/wiki/Lists#getting-a-single-item] for 'first'.", "LISTS_GET_INDEX_TOOLTIP_REMOVE_LAST": "tooltip - See [https://github.com/google/blockly/wiki/Lists#getting-and-removing-an-item] (for remove and return) and [https://github.com/google/blockly/wiki/Lists#getting-a-single-item] for 'last'.", "LISTS_GET_INDEX_TOOLTIP_REMOVE_RANDOM": "tooltip - See [https://github.com/google/blockly/wiki/Lists#getting-and-removing-an-item] (for remove and return) and [https://github.com/google/blockly/wiki/Lists#getting-a-single-item] for 'random'.", - "LISTS_SET_INDEX_HELPURL": "url - Information about putting items in lists.", + "LISTS_SET_INDEX_HELPURL": "{{Optional}} url - Information about putting items in lists.", "LISTS_SET_INDEX_SET": "block text - [https://github.com/google/blockly/wiki/Lists#in-list--set Replaces an item in a list]. [[File:Blockly-in-list-set-insert.png]]", "LISTS_SET_INDEX_INSERT": "block text - [https://github.com/google/blockly/wiki/Lists#in-list--insert-at Inserts an item into a list]. [[File:Blockly-in-list-set-insert.png]]", "LISTS_SET_INDEX_INPUT_TO": "block text - The word(s) after the position in the list and before the item to be set/inserted. [[File:Blockly-in-list-set-insert.png]]", @@ -336,7 +331,7 @@ "LISTS_SET_INDEX_TOOLTIP_INSERT_FIRST": "tooltip - See [https://github.com/google/blockly/wiki/Lists#getting-a-single-item} (even though the page describes the 'get' block, the idea is the same for the 'insert' block).", "LISTS_SET_INDEX_TOOLTIP_INSERT_LAST": "tooltip - See [https://github.com/google/blockly/wiki/Lists#getting-a-single-item} (even though the page describes the 'get' block, the idea is the same for the 'insert' block).", "LISTS_SET_INDEX_TOOLTIP_INSERT_RANDOM": "tooltip - See [https://github.com/google/blockly/wiki/Lists#getting-a-single-item} (even though the page describes the 'get' block, the idea is the same for the 'insert' block).", - "LISTS_GET_SUBLIST_HELPURL": "url - Information describing extracting a sublist from an existing list.", + "LISTS_GET_SUBLIST_HELPURL": "{{Optional}} url - Information describing extracting a sublist from an existing list.", "LISTS_GET_SUBLIST_START_FROM_START": "dropdown - Indicates that an index relative to the front of the list should be used to specify the beginning of the range from which to [https://github.com/google/blockly/wiki/Lists#getting-a-sublist get a sublist]. [[File:Blockly-get-sublist.png]] Note: If {{msg-blockly|ORDINAL_NUMBER_SUFFIX}} is defined, it will automatically appear ''after'' this number (and any other ordinal numbers on this block). See [[Translating:Blockly#Ordinal_numbers]] for more information on ordinal numbers in Blockly.", "LISTS_GET_SUBLIST_START_FROM_END": "dropdown - Indicates that an index relative to the end of the list should be used to specify the beginning of the range from which to [https://github.com/google/blockly/wiki/Lists#getting-a-sublist get a sublist].", "LISTS_GET_SUBLIST_START_FIRST": "dropdown - Indicates that the [https://github.com/google/blockly/wiki/Lists#getting-a-sublist sublist to extract] should begin with the list's first item.", @@ -345,7 +340,7 @@ "LISTS_GET_SUBLIST_END_LAST": "dropdown - Indicates that the '''last''' item in the given list should be [https://github.com/google/blockly/wiki/Lists#getting-a-sublist the end of the selected sublist]. [[File:Blockly-get-sublist.png]]", "LISTS_GET_SUBLIST_TAIL": "block text - This appears in the rightmost position ('tail') of the sublist block, as described at [https://github.com/google/blockly/wiki/Lists#getting-a-sublist https://github.com/google/blockly/wiki/Lists#getting-a-sublist]. In English and most other languages, this is the empty string. [[File:Blockly-get-sublist.png]]", "LISTS_GET_SUBLIST_TOOLTIP": "tooltip - See [https://github.com/google/blockly/wiki/Lists#getting-a-sublist https://github.com/google/blockly/wiki/Lists#getting-a-sublist] for more information. [[File:Blockly-get-sublist.png]]", - "LISTS_SORT_HELPURL": "{{optional}}\nurl - Information describing sorting a list.", + "LISTS_SORT_HELPURL": "{{Optional}} url - Information describing sorting a list.", "LISTS_SORT_TITLE": "Sort as type %1 (numeric or alphabetic) in order %2 (ascending or descending) a list of items %3.\n{{Identical|Sort}}", "LISTS_SORT_TOOLTIP": "tooltip - See [https://github.com/google/blockly/wiki/Lists#sorting-a-list].", "LISTS_SORT_ORDER_ASCENDING": "sorting order or direction from low to high value for numeric, or A-Z for alphabetic.\n{{Identical|Ascending}}", @@ -353,39 +348,39 @@ "LISTS_SORT_TYPE_NUMERIC": "sort by treating each item as a number.", "LISTS_SORT_TYPE_TEXT": "sort by treating each item alphabetically, case-sensitive.", "LISTS_SORT_TYPE_IGNORECASE": "sort by treating each item alphabetically, ignoring differences in case.", - "LISTS_SPLIT_HELPURL": "url - Information describing splitting text into a list, or joining a list into text.", + "LISTS_SPLIT_HELPURL": "{{Optional}} url - Information describing splitting text into a list, or joining a list into text.", "LISTS_SPLIT_LIST_FROM_TEXT": "dropdown - Indicates that text will be split up into a list (e.g. 'a-b-c' -> ['a', 'b', 'c']).", "LISTS_SPLIT_TEXT_FROM_LIST": "dropdown - Indicates that a list will be joined together to form text (e.g. ['a', 'b', 'c'] -> 'a-b-c').", "LISTS_SPLIT_WITH_DELIMITER": "block text - Prompts for a letter to be used as a separator when splitting or joining text.", "LISTS_SPLIT_TOOLTIP_SPLIT": "tooltip - See [https://github.com/google/blockly/wiki/Lists#make-list-from-text https://github.com/google/blockly/wiki/Lists#make-list-from-text] for more information.", "LISTS_SPLIT_TOOLTIP_JOIN": "tooltip - See [https://github.com/google/blockly/wiki/Lists#make-text-from-list https://github.com/google/blockly/wiki/Lists#make-text-from-list] for more information.", - "LISTS_REVERSE_HELPURL": "url - Information describing reversing a list.", + "LISTS_REVERSE_HELPURL": "{{Optional}} url - Information describing reversing a list.", "LISTS_REVERSE_MESSAGE0": "block text - Title of block that returns a copy of a list (%1) with the order of items reversed.", "LISTS_REVERSE_TOOLTIP": "tooltip - Short description for a block that reverses a copy of a list.", "ORDINAL_NUMBER_SUFFIX": "grammar - Text that follows an ordinal number (a number that indicates position relative to other numbers). In most languages, such text appears before the number, so this should be blank. An exception is Hungarian. See [[Translating:Blockly#Ordinal_numbers]] for more information.", - "VARIABLES_GET_HELPURL": "url - Information about ''variables'' in computer programming. Consider using your language's translation of [https://en.wikipedia.org/wiki/Variable_(computer_science) https://en.wikipedia.org/wiki/Variable_(computer_science)], if it exists.", + "VARIABLES_GET_HELPURL": "{{Optional}} url - Information about ''variables'' in computer programming. Consider using your language's translation of [https://en.wikipedia.org/wiki/Variable_(computer_science) https://en.wikipedia.org/wiki/Variable_(computer_science)], if it exists.", "VARIABLES_GET_TOOLTIP": "tooltip - This gets the value of the named variable without modifying it.", "VARIABLES_GET_CREATE_SET": "context menu - Selecting this creates a block to set (change) the value of this variable. \n\nParameters:\n* %1 - the name of the variable.", - "VARIABLES_SET_HELPURL": "url - Information about ''variables'' in computer programming. Consider using your language's translation of [https://en.wikipedia.org/wiki/Variable_(computer_science) https://en.wikipedia.org/wiki/Variable_(computer_science)], if it exists.", + "VARIABLES_SET_HELPURL": "{{Optional}} url - Information about ''variables'' in computer programming. Consider using your language's translation of [https://en.wikipedia.org/wiki/Variable_(computer_science) https://en.wikipedia.org/wiki/Variable_(computer_science)], if it exists.", "VARIABLES_SET": "block text - Change the value of a mathematical variable: '''set [the value of] x to 7'''.\n\nParameters:\n* %1 - the name of the variable.\n* %2 - the value to be assigned.", "VARIABLES_SET_TOOLTIP": "tooltip - This initializes or changes the value of the named variable.", "VARIABLES_SET_CREATE_GET": "context menu - Selecting this creates a block to get (change) the value of this variable.\n\nParameters:\n* %1 - the name of the variable.", - "PROCEDURES_DEFNORETURN_HELPURL": "url - Information about defining [https://en.wikipedia.org/wiki/Subroutine functions] that do not have return values.", + "PROCEDURES_DEFNORETURN_HELPURL": "{{Optional}} url - Information about defining [https://en.wikipedia.org/wiki/Subroutine functions] that do not have return values.", "PROCEDURES_DEFNORETURN_TITLE": "block text - This precedes the name of the function when defining it. See [https://blockly-demo.appspot.com/static/apps/code/index.html?lang=en#c84aoc this sample function definition].", "PROCEDURES_DEFNORETURN_PROCEDURE": "default name - This acts as a placeholder for the name of a function on a function definition block, as shown on [https://blockly-demo.appspot.com/static/apps/code/index.html?lang=en#w7cfju this block]. The user will replace it with the function's name.", - "PROCEDURES_BEFORE_PARAMS": "block text - This precedes the list of parameters on a function's defiition block. See [https://blockly-demo.appspot.com/static/apps/code/index.html?lang=en#voztpd this sample function with parameters].", + "PROCEDURES_BEFORE_PARAMS": "block text - This precedes the list of parameters on a function's definition block. See [https://blockly-demo.appspot.com/static/apps/code/index.html?lang=en#voztpd this sample function with parameters].", "PROCEDURES_CALL_BEFORE_PARAMS": "block text - This precedes the list of parameters on a function's caller block. See [https://blockly-demo.appspot.com/static/apps/code/index.html?lang=en#voztpd this sample function with parameters].", "PROCEDURES_DEFNORETURN_DO": "block text - This appears next to the function's 'body', the blocks that should be run when the function is called, as shown in [https://blockly-demo.appspot.com/static/apps/code/index.html?lang=en#voztpd this sample function definition].", "PROCEDURES_DEFNORETURN_TOOLTIP": "tooltip", "PROCEDURES_DEFNORETURN_COMMENT": "Placeholder text that the user is encouraged to replace with a description of what their function does.", - "PROCEDURES_DEFRETURN_HELPURL": "url - Information about defining [https://en.wikipedia.org/wiki/Subroutine functions] that have return values.", + "PROCEDURES_DEFRETURN_HELPURL": "{{Optional}} url - Information about defining [https://en.wikipedia.org/wiki/Subroutine functions] that have return values.", "PROCEDURES_DEFRETURN_RETURN": "block text - This imperative or infinite verb precedes the value that is used as the return value (output) of this function. See [https://blockly-demo.appspot.com/static/apps/code/index.html?lang=en#6ot5y5 this sample function that returns a value].", "PROCEDURES_DEFRETURN_TOOLTIP": "tooltip", "PROCEDURES_ALLOW_STATEMENTS": "Label for a checkbox that controls if statements are allowed in a function.", "PROCEDURES_DEF_DUPLICATE_WARNING": "alert - The user has created a function with two parameters that have the same name. Every parameter must have a different name.", - "PROCEDURES_CALLNORETURN_HELPURL": "url - Information about calling [https://en.wikipedia.org/wiki/Subroutine functions] that do not return values.", + "PROCEDURES_CALLNORETURN_HELPURL": "{{Optional}} url - Information about calling [https://en.wikipedia.org/wiki/Subroutine functions] that do not return values.", "PROCEDURES_CALLNORETURN_TOOLTIP": "tooltip - This block causes the body (blocks inside) of the named function definition to be run.", - "PROCEDURES_CALLRETURN_HELPURL": "url - Information about calling [https://en.wikipedia.org/wiki/Subroutine functions] that return values.", + "PROCEDURES_CALLRETURN_HELPURL": "{{Optional}} url - Information about calling [https://en.wikipedia.org/wiki/Subroutine functions] that return values.", "PROCEDURES_CALLRETURN_TOOLTIP": "tooltip - This block causes the body (blocks inside) of the named function definition to be run.\n\nParameters:\n* %1 - the name of the function.", "PROCEDURES_MUTATORCONTAINER_TITLE": "block text - This text appears on a block in a window that appears when the user clicks on the plus sign or star on a function definition block. It refers to the set of parameters (referred to by the simpler term 'inputs') to the function. See [[Translating:Blockly#function_definitions]].\n{{Identical|Input}}", "PROCEDURES_MUTATORCONTAINER_TOOLTIP": "tooltip", @@ -394,6 +389,6 @@ "PROCEDURES_HIGHLIGHT_DEF": "context menu - This appears on the context menu for function calls. Selecting it causes the corresponding function definition to be highlighted (as shown at [[Translating:Blockly#context_menus]].", "PROCEDURES_CREATE_DO": "context menu - This appears on the context menu for function definitions. Selecting it creates a block to call the function.\n\nParameters:\n* %1 - the name of the function.\n{{Identical|Create}}", "PROCEDURES_IFRETURN_TOOLTIP": "tooltip - If the first value is true, this causes the second value to be returned immediately from the enclosing function.", - "PROCEDURES_IFRETURN_HELPURL": "{{optional}}\nurl - Information about guard clauses.", + "PROCEDURES_IFRETURN_HELPURL": "{{Optional}} url - Information about guard clauses.", "PROCEDURES_IFRETURN_WARNING": "warning - This appears if the user tries to use this block outside of a function definition." } diff --git a/msg/messages.js b/msg/messages.js index c4a2c2d54..ea9d8f91e 100644 --- a/msg/messages.js +++ b/msg/messages.js @@ -62,6 +62,8 @@ Blockly.Msg.LISTS_HUE = '260'; Blockly.Msg.COLOUR_HUE = '20'; /// {{Notranslate}} Hue value for all variable blocks. Blockly.Msg.VARIABLES_HUE = '330'; +/// {{Notranslate}} Hue value for all variable dynamic blocks. +Blockly.Msg.VARIABLES_DYNAMIC_HUE = '310'; /// {{Notranslate}} Hue value for all procedure blocks. Blockly.Msg.PROCEDURES_HUE = '290'; @@ -121,12 +123,20 @@ Blockly.Msg.RENAME_VARIABLE_TITLE = 'Rename all "%1" variables to:'; // Variable creation /// button text - Text on the button used to launch the variable creation dialogue. Blockly.Msg.NEW_VARIABLE = 'Create variable...'; +/// button text - Text on the button used to launch the variable creation dialogue. +Blockly.Msg.NEW_STRING_VARIABLE = 'Create string variable...'; +/// button text - Text on the button used to launch the variable creation dialogue. +Blockly.Msg.NEW_NUMBER_VARIABLE = 'Create number variable...'; +/// button text - Text on the button used to launch the variable creation dialogue. +Blockly.Msg.NEW_COLOUR_VARIABLE = 'Create colour variable...'; +/// prompt - Prompts the user to enter the type for a variable. +Blockly.Msg.NEW_VARIABLE_TYPE_TITLE = 'New variable type:'; /// prompt - Prompts the user to enter the name for a new variable. See [https://github.com/google/blockly/wiki/Variables#dropdown-menu https://github.com/google/blockly/wiki/Variables#dropdown-menu]. Blockly.Msg.NEW_VARIABLE_TITLE = 'New variable name:'; /// alert - Tells the user that the name they entered is already in use. Blockly.Msg.VARIABLE_ALREADY_EXISTS = 'A variable named "%1" already exists.'; /// alert - Tells the user that the name they entered is already in use for another type. -Blockly.Msg.VARIABLE_ALREADY_EXISTS_FOR_ANOTHER_TYPE = 'A variable named "%1" already exists for another variable of type "%2".'; +Blockly.Msg.VARIABLE_ALREADY_EXISTS_FOR_ANOTHER_TYPE = 'A variable named "%1" already exists for another type: "%2".'; // Variable deletion. /// confirm - Ask the user to confirm their deletion of multiple uses of a variable. @@ -137,17 +147,17 @@ Blockly.Msg.CANNOT_DELETE_VARIABLE_PROCEDURE = 'Can\'t delete the variable "%1" Blockly.Msg.DELETE_VARIABLE = 'Delete the "%1" variable'; // Colour Blocks. -/// url - Information about colour. +/// {{Optional}} url - Information about colour. Blockly.Msg.COLOUR_PICKER_HELPURL = 'https://en.wikipedia.org/wiki/Color'; /// tooltip - See [https://github.com/google/blockly/wiki/Colour#picking-a-colour-from-a-palette https://github.com/google/blockly/wiki/Colour#picking-a-colour-from-a-palette]. Blockly.Msg.COLOUR_PICKER_TOOLTIP = 'Choose a colour from the palette.'; -/// url - A link that displays a random colour each time you visit it. +/// {{Optional}} url - A link that displays a random colour each time you visit it. Blockly.Msg.COLOUR_RANDOM_HELPURL = 'http://randomcolour.com'; /// block text - Title of block that generates a colour at random. Blockly.Msg.COLOUR_RANDOM_TITLE = 'random colour'; /// tooltip - See [https://github.com/google/blockly/wiki/Colour#generating-a-random-colour https://github.com/google/blockly/wiki/Colour#generating-a-random-colour]. Blockly.Msg.COLOUR_RANDOM_TOOLTIP = 'Choose a colour at random.'; -/// url - A link for color codes with percentages (0-100%) for each component, instead of the more common 0-255, which may be more difficult for beginners. +/// {{Optional}} url - A link for color codes with percentages (0-100%) for each component, instead of the more common 0-255, which may be more difficult for beginners. Blockly.Msg.COLOUR_RGB_HELPURL = 'http://www.december.com/html/spec/colorper.html'; /// block text - Title of block for [https://github.com/google/blockly/wiki/Colour#creating-a-colour-from-red-green-and-blue-components https://github.com/google/blockly/wiki/Colour#creating-a-colour-from-red-green-and-blue-components]. Blockly.Msg.COLOUR_RGB_TITLE = 'colour with'; @@ -159,7 +169,7 @@ Blockly.Msg.COLOUR_RGB_GREEN = 'green'; Blockly.Msg.COLOUR_RGB_BLUE = 'blue'; /// tooltip - See [https://github.com/google/blockly/wiki/Colour#creating-a-colour-from-red-green-and-blue-components https://github.com/google/blockly/wiki/Colour#creating-a-colour-from-red-green-and-blue-components]. Blockly.Msg.COLOUR_RGB_TOOLTIP = 'Create a colour with the specified amount of red, green, and blue. All values must be between 0 and 100.'; -/// url - A useful link that displays blending of two colors. +/// {{Optional}} url - A useful link that displays blending of two colors. Blockly.Msg.COLOUR_BLEND_HELPURL = 'http://meyerweb.com/eric/tools/color-blend/'; /// block text - A verb for blending two shades of paint. Blockly.Msg.COLOUR_BLEND_TITLE = 'blend'; @@ -173,7 +183,7 @@ Blockly.Msg.COLOUR_BLEND_RATIO = 'ratio'; Blockly.Msg.COLOUR_BLEND_TOOLTIP = 'Blends two colours together with a given ratio (0.0 - 1.0).'; // Loop Blocks. -/// url - Describes 'repeat loops' in computer programs; consider using the translation of the page [https://en.wikipedia.org/wiki/Control_flow http://en.wikipedia.org/wiki/Control_flow]. +/// {{Optional}} url - Describes 'repeat loops' in computer programs; consider using the translation of the page [https://en.wikipedia.org/wiki/Control_flow https://en.wikipedia.org/wiki/Control_flow]. Blockly.Msg.CONTROLS_REPEAT_HELPURL = 'https://en.wikipedia.org/wiki/For_loop'; /// block input text - Title of [https://github.com/google/blockly/wiki/Loops#repeat repeat block].\n\nParameters:\n* %1 - the number of times the body of the loop should be repeated. Blockly.Msg.CONTROLS_REPEAT_TITLE = 'repeat %1 times'; @@ -181,7 +191,7 @@ Blockly.Msg.CONTROLS_REPEAT_TITLE = 'repeat %1 times'; Blockly.Msg.CONTROLS_REPEAT_INPUT_DO = 'do'; /// tooltip - See [https://github.com/google/blockly/wiki/Loops#repeat https://github.com/google/blockly/wiki/Loops#repeat]. Blockly.Msg.CONTROLS_REPEAT_TOOLTIP = 'Do some statements several times.'; -/// url - Describes 'while loops' in computer programs; consider using the translation of [https://en.wikipedia.org/wiki/While_loop https://en.wikipedia.org/wiki/While_loop], if present, or [https://en.wikipedia.org/wiki/Control_flow https://en.wikipedia.org/wiki/Control_flow]. +/// {{Optional}} url - Describes 'while loops' in computer programs; consider using the translation of [https://en.wikipedia.org/wiki/While_loop https://en.wikipedia.org/wiki/While_loop], if present, or [https://en.wikipedia.org/wiki/Control_flow https://en.wikipedia.org/wiki/Control_flow]. Blockly.Msg.CONTROLS_WHILEUNTIL_HELPURL = 'https://github.com/google/blockly/wiki/Loops#repeat'; Blockly.Msg.CONTROLS_WHILEUNTIL_INPUT_DO = Blockly.Msg.CONTROLS_REPEAT_INPUT_DO; /// dropdown - Specifies that a loop should [https://github.com/google/blockly/wiki/Loops#repeat-while repeat while] the following condition is true. @@ -193,7 +203,7 @@ Blockly.Msg.CONTROLS_WHILEUNTIL_TOOLTIP_WHILE = 'While a value is true, then do /// tooltip - See [https://github.com/google/blockly/wiki/Loops#repeat-until https://github.com/google/blockly/wiki/Loops#repeat-until]. Blockly.Msg.CONTROLS_WHILEUNTIL_TOOLTIP_UNTIL = 'While a value is false, then do some statements.'; -/// url - Describes 'for loops' in computer programs. Consider using your language's translation of [https://en.wikipedia.org/wiki/For_loop https://en.wikipedia.org/wiki/For_loop], if present. +/// {{Optional}} url - Describes 'for loops' in computer programs. Consider using your language's translation of [https://en.wikipedia.org/wiki/For_loop https://en.wikipedia.org/wiki/For_loop], if present. Blockly.Msg.CONTROLS_FOR_HELPURL = 'https://github.com/google/blockly/wiki/Loops#count-with'; /// tooltip - See [https://github.com/google/blockly/wiki/Loops#count-with https://github.com/google/blockly/wiki/Loops#count-with].\n\nParameters:\n* %1 - the name of the loop variable. Blockly.Msg.CONTROLS_FOR_TOOLTIP = 'Have the variable "%1" take on the values from the start number to the end number, counting by the specified interval, and do the specified blocks.'; @@ -207,7 +217,7 @@ Blockly.Msg.CONTROLS_FOR_TOOLTIP = 'Have the variable "%1" take on the values fr Blockly.Msg.CONTROLS_FOR_TITLE = 'count with %1 from %2 to %3 by %4'; Blockly.Msg.CONTROLS_FOR_INPUT_DO = Blockly.Msg.CONTROLS_REPEAT_INPUT_DO; -/// url - Describes 'for-each loops' in computer programs. Consider using your language's translation of [https://en.wikipedia.org/wiki/Foreach https://en.wikipedia.org/wiki/Foreach] if present. +/// {{Optional}} url - Describes 'for-each loops' in computer programs. Consider using your language's translation of [https://en.wikipedia.org/wiki/Foreach https://en.wikipedia.org/wiki/Foreach] if present. Blockly.Msg.CONTROLS_FOREACH_HELPURL = 'https://github.com/google/blockly/wiki/Loops#for-each'; /// block text - Title of [https://github.com/google/blockly/wiki/Loops#for-each for each block]. /// Sequentially assigns every item in array %2 to the valiable %1. @@ -216,7 +226,7 @@ Blockly.Msg.CONTROLS_FOREACH_INPUT_DO = Blockly.Msg.CONTROLS_REPEAT_INPUT_DO; /// block text - Description of [https://github.com/google/blockly/wiki/Loops#for-each for each blocks].\n\nParameters:\n* %1 - the name of the loop variable. Blockly.Msg.CONTROLS_FOREACH_TOOLTIP = 'For each item in a list, set the variable "%1" to the item, and then do some statements.'; -/// url - Describes control flow in computer programs. Consider using your language's translation of [https://en.wikipedia.org/wiki/Control_flow https://en.wikipedia.org/wiki/Control_flow], if it exists. +/// {{Optional}} url - Describes control flow in computer programs. Consider using your language's translation of [https://en.wikipedia.org/wiki/Control_flow https://en.wikipedia.org/wiki/Control_flow], if it exists. Blockly.Msg.CONTROLS_FLOW_STATEMENTS_HELPURL = 'https://github.com/google/blockly/wiki/Loops#loop-termination-blocks'; /// dropdown - The current loop should be exited. See [https://github.com/google/blockly/wiki/Loops#break https://github.com/google/blockly/wiki/Loops#break]. Blockly.Msg.CONTROLS_FLOW_STATEMENTS_OPERATOR_BREAK = 'break out of loop'; @@ -230,7 +240,7 @@ Blockly.Msg.CONTROLS_FLOW_STATEMENTS_TOOLTIP_CONTINUE = 'Skip the rest of this l Blockly.Msg.CONTROLS_FLOW_STATEMENTS_WARNING = 'Warning: This block may only be used within a loop.'; // Logic Blocks. -/// url - Describes conditional statements (if-then-else) in computer programs. Consider using your language's translation of [https://en.wikipedia.org/wiki/If_else https://en.wikipedia.org/wiki/If_else], if present. +/// {{Optional}} url - Describes conditional statements (if-then-else) in computer programs. Consider using your language's translation of [https://en.wikipedia.org/wiki/If_else https://en.wikipedia.org/wiki/If_else], if present. Blockly.Msg.CONTROLS_IF_HELPURL = 'https://github.com/google/blockly/wiki/IfElse'; /// tooltip - Describes [https://github.com/google/blockly/wiki/IfElse#if-blocks 'if' blocks]. Consider using your language's translation of [https://en.wikipedia.org/wiki/If_statement https://en.wikipedia.org/wiki/If_statement], if present. Blockly.Msg.CONTROLS_IF_TOOLTIP_1 = 'If a value is true, then do some statements.'; @@ -285,7 +295,7 @@ Blockly.Msg.IOS_VARIABLES_VARIABLE_NAME = 'Variable name'; /// alert - Error message that is displayed when the user attempts to create a variable without a name. Blockly.Msg.IOS_VARIABLES_EMPTY_NAME_ERROR = 'You can\'t use an empty variable name.'; -/// url - Information about comparisons. +/// {{Optional}} url - Information about comparisons. Blockly.Msg.LOGIC_COMPARE_HELPURL = 'https://en.wikipedia.org/wiki/Inequality_(mathematics)'; /// tooltip - Describes the equals (=) block. Blockly.Msg.LOGIC_COMPARE_TOOLTIP_EQ = 'Return true if both inputs equal each other.'; @@ -300,7 +310,7 @@ Blockly.Msg.LOGIC_COMPARE_TOOLTIP_GT = 'Return true if the first input is greate /// tooltip - Describes the greater than or equals (≥) block. Blockly.Msg.LOGIC_COMPARE_TOOLTIP_GTE = 'Return true if the first input is greater than or equal to the second input.'; -/// url - Information about the Boolean conjunction ("and") and disjunction ("or") operators. Consider using the translation of [https://en.wikipedia.org/wiki/Boolean_logic https://en.wikipedia.org/wiki/Boolean_logic], if it exists in your language. +/// {{Optional}} url - Information about the Boolean conjunction ("and") and disjunction ("or") operators. Consider using the translation of [https://en.wikipedia.org/wiki/Boolean_logic https://en.wikipedia.org/wiki/Boolean_logic], if it exists in your language. Blockly.Msg.LOGIC_OPERATION_HELPURL = 'https://github.com/google/blockly/wiki/Logic#logical-operations'; /// tooltip - See [https://en.wikipedia.org/wiki/Logical_conjunction https://en.wikipedia.org/wiki/Logical_conjunction]. Blockly.Msg.LOGIC_OPERATION_TOOLTIP_AND = 'Return true if both inputs are true.'; @@ -311,7 +321,7 @@ Blockly.Msg.LOGIC_OPERATION_TOOLTIP_OR = 'Return true if at least one of the inp /// block text - See [https://en.wikipedia.org/wiki/Disjunction https://en.wikipedia.org/wiki/Disjunction].\n{{Identical|Or}} Blockly.Msg.LOGIC_OPERATION_OR = 'or'; -/// url - Information about logical negation. The translation of [https://en.wikipedia.org/wiki/Logical_negation https://en.wikipedia.org/wiki/Logical_negation] is recommended if it exists in the target language. +/// {{Optional}} url - Information about logical negation. The translation of [https://en.wikipedia.org/wiki/Logical_negation https://en.wikipedia.org/wiki/Logical_negation] is recommended if it exists in the target language. Blockly.Msg.LOGIC_NEGATE_HELPURL = 'https://github.com/google/blockly/wiki/Logic#not'; /// block text - This is a unary operator that returns ''false'' when the input is ''true'', and ''true'' when the input is ''false''. /// \n\nParameters:\n* %1 - the input (which should be either the value "true" or "false") @@ -319,7 +329,7 @@ Blockly.Msg.LOGIC_NEGATE_TITLE = 'not %1'; /// tooltip - See [https://en.wikipedia.org/wiki/Logical_negation https://en.wikipedia.org/wiki/Logical_negation]. Blockly.Msg.LOGIC_NEGATE_TOOLTIP = 'Returns true if the input is false. Returns false if the input is true.'; -/// url - Information about the logic values ''true'' and ''false''. Consider using the translation of [https://en.wikipedia.org/wiki/Truth_value https://en.wikipedia.org/wiki/Truth_value] if it exists in your language. +/// {{Optional}} url - Information about the logic values ''true'' and ''false''. Consider using the translation of [https://en.wikipedia.org/wiki/Truth_value https://en.wikipedia.org/wiki/Truth_value] if it exists in your language. Blockly.Msg.LOGIC_BOOLEAN_HELPURL = 'https://github.com/google/blockly/wiki/Logic#values'; /// block text - The word for the [https://en.wikipedia.org/wiki/Truth_value logical value] ''true''.\n{{Identical|True}} Blockly.Msg.LOGIC_BOOLEAN_TRUE = 'true'; @@ -328,14 +338,14 @@ Blockly.Msg.LOGIC_BOOLEAN_FALSE = 'false'; /// tooltip - Indicates that the block returns either of the two possible [https://en.wikipedia.org/wiki/Truth_value logical values]. Blockly.Msg.LOGIC_BOOLEAN_TOOLTIP = 'Returns either true or false.'; -/// url - Provide a link to the translation of [https://en.wikipedia.org/wiki/Nullable_type https://en.wikipedia.org/wiki/Nullable_type], if it exists in your language; otherwise, do not worry about translating this advanced concept. +/// {{Optional}} url - Provide a link to the translation of [https://en.wikipedia.org/wiki/Nullable_type https://en.wikipedia.org/wiki/Nullable_type], if it exists in your language; otherwise, do not worry about translating this advanced concept. Blockly.Msg.LOGIC_NULL_HELPURL = 'https://en.wikipedia.org/wiki/Nullable_type'; /// block text - In computer languages, ''null'' is a special value that indicates that no value has been set. You may use your language's word for "nothing" or "invalid".\n{{Identical|Null}} Blockly.Msg.LOGIC_NULL = 'null'; /// tooltip - This should use the word from the previous message. Blockly.Msg.LOGIC_NULL_TOOLTIP = 'Returns null.'; -/// url - Describes the programming language operator known as the ''ternary'' or ''conditional'' operator. It is recommended that you use the translation of [https://en.wikipedia.org/wiki/%3F: https://en.wikipedia.org/wiki/%3F:] if it exists. +/// {{Optional}} url - Describes the programming language operator known as the ''ternary'' or ''conditional'' operator. It is recommended that you use the translation of [https://en.wikipedia.org/wiki/%3F: https://en.wikipedia.org/wiki/%3F:] if it exists. Blockly.Msg.LOGIC_TERNARY_HELPURL = 'https://en.wikipedia.org/wiki/%3F:'; /// block input text - Label for the input whose value determines which of the other two inputs is returned. In some programming languages, this is called a ''''predicate''''. Blockly.Msg.LOGIC_TERNARY_CONDITION = 'test'; @@ -347,22 +357,22 @@ Blockly.Msg.LOGIC_TERNARY_IF_FALSE = 'if false'; Blockly.Msg.LOGIC_TERNARY_TOOLTIP = 'Check the condition in "test". If the condition is true, returns the "if true" value; otherwise returns the "if false" value.'; // Math Blocks. -/// url - Information about (real) numbers. +/// {{Optional}} url - Information about (real) numbers. Blockly.Msg.MATH_NUMBER_HELPURL = 'https://en.wikipedia.org/wiki/Number'; /// tooltip - Any positive or negative number, not necessarily an integer. Blockly.Msg.MATH_NUMBER_TOOLTIP = 'A number.'; -/// {{optional}}\nmath - The symbol for the binary operation addition. +/// {{Optional}} math - The symbol for the binary operation addition. Blockly.Msg.MATH_ADDITION_SYMBOL = '+'; -/// {{optional}}\nmath - The symbol for the binary operation indicating that the right operand should be +/// {{Optional}} math - The symbol for the binary operation indicating that the right operand should be /// subtracted from the left operand. Blockly.Msg.MATH_SUBTRACTION_SYMBOL = '-'; -/// {{optional}}\nmath - The binary operation indicating that the left operand should be divided by +/// {{Optional}} math - The binary operation indicating that the left operand should be divided by /// the right operand. Blockly.Msg.MATH_DIVISION_SYMBOL = '÷'; -/// {{optional}}\nmath - The symbol for the binary operation multiplication. +/// {{Optional}} math - The symbol for the binary operation multiplication. Blockly.Msg.MATH_MULTIPLICATION_SYMBOL = '×'; -/// {{optional}}\nmath - The symbol for the binary operation exponentiation. Specifically, if the +/// {{Optional}} math - The symbol for the binary operation exponentiation. Specifically, if the /// value of the left operand is L and the value of the right operand (the exponent) is /// R, multiply L by itself R times. (Fractional and negative exponents are also legal.) Blockly.Msg.MATH_POWER_SYMBOL = '^'; @@ -386,7 +396,7 @@ Blockly.Msg.MATH_TRIG_ACOS = 'acos'; /// [https://en.wikipedia.org/wiki/Trigonometric_functions#Sine.2C_cosine_and_tangent tangent]. Blockly.Msg.MATH_TRIG_ATAN = 'atan'; -/// url - Information about addition, subtraction, multiplication, division, and exponentiation. +/// {{Optional}} url - Information about addition, subtraction, multiplication, division, and exponentiation. Blockly.Msg.MATH_ARITHMETIC_HELPURL = 'https://en.wikipedia.org/wiki/Arithmetic'; /// tooltip - See [https://en.wikipedia.org/wiki/Addition https://en.wikipedia.org/wiki/Addition]. Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_ADD = 'Return the sum of the two numbers.'; @@ -399,7 +409,7 @@ Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_DIVIDE = 'Return the quotient of the two num /// tooltip - See [https://en.wikipedia.org/wiki/Exponentiation https://en.wikipedia.org/wiki/Exponentiation]. Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_POWER = 'Return the first number raised to the power of the second number.'; -/// url - Information about the square root operation. +/// {{Optional}} url - Information about the square root operation. Blockly.Msg.MATH_SINGLE_HELPURL = 'https://en.wikipedia.org/wiki/Square_root'; /// dropdown - This computes the positive [https://en.wikipedia.org/wiki/Square_root square root] of its input. For example, the square root of 16 is 4. Blockly.Msg.MATH_SINGLE_OP_ROOT = 'square root'; @@ -421,7 +431,7 @@ Blockly.Msg.MATH_SINGLE_TOOLTIP_EXP = 'Return e to the power of a number.'; /// tooltip - Multiplies 10 by itself n times, where n is the single numeric input. Blockly.Msg.MATH_SINGLE_TOOLTIP_POW10 = 'Return 10 to the power of a number.'; -/// url - Information about the trigonometric functions sine, cosine, tangent, and their inverses (ideally using degrees, not radians). +/// {{Optional}} url - Information about the trigonometric functions sine, cosine, tangent, and their inverses (ideally using degrees, not radians). Blockly.Msg.MATH_TRIG_HELPURL = 'https://en.wikipedia.org/wiki/Trigonometric_functions'; /// tooltip - Return the [https://en.wikipedia.org/wiki/Trigonometric_functions#Sine.2C_cosine_and_tangent sine] of an [https://en.wikipedia.org/wiki/Degree_(angle) angle in degrees], not radians. Blockly.Msg.MATH_TRIG_TOOLTIP_SIN = 'Return the sine of a degree (not radian).'; @@ -436,7 +446,7 @@ Blockly.Msg.MATH_TRIG_TOOLTIP_ACOS = 'Return the arccosine of a number.'; /// tooltip - The [https://en.wikipedia.org/wiki/Inverse_trigonometric_functions inverse] of the [https://en.wikipedia.org/wiki/Cosine#Sine.2C_cosine_and_tangent tangent] function, using [https://en.wikipedia.org/wiki/Degree_(angle) degrees], not radians. Blockly.Msg.MATH_TRIG_TOOLTIP_ATAN = 'Return the arctangent of a number.'; -/// url - Information about the mathematical constants Pi (π), e, the golden ratio (φ), √ 2, √ 1/2, and infinity (∞). +/// {{Optional}} url - Information about the mathematical constants Pi (π), e, the golden ratio (φ), √ 2, √ 1/2, and infinity (∞). Blockly.Msg.MATH_CONSTANT_HELPURL = 'https://en.wikipedia.org/wiki/Mathematical_constant'; /// tooltip - Provides the specified [https://en.wikipedia.org/wiki/Mathematical_constant mathematical constant]. Blockly.Msg.MATH_CONSTANT_TOOLTIP = 'Return one of the common constants: π (3.141…), e (2.718…), φ (1.618…), sqrt(2) (1.414…), sqrt(½) (0.707…), or ∞ (infinity).'; @@ -457,7 +467,7 @@ Blockly.Msg.MATH_IS_DIVISIBLE_BY = 'is divisible by'; /// tooltip - This block lets the user specify via a dropdown menu whether to check if the numeric input is even, odd, prime, whole, positive, negative, or divisible by a given value. Blockly.Msg.MATH_IS_TOOLTIP = 'Check if a number is an even, odd, prime, whole, positive, negative, or if it is divisible by certain number. Returns true or false.'; -/// url - Information about incrementing (increasing the value of) a variable. +/// {{Optional}} url - Information about incrementing (increasing the value of) a variable. /// For other languages, just use the translation of the Wikipedia page about /// addition ([https://en.wikipedia.org/wiki/Addition https://en.wikipedia.org/wiki/Addition]). Blockly.Msg.MATH_CHANGE_HELPURL = 'https://en.wikipedia.org/wiki/Programming_idiom#Incrementing_a_counter'; @@ -469,7 +479,7 @@ Blockly.Msg.MATH_CHANGE_TITLE_ITEM = Blockly.Msg.VARIABLES_DEFAULT_NAME; /// tooltip - This updates the value of the variable by adding to it the following numeric input.\n\nParameters:\n* %1 - the name of the variable whose value should be increased. Blockly.Msg.MATH_CHANGE_TOOLTIP = 'Add a number to variable "%1".'; -/// url - Information about how numbers are rounded to the nearest integer +/// {{Optional}} url - Information about how numbers are rounded to the nearest integer Blockly.Msg.MATH_ROUND_HELPURL = 'https://en.wikipedia.org/wiki/Rounding'; /// tooltip - See [https://en.wikipedia.org/wiki/Rounding https://en.wikipedia.org/wiki/Rounding]. Blockly.Msg.MATH_ROUND_TOOLTIP = 'Round a number up or down.'; @@ -480,7 +490,7 @@ Blockly.Msg.MATH_ROUND_OPERATOR_ROUNDUP = 'round up'; /// dropdown - This rounds its input down to the nearest whole number. For example, if the input was 3.8, the result would be 3. Blockly.Msg.MATH_ROUND_OPERATOR_ROUNDDOWN = 'round down'; -/// url - Information about applying a function to a list of numbers. (We were unable to find such information in English. Feel free to skip this and any other URLs that are difficult.) +/// {{Optional}} url - Information about applying a function to a list of numbers. (We were unable to find such information in English. Feel free to skip this and any other URLs that are difficult.) Blockly.Msg.MATH_ONLIST_HELPURL = ''; /// dropdown - This computes the sum of the numeric elements in the list. For example, the sum of the list {1, 4} is 5. Blockly.Msg.MATH_ONLIST_OPERATOR_SUM = 'sum of list'; @@ -515,14 +525,14 @@ Blockly.Msg.MATH_ONLIST_OPERATOR_RANDOM = 'random item of list'; /// tooltip - Please use same term for 'random' as in previous entry. Blockly.Msg.MATH_ONLIST_TOOLTIP_RANDOM = 'Return a random element from the list.'; -/// url - information about the modulo (remainder) operation. +/// {{Optional}} url - information about the modulo (remainder) operation. Blockly.Msg.MATH_MODULO_HELPURL = 'https://en.wikipedia.org/wiki/Modulo_operation'; /// block text - Title of block providing the remainder when dividing the first numerical input by the second. For example, the remainder of 10 divided by 3 is 1.\n\nParameters:\n* %1 - the dividend (10, in our example)\n* %2 - the divisor (3 in our example). Blockly.Msg.MATH_MODULO_TITLE = 'remainder of %1 ÷ %2'; /// tooltip - For example, the remainder of 10 divided by 3 is 1. Blockly.Msg.MATH_MODULO_TOOLTIP = 'Return the remainder from dividing the two numbers.'; -/// url - Information about constraining a numeric value to be in a specific range. (The English URL is not ideal. Recall that translating URLs is the lowest priority.) +/// {{Optional}} url - Information about constraining a numeric value to be in a specific range. (The English URL is not ideal. Recall that translating URLs is the lowest priority.) Blockly.Msg.MATH_CONSTRAIN_HELPURL = 'https://en.wikipedia.org/wiki/Clamping_(graphics)'; /// block text - The title of the block that '''constrain'''s (forces) a number to be in a given range. ///For example, if the number 150 is constrained to be between 5 and 100, the result will be 100. @@ -531,14 +541,14 @@ Blockly.Msg.MATH_CONSTRAIN_TITLE = 'constrain %1 low %2 high %3'; /// tooltip - This compares a number ''x'' to a low value ''L'' and a high value ''H''. If ''x'' is less then ''L'', the result is ''L''. If ''x'' is greater than ''H'', the result is ''H''. Otherwise, the result is ''x''. Blockly.Msg.MATH_CONSTRAIN_TOOLTIP = 'Constrain a number to be between the specified limits (inclusive).'; -/// url - Information about how computers generate random numbers. +/// {{Optional}} url - Information about how computers generate random numbers. Blockly.Msg.MATH_RANDOM_INT_HELPURL = 'https://en.wikipedia.org/wiki/Random_number_generation'; /// block text - The title of the block that generates a random integer (whole number) in the specified range. For example, if the range is from 5 to 7, this returns 5, 6, or 7 with equal likelihood. %1 is a placeholder for the lower number, %2 is the placeholder for the larger number. Blockly.Msg.MATH_RANDOM_INT_TITLE = 'random integer from %1 to %2'; /// tooltip - Return a random integer between two values specified as inputs. For example, if one input was 7 and another 9, any of the numbers 7, 8, or 9 could be produced. Blockly.Msg.MATH_RANDOM_INT_TOOLTIP = 'Return a random integer between the two specified limits, inclusive.'; -/// url - Information about how computers generate random numbers (specifically, numbers in the range from 0 to just below 1). +/// {{Optional}} url - Information about how computers generate random numbers (specifically, numbers in the range from 0 to just below 1). Blockly.Msg.MATH_RANDOM_FLOAT_HELPURL = 'https://en.wikipedia.org/wiki/Random_number_generation'; /// block text - The title of the block that generates a random number greater than or equal to 0 and less than 1. Blockly.Msg.MATH_RANDOM_FLOAT_TITLE_RANDOM = 'random fraction'; @@ -546,12 +556,12 @@ Blockly.Msg.MATH_RANDOM_FLOAT_TITLE_RANDOM = 'random fraction'; Blockly.Msg.MATH_RANDOM_FLOAT_TOOLTIP = 'Return a random fraction between 0.0 (inclusive) and 1.0 (exclusive).'; // Text Blocks. -/// url - Information about how computers represent text (sometimes referred to as ''string''s). +/// {{Optional}} url - Information about how computers represent text (sometimes referred to as ''string''s). Blockly.Msg.TEXT_TEXT_HELPURL = 'https://en.wikipedia.org/wiki/String_(computer_science)'; /// tooltip - See [https://github.com/google/blockly/wiki/Text https://github.com/google/blockly/wiki/Text]. Blockly.Msg.TEXT_TEXT_TOOLTIP = 'A letter, word, or line of text.'; -/// url - Information on concatenating/appending pieces of text. +/// {{Optional}} url - Information on concatenating/appending pieces of text. Blockly.Msg.TEXT_JOIN_HELPURL = 'https://github.com/google/blockly/wiki/Text#text-creation'; /// block text - See [https://github.com/google/blockly/wiki/Text#text-creation https://github.com/google/blockly/wiki/Text#text-creation]. Blockly.Msg.TEXT_JOIN_TITLE_CREATEWITH = 'create text with'; @@ -566,7 +576,7 @@ Blockly.Msg.TEXT_CREATE_JOIN_ITEM_TITLE_ITEM = Blockly.Msg.VARIABLES_DEFAULT_NAM /// block text - See [https://github.com/google/blockly/wiki/Text#text-creation https://github.com/google/blockly/wiki/Text#text-creation], specifically the last picture in the 'Text creation' section. Blockly.Msg.TEXT_CREATE_JOIN_ITEM_TOOLTIP = 'Add an item to the text.'; -/// url - This and the other text-related URLs are going to be hard to translate. As always, it is okay to leave untranslated or paste in the English-language URL. For these URLs, you might also consider a general URL about how computers represent text (such as the translation of [https://en.wikipedia.org/wiki/String_(computer_science) this Wikipedia page]). +/// {{Optional}} url - This and the other text-related URLs are going to be hard to translate. As always, it is okay to leave untranslated or paste in the English-language URL. For these URLs, you might also consider a general URL about how computers represent text (such as the translation of [https://en.wikipedia.org/wiki/String_(computer_science) this Wikipedia page]). Blockly.Msg.TEXT_APPEND_HELPURL = 'https://github.com/google/blockly/wiki/Text#text-modification'; /// block input text - Message that the variable name at %1 will have the item at %2 appended to it. /// [[File:blockly-append-text.png]] @@ -575,7 +585,7 @@ Blockly.Msg.TEXT_APPEND_VARIABLE = Blockly.Msg.VARIABLES_DEFAULT_NAME; /// tooltip - See [https://github.com/google/blockly/wiki/Text#text-modification https://github.com/google/blockly/wiki/Text#text-modification] for more information.\n\nParameters:\n* %1 - the name of the variable to which text should be appended Blockly.Msg.TEXT_APPEND_TOOLTIP = 'Append some text to variable "%1".'; -/// url - Information about text on computers (usually referred to as 'strings'). +/// {{Optional}} url - Information about text on computers (usually referred to as 'strings'). Blockly.Msg.TEXT_LENGTH_HELPURL = 'https://github.com/google/blockly/wiki/Text#text-modification'; /// block text - See [https://github.com/google/blockly/wiki/Text#text-length https://github.com/google/blockly/wiki/Text#text-length]. /// \n\nParameters:\n* %1 - the piece of text to take the length of @@ -583,7 +593,7 @@ Blockly.Msg.TEXT_LENGTH_TITLE = 'length of %1'; /// tooltip - See [https://github.com/google/blockly/wiki/Text#text-length https://github.com/google/blockly/wiki/Text#text-length]. Blockly.Msg.TEXT_LENGTH_TOOLTIP = 'Returns the number of letters (including spaces) in the provided text.'; -/// url - Information about empty pieces of text on computers (usually referred to as 'empty strings'). +/// {{Optional}} url - Information about empty pieces of text on computers (usually referred to as 'empty strings'). Blockly.Msg.TEXT_ISEMPTY_HELPURL = 'https://github.com/google/blockly/wiki/Text#checking-for-empty-text'; /// block text - See [https://github.com/google/blockly/wiki/Text#checking-for-empty-text https://github.com/google/blockly/wiki/Text#checking-for-empty-text]. /// \n\nParameters:\n* %1 - the piece of text to test for emptiness @@ -591,7 +601,7 @@ Blockly.Msg.TEXT_ISEMPTY_TITLE = '%1 is empty'; /// tooltip - See [https://github.com/google/blockly/wiki/Text#checking-for-empty-text https://github.com/google/blockly/wiki/Text#checking-for-empty-text]. Blockly.Msg.TEXT_ISEMPTY_TOOLTIP = 'Returns true if the provided text is empty.'; -/// url - Information about finding a character in a piece of text. +/// {{Optional}} url - Information about finding a character in a piece of text. Blockly.Msg.TEXT_INDEXOF_HELPURL = 'https://github.com/google/blockly/wiki/Text#finding-text'; /// tooltip - %1 will be replaced by either the number 0 or -1 depending on the indexing mode. See [https://github.com/google/blockly/wiki/Text#finding-text https://github.com/google/blockly/wiki/Text#finding-text]. Blockly.Msg.TEXT_INDEXOF_TOOLTIP = 'Returns the index of the first/last occurrence of the first text in the second text. Returns %1 if text is not found.'; @@ -616,7 +626,7 @@ Blockly.Msg.TEXT_INDEXOF_OPERATOR_FIRST = 'find first occurrence of text'; /// [[File:Blockly-find-text.png]]. Blockly.Msg.TEXT_INDEXOF_OPERATOR_LAST = 'find last occurrence of text'; -/// url - Information about extracting characters (letters, number, symbols, etc.) from text. +/// {{Optional}} url - Information about extracting characters (letters, number, symbols, etc.) from text. Blockly.Msg.TEXT_CHARAT_HELPURL = 'https://github.com/google/blockly/wiki/Text#extracting-text'; /// block text - Text for a block to extract a letter (or number, /// punctuation character, etc.) from a string, as shown below. %1 is added by @@ -668,7 +678,7 @@ Blockly.Msg.TEXT_CHARAT_TOOLTIP = 'Returns the letter at the specified position. /// See [https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text /// https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text]. Blockly.Msg.TEXT_GET_SUBSTRING_TOOLTIP = 'Returns a specified portion of the text.'; -/// url - Information about extracting characters from text. Reminder: urls are the +/// {{Optional}} url - Information about extracting characters from text. Reminder: urls are the /// lowest priority translations. Feel free to skip. Blockly.Msg.TEXT_GET_SUBSTRING_HELPURL = 'https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text'; /// block text - Precedes a piece of text from which a portion should be extracted. @@ -684,7 +694,7 @@ Blockly.Msg.TEXT_GET_SUBSTRING_START_FROM_START = 'get substring from letter #'; /// position) of the beginning of the region of text that should be obtained from the preceding /// piece of text. See [https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text /// https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text]. -/// Note: If {{msg-Blockly|ORDINAL_NUMBER_SUFFIX}} is defined, it will +/// Note: If {{msg-blockly|ORDINAL_NUMBER_SUFFIX}} is defined, it will /// automatically appear ''after'' this and any other /// [https://translatewiki.net/wiki/Translating:Blockly#Ordinal_numbers ordinal numbers] /// on this block. @@ -722,7 +732,7 @@ Blockly.Msg.TEXT_GET_SUBSTRING_END_LAST = 'to last letter'; /// [[File:Blockly-get-substring.png]] Blockly.Msg.TEXT_GET_SUBSTRING_TAIL = ''; -/// url - Information about the case of letters (upper-case and lower-case). +/// {{Optional}} url - Information about the case of letters (upper-case and lower-case). Blockly.Msg.TEXT_CHANGECASE_HELPURL = 'https://github.com/google/blockly/wiki/Text#adjusting-text-case'; /// tooltip - Describes a block to adjust the case of letters. For more information on this block, /// see [https://github.com/google/blockly/wiki/Text#adjusting-text-case @@ -739,7 +749,7 @@ Blockly.Msg.TEXT_CHANGECASE_OPERATOR_LOWERCASE = 'to lower case'; /// block text - Indicates that the first letter of each of the following words should be capitalized and the rest converted to lower-case. If your language does not use case, you may indicate that this is not applicable to your language. For more information on this block, see [https://github.com/google/blockly/wiki/Text#adjusting-text-case https://github.com/google/blockly/wiki/Text#adjusting-text-case]. Blockly.Msg.TEXT_CHANGECASE_OPERATOR_TITLECASE = 'to Title Case'; -/// url - Information about trimming (removing) text off the beginning and ends of pieces of text. +/// {{Optional}} url - Information about trimming (removing) text off the beginning and ends of pieces of text. Blockly.Msg.TEXT_TRIM_HELPURL = 'https://github.com/google/blockly/wiki/Text#trimming-removing-spaces'; /// tooltip - See [https://github.com/google/blockly/wiki/Text#trimming-removing-spaces /// https://github.com/google/blockly/wiki/Text#trimming-removing-spaces]. @@ -761,7 +771,7 @@ Blockly.Msg.TEXT_TRIM_OPERATOR_LEFT = 'trim spaces from left side of'; /// Note that in right-to-left scripts, this will remove spaces from the left side. Blockly.Msg.TEXT_TRIM_OPERATOR_RIGHT = 'trim spaces from right side of'; -/// url - Information about displaying text on computers. +/// {{Optional}} url - Information about displaying text on computers. Blockly.Msg.TEXT_PRINT_HELPURL = 'https://github.com/google/blockly/wiki/Text#printing-text'; /// block text - Display the input on the screen. See /// [https://github.com/google/blockly/wiki/Text#printing-text @@ -771,7 +781,7 @@ Blockly.Msg.TEXT_PRINT_TITLE = 'print %1'; /// tooltip - See [https://github.com/google/blockly/wiki/Text#printing-text /// https://github.com/google/blockly/wiki/Text#printing-text]. Blockly.Msg.TEXT_PRINT_TOOLTIP = 'Print the specified text, number or other value.'; -/// url - Information about getting text from users. +/// {{Optional}} url - Information about getting text from users. Blockly.Msg.TEXT_PROMPT_HELPURL = 'https://github.com/google/blockly/wiki/Text#getting-input-from-the-user'; /// dropdown - Specifies that a piece of text should be requested from the user with /// the following message. See [https://github.com/google/blockly/wiki/Text#printing-text @@ -793,7 +803,7 @@ Blockly.Msg.TEXT_PROMPT_TOOLTIP_TEXT = 'Prompt for user for some text.'; /// block text - Title of a block that counts the number of instances of /// a smaller pattern (%1) inside a longer string (%2). Blockly.Msg.TEXT_COUNT_MESSAGE0 = 'count %1 in %2'; -/// url - Information about counting how many times a string appears in another string. +/// {{Optional}} url - Information about counting how many times a string appears in another string. Blockly.Msg.TEXT_COUNT_HELPURL = 'https://github.com/google/blockly/wiki/Text#counting-substrings'; /// tooltip - Short description of a block that counts how many times some text occurs within some other text. Blockly.Msg.TEXT_COUNT_TOOLTIP = 'Count how many times some text occurs within some other text.'; @@ -801,7 +811,7 @@ Blockly.Msg.TEXT_COUNT_TOOLTIP = 'Count how many times some text occurs within s /// block text - Title of a block that returns a copy of text (%3) with all /// instances of some smaller text (%1) replaced with other text (%2). Blockly.Msg.TEXT_REPLACE_MESSAGE0 = 'replace %1 with %2 in %3'; -/// url - Information about replacing each copy text (or string, in computer lingo) with other text. +/// {{Optional}} url - Information about replacing each copy text (or string, in computer lingo) with other text. Blockly.Msg.TEXT_REPLACE_HELPURL = 'https://github.com/google/blockly/wiki/Text#replacing-substrings'; /// tooltip - Short description of a block that replaces copies of text in a large text with other text. Blockly.Msg.TEXT_REPLACE_TOOLTIP = 'Replace all occurances of some text within some other text.'; @@ -809,20 +819,20 @@ Blockly.Msg.TEXT_REPLACE_TOOLTIP = 'Replace all occurances of some text within s /// block text - Title of block that returns a copy of text (%1) with the order /// of letters and characters reversed. Blockly.Msg.TEXT_REVERSE_MESSAGE0 = 'reverse %1'; -/// url - Information about reversing a letters/characters in text. +/// {{Optional}} url - Information about reversing a letters/characters in text. Blockly.Msg.TEXT_REVERSE_HELPURL = 'https://github.com/google/blockly/wiki/Text#reversing-text'; /// tooltip - See [https://github.com/google/blockly/wiki/Text]. Blockly.Msg.TEXT_REVERSE_TOOLTIP = 'Reverses the order of the characters in the text.'; // Lists Blocks. -/// url - Information on empty lists. +/// {{Optional}} url - Information on empty lists. Blockly.Msg.LISTS_CREATE_EMPTY_HELPURL = 'https://github.com/google/blockly/wiki/Lists#create-empty-list'; /// block text - See [https://github.com/google/blockly/wiki/Lists#create-empty-list https://github.com/google/blockly/wiki/Lists#create-empty-list]. Blockly.Msg.LISTS_CREATE_EMPTY_TITLE = 'create empty list'; /// block text - See [https://github.com/google/blockly/wiki/Lists#create-empty-list https://github.com/google/blockly/wiki/Lists#create-empty-list]. Blockly.Msg.LISTS_CREATE_EMPTY_TOOLTIP = 'Returns a list, of length 0, containing no data records'; -/// url - Information on building lists. +/// {{Optional}} url - Information on building lists. Blockly.Msg.LISTS_CREATE_WITH_HELPURL = 'https://github.com/google/blockly/wiki/Lists#create-list-with'; /// tooltip - See [https://github.com/google/blockly/wiki/Lists#create-list-with https://github.com/google/blockly/wiki/Lists#create-list-with]. Blockly.Msg.LISTS_CREATE_WITH_TOOLTIP = 'Create a list with any number of items.'; @@ -836,16 +846,16 @@ Blockly.Msg.LISTS_CREATE_WITH_ITEM_TITLE = Blockly.Msg.VARIABLES_DEFAULT_NAME; /// tooltip - See [https://github.com/google/blockly/wiki/Lists#changing-number-of-inputs https://github.com/google/blockly/wiki/Lists#changing-number-of-inputs]. Blockly.Msg.LISTS_CREATE_WITH_ITEM_TOOLTIP = 'Add an item to the list.'; -/// url - Information about [https://github.com/google/blockly/wiki/Lists#create-list-with creating a list with multiple copies of a single item]. +/// {{Optional}} url - Information about [https://github.com/google/blockly/wiki/Lists#create-list-with creating a list with multiple copies of a single item]. Blockly.Msg.LISTS_REPEAT_HELPURL = 'https://github.com/google/blockly/wiki/Lists#create-list-with'; -/// url - See [https://github.com/google/blockly/wiki/Lists#create-list-with creating a list with multiple copies of a single item]. +/// {{Optional}} url - See [https://github.com/google/blockly/wiki/Lists#create-list-with creating a list with multiple copies of a single item]. Blockly.Msg.LISTS_REPEAT_TOOLTIP = 'Creates a list consisting of the given value repeated the specified number of times.'; /// block text - See [https://github.com/google/blockly/wiki/Lists#create-list-with /// https://github.com/google/blockly/wiki/Lists#create-list-with]. ///\n\nParameters:\n* %1 - the item (text) to be repeated\n* %2 - the number of times to repeat it Blockly.Msg.LISTS_REPEAT_TITLE = 'create list with item %1 repeated %2 times'; -/// url - Information about how the length of a list is computed (i.e., by the total number of elements, not the number of different elements). +/// {{Optional}} url - Information about how the length of a list is computed (i.e., by the total number of elements, not the number of different elements). Blockly.Msg.LISTS_LENGTH_HELPURL = 'https://github.com/google/blockly/wiki/Lists#length-of'; /// block text - See [https://github.com/google/blockly/wiki/Lists#length-of https://github.com/google/blockly/wiki/Lists#length-of]. /// \n\nParameters:\n* %1 - the list whose length is desired @@ -853,7 +863,7 @@ Blockly.Msg.LISTS_LENGTH_TITLE = 'length of %1'; /// tooltip - See [https://github.com/google/blockly/wiki/Lists#length-of https://github.com/google/blockly/wiki/Lists#length-of Blockly:Lists:length of]. Blockly.Msg.LISTS_LENGTH_TOOLTIP = 'Returns the length of a list.'; -/// url - See [https://github.com/google/blockly/wiki/Lists#is-empty https://github.com/google/blockly/wiki/Lists#is-empty]. +/// {{Optional}} url - See [https://github.com/google/blockly/wiki/Lists#is-empty https://github.com/google/blockly/wiki/Lists#is-empty]. Blockly.Msg.LISTS_ISEMPTY_HELPURL = 'https://github.com/google/blockly/wiki/Lists#is-empty'; /// block text - See [https://github.com/google/blockly/wiki/Lists#is-empty /// https://github.com/google/blockly/wiki/Lists#is-empty]. @@ -866,7 +876,7 @@ Blockly.Msg.LISTS_ISEMPTY_TOOLTIP = 'Returns true if the list is empty.'; /// block text - Title of blocks operating on [https://github.com/google/blockly/wiki/Lists lists]. Blockly.Msg.LISTS_INLIST = 'in list'; -/// url - See [https://github.com/google/blockly/wiki/Lists#getting-items-from-a-list +/// {{Optional}} url - See [https://github.com/google/blockly/wiki/Lists#getting-items-from-a-list /// https://github.com/google/blockly/wiki/Lists#getting-items-from-a-list]. Blockly.Msg.LISTS_INDEX_OF_HELPURL = 'https://github.com/google/blockly/wiki/Lists#getting-items-from-a-list'; Blockly.Msg.LISTS_INDEX_OF_INPUT_IN_LIST = Blockly.Msg.LISTS_INLIST; @@ -899,7 +909,7 @@ Blockly.Msg.LISTS_GET_INDEX_GET_REMOVE = 'get and remove'; Blockly.Msg.LISTS_GET_INDEX_REMOVE = 'remove'; /// dropdown - Indicates that an index relative to the front of the list should be used to /// [https://github.com/google/blockly/wiki/Lists#getting-a-single-item get and/or remove -/// an item from a list]. Note: If {{msg-Blockly|ORDINAL_NUMBER_SUFFIX}} is defined, it will +/// an item from a list]. Note: If {{msg-blockly|ORDINAL_NUMBER_SUFFIX}} is defined, it will /// automatically appear ''after'' this number (and any other ordinal numbers on this block). /// See [[Translating:Blockly#Ordinal_numbers]] for more information on ordinal numbers in Blockly. /// [[File:Blockly-list-get-item.png]] @@ -954,7 +964,7 @@ Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_FIRST = 'Removes the first item in a Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_LAST = 'Removes the last item in a list.'; /// tooltip - See [https://github.com/google/blockly/wiki/Lists#getting-and-removing-an-item] (for remove and return) and [https://github.com/google/blockly/wiki/Lists#getting-a-single-item] for 'random'. Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_RANDOM = 'Removes a random item in a list.'; -/// url - Information about putting items in lists. +/// {{Optional}} url - Information about putting items in lists. Blockly.Msg.LISTS_SET_INDEX_HELPURL = 'https://github.com/google/blockly/wiki/Lists#in-list--set'; Blockly.Msg.LISTS_SET_INDEX_INPUT_IN_LIST = Blockly.Msg.LISTS_INLIST; /// block text - [https://github.com/google/blockly/wiki/Lists#in-list--set @@ -985,14 +995,14 @@ Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_LAST = 'Append the item to the end of /// tooltip - See [https://github.com/google/blockly/wiki/Lists#getting-a-single-item} (even though the page describes the "get" block, the idea is the same for the "insert" block). Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_RANDOM = 'Inserts the item randomly in a list.'; -/// url - Information describing extracting a sublist from an existing list. +/// {{Optional}} url - Information describing extracting a sublist from an existing list. Blockly.Msg.LISTS_GET_SUBLIST_HELPURL = 'https://github.com/google/blockly/wiki/Lists#getting-a-sublist'; Blockly.Msg.LISTS_GET_SUBLIST_INPUT_IN_LIST = Blockly.Msg.LISTS_INLIST; /// dropdown - Indicates that an index relative to the front of the list should be used /// to specify the beginning of the range from which to /// [https://github.com/google/blockly/wiki/Lists#getting-a-sublist get a sublist]. /// [[File:Blockly-get-sublist.png]] -/// Note: If {{msg-Blockly|ORDINAL_NUMBER_SUFFIX}} is defined, it will +/// Note: If {{msg-blockly|ORDINAL_NUMBER_SUFFIX}} is defined, it will /// automatically appear ''after'' this number (and any other ordinal numbers on this block). /// See [[Translating:Blockly#Ordinal_numbers]] for more information on ordinal numbers in Blockly. Blockly.Msg.LISTS_GET_SUBLIST_START_FROM_START = 'get sub-list from #'; @@ -1031,7 +1041,7 @@ Blockly.Msg.LISTS_GET_SUBLIST_TAIL = ''; /// [[File:Blockly-get-sublist.png]] Blockly.Msg.LISTS_GET_SUBLIST_TOOLTIP = 'Creates a copy of the specified portion of a list.'; -/// {{optional}}\nurl - Information describing sorting a list. +/// {{Optional}} url - Information describing sorting a list. Blockly.Msg.LISTS_SORT_HELPURL = 'https://github.com/google/blockly/wiki/Lists#sorting-a-list'; /// Sort as type %1 (numeric or alphabetic) in order %2 (ascending or descending) a list of items %3.\n{{Identical|Sort}} Blockly.Msg.LISTS_SORT_TITLE = 'sort %1 %2 %3'; @@ -1048,7 +1058,7 @@ Blockly.Msg.LISTS_SORT_TYPE_TEXT = 'alphabetic'; /// sort by treating each item alphabetically, ignoring differences in case. Blockly.Msg.LISTS_SORT_TYPE_IGNORECASE = 'alphabetic, ignore case'; -/// url - Information describing splitting text into a list, or joining a list into text. +/// {{Optional}} url - Information describing splitting text into a list, or joining a list into text. Blockly.Msg.LISTS_SPLIT_HELPURL = 'https://github.com/google/blockly/wiki/Lists#splitting-strings-and-joining-lists'; /// dropdown - Indicates that text will be split up into a list (e.g. "a-b-c" -> ["a", "b", "c"]). Blockly.Msg.LISTS_SPLIT_LIST_FROM_TEXT = 'make list from text'; @@ -1063,7 +1073,7 @@ Blockly.Msg.LISTS_SPLIT_TOOLTIP_SPLIT = 'Split text into a list of texts, breaki /// https://github.com/google/blockly/wiki/Lists#make-text-from-list] for more information. Blockly.Msg.LISTS_SPLIT_TOOLTIP_JOIN = 'Join a list of texts into one text, separated by a delimiter.'; -/// url - Information describing reversing a list. +/// {{Optional}} url - Information describing reversing a list. Blockly.Msg.LISTS_REVERSE_HELPURL = 'https://github.com/google/blockly/wiki/Lists#reversing-a-list'; /// block text - Title of block that returns a copy of a list (%1) with the order of items reversed. Blockly.Msg.LISTS_REVERSE_MESSAGE0 = 'reverse %1'; @@ -1077,7 +1087,7 @@ Blockly.Msg.LISTS_REVERSE_TOOLTIP = 'Reverse a copy of a list.'; Blockly.Msg.ORDINAL_NUMBER_SUFFIX = ''; // Variables Blocks. -/// url - Information about ''variables'' in computer programming. Consider using your language's translation of [https://en.wikipedia.org/wiki/Variable_(computer_science) https://en.wikipedia.org/wiki/Variable_(computer_science)], if it exists. +/// {{Optional}} url - Information about ''variables'' in computer programming. Consider using your language's translation of [https://en.wikipedia.org/wiki/Variable_(computer_science) https://en.wikipedia.org/wiki/Variable_(computer_science)], if it exists. Blockly.Msg.VARIABLES_GET_HELPURL = 'https://github.com/google/blockly/wiki/Variables#get'; /// tooltip - This gets the value of the named variable without modifying it. Blockly.Msg.VARIABLES_GET_TOOLTIP = 'Returns the value of this variable.'; @@ -1085,7 +1095,7 @@ Blockly.Msg.VARIABLES_GET_TOOLTIP = 'Returns the value of this variable.'; /// \n\nParameters:\n* %1 - the name of the variable. Blockly.Msg.VARIABLES_GET_CREATE_SET = 'Create "set %1"'; -/// url - Information about ''variables'' in computer programming. Consider using your language's translation of [https://en.wikipedia.org/wiki/Variable_(computer_science) https://en.wikipedia.org/wiki/Variable_(computer_science)], if it exists. +/// {{Optional}} url - Information about ''variables'' in computer programming. Consider using your language's translation of [https://en.wikipedia.org/wiki/Variable_(computer_science) https://en.wikipedia.org/wiki/Variable_(computer_science)], if it exists. Blockly.Msg.VARIABLES_SET_HELPURL = 'https://github.com/google/blockly/wiki/Variables#set'; /// block text - Change the value of a mathematical variable: '''set [the value of] x to 7'''.\n\nParameters:\n* %1 - the name of the variable.\n* %2 - the value to be assigned. Blockly.Msg.VARIABLES_SET = 'set %1 to %2'; @@ -1096,7 +1106,7 @@ Blockly.Msg.VARIABLES_SET_TOOLTIP = 'Sets this variable to be equal to the input Blockly.Msg.VARIABLES_SET_CREATE_GET = 'Create "get %1"'; // Procedures Blocks. -/// url - Information about defining [https://en.wikipedia.org/wiki/Subroutine functions] that do not have return values. +/// {{Optional}} url - Information about defining [https://en.wikipedia.org/wiki/Subroutine functions] that do not have return values. Blockly.Msg.PROCEDURES_DEFNORETURN_HELPURL = 'https://en.wikipedia.org/wiki/Subroutine'; /// block text - This precedes the name of the function when defining it. See /// [https://blockly-demo.appspot.com/static/apps/code/index.html?lang=en#c84aoc this sample @@ -1107,7 +1117,7 @@ Blockly.Msg.PROCEDURES_DEFNORETURN_TITLE = 'to'; /// [https://blockly-demo.appspot.com/static/apps/code/index.html?lang=en#w7cfju this block]. /// The user will replace it with the function's name. Blockly.Msg.PROCEDURES_DEFNORETURN_PROCEDURE = 'do something'; -/// block text - This precedes the list of parameters on a function's defiition block. See +/// block text - This precedes the list of parameters on a function's definition block. See /// [https://blockly-demo.appspot.com/static/apps/code/index.html?lang=en#voztpd this sample /// function with parameters]. Blockly.Msg.PROCEDURES_BEFORE_PARAMS = 'with:'; @@ -1124,7 +1134,7 @@ Blockly.Msg.PROCEDURES_DEFNORETURN_DO = ''; Blockly.Msg.PROCEDURES_DEFNORETURN_TOOLTIP = 'Creates a function with no output.'; /// Placeholder text that the user is encouraged to replace with a description of what their function does. Blockly.Msg.PROCEDURES_DEFNORETURN_COMMENT = 'Describe this function...'; -/// url - Information about defining [https://en.wikipedia.org/wiki/Subroutine functions] that have return values. +/// {{Optional}} url - Information about defining [https://en.wikipedia.org/wiki/Subroutine functions] that have return values. Blockly.Msg.PROCEDURES_DEFRETURN_HELPURL = 'https://en.wikipedia.org/wiki/Subroutine'; Blockly.Msg.PROCEDURES_DEFRETURN_TITLE = Blockly.Msg.PROCEDURES_DEFNORETURN_TITLE; Blockly.Msg.PROCEDURES_DEFRETURN_PROCEDURE = Blockly.Msg.PROCEDURES_DEFNORETURN_PROCEDURE; @@ -1143,12 +1153,12 @@ Blockly.Msg.PROCEDURES_ALLOW_STATEMENTS = 'allow statements'; /// alert - The user has created a function with two parameters that have the same name. Every parameter must have a different name. Blockly.Msg.PROCEDURES_DEF_DUPLICATE_WARNING = 'Warning: This function has duplicate parameters.'; -/// url - Information about calling [https://en.wikipedia.org/wiki/Subroutine functions] that do not return values. +/// {{Optional}} url - Information about calling [https://en.wikipedia.org/wiki/Subroutine functions] that do not return values. Blockly.Msg.PROCEDURES_CALLNORETURN_HELPURL = 'https://en.wikipedia.org/wiki/Subroutine'; /// tooltip - This block causes the body (blocks inside) of the named function definition to be run. Blockly.Msg.PROCEDURES_CALLNORETURN_TOOLTIP = 'Run the user-defined function "%1".'; -/// url - Information about calling [https://en.wikipedia.org/wiki/Subroutine functions] that return values. +/// {{Optional}} url - Information about calling [https://en.wikipedia.org/wiki/Subroutine functions] that return values. Blockly.Msg.PROCEDURES_CALLRETURN_HELPURL = 'https://en.wikipedia.org/wiki/Subroutine'; /// tooltip - This block causes the body (blocks inside) of the named function definition to be run.\n\nParameters:\n* %1 - the name of the function. Blockly.Msg.PROCEDURES_CALLRETURN_TOOLTIP = 'Run the user-defined function "%1" and use its output.'; @@ -1179,7 +1189,7 @@ Blockly.Msg.PROCEDURES_CREATE_DO = 'Create "%1"'; /// tooltip - If the first value is true, this causes the second value to be returned /// immediately from the enclosing function. Blockly.Msg.PROCEDURES_IFRETURN_TOOLTIP = 'If a value is true, then return a second value.'; -/// {{optional}}\nurl - Information about guard clauses. +/// {{Optional}} url - Information about guard clauses. Blockly.Msg.PROCEDURES_IFRETURN_HELPURL = 'http://c2.com/cgi/wiki?GuardClause'; /// warning - This appears if the user tries to use this block outside of a function definition. Blockly.Msg.PROCEDURES_IFRETURN_WARNING = 'Warning: This block may be used only within a function definition.'; diff --git a/package.json b/package.json index 985c15170..d843adfd5 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,8 @@ "license": "Apache-2.0", "private": true, "devDependencies": { - "jshint": "latest" + "jshint": "latest", + "eslint": "^4.16" }, "jshintConfig": { "globalstrict": true, @@ -41,12 +42,9 @@ "unused": true }, "dependencies": { + "google-closure-library": "^20180204.0.0", "install": "^0.8.8", "npm": "^4.4.4", - "closure-library": "^1.43629075.2", "webdriverio": "^4.6.2" - }, - "devDependencies": { - "eslint": "2.9.0" } } diff --git a/php_compressed.js b/php_compressed.js index 219da66d3..67672e05d 100644 --- a/php_compressed.js +++ b/php_compressed.js @@ -7,12 +7,13 @@ Blockly.PHP.ORDER_ATOMIC=0;Blockly.PHP.ORDER_CLONE=1;Blockly.PHP.ORDER_NEW=1;Blo Blockly.PHP.ORDER_DIVISION=8.2;Blockly.PHP.ORDER_MODULUS=8.3;Blockly.PHP.ORDER_ADDITION=9.1;Blockly.PHP.ORDER_SUBTRACTION=9.2;Blockly.PHP.ORDER_STRING_CONCAT=9.3;Blockly.PHP.ORDER_BITWISE_SHIFT=10;Blockly.PHP.ORDER_RELATIONAL=11;Blockly.PHP.ORDER_EQUALITY=12;Blockly.PHP.ORDER_REFERENCE=13;Blockly.PHP.ORDER_BITWISE_AND=13;Blockly.PHP.ORDER_BITWISE_XOR=14;Blockly.PHP.ORDER_BITWISE_OR=15;Blockly.PHP.ORDER_LOGICAL_AND=16;Blockly.PHP.ORDER_LOGICAL_OR=17;Blockly.PHP.ORDER_IF_NULL=18; Blockly.PHP.ORDER_CONDITIONAL=19;Blockly.PHP.ORDER_ASSIGNMENT=20;Blockly.PHP.ORDER_LOGICAL_AND_WEAK=21;Blockly.PHP.ORDER_LOGICAL_XOR=22;Blockly.PHP.ORDER_LOGICAL_OR_WEAK=23;Blockly.PHP.ORDER_COMMA=24;Blockly.PHP.ORDER_NONE=99; Blockly.PHP.ORDER_OVERRIDES=[[Blockly.PHP.ORDER_MEMBER,Blockly.PHP.ORDER_FUNCTION_CALL],[Blockly.PHP.ORDER_MEMBER,Blockly.PHP.ORDER_MEMBER],[Blockly.PHP.ORDER_LOGICAL_NOT,Blockly.PHP.ORDER_LOGICAL_NOT],[Blockly.PHP.ORDER_MULTIPLICATION,Blockly.PHP.ORDER_MULTIPLICATION],[Blockly.PHP.ORDER_ADDITION,Blockly.PHP.ORDER_ADDITION],[Blockly.PHP.ORDER_LOGICAL_AND,Blockly.PHP.ORDER_LOGICAL_AND],[Blockly.PHP.ORDER_LOGICAL_OR,Blockly.PHP.ORDER_LOGICAL_OR]]; -Blockly.PHP.init=function(a){Blockly.PHP.definitions_=Object.create(null);Blockly.PHP.functionNames_=Object.create(null);Blockly.PHP.variableDB_?Blockly.PHP.variableDB_.reset():Blockly.PHP.variableDB_=new Blockly.Names(Blockly.PHP.RESERVED_WORDS_,"$");var b=[],c;a=Blockly.Variables.allVariables(a);for(var d=0;c=a[d];d++)c=c.name,b[d]=Blockly.PHP.variableDB_.getName(c,Blockly.Variables.NAME_TYPE)+";";Blockly.PHP.definitions_.variables=b.join("\n")}; -Blockly.PHP.finish=function(a){var b=[],c;for(c in Blockly.PHP.definitions_)b.push(Blockly.PHP.definitions_[c]);delete Blockly.PHP.definitions_;delete Blockly.PHP.functionNames_;Blockly.PHP.variableDB_.reset();return b.join("\n\n")+"\n\n\n"+a};Blockly.PHP.scrubNakedValue=function(a){return a+";\n"};Blockly.PHP.quote_=function(a){a=a.replace(/\\/g,"\\\\").replace(/\n/g,"\\\n").replace(/'/g,"\\'");return"'"+a+"'"}; +Blockly.PHP.init=function(a){Blockly.PHP.definitions_=Object.create(null);Blockly.PHP.functionNames_=Object.create(null);Blockly.PHP.variableDB_?Blockly.PHP.variableDB_.reset():Blockly.PHP.variableDB_=new Blockly.Names(Blockly.PHP.RESERVED_WORDS_,"$");Blockly.PHP.variableDB_.setVariableMap(a.getVariableMap());for(var b=[],c=Blockly.Variables.allDeveloperVariables(a),d=0;dc?Blockly.PHP.valueToCode(a,b,Blockly.PHP.ORDER_SUBTRACTION)||g:d?Blockly.PHP.valueToCode(a,b,Blockly.PHP.ORDER_UNARY_NEGATION)||g:Blockly.PHP.valueToCode(a,b,e)||g;if(Blockly.isNumber(a))a=parseFloat(a)+c,d&&(a=-a);else{if(0c&& -(a=a+" - "+-c,f=Blockly.PHP.ORDER_SUBTRACTION);d&&(a=c?"-("+a+")":"-"+a,f=Blockly.PHP.ORDER_UNARY_NEGATION);f=Math.floor(f);e=Math.floor(e);f&&e>=f&&(a="("+a+")")}return a};Blockly.PHP.colour={};Blockly.PHP.colour_picker=function(a){return["'"+a.getFieldValue("COLOUR")+"'",Blockly.PHP.ORDER_ATOMIC]};Blockly.PHP.colour_random=function(a){return[Blockly.PHP.provideFunction_("colour_random",["function "+Blockly.PHP.FUNCTION_NAME_PLACEHOLDER_+"() {"," return '#' . str_pad(dechex(mt_rand(0, 0xFFFFFF)), 6, '0', STR_PAD_LEFT);","}"])+"()",Blockly.PHP.ORDER_FUNCTION_CALL]}; +Blockly.PHP.getAdjusted=function(a,b,c,d,e){c=c||0;e=e||Blockly.PHP.ORDER_NONE;a.workspace.options.oneBasedIndex&&c--;var f=a.workspace.options.oneBasedIndex?"1":"0";a=0c?Blockly.PHP.valueToCode(a,b,Blockly.PHP.ORDER_SUBTRACTION)||f:d?Blockly.PHP.valueToCode(a,b,Blockly.PHP.ORDER_UNARY_NEGATION)||f:Blockly.PHP.valueToCode(a,b,e)||f;if(Blockly.isNumber(a))a=parseFloat(a)+c,d&&(a=-a);else{if(0c&& +(a=a+" - "+-c,g=Blockly.PHP.ORDER_SUBTRACTION);d&&(a=c?"-("+a+")":"-"+a,g=Blockly.PHP.ORDER_UNARY_NEGATION);g=Math.floor(g);e=Math.floor(e);g&&e>=g&&(a="("+a+")")}return a};Blockly.PHP.colour={};Blockly.PHP.colour_picker=function(a){return["'"+a.getFieldValue("COLOUR")+"'",Blockly.PHP.ORDER_ATOMIC]};Blockly.PHP.colour_random=function(a){return[Blockly.PHP.provideFunction_("colour_random",["function "+Blockly.PHP.FUNCTION_NAME_PLACEHOLDER_+"() {"," return '#' . str_pad(dechex(mt_rand(0, 0xFFFFFF)), 6, '0', STR_PAD_LEFT);","}"])+"()",Blockly.PHP.ORDER_FUNCTION_CALL]}; Blockly.PHP.colour_rgb=function(a){var b=Blockly.PHP.valueToCode(a,"RED",Blockly.PHP.ORDER_COMMA)||0,c=Blockly.PHP.valueToCode(a,"GREEN",Blockly.PHP.ORDER_COMMA)||0;a=Blockly.PHP.valueToCode(a,"BLUE",Blockly.PHP.ORDER_COMMA)||0;return[Blockly.PHP.provideFunction_("colour_rgb",["function "+Blockly.PHP.FUNCTION_NAME_PLACEHOLDER_+"($r, $g, $b) {"," $r = round(max(min($r, 100), 0) * 2.55);"," $g = round(max(min($g, 100), 0) * 2.55);"," $b = round(max(min($b, 100), 0) * 2.55);"," $hex = '#';"," $hex .= str_pad(dechex($r), 2, '0', STR_PAD_LEFT);", " $hex .= str_pad(dechex($g), 2, '0', STR_PAD_LEFT);"," $hex .= str_pad(dechex($b), 2, '0', STR_PAD_LEFT);"," return $hex;","}"])+"("+b+", "+c+", "+a+")",Blockly.PHP.ORDER_FUNCTION_CALL]}; Blockly.PHP.colour_blend=function(a){var b=Blockly.PHP.valueToCode(a,"COLOUR1",Blockly.PHP.ORDER_COMMA)||"'#000000'",c=Blockly.PHP.valueToCode(a,"COLOUR2",Blockly.PHP.ORDER_COMMA)||"'#000000'";a=Blockly.PHP.valueToCode(a,"RATIO",Blockly.PHP.ORDER_COMMA)||.5;return[Blockly.PHP.provideFunction_("colour_blend",["function "+Blockly.PHP.FUNCTION_NAME_PLACEHOLDER_+"($c1, $c2, $ratio) {"," $ratio = max(min($ratio, 1), 0);"," $r1 = hexdec(substr($c1, 1, 2));"," $g1 = hexdec(substr($c1, 3, 2));"," $b1 = hexdec(substr($c1, 5, 2));", @@ -45,11 +46,10 @@ Blockly.PHP.logic_operation=function(a){var b="AND"==a.getFieldValue("OP")?"&&": Blockly.PHP.logic_boolean=function(a){return["TRUE"==a.getFieldValue("BOOL")?"true":"false",Blockly.PHP.ORDER_ATOMIC]};Blockly.PHP.logic_null=function(a){return["null",Blockly.PHP.ORDER_ATOMIC]};Blockly.PHP.logic_ternary=function(a){var b=Blockly.PHP.valueToCode(a,"IF",Blockly.PHP.ORDER_CONDITIONAL)||"false",c=Blockly.PHP.valueToCode(a,"THEN",Blockly.PHP.ORDER_CONDITIONAL)||"null";a=Blockly.PHP.valueToCode(a,"ELSE",Blockly.PHP.ORDER_CONDITIONAL)||"null";return[b+" ? "+c+" : "+a,Blockly.PHP.ORDER_CONDITIONAL]};Blockly.PHP.loops={}; Blockly.PHP.controls_repeat_ext=function(a){var b=a.getField("TIMES")?String(Number(a.getFieldValue("TIMES"))):Blockly.PHP.valueToCode(a,"TIMES",Blockly.PHP.ORDER_ASSIGNMENT)||"0",c=Blockly.PHP.statementToCode(a,"DO");c=Blockly.PHP.addLoopTrap(c,a.id);a="";var d=Blockly.PHP.variableDB_.getDistinctName("count",Blockly.Variables.NAME_TYPE),e=b;b.match(/^\w+$/)||Blockly.isNumber(b)||(e=Blockly.PHP.variableDB_.getDistinctName("repeat_end",Blockly.Variables.NAME_TYPE),a+=e+" = "+b+";\n");return a+("for ("+ d+" = 0; "+d+" < "+e+"; "+d+"++) {\n"+c+"}\n")};Blockly.PHP.controls_repeat=Blockly.PHP.controls_repeat_ext;Blockly.PHP.controls_whileUntil=function(a){var b="UNTIL"==a.getFieldValue("MODE"),c=Blockly.PHP.valueToCode(a,"BOOL",b?Blockly.PHP.ORDER_LOGICAL_NOT:Blockly.PHP.ORDER_NONE)||"false",d=Blockly.PHP.statementToCode(a,"DO");d=Blockly.PHP.addLoopTrap(d,a.id);b&&(c="!"+c);return"while ("+c+") {\n"+d+"}\n"}; -Blockly.PHP.controls_for=function(a){var b=Blockly.PHP.variableDB_.getName(a.getFieldValue("VAR"),Blockly.Variables.NAME_TYPE),c=Blockly.PHP.valueToCode(a,"FROM",Blockly.PHP.ORDER_ASSIGNMENT)||"0",d=Blockly.PHP.valueToCode(a,"TO",Blockly.PHP.ORDER_ASSIGNMENT)||"0",e=Blockly.PHP.valueToCode(a,"BY",Blockly.PHP.ORDER_ASSIGNMENT)||"1",g=Blockly.PHP.statementToCode(a,"DO");g=Blockly.PHP.addLoopTrap(g,a.id);if(Blockly.isNumber(c)&&Blockly.isNumber(d)&&Blockly.isNumber(e)){var f=parseFloat(c)<=parseFloat(d); -a="for ("+b+" = "+c+"; "+b+(f?" <= ":" >= ")+d+"; "+b;b=Math.abs(parseFloat(e));a=(1==b?a+(f?"++":"--"):a+((f?" += ":" -= ")+b))+(") {\n"+g+"}\n")}else a="",f=c,c.match(/^\w+$/)||Blockly.isNumber(c)||(f=Blockly.PHP.variableDB_.getDistinctName(b+"_start",Blockly.Variables.NAME_TYPE),a+=f+" = "+c+";\n"),c=d,d.match(/^\w+$/)||Blockly.isNumber(d)||(c=Blockly.PHP.variableDB_.getDistinctName(b+"_end",Blockly.Variables.NAME_TYPE),a+=c+" = "+d+";\n"),d=Blockly.PHP.variableDB_.getDistinctName(b+"_inc",Blockly.Variables.NAME_TYPE), -a+=d+" = ",a=Blockly.isNumber(e)?a+(Math.abs(e)+";\n"):a+("abs("+e+");\n"),a=a+("if ("+f+" > "+c+") {\n")+(Blockly.PHP.INDENT+d+" = -"+d+";\n"),a+="}\n",a+="for ("+b+" = "+f+"; "+d+" >= 0 ? "+b+" <= "+c+" : "+b+" >= "+c+"; "+b+" += "+d+") {\n"+g+"}\n";return a}; -Blockly.PHP.controls_forEach=function(a){var b=Blockly.PHP.variableDB_.getName(a.getFieldValue("VAR"),Blockly.Variables.NAME_TYPE),c=Blockly.PHP.valueToCode(a,"LIST",Blockly.PHP.ORDER_ASSIGNMENT)||"[]",d=Blockly.PHP.statementToCode(a,"DO");d=Blockly.PHP.addLoopTrap(d,a.id);return""+("foreach ("+c+" as "+b+") {\n"+d+"}\n")}; -Blockly.PHP.controls_flow_statements=function(a){switch(a.getFieldValue("FLOW")){case "BREAK":return"break;\n";case "CONTINUE":return"continue;\n"}throw"Unknown flow statement.";};Blockly.PHP.math={};Blockly.PHP.math_number=function(a){a=parseFloat(a.getFieldValue("NUM"));Infinity==a?a="INF":-Infinity==a&&(a="-INF");return[a,Blockly.PHP.ORDER_ATOMIC]}; +Blockly.PHP.controls_for=function(a){var b=Blockly.PHP.variableDB_.getName(a.getFieldValue("VAR"),Blockly.Variables.NAME_TYPE),c=Blockly.PHP.valueToCode(a,"FROM",Blockly.PHP.ORDER_ASSIGNMENT)||"0",d=Blockly.PHP.valueToCode(a,"TO",Blockly.PHP.ORDER_ASSIGNMENT)||"0",e=Blockly.PHP.valueToCode(a,"BY",Blockly.PHP.ORDER_ASSIGNMENT)||"1",f=Blockly.PHP.statementToCode(a,"DO");f=Blockly.PHP.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.PHP.variableDB_.getDistinctName(b+"_start",Blockly.Variables.NAME_TYPE),a+=g+" = "+c+";\n"),c=d,d.match(/^\w+$/)||Blockly.isNumber(d)||(c=Blockly.PHP.variableDB_.getDistinctName(b+"_end",Blockly.Variables.NAME_TYPE),a+=c+" = "+d+";\n"),d=Blockly.PHP.variableDB_.getDistinctName(b+"_inc",Blockly.Variables.NAME_TYPE), +a+=d+" = ",a=Blockly.isNumber(e)?a+(Math.abs(e)+";\n"):a+("abs("+e+");\n"),a=a+("if ("+g+" > "+c+") {\n")+(Blockly.PHP.INDENT+d+" = -"+d+";\n"),a+="}\n",a+="for ("+b+" = "+g+"; "+d+" >= 0 ? "+b+" <= "+c+" : "+b+" >= "+c+"; "+b+" += "+d+") {\n"+f+"}\n";return a}; +Blockly.PHP.controls_forEach=function(a){var b=Blockly.PHP.variableDB_.getName(a.getFieldValue("VAR"),Blockly.Variables.NAME_TYPE),c=Blockly.PHP.valueToCode(a,"LIST",Blockly.PHP.ORDER_ASSIGNMENT)||"[]",d=Blockly.PHP.statementToCode(a,"DO");d=Blockly.PHP.addLoopTrap(d,a.id);return"foreach ("+c+" as "+b+") {\n"+d+"}\n"};Blockly.PHP.controls_flow_statements=function(a){switch(a.getFieldValue("FLOW")){case "BREAK":return"break;\n";case "CONTINUE":return"continue;\n"}throw"Unknown flow statement.";};Blockly.PHP.math={};Blockly.PHP.math_number=function(a){a=parseFloat(a.getFieldValue("NUM"));var b=0<=a?Blockly.PHP.ORDER_ATOMIC:Blockly.PHP.ORDER_UNARY_NEGATION;Infinity==a?a="INF":-Infinity==a&&(a="-INF");return[a,b]}; Blockly.PHP.math_arithmetic=function(a){var b={ADD:[" + ",Blockly.PHP.ORDER_ADDITION],MINUS:[" - ",Blockly.PHP.ORDER_SUBTRACTION],MULTIPLY:[" * ",Blockly.PHP.ORDER_MULTIPLICATION],DIVIDE:[" / ",Blockly.PHP.ORDER_DIVISION],POWER:[" ** ",Blockly.PHP.ORDER_POWER]}[a.getFieldValue("OP")],c=b[0];b=b[1];var d=Blockly.PHP.valueToCode(a,"A",b)||"0";a=Blockly.PHP.valueToCode(a,"B",b)||"0";return[d+c+a,b]}; Blockly.PHP.math_single=function(a){var b=a.getFieldValue("OP");if("NEG"==b)return a=Blockly.PHP.valueToCode(a,"NUM",Blockly.PHP.ORDER_UNARY_NEGATION)||"0","-"==a[0]&&(a=" "+a),["-"+a,Blockly.PHP.ORDER_UNARY_NEGATION];a="SIN"==b||"COS"==b||"TAN"==b?Blockly.PHP.valueToCode(a,"NUM",Blockly.PHP.ORDER_DIVISION)||"0":Blockly.PHP.valueToCode(a,"NUM",Blockly.PHP.ORDER_NONE)||"0";switch(b){case "ABS":var c="abs("+a+")";break;case "ROOT":c="sqrt("+a+")";break;case "LN":c="log("+a+")";break;case "EXP":c="exp("+ a+")";break;case "POW10":c="pow(10,"+a+")";break;case "ROUND":c="round("+a+")";break;case "ROUNDUP":c="ceil("+a+")";break;case "ROUNDDOWN":c="floor("+a+")";break;case "SIN":c="sin("+a+" / 180 * pi())";break;case "COS":c="cos("+a+" / 180 * pi())";break;case "TAN":c="tan("+a+" / 180 * pi())"}if(c)return[c,Blockly.PHP.ORDER_FUNCTION_CALL];switch(b){case "LOG10":c="log("+a+") / log(10)";break;case "ASIN":c="asin("+a+") / pi() * 180";break;case "ACOS":c="acos("+a+") / pi() * 180";break;case "ATAN":c="atan("+ @@ -65,16 +65,16 @@ Blockly.PHP.math_on_list=function(a){var b=a.getFieldValue("OP");switch(b){case Blockly.PHP.math_constrain=function(a){var b=Blockly.PHP.valueToCode(a,"VALUE",Blockly.PHP.ORDER_COMMA)||"0",c=Blockly.PHP.valueToCode(a,"LOW",Blockly.PHP.ORDER_COMMA)||"0";a=Blockly.PHP.valueToCode(a,"HIGH",Blockly.PHP.ORDER_COMMA)||"Infinity";return["min(max("+b+", "+c+"), "+a+")",Blockly.PHP.ORDER_FUNCTION_CALL]}; Blockly.PHP.math_random_int=function(a){var b=Blockly.PHP.valueToCode(a,"FROM",Blockly.PHP.ORDER_COMMA)||"0";a=Blockly.PHP.valueToCode(a,"TO",Blockly.PHP.ORDER_COMMA)||"0";return[Blockly.PHP.provideFunction_("math_random_int",["function "+Blockly.PHP.FUNCTION_NAME_PLACEHOLDER_+"($a, $b) {"," if ($a > $b) {"," return rand($b, $a);"," }"," return rand($a, $b);","}"])+"("+b+", "+a+")",Blockly.PHP.ORDER_FUNCTION_CALL]}; Blockly.PHP.math_random_float=function(a){return["(float)rand()/(float)getrandmax()",Blockly.PHP.ORDER_FUNCTION_CALL]};Blockly.PHP.procedures={}; -Blockly.PHP.procedures_defreturn=function(a){for(var b=[],c,d=a.workspace.getAllVariables()||[],e=0;c=d[e];e++)c=c.name,-1==a.arguments_.indexOf(c)&&b.push(Blockly.PHP.variableDB_.getName(c,Blockly.Variables.NAME_TYPE));b=b.length?Blockly.PHP.INDENT+"global "+b.join(", ")+";\n":"";d=Blockly.PHP.variableDB_.getName(a.getFieldValue("NAME"),Blockly.Procedures.NAME_TYPE);c=Blockly.PHP.statementToCode(a,"STACK");Blockly.PHP.STATEMENT_PREFIX&&(e=a.id.replace(/\$/g,"$$$$"),c=Blockly.PHP.prefixLines(Blockly.PHP.STATEMENT_PREFIX.replace(/%1/g,"'"+ -e+"'"),Blockly.PHP.INDENT)+c);Blockly.PHP.INFINITE_LOOP_TRAP&&(c=Blockly.PHP.INFINITE_LOOP_TRAP.replace(/%1/g,"'"+a.id+"'")+c);var g=Blockly.PHP.valueToCode(a,"RETURN",Blockly.PHP.ORDER_NONE)||"";g&&(g=Blockly.PHP.INDENT+"return "+g+";\n");var f=[];for(e=0;e' + + ' ' + + ' name1' + + ' ' + + ''); + Blockly.Xml.domToWorkspace(dom, workspace); + + var firedEvents = workspace.undoStack_; + // Expect two events: varCreate and block create. + assertEquals(2, firedEvents.length); + + var event0 = firedEvents[0]; + var event1 = firedEvents[1]; + assertEquals('var_create', event0.type); + assertEquals('create', event1.type); + + // Expect the events to have the same group ID. + assertEquals(event0.group, event1.group); + + // Expect the workspace to have a variable with ID 'id1'. + assertNotNull(workspace.getVariableById('id1')); + assertEquals('id1', event0.varId); + } finally { + eventTest_tearDownWithMockBlocks(); + Blockly.Events.fire = savedFireFunc; + } +} diff --git a/tests/jsunit/field_angle_test.js b/tests/jsunit/field_angle_test.js index e5646d96c..66b6ab009 100644 --- a/tests/jsunit/field_angle_test.js +++ b/tests/jsunit/field_angle_test.js @@ -37,3 +37,8 @@ function test_fieldangle_constructor() { assertEquals(new Blockly.FieldAngle('bad').getValue(), '0'); assertEquals(new Blockly.FieldAngle(NaN).getValue(), '0'); } + +function test_fieldangle_fromJson() { + assertEquals(Blockly.FieldAngle.fromJson({}).getValue(), '0'); + assertEquals(Blockly.FieldAngle.fromJson({ angle: 1 }).getValue(), '1'); +} diff --git a/tests/jsunit/field_number_test.js b/tests/jsunit/field_number_test.js index 20f277036..507af7a4b 100644 --- a/tests/jsunit/field_number_test.js +++ b/tests/jsunit/field_number_test.js @@ -61,3 +61,20 @@ function test_fieldnumber_constructor() { field = new Blockly.FieldNumber(NaN); assertEquals(field.getValue(), '0'); } + +function test_fieldnumber_fromJson() { + assertEquals(Blockly.FieldNumber.fromJson({}).getValue(), '0'); + assertEquals(Blockly.FieldNumber.fromJson({ value: 1 }).getValue(), '1'); + + // All options + var field = Blockly.FieldNumber.fromJson({ + value: 0, + min: -128, + max: 127, + precision: 1 + }); + assertEquals(field.getValue(), '0'); + assertEquals(field.min_, -128); + assertEquals(field.max_, 127); + assertEquals(field.precision_, 1); +} diff --git a/tests/jsunit/field_variable_test.js b/tests/jsunit/field_variable_test.js index 0ef296273..3f8785cb9 100644 --- a/tests/jsunit/field_variable_test.js +++ b/tests/jsunit/field_variable_test.js @@ -44,10 +44,20 @@ function fieldVariable_mockBlock(workspace) { return {'workspace': workspace, 'isShadow': function(){return false;}}; } +function fieldVariable_createAndInitField(workspace) { + var fieldVariable = new Blockly.FieldVariable('name1'); + var mockBlock = fieldVariable_mockBlock(workspace); + fieldVariable.setSourceBlock(mockBlock); + // No view to initialize, but the model still needs work. + fieldVariable.initModel(); + return fieldVariable; +} + function test_fieldVariable_Constructor() { workspace = new Blockly.Workspace(); var fieldVariable = new Blockly.FieldVariable('name1'); - assertEquals('name1', fieldVariable.getText()); + // The field does not have a variable until after init() is called. + assertEquals('', fieldVariable.getText()); workspace.dispose(); } @@ -55,52 +65,38 @@ function test_fieldVariable_setValueMatchId() { // Expect the fieldVariable value to be set to variable name fieldVariableTestWithMocks_setUp(); workspace.createVariable('name2', null, 'id2'); - var fieldVariable = new Blockly.FieldVariable('name1'); - var mockBlock = fieldVariable_mockBlock(workspace); - fieldVariable.setSourceBlock(mockBlock); + + var fieldVariable = fieldVariable_createAndInitField(workspace); + + var oldId = fieldVariable.getValue(); var event = new Blockly.Events.BlockChange( - mockBlock, 'field', undefined, 'name1', 'id2'); + fieldVariable.sourceBlock_, 'field', undefined, oldId, 'id2'); setUpMockMethod(mockControl_, Blockly.Events, 'fire', [event], null); fieldVariable.setValue('id2'); assertEquals('name2', fieldVariable.getText()); - assertEquals('id2', fieldVariable.value_); - fieldVariableTestWithMocks_tearDown(); -} - -function test_fieldVariable_setValueMatchName() { - // Expect the fieldVariable value to be set to variable name - fieldVariableTestWithMocks_setUp(); - workspace.createVariable('name2', null, 'id2'); - var fieldVariable = new Blockly.FieldVariable('name1'); - var mockBlock = fieldVariable_mockBlock(workspace); - fieldVariable.setSourceBlock(mockBlock); - var event = new Blockly.Events.BlockChange( - mockBlock, 'field', undefined, 'name1', 'id2'); - setUpMockMethod(mockControl_, Blockly.Events, 'fire', [event], null); - - fieldVariable.setValue('name2'); - assertEquals('name2', fieldVariable.getText()); - assertEquals('id2', fieldVariable.value_); + assertEquals('id2', fieldVariable.getValue()); fieldVariableTestWithMocks_tearDown(); } function test_fieldVariable_setValueNoVariable() { - // Expect the fieldVariable value to be set to the passed in string. No error - // should be thrown. fieldVariableTestWithMocks_setUp(); - var fieldVariable = new Blockly.FieldVariable('name1'); - var mockBlock = {'workspace': workspace, - 'isShadow': function(){return false;}}; - fieldVariable.setSourceBlock(mockBlock); - var event = new Blockly.Events.BlockChange( - mockBlock, 'field', undefined, 'name1', 'id1'); - setUpMockMethod(mockControl_, Blockly.Events, 'fire', [event], null); - fieldVariable.setValue('id1'); - assertEquals('id1', fieldVariable.getText()); - assertEquals('id1', fieldVariable.value_); - fieldVariableTestWithMocks_tearDown(); + var fieldVariable = fieldVariable_createAndInitField(workspace); + var mockBlock = fieldVariable.sourceBlock_; + mockBlock.isShadow = function() { + return false; + }; + + try { + fieldVariable.setValue('id1'); + // Calling setValue with a variable that doesn't exist throws an error. + fail(); + } catch (e) { + // expected + } finally { + fieldVariableTestWithMocks_tearDown(); + } } function test_fieldVariable_dropdownCreateVariablesExist() { @@ -108,12 +104,12 @@ function test_fieldVariable_dropdownCreateVariablesExist() { workspace = new Blockly.Workspace(); workspace.createVariable('name1', '', 'id1'); workspace.createVariable('name2', '', 'id2'); + + var fieldVariable = fieldVariable_createAndInitField(workspace); + var result_options = Blockly.FieldVariable.dropdownCreate.call( - { - 'sourceBlock_': {'workspace': workspace}, - 'getText': function(){return 'name1';}, - 'getVariableTypes_': function(){return [''];} - }); + fieldVariable); + assertEquals(result_options.length, 3); isEqualArrays(result_options[0], ['name1', 'id1']); isEqualArrays(result_options[1], ['name2', 'id2']); @@ -121,77 +117,30 @@ function test_fieldVariable_dropdownCreateVariablesExist() { workspace.dispose(); } -function test_fieldVariable_dropdownCreateVariablesExist() { - // Expect that the dropdown options will contain the variables that exist. - workspace = new Blockly.Workspace(); - workspace.createVariable('name1', '', 'id1'); - workspace.createVariable('name2', '', 'id2'); - var result_options = Blockly.FieldVariable.dropdownCreate.call( - { - 'sourceBlock_': {'workspace': workspace}, - 'getText': function(){return 'name1';}, - 'getVariableTypes_': function(){return [''];} - }); - assertEquals(result_options.length, 3); - isEqualArrays(result_options[0], ['name1', 'id1']); - isEqualArrays(result_options[1], ['name2', 'id2']); - - workspace.dispose(); -} - -function test_fieldVariable_dropdownVariableAndTypeDoesNotExist() { - // Expect a variable will be created for the selected option. Expect the - // workspace variable map to contain the new variable once. +function test_fieldVariable_setValueNull() { + // This should no longer create a variable for the selected option. fieldVariableTestWithMocks_setUp(); setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null, ['id1', null]); - var result_options = Blockly.FieldVariable.dropdownCreate.call( - { - 'sourceBlock_': {'workspace': workspace}, - 'getText': function(){return 'name1';}, - 'getVariableTypes_': function(){return [''];} - }); + var fieldVariable = fieldVariable_createAndInitField(workspace); + try { + fieldVariable.setValue(null); + fail(); + } catch (e) { + // expected + } finally { + fieldVariableTestWithMocks_tearDown(); + } - // Check the options. - assertEquals(2, result_options.length); - isEqualArrays(result_options[0], ['name1', 'id1']); - // Check the variable map. - assertEquals(1, workspace.getAllVariables().length); - checkVariableValues(workspace, 'name1', '', 'id1'); - - fieldVariableTestWithMocks_tearDown(); -} - -function test_fieldVariable_dropdownVariableDoesNotExistTypeDoes() { - // Expect a variable will be created for the selected option. Expect the - // workspace variable map to contain the new variable once. - fieldVariableTestWithMocks_setUp(); - workspace.createVariable('name1', '', 'id1'); - setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null, ['id2', null]); - - var result_options = Blockly.FieldVariable.dropdownCreate.call( - { - 'sourceBlock_': {'workspace': workspace}, - 'getText': function(){return 'name2';}, - 'getVariableTypes_': function(){return [''];} - }); - - assertEquals(3, result_options.length); - isEqualArrays(result_options[0], ['name1', 'id1']); - isEqualArrays(result_options[1], ['name2', 'id2']); - assertEquals(2, workspace.variableMap_.getAllVariables().length); - checkVariableValues(workspace, 'name1', '', 'id1'); - checkVariableValues(workspace, 'name2', '', 'id2'); - - fieldVariableTestWithMocks_tearDown(); } function test_fieldVariable_getVariableTypes_undefinedVariableTypes() { // Expect that since variableTypes is undefined, only type empty string - // will be returned. + // will be returned (regardless of what types are available on the workspace). workspace = new Blockly.Workspace(); workspace.createVariable('name1', 'type1'); workspace.createVariable('name2', 'type2'); + var fieldVariable = new Blockly.FieldVariable('name1'); var resultTypes = fieldVariable.getVariableTypes_(); isEqualArrays(resultTypes, ['']); @@ -199,28 +148,36 @@ function test_fieldVariable_getVariableTypes_undefinedVariableTypes() { } function test_fieldVariable_getVariableTypes_givenVariableTypes() { - // Expect that since variableTypes is undefined, only type empty string - // will be returned. + // Expect that since variableTypes is defined, it will be the return value, + // regardless of what types are available on the workspace. workspace = new Blockly.Workspace(); workspace.createVariable('name1', 'type1'); workspace.createVariable('name2', 'type2'); - var fieldVariable = new Blockly.FieldVariable('name1', null, ['type1', 'type2']); + + var fieldVariable = new Blockly.FieldVariable( + 'name1', null, ['type1', 'type2'], 'type1'); var resultTypes = fieldVariable.getVariableTypes_(); isEqualArrays(resultTypes, ['type1', 'type2']); + assertEquals('Default type was wrong', 'type1', fieldVariable.defaultType_); workspace.dispose(); } function test_fieldVariable_getVariableTypes_nullVariableTypes() { // Expect all variable types to be returned. + // The variable does not need to be initialized to do this--it just needs a + // pointer to the workspace. workspace = new Blockly.Workspace(); workspace.createVariable('name1', 'type1'); workspace.createVariable('name2', 'type2'); + var fieldVariable = new Blockly.FieldVariable('name1'); var mockBlock = fieldVariable_mockBlock(workspace); fieldVariable.setSourceBlock(mockBlock); fieldVariable.variableTypes = null; + var resultTypes = fieldVariable.getVariableTypes_(); - isEqualArrays(resultTypes, ['type1', 'type2']); + // The empty string is always one of the options. + isEqualArrays(resultTypes, ['type1', 'type2', '']); workspace.dispose(); } @@ -229,15 +186,52 @@ function test_fieldVariable_getVariableTypes_emptyListVariableTypes() { workspace = new Blockly.Workspace(); workspace.createVariable('name1', 'type1'); workspace.createVariable('name2', 'type2'); + var fieldVariable = new Blockly.FieldVariable('name1'); var mockBlock = fieldVariable_mockBlock(workspace); fieldVariable.setSourceBlock(mockBlock); fieldVariable.variableTypes = []; + try { fieldVariable.getVariableTypes_(); + fail(); } catch (e) { //expected } finally { workspace.dispose(); } } + +function test_fieldVariable_defaultType_exists() { + var fieldVariable = new Blockly.FieldVariable(null, null, ['b'], 'b'); + assertEquals('The variable field\'s default type should be "b"', + 'b', fieldVariable.defaultType_); +} + +function test_fieldVariable_noDefaultType() { + var fieldVariable = new Blockly.FieldVariable(null); + assertEquals('The variable field\'s default type should be the empty string', + '', fieldVariable.defaultType_); + assertNull('The variable field\'s allowed types should be null', + fieldVariable.variableTypes); +} + +function test_fieldVariable_defaultTypeMismatch() { + try { + var fieldVariable = new Blockly.FieldVariable(null, null, ['a'], 'b'); + fail('Variable field creation should have failed due to an invalid ' + + 'default type'); + } catch (e) { + // expected + } +} + +function test_fieldVariable_defaultTypeMismatch_empty() { + try { + var fieldVariable = new Blockly.FieldVariable(null, null, ['a']); + fail('Variable field creation should have failed due to an invalid ' + + 'default type'); + } catch (e) { + // expected + } +} diff --git a/tests/jsunit/index.html b/tests/jsunit/index.html index 500f2cb37..3987465a6 100644 --- a/tests/jsunit/index.html +++ b/tests/jsunit/index.html @@ -23,6 +23,7 @@ + diff --git a/tests/jsunit/procedures_test.js b/tests/jsunit/procedures_test.js index 0284f3978..9d1468af2 100644 --- a/tests/jsunit/procedures_test.js +++ b/tests/jsunit/procedures_test.js @@ -41,10 +41,10 @@ function proceduresTest_setUpWithMockBlocks() { 'name': 'NAME', 'variable': 'item' } - ], + ] }]); Blockly.Blocks['procedure_mock_block'].getProcedureDef = function() { - return [this.getFieldValue('NAME'), [], false]; + return [this.getField('NAME').getText(), [], false]; }; } @@ -63,8 +63,9 @@ function test_isNameUsed_NoBlocks() { function test_isNameUsed_False() { proceduresTest_setUpWithMockBlocks(); + workspace.createVariable('name2', '', 'id2'); var block = new Blockly.Block(workspace, 'procedure_mock_block'); - block.setFieldValue('name2', 'NAME'); + block.setFieldValue('id2', 'NAME'); var result = Blockly.Procedures.isNameUsed('name1', workspace); assertFalse(result); @@ -73,8 +74,9 @@ function test_isNameUsed_False() { function test_isNameUsed_True() { proceduresTest_setUpWithMockBlocks(); + workspace.createVariable('name1', '', 'id1'); var block = new Blockly.Block(workspace, 'procedure_mock_block'); - block.setFieldValue('name1', 'NAME'); + block.setFieldValue('id1', 'NAME'); var result = Blockly.Procedures.isNameUsed('name1', workspace); assertTrue(result); diff --git a/tests/jsunit/test_utilities.js b/tests/jsunit/test_utilities.js index 6a80a3549..0c256e6f7 100644 --- a/tests/jsunit/test_utilities.js +++ b/tests/jsunit/test_utilities.js @@ -89,3 +89,55 @@ function checkVariableValues(container, name, type, id) { assertEquals(type, variable.type); assertEquals(id, variable.getId()); } + +/** + * Create a test get_var_block. + * Will fail if get_var_block isn't defined. + * @param {!Blockly.Workspace} workspace The workspace on which to create the + * block. + * @param {!string} variable_id The id of the variable to reference. + * @return {!Blockly.Block} The created block. + */ +function createMockVarBlock(workspace, variable_id) { + if (!Blockly.Blocks['get_var_block']) { + fail(); + } + // Turn off events to avoid testing XML at the same time. + Blockly.Events.disable(); + var block = new Blockly.Block(workspace, 'get_var_block'); + block.inputList[0].fieldRow[0].setValue(variable_id); + Blockly.Events.enable(); + return block; +} + +function createTwoVariablesAndBlocks(workspace) { + // Create two variables of different types. + workspace.createVariable('name1', 'type1', 'id1'); + workspace.createVariable('name2', 'type2', 'id2'); + // Create blocks to refer to both of them. + createMockVarBlock(workspace, 'id1'); + createMockVarBlock(workspace, 'id2'); +} + +function createVariableAndBlock(workspace) { + workspace.createVariable('name1', 'type1', 'id1'); + createMockVarBlock(workspace, 'id1'); +} + +function defineGetVarBlock() { + Blockly.defineBlocksWithJsonArray([{ + "type": "get_var_block", + "message0": "%1", + "args0": [ + { + "type": "field_variable", + "name": "VAR", + "variableTypes": ["", "type1", "type2"] + } + ] + }]); +} + +function undefineGetVarBlock() { + delete Blockly.Blocks['get_var_block']; +} diff --git a/tests/jsunit/variable_map_test.js b/tests/jsunit/variable_map_test.js index b2c0ccb95..3ccf674e2 100644 --- a/tests/jsunit/variable_map_test.js +++ b/tests/jsunit/variable_map_test.js @@ -43,18 +43,24 @@ function variableMapTest_tearDown() { variable_map = null; } -function test_getVariable_Trivial() { +function test_getVariable_ByNameAndType() { variableMapTest_setUp(); var var_1 = variable_map.createVariable('name1', 'type1', 'id1'); var var_2 = variable_map.createVariable('name2', 'type1', 'id2'); var var_3 = variable_map.createVariable('name3', 'type2', 'id3'); - var result_1 = variable_map.getVariable('name1'); - var result_2 = variable_map.getVariable('name2'); - var result_3 = variable_map.getVariable('name3'); + var result_1 = variable_map.getVariable('name1', 'type1'); + var result_2 = variable_map.getVariable('name2', 'type1'); + var result_3 = variable_map.getVariable('name3', 'type2'); + // Searching by name + type is correct. assertEquals(var_1, result_1); assertEquals(var_2, result_2); assertEquals(var_3, result_3); + + // Searching only by name defaults to the '' type. + assertNull(variable_map.getVariable('name1')); + assertNull(variable_map.getVariable('name2')); + assertNull(variable_map.getVariable('name3')); variableMapTest_tearDown(); } @@ -105,7 +111,7 @@ function test_createVariableAlreadyExists() { var varMapLength = variable_map.variableMap_[keys[0]].length; assertEquals(1, varMapLength); - variable_map.createVariable('name1'); + variable_map.createVariable('name1', 'type1'); checkVariableValues(variable_map, 'name1', 'type1', 'id1'); // Check that the size of the variableMap_ did not change. keys = Object.keys(variable_map.variableMap_); @@ -115,6 +121,26 @@ function test_createVariableAlreadyExists() { variableMapTest_tearDown(); } +function test_createVariableNameAlreadyExists() { + // Expect that when a variable with the same name but a different type already + // exists, the new variable is created. + variableMapTest_setUp(); + variable_map.createVariable('name1', 'type1', 'id1'); + + // Assert there is only one variable in the variable_map. + var keys = Object.keys(variable_map.variableMap_); + assertEquals(1, keys.length); + var varMapLength = variable_map.variableMap_[keys[0]].length; + assertEquals(1, varMapLength); + + variable_map.createVariable('name1', 'type2', 'id2'); + checkVariableValues(variable_map, 'name1', 'type1', 'id1'); + checkVariableValues(variable_map, 'name1', 'type2', 'id2'); + // Check that the size of the variableMap_ did change. + keys = Object.keys(variable_map.variableMap_); + assertEquals(2, keys.length); + variableMapTest_tearDown(); +} function test_createVariableNullAndUndefinedType() { variableMapTest_setUp(); variable_map.createVariable('name1', null, 'id1'); @@ -243,14 +269,16 @@ function test_getVariableTypes_Trivial() { variable_map.createVariable('name3', 'type2', 'id3'); variable_map.createVariable('name4', 'type3', 'id4'); var result_array = variable_map.getVariableTypes(); - isEqualArrays(['type1', 'type2', 'type3'], result_array); + // The empty string is always an option. + isEqualArrays(['type1', 'type2', 'type3', ''], result_array); variableMapTest_tearDown(); } function test_getVariableTypes_None() { variableMapTest_setUp(); + // The empty string is always an option. var result_array = variable_map.getVariableTypes(); - isEqualArrays([], result_array); + isEqualArrays([''], result_array); variableMapTest_tearDown(); } diff --git a/tests/jsunit/variables_test.js b/tests/jsunit/variables_test.js new file mode 100644 index 000000000..b6b305bdc --- /dev/null +++ b/tests/jsunit/variables_test.js @@ -0,0 +1,99 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2018 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 Tests for variable utility functions in Blockly + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.require('goog.testing'); + +function variablesTest_setUp() { + defineGetVarBlock(); + var workspace = new Blockly.Workspace(); + workspace.createVariable('foo', 'type1', '1'); + workspace.createVariable('bar', 'type1', '2'); + workspace.createVariable('baz', 'type1', '3'); + return workspace; +} + +function variablesTest_tearDown(workspace) { + undefineGetVarBlock(); + workspace.dispose(); +} + +function buildVariablesTest(testImpl) { + return function() { + var workspace = variablesTest_setUp(); + try { + testImpl(workspace); + } finally { + variablesTest_tearDown(workspace); + } + }; +} + +var test_allUsedVarModels = buildVariablesTest( + function(workspace) { + createMockVarBlock(workspace, '1'); + createMockVarBlock(workspace, '2'); + createMockVarBlock(workspace, '3'); + + var result = Blockly.Variables.allUsedVarModels(workspace); + assertEquals('Expected three variables in the list of used variables', + 3, result.length); + } +); + +var test_allUsedVarModels_someUnused = buildVariablesTest( + function(workspace) { + createMockVarBlock(workspace, '2'); + + var result = Blockly.Variables.allUsedVarModels(workspace); + assertEquals('Expected one variable in the list of used variables', + 1, result.length); + assertEquals('Expected variable with ID 2 in the list of used variables', + '2', result[0].getId()); + } +); + +var test_allUsedVarModels_varUsedTwice = buildVariablesTest( + function(workspace) { + createMockVarBlock(workspace, '2'); + createMockVarBlock(workspace, '2'); + + var result = Blockly.Variables.allUsedVarModels(workspace); + // Using the same variable multiple times should not change the number of + // elements in the list. + assertEquals('Expected one variable in the list of used variables', + 1, result.length); + assertEquals('Expected variable with ID 2 in the list of used variables', + '2', result[0].getId()); + } +); + +var test_allUsedVarModels_allUnused = buildVariablesTest( + function(workspace) { + var result = Blockly.Variables.allUsedVarModels(workspace); + assertEquals('Expected no variables in the list of used variables', + 0, result.length); + } +); diff --git a/tests/jsunit/workspace_test.js b/tests/jsunit/workspace_test.js index a96a34ac4..18cb30403 100644 --- a/tests/jsunit/workspace_test.js +++ b/tests/jsunit/workspace_test.js @@ -24,38 +24,19 @@ goog.require('goog.testing.MockControl'); var workspace; var mockControl_; -Blockly.defineBlocksWithJsonArray([{ - "type": "get_var_block", - "message0": "%1", - "args0": [ - { - "type": "field_variable", - "name": "VAR", - } - ] -}]); function workspaceTest_setUp() { + defineGetVarBlock(); workspace = new Blockly.Workspace(); mockControl_ = new goog.testing.MockControl(); } function workspaceTest_tearDown() { + undefineGetVarBlock(); mockControl_.$tearDown(); workspace.dispose(); } -/** - * Create a test get_var_block. - * @param {?string} variable_name The string to put into the variable field. - * @return {!Blockly.Block} The created block. - */ -function createMockBlock(variable_name) { - var block = new Blockly.Block(workspace, 'get_var_block'); - block.inputList[0].fieldRow[0].setValue(variable_name); - return block; -} - function test_emptyWorkspace() { workspaceTest_setUp(); try { @@ -160,13 +141,15 @@ function test_deleteVariable_InternalTrivial() { workspaceTest_setUp(); var var_1 = workspace.createVariable('name1', 'type1', 'id1'); workspace.createVariable('name2', 'type2', 'id2'); - createMockBlock('name1'); - createMockBlock('name1'); - createMockBlock('name2'); + createMockVarBlock(workspace, 'id1'); + createMockVarBlock(workspace, 'id1'); + createMockVarBlock(workspace, 'id2'); - workspace.deleteVariableInternal_(var_1); - var variable = workspace.getVariable('name1'); - var block_var_name = workspace.topBlocks_[0].getVars()[0]; + var uses = workspace.getVariableUsesById(var_1.getId()); + workspace.deleteVariableInternal_(var_1, uses); + + var variable = workspace.getVariableById('id1'); + var block_var_name = workspace.topBlocks_[0].getVarModels()[0].name; assertNull(variable); checkVariableValues(workspace, 'name2', 'type2', 'id2'); assertEquals('name2', block_var_name); @@ -175,83 +158,27 @@ function test_deleteVariable_InternalTrivial() { // TODO(marisaleung): Test the alert for deleting a variable that is a procedure. -function test_updateVariableStore_TrivialNoClear() { - workspaceTest_setUp(); - workspace.createVariable('name1', 'type1', 'id1'); - workspace.createVariable('name2', 'type2', 'id2'); - setUpMockMethod(mockControl_, Blockly.Variables, 'allUsedVariables', - [workspace], [['name1', 'name2']]); - - try { - workspace.updateVariableStore(); - checkVariableValues(workspace, 'name1', 'type1', 'id1'); - checkVariableValues(workspace, 'name2', 'type2', 'id2'); - } finally { - workspaceTest_tearDown(); - } -} - -function test_updateVariableStore_NameNotInvariableMap_NoClear() { - workspaceTest_setUp(); - setUpMockMethod(mockControl_, Blockly.Variables, 'allUsedVariables', - [workspace], [['name1']]); - setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null, ['1']); - - try { - workspace.updateVariableStore(); - checkVariableValues(workspace, 'name1', '', '1'); - } finally { - workspaceTest_tearDown(); - } -} - -function test_updateVariableStore_ClearAndAllInUse() { - workspaceTest_setUp(); - workspace.createVariable('name1', 'type1', 'id1'); - workspace.createVariable('name2', 'type2', 'id2'); - setUpMockMethod(mockControl_, Blockly.Variables, 'allUsedVariables', - [workspace], [['name1', 'name2']]); - - try { - workspace.updateVariableStore(true); - checkVariableValues(workspace, 'name1', 'type1', 'id1'); - checkVariableValues(workspace, 'name2', 'type2', 'id2'); - } finally { - workspaceTest_tearDown(); - } -} - -function test_updateVariableStore_ClearAndOneInUse() { - workspaceTest_setUp(); - workspace.createVariable('name1', 'type1', 'id1'); - workspace.createVariable('name2', 'type2', 'id2'); - setUpMockMethod(mockControl_, Blockly.Variables, 'allUsedVariables', - [workspace], [['name1']]); - - try { - workspace.updateVariableStore(true); - checkVariableValues(workspace, 'name1', 'type1', 'id1'); - var variabe = workspace.getVariable('name2'); - assertNull(variable); - } finally { - workspaceTest_tearDown(); - } -} - function test_addTopBlock_TrivialFlyoutIsTrue() { workspaceTest_setUp(); + var targetWorkspace = new Blockly.Workspace(); workspace.isFlyout = true; - var block = createMockBlock(); - workspace.removeTopBlock(block); - setUpMockMethod(mockControl_, Blockly.Variables, 'allUsedVariables', [block], - [['name1']]); - setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null, ['1']); + workspace.targetWorkspace = targetWorkspace; + targetWorkspace.createVariable('name1', '', '1'); + + // Flyout.init usually does this binding. + workspace.variableMap_ = targetWorkspace.getVariableMap(); try { + var block = createMockVarBlock(workspace, '1'); + workspace.removeTopBlock(block); workspace.addTopBlock(block); checkVariableValues(workspace, 'name1', '', '1'); } finally { workspaceTest_tearDown(); + // Have to dispose of the main workspace after the flyout workspace, because + // it holds the variable map. + // Normally the main workspace disposes of the flyout workspace. + targetWorkspace.dispose(); } } @@ -289,50 +216,38 @@ function test_clear_NoVariables() { } } -function test_renameVariable_NoBlocks() { - // Expect 'renameVariable' to create new variable with newName. +function test_renameVariable_NoReference() { + // Test renaming a variable in the simplest case: when no blocks refer to it. workspaceTest_setUp(); + var id = 'id1'; + var type = 'type1'; var oldName = 'name1'; var newName = 'name2'; - // Mocked setGroup to ensure only one call to the mocked genUid. - setUpMockMethod(mockControl_, Blockly.Events, 'setGroup', [true, false], - null); - setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null, ['1']); + workspace.createVariable(oldName, type, id); try { - workspace.renameVariable(oldName, newName); - checkVariableValues(workspace, 'name2', '', '1'); - var variable = workspace.getVariable(oldName); - assertNull(variable); + workspace.renameVariableById(id, newName); + checkVariableValues(workspace, newName, type, id); + // Renaming should not have created a new variable. + assertEquals(1, workspace.getAllVariables().length); } finally { workspaceTest_tearDown(); } } -function test_renameVariable_SameNameNoBlocks() { - // Expect 'renameVariable' to create new variable with newName. - workspaceTest_setUp(); - var name = 'name1'; - workspace.createVariable(name, 'type1', 'id1'); - - workspace.renameVariable(name, name); - checkVariableValues(workspace, name, 'type1', 'id1'); - workspaceTest_tearDown(); -} - -function test_renameVariable_OnlyOldNameBlockExists() { +function test_renameVariable_ReferenceExists() { + // Test renaming a variable when a reference to it exists. // Expect 'renameVariable' to change oldName variable name to newName. workspaceTest_setUp(); - var oldName = 'name1'; var newName = 'name2'; - workspace.createVariable(oldName, 'type1', 'id1'); - createMockBlock(oldName); - workspace.renameVariable(oldName, newName); + createVariableAndBlock(workspace); + + workspace.renameVariableById('id1', newName); checkVariableValues(workspace, newName, 'type1', 'id1'); - var variable = workspace.getVariable(oldName); - var block_var_name = workspace.topBlocks_[0].getVars()[0]; - assertNull(variable); + // Renaming should not have created a new variable. + assertEquals(1, workspace.getAllVariables().length); + var block_var_name = workspace.topBlocks_[0].getVarModels()[0].name; assertEquals(newName, block_var_name); workspaceTest_tearDown(); } @@ -341,140 +256,120 @@ function test_renameVariable_TwoVariablesSameType() { // Expect 'renameVariable' to change oldName variable name to newName. // Expect oldName block name to change to newName workspaceTest_setUp(); + var id1 = 'id1'; + var id2 = 'id2'; + var type = 'type1'; + var oldName = 'name1'; var newName = 'name2'; - workspace.createVariable(oldName, 'type1', 'id1'); - workspace.createVariable(newName, 'type1', 'id2'); - createMockBlock(oldName); - createMockBlock(newName); + // Create two variables of the same type. + workspace.createVariable(oldName, type, id1); + workspace.createVariable(newName, type, id2); + // Create blocks to refer to both of them. + createMockVarBlock(workspace, id1); + createMockVarBlock(workspace, id2); - workspace.renameVariable(oldName, newName); - checkVariableValues(workspace, newName, 'type1', 'id2'); - var variable = workspace.getVariable(oldName); - var block_var_name_1 = workspace.topBlocks_[0].getVars()[0]; - var block_var_name_2 = workspace.topBlocks_[1].getVars()[0]; + workspace.renameVariableById(id1, newName); + checkVariableValues(workspace, newName, type, id2); + // The old variable should have been deleted. + var variable = workspace.getVariableById(id1); assertNull(variable); + + // There should only be one variable left. + assertEquals(1, workspace.getAllVariables().length); + + // References should have the correct names. + var block_var_name_1 = workspace.topBlocks_[0].getVarModels()[0].name; + var block_var_name_2 = workspace.topBlocks_[1].getVarModels()[0].name; assertEquals(newName, block_var_name_1); assertEquals(newName, block_var_name_2); + workspaceTest_tearDown(); } function test_renameVariable_TwoVariablesDifferentType() { - // Expect triggered error because of different types + // Expect the rename to succeed, because variables with different types are + // allowed to have the same name. workspaceTest_setUp(); - var oldName = 'name1'; - var newName = 'name2'; - workspace.createVariable(oldName, 'type1', 'id1'); - workspace.createVariable(newName, 'type2', 'id2'); - createMockBlock(oldName); - createMockBlock(newName); + createTwoVariablesAndBlocks(workspace); - try { - workspace.renameVariable(oldName, newName); - fail(); - } catch (e) { - // expected - } - checkVariableValues(workspace, oldName, 'type1', 'id1'); + var newName = 'name2'; + workspace.renameVariableById('id1', newName); + + checkVariableValues(workspace, newName, 'type1', 'id1'); checkVariableValues(workspace, newName, 'type2', 'id2'); - var block_var_name_1 = workspace.topBlocks_[0].getVars()[0]; - var block_var_name_2 = workspace.topBlocks_[1].getVars()[0]; - assertEquals(oldName, block_var_name_1); + + // References shoul have the correct names. + var block_var_name_1 = workspace.topBlocks_[0].getVarModels()[0].name; + var block_var_name_2 = workspace.topBlocks_[1].getVarModels()[0].name; + assertEquals(newName, block_var_name_1); assertEquals(newName, block_var_name_2); + workspaceTest_tearDown(); } function test_renameVariable_OldCase() { - // Expect triggered error because of different types + // Rename a variable with a single reference. Update only the capitalization. workspaceTest_setUp(); - var oldCase = 'Name1'; - var newName = 'name1'; - workspace.createVariable(oldCase, 'type1', 'id1'); - createMockBlock(oldCase); + var newName = 'Name1'; - workspace.renameVariable(oldCase, newName); + createVariableAndBlock(workspace); + + workspace.renameVariableById('id1', newName); checkVariableValues(workspace, newName, 'type1', 'id1'); - var result_oldCase = workspace.getVariable(oldCase).name; - assertNotEquals(oldCase, result_oldCase); + var variable = workspace.getVariableById('id1'); + assertNotEquals('name1', variable.name); workspaceTest_tearDown(); } function test_renameVariable_TwoVariablesAndOldCase() { - // Expect triggered error because of different types + // Test renaming a variable to an in-use name, but with different + // capitalization. The new capitalization should apply everywhere. + + // TODO (fenichel): What about different capitalization but also different + // types? workspaceTest_setUp(); var oldName = 'name1'; var oldCase = 'Name2'; var newName = 'name2'; - workspace.createVariable(oldName, 'type1', 'id1'); - workspace.createVariable(oldCase, 'type1', 'id2'); - createMockBlock(oldName); - createMockBlock(oldCase); - workspace.renameVariable(oldName, newName); + var id1 = 'id1'; + var id2 = 'id2'; - checkVariableValues(workspace, newName, 'type1', 'id2'); - var variable = workspace.getVariable(oldName); - var result_oldCase = workspace.getVariable(oldCase).name; - var block_var_name_1 = workspace.topBlocks_[0].getVars()[0]; - var block_var_name_2 = workspace.topBlocks_[1].getVars()[0]; + var type = 'type1'; + + workspace.createVariable(oldName, type, id1); + workspace.createVariable(oldCase, type, id2); + createMockVarBlock(workspace, id1); + createMockVarBlock(workspace, id2); + + workspace.renameVariableById(id1, newName); + + checkVariableValues(workspace, newName, type, id2); + + // The old variable should have been deleted. + var variable = workspace.getVariableById(id1); assertNull(variable); - assertNotEquals(oldCase, result_oldCase); + + // There should only be one variable left. + assertEquals(1, workspace.getAllVariables().length); + + // Blocks should now use the new capitalization. + var block_var_name_1 = workspace.topBlocks_[0].getVarModels()[0].name; + var block_var_name_2 = workspace.topBlocks_[1].getVarModels()[0].name; assertEquals(newName, block_var_name_1); assertEquals(newName, block_var_name_2); workspaceTest_tearDown(); } -// Extra testing not required for renameVariableById. It calls renameVariable -// and that has extensive testing. -function test_renameVariableById_TwoVariablesSameType() { - // Expect 'renameVariableById' to change oldName variable name to newName. - // Expect oldName block name to change to newName - workspaceTest_setUp(); - var oldName = 'name1'; - var newName = 'name2'; - workspace.createVariable(oldName, 'type1', 'id1'); - workspace.createVariable(newName, 'type1', 'id2'); - createMockBlock(oldName); - createMockBlock(newName); - - workspace.renameVariableById('id1', newName); - checkVariableValues(workspace, newName, 'type1', 'id2'); - var variable = workspace.getVariable(oldName); - var block_var_name_1 = workspace.topBlocks_[0].getVars()[0]; - var block_var_name_2 = workspace.topBlocks_[1].getVars()[0]; - assertNull(variable); - assertEquals(newName, block_var_name_1); - assertEquals(newName, block_var_name_2); - workspaceTest_tearDown(); -} - -function test_deleteVariable_Trivial() { - workspaceTest_setUp(); - workspace.createVariable('name1', 'type1', 'id1'); - workspace.createVariable('name2', 'type1', 'id2'); - createMockBlock('name1'); - createMockBlock('name2'); - - workspace.deleteVariable('name1'); - checkVariableValues(workspace, 'name2', 'type1', 'id2'); - var variable = workspace.getVariable('name1'); - var block_var_name = workspace.topBlocks_[0].getVars()[0]; - assertNull(variable); - assertEquals('name2', block_var_name); - workspaceTest_tearDown(); -} - function test_deleteVariableById_Trivial() { workspaceTest_setUp(); - workspace.createVariable('name1', 'type1', 'id1'); - workspace.createVariable('name2', 'type1', 'id2'); - createMockBlock('name1'); - createMockBlock('name2'); + createTwoVariablesAndBlocks(workspace); workspace.deleteVariableById('id1'); - checkVariableValues(workspace, 'name2', 'type1', 'id2'); - var variable = workspace.getVariable('name1'); - var block_var_name = workspace.topBlocks_[0].getVars()[0]; + checkVariableValues(workspace, 'name2', 'type2', 'id2'); + var variable = workspace.getVariableById('id1'); + var block_var_name = workspace.topBlocks_[0].getVarModels()[0].name; assertNull(variable); assertEquals('name2', block_var_name); workspaceTest_tearDown(); diff --git a/tests/jsunit/workspace_undo_redo_test.js b/tests/jsunit/workspace_undo_redo_test.js index 33dc855d7..ce58c97d9 100644 --- a/tests/jsunit/workspace_undo_redo_test.js +++ b/tests/jsunit/workspace_undo_redo_test.js @@ -33,16 +33,6 @@ goog.require('goog.testing.MockControl'); var workspace; var mockControl_; var savedFireFunc = Blockly.Events.fire; -Blockly.defineBlocksWithJsonArray([{ - "type": "get_var_block", - "message0": "%1", - "args0": [ - { - "type": "field_variable", - "name": "VAR", - } - ] -}]); function temporary_fireEvent(event) { if (!Blockly.Events.isEnabled()) { @@ -53,28 +43,19 @@ function temporary_fireEvent(event) { } function undoRedoTest_setUp() { + defineGetVarBlock(); workspace = new Blockly.Workspace(); mockControl_ = new goog.testing.MockControl(); Blockly.Events.fire = temporary_fireEvent; } function undoRedoTest_tearDown() { + undefineGetVarBlock(); mockControl_.$tearDown(); workspace.dispose(); Blockly.Events.fire = savedFireFunc; } -/** - * Create a test get_var_block. - * @param {string} variableName The string to put into the variable field. - * @return {!Blockly.Block} The created block. - */ -function createMockBlock(variableName) { - var block = new Blockly.Block(workspace, 'get_var_block'); - block.inputList[0].fieldRow[0].setValue(variableName); - return block; -} - /** * Check that the top block with the given index contains a variable with * the given name. @@ -82,7 +63,7 @@ function createMockBlock(variableName) { * @param {string} name The expected name of the variable in the block. */ function undoRedoTest_checkBlockVariableName(blockIndex, name) { - var blockVarName = workspace.topBlocks_[blockIndex].getVars()[0]; + var blockVarName = workspace.topBlocks_[blockIndex].getVarModels()[0].name; assertEquals(name, blockVarName); } @@ -91,10 +72,14 @@ function createTwoVarsEmptyType() { workspace.createVariable('name2', '', 'id2'); } -function test_undoCreateVariable_Trivial() { - undoRedoTest_setUp(); +function createTwoVarsDifferentTypes() { workspace.createVariable('name1', 'type1', 'id1'); workspace.createVariable('name2', 'type2', 'id2'); +} + +function test_undoCreateVariable_Trivial() { + undoRedoTest_setUp(); + createTwoVarsDifferentTypes(); workspace.undo(); checkVariableValues(workspace, 'name1', 'type1', 'id1'); @@ -107,8 +92,7 @@ function test_undoCreateVariable_Trivial() { function test_redoAndUndoCreateVariable_Trivial() { undoRedoTest_setUp(); - workspace.createVariable('name1', 'type1', 'id1'); - workspace.createVariable('name2', 'type2', 'id2'); + createTwoVarsDifferentTypes(); workspace.undo(); workspace.undo(true); @@ -129,8 +113,7 @@ function test_redoAndUndoCreateVariable_Trivial() { function test_undoDeleteVariable_NoBlocks() { undoRedoTest_setUp(); - workspace.createVariable('name1', 'type1', 'id1'); - workspace.createVariable('name2', 'type2', 'id2'); + createTwoVarsDifferentTypes(); workspace.deleteVariableById('id1'); workspace.deleteVariableById('id2'); @@ -146,10 +129,9 @@ function test_undoDeleteVariable_NoBlocks() { function test_undoDeleteVariable_WithBlocks() { undoRedoTest_setUp(); - workspace.createVariable('name1', 'type1', 'id1'); - workspace.createVariable('name2', 'type2', 'id2'); - createMockBlock('name1'); - createMockBlock('name2'); + + createTwoVariablesAndBlocks(workspace); + workspace.deleteVariableById('id1'); workspace.deleteVariableById('id2'); @@ -168,8 +150,9 @@ function test_undoDeleteVariable_WithBlocks() { function test_redoAndUndoDeleteVariable_NoBlocks() { undoRedoTest_setUp(); - workspace.createVariable('name1', 'type1', 'id1'); - workspace.createVariable('name2', 'type2', 'id2'); + + createTwoVarsDifferentTypes(); + workspace.deleteVariableById('id1'); workspace.deleteVariableById('id2'); @@ -190,10 +173,9 @@ function test_redoAndUndoDeleteVariable_NoBlocks() { function test_redoAndUndoDeleteVariable_WithBlocks() { undoRedoTest_setUp(); - workspace.createVariable('name1', 'type1', 'id1'); - workspace.createVariable('name2', 'type2', 'id2'); - createMockBlock('name1'); - createMockBlock('name2'); + + createTwoVariablesAndBlocks(workspace); + workspace.deleteVariableById('id1'); workspace.deleteVariableById('id2'); @@ -241,10 +223,11 @@ function test_redoAndUndoDeleteVariableTwice_NoBlocks() { function test_redoAndUndoDeleteVariableTwice_WithBlocks() { undoRedoTest_setUp(); - workspace.createVariable('name1', 'type1', 'id1'); - createMockBlock('name1'); - workspace.deleteVariableById('id1'); - workspace.deleteVariableById('id1'); + var id = 'id1'; + workspace.createVariable('name1', 'type1', id); + createMockVarBlock(workspace, id); + workspace.deleteVariableById(id); + workspace.deleteVariableById(id); // Check the undoStack only recorded one delete event. var undoStack = workspace.undoStack_; @@ -255,45 +238,27 @@ function test_redoAndUndoDeleteVariableTwice_WithBlocks() { // undo delete workspace.undo(); undoRedoTest_checkBlockVariableName(0, 'name1'); - checkVariableValues(workspace, 'name1', 'type1', 'id1'); + checkVariableValues(workspace, 'name1', 'type1', id); // redo delete workspace.undo(true); assertEquals(0, workspace.topBlocks_.length); - assertNull(workspace.getVariableById('id1')); + assertNull(workspace.getVariableById(id)); // redo delete, nothing should happen workspace.undo(true); assertEquals(0, workspace.topBlocks_.length); - assertNull(workspace.getVariableById('id1')); - undoRedoTest_tearDown(); -} - -function test_undoRedoRenameVariable_NeitherVariableExists() { - // Expect that a variable with the name, 'name2', and the generated UUID, - // 'id2', to be created when rename is called. Undo removes this variable - // and redo recreates it. - undoRedoTest_setUp(); - setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null, - ['rename_group', 'id2', 'delete_group']); - workspace.renameVariable('name1', 'name2'); - - workspace.undo(); - assertNull(workspace.getVariableById('id2')); - - workspace.undo(true); - checkVariableValues(workspace, 'name2', '', 'id2'); + assertNull(workspace.getVariableById(id)); undoRedoTest_tearDown(); } function test_undoRedoRenameVariable_OneExists_NoBlocks() { undoRedoTest_setUp(); workspace.createVariable('name1', '', 'id1'); - workspace.renameVariable('name1', 'name2'); + workspace.renameVariableById('id1', 'name2'); workspace.undo(); checkVariableValues(workspace, 'name1', '', 'id1'); - assertNull(workspace.getVariable('name2')); workspace.undo(true); checkVariableValues(workspace, 'name2', '', 'id1'); @@ -303,13 +268,12 @@ function test_undoRedoRenameVariable_OneExists_NoBlocks() { function test_undoRedoRenameVariable_OneExists_WithBlocks() { undoRedoTest_setUp(); workspace.createVariable('name1', '', 'id1'); - createMockBlock('name1'); - workspace.renameVariable('name1', 'name2'); + createMockVarBlock(workspace, 'id1'); + workspace.renameVariableById('id1', 'name2'); workspace.undo(); undoRedoTest_checkBlockVariableName(0, 'name1'); checkVariableValues(workspace, 'name1', '', 'id1'); - assertNull(workspace.getVariable('name2')); workspace.undo(true); checkVariableValues(workspace, 'name2', '', 'id1'); @@ -320,7 +284,7 @@ function test_undoRedoRenameVariable_OneExists_WithBlocks() { function test_undoRedoRenameVariable_BothExist_NoBlocks() { undoRedoTest_setUp(); createTwoVarsEmptyType(); - workspace.renameVariable('name1', 'name2'); + workspace.renameVariableById('id1', 'name2'); workspace.undo(); checkVariableValues(workspace, 'name1', '', 'id1'); @@ -328,16 +292,16 @@ function test_undoRedoRenameVariable_BothExist_NoBlocks() { workspace.undo(true); checkVariableValues(workspace, 'name2', '', 'id2'); - assertNull(workspace.getVariable('name1')); + assertNull(workspace.getVariableById('id1')); undoRedoTest_tearDown(); } function test_undoRedoRenameVariable_BothExist_WithBlocks() { undoRedoTest_setUp(); createTwoVarsEmptyType(); - createMockBlock('name1'); - createMockBlock('name2'); - workspace.renameVariable('name1', 'name2'); + createMockVarBlock(workspace, 'id1'); + createMockVarBlock(workspace, 'id2'); + workspace.renameVariableById('id1', 'name2'); workspace.undo(); undoRedoTest_checkBlockVariableName(0, 'name1'); @@ -354,7 +318,7 @@ function test_undoRedoRenameVariable_BothExist_WithBlocks() { function test_undoRedoRenameVariable_BothExistCaseChange_NoBlocks() { undoRedoTest_setUp(); createTwoVarsEmptyType(); - workspace.renameVariable('name1', 'Name2'); + workspace.renameVariableById('id1', 'Name2'); workspace.undo(); checkVariableValues(workspace, 'name1', '', 'id1'); @@ -369,9 +333,9 @@ function test_undoRedoRenameVariable_BothExistCaseChange_NoBlocks() { function test_undoRedoRenameVariable_BothExistCaseChange_WithBlocks() { undoRedoTest_setUp(); createTwoVarsEmptyType(); - createMockBlock('name1'); - createMockBlock('name2'); - workspace.renameVariable('name1', 'Name2'); + createMockVarBlock(workspace, 'id1'); + createMockVarBlock(workspace, 'id2'); + workspace.renameVariableById('id1', 'Name2'); workspace.undo(); undoRedoTest_checkBlockVariableName(0, 'name1'); @@ -381,7 +345,7 @@ function test_undoRedoRenameVariable_BothExistCaseChange_WithBlocks() { workspace.undo(true); checkVariableValues(workspace, 'Name2', '', 'id2'); - assertNull(workspace.getVariable('name1')); + assertNull(workspace.getVariableById('id1')); undoRedoTest_checkBlockVariableName(0, 'Name2'); undoRedoTest_checkBlockVariableName(1, 'Name2'); undoRedoTest_tearDown(); @@ -390,7 +354,7 @@ function test_undoRedoRenameVariable_BothExistCaseChange_WithBlocks() { function test_undoRedoRenameVariable_OnlyCaseChange_NoBlocks() { undoRedoTest_setUp(); workspace.createVariable('name1', '', 'id1'); - workspace.renameVariable('name1', 'Name1'); + workspace.renameVariableById('id1', 'Name1'); workspace.undo(); checkVariableValues(workspace, 'name1', '', 'id1'); @@ -403,8 +367,8 @@ function test_undoRedoRenameVariable_OnlyCaseChange_NoBlocks() { function test_undoRedoRenameVariable_OnlyCaseChange_WithBlocks() { undoRedoTest_setUp(); workspace.createVariable('name1', '', 'id1'); - createMockBlock('name1'); - workspace.renameVariable('name1', 'Name1'); + createMockVarBlock(workspace, 'id1'); + workspace.renameVariableById('id1', 'Name1'); workspace.undo(); undoRedoTest_checkBlockVariableName(0, 'name1'); diff --git a/tests/jsunit/xml_test.js b/tests/jsunit/xml_test.js index 1a6dbce3d..9c3da790b 100644 --- a/tests/jsunit/xml_test.js +++ b/tests/jsunit/xml_test.js @@ -285,11 +285,13 @@ function test_appendDomToWorkspace() { function test_blockToDom_fieldToDom_trivial() { xmlTest_setUpWithMockBlocks(); - workspace.createVariable('name1', 'type1', 'id1'); + // TODO (#1199): make a similar test where the variable is given a non-empty + // type.f + workspace.createVariable('name1', '', 'id1'); var block = new Blockly.Block(workspace, 'field_variable_test_block'); - block.inputList[0].fieldRow[0].setValue('name1'); + block.inputList[0].fieldRow[0].setValue('id1'); var resultFieldDom = Blockly.Xml.blockToDom(block).childNodes[0]; - xmlTest_checkVariableFieldDomValues(resultFieldDom, 'VAR', 'type1', 'id1', + xmlTest_checkVariableFieldDomValues(resultFieldDom, 'VAR', '', 'id1', 'name1'); xmlTest_tearDownWithMockBlocks(); } @@ -297,13 +299,20 @@ function test_blockToDom_fieldToDom_trivial() { function test_blockToDom_fieldToDom_defaultCase() { xmlTest_setUpWithMockBlocks(); setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null, ['1', '1']); - workspace.createVariable('name1'); - var block = new Blockly.Block(workspace, 'field_variable_test_block'); - block.inputList[0].fieldRow[0].setValue('name1'); - var resultFieldDom = Blockly.Xml.blockToDom(block).childNodes[0]; - // Expect type is '' and id is '1' since we don't specify type and id. - xmlTest_checkVariableFieldDomValues(resultFieldDom, 'VAR', '', '1', 'name1'); - xmlTest_tearDownWithMockBlocks(); + try { + workspace.createVariable('name1'); + + Blockly.Events.disable(); + var block = new Blockly.Block(workspace, 'field_variable_test_block'); + block.inputList[0].fieldRow[0].setValue('1'); + Blockly.Events.enable(); + + var resultFieldDom = Blockly.Xml.blockToDom(block).childNodes[0]; + // Expect type is '' and id is '1' since we don't specify type and id. + xmlTest_checkVariableFieldDomValues(resultFieldDom, 'VAR', '', '1', 'name1'); + } finally { + xmlTest_tearDownWithMockBlocks(); + } } function test_blockToDom_fieldToDom_notAFieldVariable() { @@ -343,14 +352,18 @@ function test_variablesToDom_oneVariable() { function test_variablesToDom_twoVariables_oneBlock() { xmlTest_setUpWithMockBlocks(); - workspace.createVariable('name1', 'type1', 'id1'); + workspace.createVariable('name1', '', 'id1'); workspace.createVariable('name2', 'type2', 'id2'); + // If events are enabled during block construction, it will create a default + // variable. + Blockly.Events.disable(); var block = new Blockly.Block(workspace, 'field_variable_test_block'); - block.inputList[0].fieldRow[0].setValue('name1'); + block.inputList[0].fieldRow[0].setValue('id1'); + Blockly.Events.enable(); var resultDom = Blockly.Xml.variablesToDom(workspace.getAllVariables()); assertEquals(2, resultDom.children.length); - xmlTest_checkVariableDomValues(resultDom.children[0], 'type1', 'id1', + xmlTest_checkVariableDomValues(resultDom.children[0], '', 'id1', 'name1'); xmlTest_checkVariableDomValues(resultDom.children[1], 'type2', 'id2', 'name2'); @@ -378,14 +391,12 @@ function test_variableFieldXml_caseSensitive() { } }; - var generatedXml = Blockly.Variables.generateVariableFieldXml_(mockVariableModel); - // The field contains this XML tag as a result of how we're generating this - // XML. This is not desirable, but the goal of this test is to make sure - // we're preserving case-sensitivity. - var xmlns = 'xmlns="http://www.w3.org/1999/xhtml"'; + var generatedXml = + Blockly.Variables.generateVariableFieldXml_(mockVariableModel); var goldenXml = - '' + name + ''; + '>' + name + ''; assertEquals(goldenXml, generatedXml); } diff --git a/tests/playground.html b/tests/playground.html index f062bf542..596e8f373 100644 --- a/tests/playground.html +++ b/tests/playground.html @@ -12,6 +12,7 @@ + @@ -21,6 +22,7 @@ + @@ -30,6 +32,7 @@ + @@ -39,6 +42,7 @@ + @@ -48,6 +52,7 @@ + @@ -57,11 +62,11 @@ +