diff --git a/.eslintignore b/.eslintignore index 38c86f3d1..45e036d9c 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,10 +1,7 @@ *_compressed*.js -blockly_uncompressed.js -gulpfile.js /msg/* /build/* /dist/* -/core/utils/global.js /tests/blocks/* /tests/themes/* /tests/compile/* diff --git a/.release-please-manifest.json b/.release-please-manifest.json index c6f67ad42..32ac6588b 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.20211209.0" + ".": "8.0.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb..cbe1c7ee2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1,77 @@ +# Changelog + +## [8.0.0](https://github.com/google/blockly/compare/blockly-v7.20211209.0...blockly-v8.0.0) (2022-03-31) + + +### ⚠ BREAKING CHANGES + +* change paste to return the pasted thing to support keyboard nav (#5996) +* **blocks:** ...and rename Blockly.blocks.all (blocks/all.js) to Blockly.libraryBlocks (blocks/blocks.js +* * refactor(blocks): Make loopTypes a Set +* allows previously internal constants to be configurable (#5897) +* * refactor(blocks): Make loopTypes a Set +* remove unused constants from internalConstants (#5889) + +### Features + +* add mocha failure messages to console output ([#5984](https://github.com/google/blockly/issues/5984)) ([7d250fa](https://github.com/google/blockly/commit/7d250fa9cfb30f95e7af523720b66c8b001df15c)) +* Allow developers to set a custom tooltip rendering function. ([#5956](https://github.com/google/blockly/issues/5956)) ([6841ccc](https://github.com/google/blockly/commit/6841ccc99fdbcc5f6d5a63bb36cb3b6ebd2be246)) +* **blocks:** Export block definitions ([#5908](https://github.com/google/blockly/issues/5908)) ([ffb8907](https://github.com/google/blockly/commit/ffb8907db8d0f11609c1fe14b2a450d3e639a871)) +* make mocha fail if it encounters 0 tests ([#5981](https://github.com/google/blockly/issues/5981)) ([0b2bf3a](https://github.com/google/blockly/commit/0b2bf3ae9d0c777f4d13d47692f5ae224dff1ec8)) +* **tests:** Add a test to validate `scripts/migration/renamings.js` ([#5980](https://github.com/google/blockly/issues/5980)) ([3c723f0](https://github.com/google/blockly/commit/3c723f0199b1f3b5eaac58f064b02d52b60d0ddb)) +* **tests:** Use official semver.org RegExp ([#5990](https://github.com/google/blockly/issues/5990)) ([afc4088](https://github.com/google/blockly/commit/afc4088ce278f97585f9ff5e65a921f7c4c65531)) + + +### Bug Fixes + +* Adds check for changedTouches ([#5869](https://github.com/google/blockly/issues/5869)) ([3f4f505](https://github.com/google/blockly/commit/3f4f5057919fdb4a329e9d2b15378c5c5831ae3b)) +* advanced playground and playground to work when hosted ([#6021](https://github.com/google/blockly/issues/6021)) ([364bf14](https://github.com/google/blockly/commit/364bf14ce6932f426591e3f53c1d066771ddcb8e)) +* always rename caller to legal name ([#6014](https://github.com/google/blockly/issues/6014)) ([c430800](https://github.com/google/blockly/commit/c4308007bc4b58d51adf1fda7b51ffa9f1d3f093)) +* **blocks:** correct the callType_ of procedures_defreturn ([#5974](https://github.com/google/blockly/issues/5974)) ([b34db5b](https://github.com/google/blockly/commit/b34db5bd01f7b532ebabc80264ca9fc804a76c75)) +* **build:** Correctly handle deep export paths in UMD wrapper ([#5945](https://github.com/google/blockly/issues/5945)) ([71ab146](https://github.com/google/blockly/commit/71ab146bc21aef9bdd6b2385c1df5f51e3ff5b58)) +* bumping a block after duplicate breaking undo ([#5844](https://github.com/google/blockly/issues/5844)) ([5204569](https://github.com/google/blockly/commit/5204569cff58c1ead7c15165a1351fa6a2ba2ad3)) +* change getCandidate_ and showInsertionMarker_ to be more dynamic ([#5722](https://github.com/google/blockly/issues/5722)) ([68d8113](https://github.com/google/blockly/commit/68d81132b851d20884ee9da41719fa62cdfce0ee)) +* change paste to return the pasted thing to support keyboard nav ([#5996](https://github.com/google/blockly/issues/5996)) ([20f1475](https://github.com/google/blockly/commit/20f1475afc1abf4b5e600219c2981150fc621ba5)) +* Change the truthy tests of width and height in WorkspaceSvg.setCachedParentSvgSize to actual comparisons with null so that zero value can be saved into the cache ([#5997](https://github.com/google/blockly/issues/5997)) ([fec44d9](https://github.com/google/blockly/commit/fec44d917e4b8475beba28e4769a50982425e887)) +* comments not being restored when dragging ([#6011](https://github.com/google/blockly/issues/6011)) ([85ce3b8](https://github.com/google/blockly/commit/85ce3b82c6c32e8a2a1608c6d604262ea0e5c38d)) +* convert the common renderer to an ES6 class ([#5978](https://github.com/google/blockly/issues/5978)) ([c1004be](https://github.com/google/blockly/commit/c1004be1f24debe1df1566e6067cf2f6769d51aa)) +* convert the Workspace class to an ES6 class ([#5977](https://github.com/google/blockly/issues/5977)) ([e2eaebe](https://github.com/google/blockly/commit/e2eaebec47b08a83eb36d0d04cefa254d1c5d666)) +* custom block context menus ([#5976](https://github.com/google/blockly/issues/5976)) ([8058df2](https://github.com/google/blockly/commit/8058df2a71dcecdc1190ae1d6f5dcccfafc980e8)) +* Don't throw if drag surface is empty. ([#5695](https://github.com/google/blockly/issues/5695)) ([769a25f](https://github.com/google/blockly/commit/769a25f4badffd2409ce19535344c98f5d8b01c9)) +* export Blockly.Names.NameType and Blockly.Input.Align correctly ([#6030](https://github.com/google/blockly/issues/6030)) ([2c15d00](https://github.com/google/blockly/commit/2c15d002ababcba7f34c526c05f231735e3e0169)) +* Export loopTypes from Blockly.blocks.loops ([#5900](https://github.com/google/blockly/issues/5900)) ([4f74210](https://github.com/google/blockly/commit/4f74210e74ef0b06216ab0f288268192674d69c9)) +* Export loopTypes from Blockly.blocks.loops ([#5900](https://github.com/google/blockly/issues/5900)) ([74ef1cb](https://github.com/google/blockly/commit/74ef1cbf521f7c6447ea9672ddbfe861d2292e5f)) +* Fix bug where workspace comments could not be created. ([#6024](https://github.com/google/blockly/issues/6024)) ([2cf8eb8](https://github.com/google/blockly/commit/2cf8eb87dcb029ba152b63b01ee7e4df431d1bb6)) +* Fix downloading screenshots on the playground. ([#6025](https://github.com/google/blockly/issues/6025)) ([ca6e590](https://github.com/google/blockly/commit/ca6e590101d511a8d98a5c5438af32ff6749e020)) +* fix keycodes type ([#5805](https://github.com/google/blockly/issues/5805)) ([0a96543](https://github.com/google/blockly/commit/0a96543a1179636e4efeb3c654c075952aca0c9f)) +* Fixed the label closure on demo/blockfactory ([#5833](https://github.com/google/blockly/issues/5833)) ([e8ea2e9](https://github.com/google/blockly/commit/e8ea2e9902fb9f642459e7341c3d59e19f359fca)) +* **generators:** Fix an operator precedence issue in the math_number_property generators to remove extra parentheses ([#5685](https://github.com/google/blockly/issues/5685)) ([a31003f](https://github.com/google/blockly/commit/a31003fab964e529152389029ec3126a3802851b)) +* incorrect module for event data in renamings database ([#6012](https://github.com/google/blockly/issues/6012)) ([e502eaa](https://github.com/google/blockly/commit/e502eaa6e1c88b2bb34e9a87917a15098b81cfa3)) +* Move [@alias](https://github.com/alias) onto classes instead of constructors ([#6003](https://github.com/google/blockly/issues/6003)) ([1647a32](https://github.com/google/blockly/commit/1647a3299ac48b5924f987015d8f3c47593922af)) +* move test helpers from samples into core ([#5969](https://github.com/google/blockly/issues/5969)) ([2edd228](https://github.com/google/blockly/commit/2edd22811752f05e16c68d593e5d1b809e24ed25)) +* move the dropdown div to a namespace instead of a class with only static properties ([#5979](https://github.com/google/blockly/issues/5979)) ([543cb8e](https://github.com/google/blockly/commit/543cb8e1b1c1a7fca5a1629f42f71c9b18e1a255)) +* msg imports in type definitions ([#5858](https://github.com/google/blockly/issues/5858)) ([07a75de](https://github.com/google/blockly/commit/07a75dee8de13b6c5a02959325a0155d413d6712)) +* opening/closing the mutators ([#6000](https://github.com/google/blockly/issues/6000)) ([243fc52](https://github.com/google/blockly/commit/243fc52a96e1089aad89ff6b642c6605d8f71afd)) +* playground access to Blockly ([9e1cda8](https://github.com/google/blockly/commit/9e1cda8f45cea1707c5a228d5ce79b4cd81566f8)) +* playground test blocks, text area listeners, and show/hide buttons ([#6015](https://github.com/google/blockly/issues/6015)) ([7abf3de](https://github.com/google/blockly/commit/7abf3de910a35e1a6086a3243570627a41e73339)) +* procedure param edits breaking undo ([#5845](https://github.com/google/blockly/issues/5845)) ([8a71f87](https://github.com/google/blockly/commit/8a71f879504503f4aec1140fe653d93602c664df)) +* re-expose HSV_VALUE and HSV_SATURATION as settable properties on Blockly ([#5821](https://github.com/google/blockly/issues/5821)) ([0e5f3ce](https://github.com/google/blockly/commit/0e5f3ce6074fbbb2923e9a62bffefeae0a813be8)) +* re-expose HSV_VALUE and HSV_SATURATION as settable properties on Blockly ([#5821](https://github.com/google/blockly/issues/5821)) ([6fc3316](https://github.com/google/blockly/commit/6fc3316309534270106050f0e1fecb7a09b8e62c)) +* revert "Delete events should animate when played ([#5919](https://github.com/google/blockly/issues/5919))" ([#6031](https://github.com/google/blockly/issues/6031)) ([c4a25eb](https://github.com/google/blockly/commit/c4a25eb3c432b0e8a9a18aae42839d163a177c48)) +* revert converting test helpers to es modules ([#5982](https://github.com/google/blockly/issues/5982)) ([01d4597](https://github.com/google/blockly/commit/01d45972d4df8b5e4afa4a19d93defb8961fea57)) +* setting null for a font style on a theme ([#5831](https://github.com/google/blockly/issues/5831)) ([835fb02](https://github.com/google/blockly/commit/835fb02343df0a4b9dab7704a4b3d8be8e9a497c)) +* **tests:** Enable --debug for test:compile:advanced; fix some errors ([#5959](https://github.com/google/blockly/issues/5959)) ([88334be](https://github.com/google/blockly/commit/88334bea80aa26c08705f16aba5e79dd708158f9)) +* **tests:** Enable `--debug` for `test:compile:advanced`; fix some errors (and demote the rest to warnings) ([#5983](https://github.com/google/blockly/issues/5983)) ([e11b583](https://github.com/google/blockly/commit/e11b5834e5e4e8fe991be32afb08eafa7c8adffd)) +* TypeScript exporting of the serialization functions ([#5890](https://github.com/google/blockly/issues/5890)) ([5d7c890](https://github.com/google/blockly/commit/5d7c890243ba7d0501514ba48778715097ce5a3b)) +* undo/redo for auto disabling if-return blocks ([#6018](https://github.com/google/blockly/issues/6018)) ([c7a359a](https://github.com/google/blockly/commit/c7a359a8424287f139752573a27a8a6eb97cb7b3)) +* update the playground to load compressed when hosted ([#5835](https://github.com/google/blockly/issues/5835)) ([2adf326](https://github.com/google/blockly/commit/2adf326c230589800880faa9599eca2ecc94d283)) +* Update typings for q1 2022 release ([#6051](https://github.com/google/blockly/issues/6051)) ([69f3d4a](https://github.com/google/blockly/commit/69f3d4ae89ce16a558443dd0a772e35b62c096d3)) +* Use correct namespace for svgMath functions ([#5813](https://github.com/google/blockly/issues/5813)) ([b8cc983](https://github.com/google/blockly/commit/b8cc983324338b2cbd536425c93ff3e7d512751e)) +* Use correct namespace for svgMath functions ([#5813](https://github.com/google/blockly/issues/5813)) ([025bab6](https://github.com/google/blockly/commit/025bab656669f99ebdb8b95bea39ebae296f1495)) + + +### Code Refactoring + +* allows previously internal constants to be configurable ([#5897](https://github.com/google/blockly/issues/5897)) ([4b5733e](https://github.com/google/blockly/commit/4b5733e7c85f2e196719550a3cfdcbcbd61739df)) +* **blocks:** Rename Blockly.blocks.* modules to Blockly.libraryBlocks.* ([#5953](https://github.com/google/blockly/issues/5953)) ([5078dcb](https://github.com/google/blockly/commit/5078dcbc6d4d48422313732e87191b29569b5eed)) +* remove unused constants from internalConstants ([#5889](https://github.com/google/blockly/issues/5889)) ([f0b1077](https://github.com/google/blockly/commit/f0b10776eb0657a5446adcfc62ad13f419c14271)) diff --git a/blockly_compressed.js b/blockly_compressed.js index 5397c2f95..6b95d5457 100644 --- a/blockly_compressed.js +++ b/blockly_compressed.js @@ -7,76 +7,89 @@ } else if (typeof exports === 'object') { // Node.js module.exports = factory(); } else { // Browser - root.Blockly = factory(); + var factoryExports = factory(); + root.Blockly = factoryExports; } }(this, function() { -const $={}; +var $={}; /* Copyright The Closure Library Authors. SPDX-License-Identifier: Apache-2.0 */ -var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.createTemplateTagFirstArg=function(a){return a.raw=a};$jscomp.createTemplateTagFirstArgWithRaw=function(a,b){a.raw=b;return a};$jscomp.arrayIteratorImpl=function(a){var b=0;return function(){return b>>0,$jscomp.propertyToPolyfillSymbol[e]=$jscomp.IS_SYMBOL_NATIVE? -$jscomp.global.Symbol(e):$jscomp.POLYFILL_PREFIX+c+"$"+e),$jscomp.defineProperty(d,$jscomp.propertyToPolyfillSymbol[e],{configurable:!0,writable:!0,value:b})))};$jscomp.assign=$jscomp.TRUST_ES6_POLYFILLS&&"function"==typeof Object.assign?Object.assign:function(a,b){for(var c=1;c=f}},"es6","es3");$jscomp.initSymbol=function(){}; -$jscomp.polyfill("Symbol",function(a){if(a)return a;var b=function(f,g){this.$jscomp$symbol$id_=f;$jscomp.defineProperty(this,"description",{configurable:!0,writable:!0,value:g})};b.prototype.toString=function(){return this.$jscomp$symbol$id_};var c="jscomp_symbol_"+(1E9*Math.random()>>>0)+"_",d=0,e=function(f){if(this instanceof e)throw new TypeError("Symbol is not a constructor");return new b(c+(f||"")+"_"+d++,f)};return e},"es6","es3"); -$jscomp.polyfill("Symbol.iterator",function(a){if(a)return a;a=Symbol("Symbol.iterator");for(var b="Array Int8Array Uint8Array Uint8ClampedArray Int16Array Uint16Array Int32Array Uint32Array Float32Array Float64Array".split(" "),c=0;cc&&(c=Math.max(0,e+c));if(null==d||d>e)d=e;d=Number(d);0>d&&(d=Math.max(0,e+d));for(c=Number(c||0);cc&&(c=Math.max(c+e,0));c>>/g,a),module$exports$Blockly$Css.content="",a=document.createElement("style"),a.id="blockly-common-style",b=document.createTextNode(b),a.appendChild(b),document.head.insertBefore(a,document.head.firstChild)))};module$exports$Blockly$Css.content='\n .blocklySvg {\n background-color: #fff;\n outline: none;\n overflow: hidden; /* IE overflows by default. */\n position: absolute;\n display: block;\n }\n\n .blocklyWidgetDiv {\n display: none;\n position: absolute;\n z-index: 99999; /* big value for bootstrap3 compatibility */\n }\n\n .injectionDiv {\n height: 100%;\n position: relative;\n overflow: hidden; /* So blocks in drag surface disappear at edges */\n touch-action: none;\n }\n\n .blocklyNonSelectable {\n user-select: none;\n -ms-user-select: none;\n -webkit-user-select: none;\n }\n\n .blocklyWsDragSurface {\n display: none;\n position: absolute;\n top: 0;\n left: 0;\n }\n\n /* Added as a separate rule with multiple classes to make it more specific\n than a bootstrap rule that selects svg:root. See issue #1275 for context.\n */\n .blocklyWsDragSurface.blocklyOverflowVisible {\n overflow: visible;\n }\n\n .blocklyBlockDragSurface {\n display: none;\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n overflow: visible !important;\n z-index: 50; /* Display below toolbox, but above everything else. */\n }\n\n .blocklyBlockCanvas.blocklyCanvasTransitioning,\n .blocklyBubbleCanvas.blocklyCanvasTransitioning {\n transition: transform .5s;\n }\n\n .blocklyTooltipDiv {\n background-color: #ffffc7;\n border: 1px solid #ddc;\n box-shadow: 4px 4px 20px 1px rgba(0,0,0,.15);\n color: #000;\n display: none;\n font: 9pt sans-serif;\n opacity: .9;\n padding: 2px;\n position: absolute;\n z-index: 100000; /* big value for bootstrap3 compatibility */\n }\n\n .blocklyDropDownDiv {\n position: absolute;\n left: 0;\n top: 0;\n z-index: 1000;\n display: none;\n border: 1px solid;\n border-color: #dadce0;\n background-color: #fff;\n border-radius: 2px;\n padding: 4px;\n box-shadow: 0 0 3px 1px rgba(0,0,0,.3);\n }\n\n .blocklyDropDownDiv.blocklyFocused {\n box-shadow: 0 0 6px 1px rgba(0,0,0,.3);\n }\n\n .blocklyDropDownContent {\n max-height: 300px; // @todo: spec for maximum height.\n overflow: auto;\n overflow-x: hidden;\n position: relative;\n }\n\n .blocklyDropDownArrow {\n position: absolute;\n left: 0;\n top: 0;\n width: 16px;\n height: 16px;\n z-index: -1;\n background-color: inherit;\n border-color: inherit;\n }\n\n .blocklyDropDownButton {\n display: inline-block;\n float: left;\n padding: 0;\n margin: 4px;\n border-radius: 4px;\n outline: none;\n border: 1px solid;\n transition: box-shadow .1s;\n cursor: pointer;\n }\n\n .blocklyArrowTop {\n border-top: 1px solid;\n border-left: 1px solid;\n border-top-left-radius: 4px;\n border-color: inherit;\n }\n\n .blocklyArrowBottom {\n border-bottom: 1px solid;\n border-right: 1px solid;\n border-bottom-right-radius: 4px;\n border-color: inherit;\n }\n\n .blocklyResizeSE {\n cursor: se-resize;\n fill: #aaa;\n }\n\n .blocklyResizeSW {\n cursor: sw-resize;\n fill: #aaa;\n }\n\n .blocklyResizeLine {\n stroke: #515A5A;\n stroke-width: 1;\n }\n\n .blocklyHighlightedConnectionPath {\n fill: none;\n stroke: #fc3;\n stroke-width: 4px;\n }\n\n .blocklyPathLight {\n fill: none;\n stroke-linecap: round;\n stroke-width: 1;\n }\n\n .blocklySelected>.blocklyPathLight {\n display: none;\n }\n\n .blocklyDraggable {\n /* backup for browsers (e.g. IE11) that don\'t support grab */\n cursor: url("<<>>/handopen.cur"), auto;\n cursor: grab;\n cursor: -webkit-grab;\n }\n\n /* backup for browsers (e.g. IE11) that don\'t support grabbing */\n .blocklyDragging {\n /* backup for browsers (e.g. IE11) that don\'t support grabbing */\n cursor: url("<<>>/handclosed.cur"), auto;\n cursor: grabbing;\n cursor: -webkit-grabbing;\n }\n\n /* Changes cursor on mouse down. Not effective in Firefox because of\n https://bugzilla.mozilla.org/show_bug.cgi?id=771241 */\n .blocklyDraggable:active {\n /* backup for browsers (e.g. IE11) that don\'t support grabbing */\n cursor: url("<<>>/handclosed.cur"), auto;\n cursor: grabbing;\n cursor: -webkit-grabbing;\n }\n\n /* Change the cursor on the whole drag surface in case the mouse gets\n ahead of block during a drag. This way the cursor is still a closed hand.\n */\n .blocklyBlockDragSurface .blocklyDraggable {\n /* backup for browsers (e.g. IE11) that don\'t support grabbing */\n cursor: url("<<>>/handclosed.cur"), auto;\n cursor: grabbing;\n cursor: -webkit-grabbing;\n }\n\n .blocklyDragging.blocklyDraggingDelete {\n cursor: url("<<>>/handdelete.cur"), auto;\n }\n\n .blocklyDragging>.blocklyPath,\n .blocklyDragging>.blocklyPathLight {\n fill-opacity: .8;\n stroke-opacity: .8;\n }\n\n .blocklyDragging>.blocklyPathDark {\n display: none;\n }\n\n .blocklyDisabled>.blocklyPath {\n fill-opacity: .5;\n stroke-opacity: .5;\n }\n\n .blocklyDisabled>.blocklyPathLight,\n .blocklyDisabled>.blocklyPathDark {\n display: none;\n }\n\n .blocklyInsertionMarker>.blocklyPath,\n .blocklyInsertionMarker>.blocklyPathLight,\n .blocklyInsertionMarker>.blocklyPathDark {\n fill-opacity: .2;\n stroke: none;\n }\n\n .blocklyMultilineText {\n font-family: monospace;\n }\n\n .blocklyNonEditableText>text {\n pointer-events: none;\n }\n\n .blocklyFlyout {\n position: absolute;\n z-index: 20;\n }\n\n .blocklyText text {\n cursor: default;\n }\n\n /*\n Don\'t allow users to select text. It gets annoying when trying to\n drag a block and selected text moves instead.\n */\n .blocklySvg text,\n .blocklyBlockDragSurface text {\n user-select: none;\n -ms-user-select: none;\n -webkit-user-select: none;\n cursor: inherit;\n }\n\n .blocklyHidden {\n display: none;\n }\n\n .blocklyFieldDropdown:not(.blocklyHidden) {\n display: block;\n }\n\n .blocklyIconGroup {\n cursor: default;\n }\n\n .blocklyIconGroup:not(:hover),\n .blocklyIconGroupReadonly {\n opacity: .6;\n }\n\n .blocklyIconShape {\n fill: #00f;\n stroke: #fff;\n stroke-width: 1px;\n }\n\n .blocklyIconSymbol {\n fill: #fff;\n }\n\n .blocklyMinimalBody {\n margin: 0;\n padding: 0;\n }\n\n .blocklyHtmlInput {\n border: none;\n border-radius: 4px;\n height: 100%;\n margin: 0;\n outline: none;\n padding: 0;\n width: 100%;\n text-align: center;\n display: block;\n box-sizing: border-box;\n }\n\n /* Edge and IE introduce a close icon when the input value is longer than a\n certain length. This affects our sizing calculations of the text input.\n Hiding the close icon to avoid that. */\n .blocklyHtmlInput::-ms-clear {\n display: none;\n }\n\n .blocklyMainBackground {\n stroke-width: 1;\n stroke: #c6c6c6; /* Equates to #ddd due to border being off-pixel. */\n }\n\n .blocklyMutatorBackground {\n fill: #fff;\n stroke: #ddd;\n stroke-width: 1;\n }\n\n .blocklyFlyoutBackground {\n fill: #ddd;\n fill-opacity: .8;\n }\n\n .blocklyMainWorkspaceScrollbar {\n z-index: 20;\n }\n\n .blocklyFlyoutScrollbar {\n z-index: 30;\n }\n\n .blocklyScrollbarHorizontal,\n .blocklyScrollbarVertical {\n position: absolute;\n outline: none;\n }\n\n .blocklyScrollbarBackground {\n opacity: 0;\n }\n\n .blocklyScrollbarHandle {\n fill: #ccc;\n }\n\n .blocklyScrollbarBackground:hover+.blocklyScrollbarHandle,\n .blocklyScrollbarHandle:hover {\n fill: #bbb;\n }\n\n /* Darken flyout scrollbars due to being on a grey background. */\n /* By contrast, workspace scrollbars are on a white background. */\n .blocklyFlyout .blocklyScrollbarHandle {\n fill: #bbb;\n }\n\n .blocklyFlyout .blocklyScrollbarBackground:hover+.blocklyScrollbarHandle,\n .blocklyFlyout .blocklyScrollbarHandle:hover {\n fill: #aaa;\n }\n\n .blocklyInvalidInput {\n background: #faa;\n }\n\n .blocklyVerticalMarker {\n stroke-width: 3px;\n fill: rgba(255,255,255,.5);\n pointer-events: none;\n }\n\n .blocklyComputeCanvas {\n position: absolute;\n width: 0;\n height: 0;\n }\n\n .blocklyNoPointerEvents {\n pointer-events: none;\n }\n\n .blocklyContextMenu {\n border-radius: 4px;\n max-height: 100%;\n }\n\n .blocklyDropdownMenu {\n border-radius: 2px;\n padding: 0 !important;\n }\n\n .blocklyDropdownMenu .blocklyMenuItem {\n /* 28px on the left for icon or checkbox. */\n padding-left: 28px;\n }\n\n /* BiDi override for the resting state. */\n .blocklyDropdownMenu .blocklyMenuItemRtl {\n /* Flip left/right padding for BiDi. */\n padding-left: 5px;\n padding-right: 28px;\n }\n\n .blocklyWidgetDiv .blocklyMenu {\n background: #fff;\n border: 1px solid transparent;\n box-shadow: 0 0 3px 1px rgba(0,0,0,.3);\n font: normal 13px Arial, sans-serif;\n margin: 0;\n outline: none;\n padding: 4px 0;\n position: absolute;\n overflow-y: auto;\n overflow-x: hidden;\n max-height: 100%;\n z-index: 20000; /* Arbitrary, but some apps depend on it... */\n }\n\n .blocklyWidgetDiv .blocklyMenu.blocklyFocused {\n box-shadow: 0 0 6px 1px rgba(0,0,0,.3);\n }\n\n .blocklyDropDownDiv .blocklyMenu {\n background: inherit; /* Compatibility with gapi, reset from goog-menu */\n border: inherit; /* Compatibility with gapi, reset from goog-menu */\n font: normal 13px "Helvetica Neue", Helvetica, sans-serif;\n outline: none;\n position: relative; /* Compatibility with gapi, reset from goog-menu */\n z-index: 20000; /* Arbitrary, but some apps depend on it... */\n }\n\n /* State: resting. */\n .blocklyMenuItem {\n border: none;\n color: #000;\n cursor: pointer;\n list-style: none;\n margin: 0;\n /* 7em on the right for shortcut. */\n min-width: 7em;\n padding: 6px 15px;\n white-space: nowrap;\n }\n\n /* State: disabled. */\n .blocklyMenuItemDisabled {\n color: #ccc;\n cursor: inherit;\n }\n\n /* State: hover. */\n .blocklyMenuItemHighlight {\n background-color: rgba(0,0,0,.1);\n }\n\n /* State: selected/checked. */\n .blocklyMenuItemCheckbox {\n height: 16px;\n position: absolute;\n width: 16px;\n }\n\n .blocklyMenuItemSelected .blocklyMenuItemCheckbox {\n background: url(<<>>/sprites.png) no-repeat -48px -16px;\n float: left;\n margin-left: -24px;\n position: static; /* Scroll with the menu. */\n }\n\n .blocklyMenuItemRtl .blocklyMenuItemCheckbox {\n float: right;\n margin-right: -24px;\n }\n';var module$contents$Blockly$utils$string_wrapLine,module$contents$Blockly$utils$string_wrapScore,module$contents$Blockly$utils$string_wrapMutate,module$contents$Blockly$utils$string_wrapToText; +$.$jscomp=$.$jscomp||{};$.$jscomp.scope={};$.$jscomp.createTemplateTagFirstArg=function(a){return a.raw=a};$.$jscomp.createTemplateTagFirstArgWithRaw=function(a,b){a.raw=b;return a};$.$jscomp.arrayIteratorImpl=function(a){var b=0;return function(){return b>>0,$.$jscomp.propertyToPolyfillSymbol[e]= +$.$jscomp.IS_SYMBOL_NATIVE?$.$jscomp.global.Symbol(e):$.$jscomp.POLYFILL_PREFIX+c+"$"+e),$.$jscomp.defineProperty(d,$.$jscomp.propertyToPolyfillSymbol[e],{configurable:!0,writable:!0,value:b})))};$.$jscomp.assign=$.$jscomp.TRUST_ES6_POLYFILLS&&"function"==typeof Object.assign?Object.assign:function(a,b){for(var c=1;c=f}},"es6","es3");$.$jscomp.initSymbol=function(){}; +$.$jscomp.polyfill("Symbol",function(a){if(a)return a;var b=function(f,g){this.$jscomp$symbol$id_=f;$.$jscomp.defineProperty(this,"description",{configurable:!0,writable:!0,value:g})};b.prototype.toString=function(){return this.$jscomp$symbol$id_};var c="jscomp_symbol_"+(1E9*Math.random()>>>0)+"_",d=0,e=function(f){if(this instanceof e)throw new TypeError("Symbol is not a constructor");return new b(c+(f||"")+"_"+d++,f)};return e},"es6","es3"); +$.$jscomp.polyfill("Symbol.iterator",function(a){if(a)return a;a=Symbol("Symbol.iterator");for(var b="Array Int8Array Uint8Array Uint8ClampedArray Int16Array Uint16Array Int32Array Uint32Array Float32Array Float64Array".split(" "),c=0;cc&&(c=Math.max(0,e+c));if(null==d||d>e)d=e;d=Number(d);0>d&&(d=Math.max(0,e+d));for(c=Number(c||0);c>>/g,a),module$exports$Blockly$Css.content="",a=document.createElement("style"),a.id="blockly-common-style",b=document.createTextNode(b),a.appendChild(b),document.head.insertBefore(a,document.head.firstChild)))};module$exports$Blockly$Css.content='\n.blocklySvg {\n background-color: #fff;\n outline: none;\n overflow: hidden; /* IE overflows by default. */\n position: absolute;\n display: block;\n}\n\n.blocklyWidgetDiv {\n display: none;\n position: absolute;\n z-index: 99999; /* big value for bootstrap3 compatibility */\n}\n\n.injectionDiv {\n height: 100%;\n position: relative;\n overflow: hidden; /* So blocks in drag surface disappear at edges */\n touch-action: none;\n}\n\n.blocklyNonSelectable {\n user-select: none;\n -ms-user-select: none;\n -webkit-user-select: none;\n}\n\n.blocklyWsDragSurface {\n display: none;\n position: absolute;\n top: 0;\n left: 0;\n}\n\n/* Added as a separate rule with multiple classes to make it more specific\n than a bootstrap rule that selects svg:root. See issue #1275 for context.\n*/\n.blocklyWsDragSurface.blocklyOverflowVisible {\n overflow: visible;\n}\n\n.blocklyBlockDragSurface {\n display: none;\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n overflow: visible !important;\n z-index: 50; /* Display below toolbox, but above everything else. */\n}\n\n.blocklyBlockCanvas.blocklyCanvasTransitioning,\n.blocklyBubbleCanvas.blocklyCanvasTransitioning {\n transition: transform .5s;\n}\n\n.blocklyTooltipDiv {\n background-color: #ffffc7;\n border: 1px solid #ddc;\n box-shadow: 4px 4px 20px 1px rgba(0,0,0,.15);\n color: #000;\n display: none;\n font: 9pt sans-serif;\n opacity: .9;\n padding: 2px;\n position: absolute;\n z-index: 100000; /* big value for bootstrap3 compatibility */\n}\n\n.blocklyDropDownDiv {\n position: absolute;\n left: 0;\n top: 0;\n z-index: 1000;\n display: none;\n border: 1px solid;\n border-color: #dadce0;\n background-color: #fff;\n border-radius: 2px;\n padding: 4px;\n box-shadow: 0 0 3px 1px rgba(0,0,0,.3);\n}\n\n.blocklyDropDownDiv.blocklyFocused {\n box-shadow: 0 0 6px 1px rgba(0,0,0,.3);\n}\n\n.blocklyDropDownContent {\n max-height: 300px; // @todo: spec for maximum height.\n overflow: auto;\n overflow-x: hidden;\n position: relative;\n}\n\n.blocklyDropDownArrow {\n position: absolute;\n left: 0;\n top: 0;\n width: 16px;\n height: 16px;\n z-index: -1;\n background-color: inherit;\n border-color: inherit;\n}\n\n.blocklyDropDownButton {\n display: inline-block;\n float: left;\n padding: 0;\n margin: 4px;\n border-radius: 4px;\n outline: none;\n border: 1px solid;\n transition: box-shadow .1s;\n cursor: pointer;\n}\n\n.blocklyArrowTop {\n border-top: 1px solid;\n border-left: 1px solid;\n border-top-left-radius: 4px;\n border-color: inherit;\n}\n\n.blocklyArrowBottom {\n border-bottom: 1px solid;\n border-right: 1px solid;\n border-bottom-right-radius: 4px;\n border-color: inherit;\n}\n\n.blocklyResizeSE {\n cursor: se-resize;\n fill: #aaa;\n}\n\n.blocklyResizeSW {\n cursor: sw-resize;\n fill: #aaa;\n}\n\n.blocklyResizeLine {\n stroke: #515A5A;\n stroke-width: 1;\n}\n\n.blocklyHighlightedConnectionPath {\n fill: none;\n stroke: #fc3;\n stroke-width: 4px;\n}\n\n.blocklyPathLight {\n fill: none;\n stroke-linecap: round;\n stroke-width: 1;\n}\n\n.blocklySelected>.blocklyPathLight {\n display: none;\n}\n\n.blocklyDraggable {\n /* backup for browsers (e.g. IE11) that don\'t support grab */\n cursor: url("<<>>/handopen.cur"), auto;\n cursor: grab;\n cursor: -webkit-grab;\n}\n\n /* backup for browsers (e.g. IE11) that don\'t support grabbing */\n.blocklyDragging {\n /* backup for browsers (e.g. IE11) that don\'t support grabbing */\n cursor: url("<<>>/handclosed.cur"), auto;\n cursor: grabbing;\n cursor: -webkit-grabbing;\n}\n\n /* Changes cursor on mouse down. Not effective in Firefox because of\n https://bugzilla.mozilla.org/show_bug.cgi?id=771241 */\n.blocklyDraggable:active {\n /* backup for browsers (e.g. IE11) that don\'t support grabbing */\n cursor: url("<<>>/handclosed.cur"), auto;\n cursor: grabbing;\n cursor: -webkit-grabbing;\n}\n\n/* Change the cursor on the whole drag surface in case the mouse gets\n ahead of block during a drag. This way the cursor is still a closed hand.\n */\n.blocklyBlockDragSurface .blocklyDraggable {\n /* backup for browsers (e.g. IE11) that don\'t support grabbing */\n cursor: url("<<>>/handclosed.cur"), auto;\n cursor: grabbing;\n cursor: -webkit-grabbing;\n}\n\n.blocklyDragging.blocklyDraggingDelete {\n cursor: url("<<>>/handdelete.cur"), auto;\n}\n\n.blocklyDragging>.blocklyPath,\n.blocklyDragging>.blocklyPathLight {\n fill-opacity: .8;\n stroke-opacity: .8;\n}\n\n.blocklyDragging>.blocklyPathDark {\n display: none;\n}\n\n.blocklyDisabled>.blocklyPath {\n fill-opacity: .5;\n stroke-opacity: .5;\n}\n\n.blocklyDisabled>.blocklyPathLight,\n.blocklyDisabled>.blocklyPathDark {\n display: none;\n}\n\n.blocklyInsertionMarker>.blocklyPath,\n.blocklyInsertionMarker>.blocklyPathLight,\n.blocklyInsertionMarker>.blocklyPathDark {\n fill-opacity: .2;\n stroke: none;\n}\n\n.blocklyMultilineText {\n font-family: monospace;\n}\n\n.blocklyNonEditableText>text {\n pointer-events: none;\n}\n\n.blocklyFlyout {\n position: absolute;\n z-index: 20;\n}\n\n.blocklyText text {\n cursor: default;\n}\n\n/*\n Don\'t allow users to select text. It gets annoying when trying to\n drag a block and selected text moves instead.\n*/\n.blocklySvg text,\n.blocklyBlockDragSurface text {\n user-select: none;\n -ms-user-select: none;\n -webkit-user-select: none;\n cursor: inherit;\n}\n\n.blocklyHidden {\n display: none;\n}\n\n.blocklyFieldDropdown:not(.blocklyHidden) {\n display: block;\n}\n\n.blocklyIconGroup {\n cursor: default;\n}\n\n.blocklyIconGroup:not(:hover),\n.blocklyIconGroupReadonly {\n opacity: .6;\n}\n\n.blocklyIconShape {\n fill: #00f;\n stroke: #fff;\n stroke-width: 1px;\n}\n\n.blocklyIconSymbol {\n fill: #fff;\n}\n\n.blocklyMinimalBody {\n margin: 0;\n padding: 0;\n}\n\n.blocklyHtmlInput {\n border: none;\n border-radius: 4px;\n height: 100%;\n margin: 0;\n outline: none;\n padding: 0;\n width: 100%;\n text-align: center;\n display: block;\n box-sizing: border-box;\n}\n\n/* Edge and IE introduce a close icon when the input value is longer than a\n certain length. This affects our sizing calculations of the text input.\n Hiding the close icon to avoid that. */\n.blocklyHtmlInput::-ms-clear {\n display: none;\n}\n\n.blocklyMainBackground {\n stroke-width: 1;\n stroke: #c6c6c6; /* Equates to #ddd due to border being off-pixel. */\n}\n\n.blocklyMutatorBackground {\n fill: #fff;\n stroke: #ddd;\n stroke-width: 1;\n}\n\n.blocklyFlyoutBackground {\n fill: #ddd;\n fill-opacity: .8;\n}\n\n.blocklyMainWorkspaceScrollbar {\n z-index: 20;\n}\n\n.blocklyFlyoutScrollbar {\n z-index: 30;\n}\n\n.blocklyScrollbarHorizontal,\n.blocklyScrollbarVertical {\n position: absolute;\n outline: none;\n}\n\n.blocklyScrollbarBackground {\n opacity: 0;\n}\n\n.blocklyScrollbarHandle {\n fill: #ccc;\n}\n\n.blocklyScrollbarBackground:hover+.blocklyScrollbarHandle,\n.blocklyScrollbarHandle:hover {\n fill: #bbb;\n}\n\n/* Darken flyout scrollbars due to being on a grey background. */\n/* By contrast, workspace scrollbars are on a white background. */\n.blocklyFlyout .blocklyScrollbarHandle {\n fill: #bbb;\n}\n\n.blocklyFlyout .blocklyScrollbarBackground:hover+.blocklyScrollbarHandle,\n.blocklyFlyout .blocklyScrollbarHandle:hover {\n fill: #aaa;\n}\n\n.blocklyInvalidInput {\n background: #faa;\n}\n\n.blocklyVerticalMarker {\n stroke-width: 3px;\n fill: rgba(255,255,255,.5);\n pointer-events: none;\n}\n\n.blocklyComputeCanvas {\n position: absolute;\n width: 0;\n height: 0;\n}\n\n.blocklyNoPointerEvents {\n pointer-events: none;\n}\n\n.blocklyContextMenu {\n border-radius: 4px;\n max-height: 100%;\n}\n\n.blocklyDropdownMenu {\n border-radius: 2px;\n padding: 0 !important;\n}\n\n.blocklyDropdownMenu .blocklyMenuItem {\n /* 28px on the left for icon or checkbox. */\n padding-left: 28px;\n}\n\n/* BiDi override for the resting state. */\n.blocklyDropdownMenu .blocklyMenuItemRtl {\n /* Flip left/right padding for BiDi. */\n padding-left: 5px;\n padding-right: 28px;\n}\n\n.blocklyWidgetDiv .blocklyMenu {\n background: #fff;\n border: 1px solid transparent;\n box-shadow: 0 0 3px 1px rgba(0,0,0,.3);\n font: normal 13px Arial, sans-serif;\n margin: 0;\n outline: none;\n padding: 4px 0;\n position: absolute;\n overflow-y: auto;\n overflow-x: hidden;\n max-height: 100%;\n z-index: 20000; /* Arbitrary, but some apps depend on it... */\n}\n\n.blocklyWidgetDiv .blocklyMenu.blocklyFocused {\n box-shadow: 0 0 6px 1px rgba(0,0,0,.3);\n}\n\n.blocklyDropDownDiv .blocklyMenu {\n background: inherit; /* Compatibility with gapi, reset from goog-menu */\n border: inherit; /* Compatibility with gapi, reset from goog-menu */\n font: normal 13px "Helvetica Neue", Helvetica, sans-serif;\n outline: none;\n position: relative; /* Compatibility with gapi, reset from goog-menu */\n z-index: 20000; /* Arbitrary, but some apps depend on it... */\n}\n\n/* State: resting. */\n.blocklyMenuItem {\n border: none;\n color: #000;\n cursor: pointer;\n list-style: none;\n margin: 0;\n /* 7em on the right for shortcut. */\n min-width: 7em;\n padding: 6px 15px;\n white-space: nowrap;\n}\n\n/* State: disabled. */\n.blocklyMenuItemDisabled {\n color: #ccc;\n cursor: inherit;\n}\n\n/* State: hover. */\n.blocklyMenuItemHighlight {\n background-color: rgba(0,0,0,.1);\n}\n\n/* State: selected/checked. */\n.blocklyMenuItemCheckbox {\n height: 16px;\n position: absolute;\n width: 16px;\n}\n\n.blocklyMenuItemSelected .blocklyMenuItemCheckbox {\n background: url(<<>>/sprites.png) no-repeat -48px -16px;\n float: left;\n margin-left: -24px;\n position: static; /* Scroll with the menu. */\n}\n\n.blocklyMenuItemRtl .blocklyMenuItemCheckbox {\n float: right;\n margin-right: -24px;\n}\n';var module$contents$Blockly$utils$string_wrapLine,module$contents$Blockly$utils$string_wrapScore,module$contents$Blockly$utils$string_wrapMutate,module$contents$Blockly$utils$string_wrapToText; $.module$exports$Blockly$utils$string={startsWith:function(a,b){return 0===a.lastIndexOf(b,0)},shortestStringLength:function(a){return a.length?a.reduce(function(b,c){return b.lengthb&&(b=c[d].length);var e=-Infinity,f=1;do{d=e;var g=a;a=[];e=c.length/f;for(var h=1,k=0;kd);return g}; module$contents$Blockly$utils$string_wrapScore=function(a,b,c){for(var d=[0],e=[],f=0;fd&&(d=h,e=g)}return e?module$contents$Blockly$utils$string_wrapMutate(a,e,c):b};module$contents$Blockly$utils$string_wrapToText=function(a,b){for(var c=[],d=0;dmodule$exports$Blockly$Tooltip.RADIUS_OK&&(0,module$exports$Blockly$Tooltip.hide)()}else module$contents$Blockly$Tooltip_poisonedElement!==module$contents$Blockly$Tooltip_element&&(clearTimeout(module$contents$Blockly$Tooltip_showPid),module$contents$Blockly$Tooltip_lastX=a.pageX,module$contents$Blockly$Tooltip_lastY=a.pageY,module$contents$Blockly$Tooltip_showPid=setTimeout(module$contents$Blockly$Tooltip_show, module$exports$Blockly$Tooltip.HOVER_MS))};module$exports$Blockly$Tooltip.dispose=function(){module$contents$Blockly$Tooltip_poisonedElement=module$contents$Blockly$Tooltip_element=null;(0,module$exports$Blockly$Tooltip.hide)()};module$exports$Blockly$Tooltip.hide=function(){module$contents$Blockly$Tooltip_visible&&(module$contents$Blockly$Tooltip_visible=!1,module$contents$Blockly$Tooltip_DIV&&(module$contents$Blockly$Tooltip_DIV.style.display="none"));module$contents$Blockly$Tooltip_showPid&&clearTimeout(module$contents$Blockly$Tooltip_showPid)}; module$exports$Blockly$Tooltip.block=function(){(0,module$exports$Blockly$Tooltip.hide)();module$contents$Blockly$Tooltip_blocked=!0};module$exports$Blockly$Tooltip.unblock=function(){module$contents$Blockly$Tooltip_blocked=!1}; -var module$contents$Blockly$Tooltip_show=function(){if(!module$contents$Blockly$Tooltip_blocked&&(module$contents$Blockly$Tooltip_poisonedElement=module$contents$Blockly$Tooltip_element,module$contents$Blockly$Tooltip_DIV)){module$contents$Blockly$Tooltip_DIV.textContent="";var a=(0,module$exports$Blockly$Tooltip.getTooltipOfObject)(module$contents$Blockly$Tooltip_element);a=(0,$.module$exports$Blockly$utils$string.wrap)(a,module$exports$Blockly$Tooltip.LIMIT);a=a.split("\n");for(var b=0;bc+window.scrollY&&(e-=module$contents$Blockly$Tooltip_DIV.offsetHeight+2*module$exports$Blockly$Tooltip.OFFSET_Y);a?d=Math.max(module$exports$Blockly$Tooltip.MARGINS-window.scrollX,d):d+module$contents$Blockly$Tooltip_DIV.offsetWidth>b+window.scrollX-2*module$exports$Blockly$Tooltip.MARGINS&& -(d=b-module$contents$Blockly$Tooltip_DIV.offsetWidth-2*module$exports$Blockly$Tooltip.MARGINS);module$contents$Blockly$Tooltip_DIV.style.top=e+"px";module$contents$Blockly$Tooltip_DIV.style.left=d+"px"}};var module$exports$Blockly$utils$dom={SVG_NS:"http://www.w3.org/2000/svg",HTML_NS:"http://www.w3.org/1999/xhtml",XLINK_NS:"http://www.w3.org/1999/xlink",NodeType:{ELEMENT_NODE:1,TEXT_NODE:3,COMMENT_NODE:8,DOCUMENT_POSITION_CONTAINED_BY:16}},module$contents$Blockly$utils$dom_cacheWidths=null,module$contents$Blockly$utils$dom_cacheReference=0,module$contents$Blockly$utils$dom_canvasContext=null; +var module$contents$Blockly$Tooltip_renderContent=function(){module$contents$Blockly$Tooltip_DIV&&module$contents$Blockly$Tooltip_element&&("function"===typeof module$contents$Blockly$Tooltip_customTooltip?module$contents$Blockly$Tooltip_customTooltip(module$contents$Blockly$Tooltip_DIV,module$contents$Blockly$Tooltip_element):module$contents$Blockly$Tooltip_renderDefaultContent())},module$contents$Blockly$Tooltip_renderDefaultContent=function(){var a=(0,module$exports$Blockly$Tooltip.getTooltipOfObject)(module$contents$Blockly$Tooltip_element); +a=(0,$.module$exports$Blockly$utils$string.wrap)(a,module$exports$Blockly$Tooltip.LIMIT);a=a.split("\n");for(var b=0;bc+window.scrollY&&(e-=module$contents$Blockly$Tooltip_DIV.offsetHeight+2*module$exports$Blockly$Tooltip.OFFSET_Y);a?d=Math.max(module$exports$Blockly$Tooltip.MARGINS-window.scrollX,d):d+module$contents$Blockly$Tooltip_DIV.offsetWidth>b+window.scrollX-2*module$exports$Blockly$Tooltip.MARGINS&&(d=b-module$contents$Blockly$Tooltip_DIV.offsetWidth- +2*module$exports$Blockly$Tooltip.MARGINS);return{x:d,y:e}},module$contents$Blockly$Tooltip_show=function(){if(!module$contents$Blockly$Tooltip_blocked&&(module$contents$Blockly$Tooltip_poisonedElement=module$contents$Blockly$Tooltip_element,module$contents$Blockly$Tooltip_DIV)){module$contents$Blockly$Tooltip_DIV.textContent="";module$contents$Blockly$Tooltip_renderContent();var a=module$contents$Blockly$Tooltip_element.RTL;module$contents$Blockly$Tooltip_DIV.style.direction=a?"rtl":"ltr";module$contents$Blockly$Tooltip_DIV.style.display= +"block";module$contents$Blockly$Tooltip_visible=!0;a=module$contents$Blockly$Tooltip_getPosition(a);var b=a.y;module$contents$Blockly$Tooltip_DIV.style.left=a.x+"px";module$contents$Blockly$Tooltip_DIV.style.top=b+"px"}};var module$exports$Blockly$utils$dom={SVG_NS:"http://www.w3.org/2000/svg",HTML_NS:"http://www.w3.org/1999/xhtml",XLINK_NS:"http://www.w3.org/1999/xlink",NodeType:{ELEMENT_NODE:1,TEXT_NODE:3,COMMENT_NODE:8,DOCUMENT_POSITION_CONTAINED_BY:16}},module$contents$Blockly$utils$dom_cacheWidths=null,module$contents$Blockly$utils$dom_cacheReference=0,module$contents$Blockly$utils$dom_canvasContext=null; module$exports$Blockly$utils$dom.createSvgElement=function(a,b,c){a=document.createElementNS(module$exports$Blockly$utils$dom.SVG_NS,String(a));for(var d in b)a.setAttribute(d,b[d]);document.body.runtimeStyle&&(a.runtimeStyle=a.currentStyle=a.style);c&&c.appendChild(a);return a};module$exports$Blockly$utils$dom.addClass=function(a,b){var c=a.getAttribute("class")||"";if(-1!==(" "+c+" ").indexOf(" "+b+" "))return!1;c&&(c+=" ");a.setAttribute("class",c+b);return!0}; module$exports$Blockly$utils$dom.removeClasses=function(a,b){b=b.split(" ");for(var c=0;cb.oldScale&&(0,module$exports$Blockly$bumpObjects.bumpTopObjectsIntoBounds)(a)}}},module$contents$Blockly$bumpObjects_extractObjectFromEvent=function(a,b){var c=null;switch(b.type){case module$exports$Blockly$Events$utils.CREATE:case module$exports$Blockly$Events$utils.MOVE:(c=a.getBlockById(b.blockId))&&(c=c.getRootBlock());break;case module$exports$Blockly$Events$utils.COMMENT_CREATE:case module$exports$Blockly$Events$utils.COMMENT_MOVE:c= -a.getCommentById(b.commentId)}return c};module$exports$Blockly$bumpObjects.bumpTopObjectsIntoBounds=function(a){var b=a.getMetricsManager();if(b.hasFixedEdges()&&!a.isDragging()){b=b.getScrollMetrics(!0);for(var c=a.getTopBoundedElements(),d=0,e;e=c[d];d++)(0,module$exports$Blockly$bumpObjects.bumpIntoBounds)(a,b,e)}};var module$exports$Blockly$utils$Coordinate={Coordinate:function(a,b){this.x=a;this.y=b}};module$exports$Blockly$utils$Coordinate.Coordinate.equals=function(a,b){return a===b?!0:a&&b?a.x===b.x&&a.y===b.y:!1};module$exports$Blockly$utils$Coordinate.Coordinate.distance=function(a,b){var c=a.x-b.x;a=a.y-b.y;return Math.sqrt(c*c+a*a)};module$exports$Blockly$utils$Coordinate.Coordinate.magnitude=function(a){return Math.sqrt(a.x*a.x+a.y*a.y)}; -module$exports$Blockly$utils$Coordinate.Coordinate.difference=function(a,b){return new module$exports$Blockly$utils$Coordinate.Coordinate(a.x-b.x,a.y-b.y)};module$exports$Blockly$utils$Coordinate.Coordinate.sum=function(a,b){return new module$exports$Blockly$utils$Coordinate.Coordinate(a.x+b.x,a.y+b.y)};module$exports$Blockly$utils$Coordinate.Coordinate.prototype.clone=function(){return new module$exports$Blockly$utils$Coordinate.Coordinate(this.x,this.y)}; -module$exports$Blockly$utils$Coordinate.Coordinate.prototype.scale=function(a){this.x*=a;this.y*=a;return this};module$exports$Blockly$utils$Coordinate.Coordinate.prototype.translate=function(a,b){this.x+=a;this.y+=b;return this};var module$exports$Blockly$utils$Size={Size:function(a,b){this.width=a;this.height=b}};module$exports$Blockly$utils$Size.Size.equals=function(a,b){return a===b?!0:a&&b?a.width===b.width&&a.height===b.height:!1};var module$exports$Blockly$utils$style={getSize:function(a){if("none"!==module$contents$Blockly$utils$style_getStyle(a,"display"))return module$contents$Blockly$utils$style_getSizeWithDisplay(a);var b=a.style,c=b.display,d=b.visibility,e=b.position;b.visibility="hidden";b.position="absolute";b.display="inline";var f=a.offsetWidth;a=a.offsetHeight;b.display=c;b.position=e;b.visibility=d;return new module$exports$Blockly$utils$Size.Size(f,a)}},module$contents$Blockly$utils$style_getSizeWithDisplay= +a.getCommentById(b.commentId)}return c};module$exports$Blockly$bumpObjects.bumpTopObjectsIntoBounds=function(a){var b=a.getMetricsManager();if(b.hasFixedEdges()&&!a.isDragging()){b=b.getScrollMetrics(!0);for(var c=a.getTopBoundedElements(),d=0,e;e=c[d];d++)(0,module$exports$Blockly$bumpObjects.bumpIntoBounds)(a,b,e)}};var module$exports$Blockly$utils$Coordinate={Coordinate:function(a,b){this.x=a;this.y=b}};module$exports$Blockly$utils$Coordinate.Coordinate.prototype.clone=function(){return new module$exports$Blockly$utils$Coordinate.Coordinate(this.x,this.y)};module$exports$Blockly$utils$Coordinate.Coordinate.prototype.scale=function(a){this.x*=a;this.y*=a;return this};module$exports$Blockly$utils$Coordinate.Coordinate.prototype.translate=function(a,b){this.x+=a;this.y+=b;return this}; +module$exports$Blockly$utils$Coordinate.Coordinate.equals=function(a,b){return a===b?!0:a&&b?a.x===b.x&&a.y===b.y:!1};module$exports$Blockly$utils$Coordinate.Coordinate.distance=function(a,b){var c=a.x-b.x;a=a.y-b.y;return Math.sqrt(c*c+a*a)};module$exports$Blockly$utils$Coordinate.Coordinate.magnitude=function(a){return Math.sqrt(a.x*a.x+a.y*a.y)}; +module$exports$Blockly$utils$Coordinate.Coordinate.difference=function(a,b){return new module$exports$Blockly$utils$Coordinate.Coordinate(a.x-b.x,a.y-b.y)};module$exports$Blockly$utils$Coordinate.Coordinate.sum=function(a,b){return new module$exports$Blockly$utils$Coordinate.Coordinate(a.x+b.x,a.y+b.y)};var module$exports$Blockly$utils$Size={Size:function(a,b){this.width=a;this.height=b}};module$exports$Blockly$utils$Size.Size.equals=function(a,b){return a===b?!0:a&&b?a.width===b.width&&a.height===b.height:!1};var module$exports$Blockly$utils$style={getSize:function(a){if("none"!==module$contents$Blockly$utils$style_getStyle(a,"display"))return module$contents$Blockly$utils$style_getSizeWithDisplay(a);var b=a.style,c=b.display,d=b.visibility,e=b.position;b.visibility="hidden";b.position="absolute";b.display="inline";var f=a.offsetWidth;a=a.offsetHeight;b.display=c;b.position=e;b.visibility=d;return new module$exports$Blockly$utils$Size.Size(f,a)}},module$contents$Blockly$utils$style_getSizeWithDisplay= function(a){return new module$exports$Blockly$utils$Size.Size(a.offsetWidth,a.offsetHeight)},module$contents$Blockly$utils$style_getStyle=function(a,b){return(0,module$exports$Blockly$utils$style.getComputedStyle)(a,b)||(0,module$exports$Blockly$utils$style.getCascadedStyle)(a,b)||a.style&&a.style[b]}; module$exports$Blockly$utils$style.getComputedStyle=function(a,b){return document.defaultView&&document.defaultView.getComputedStyle&&(a=document.defaultView.getComputedStyle(a,null))?a[b]||a.getPropertyValue(b)||"":""};module$exports$Blockly$utils$style.getCascadedStyle=function(a,b){return a.currentStyle?a.currentStyle[b]:null}; module$exports$Blockly$utils$style.getPageOffset=function(a){var b=new module$exports$Blockly$utils$Coordinate.Coordinate(0,0);a=a.getBoundingClientRect();var c=document.documentElement;c=new module$exports$Blockly$utils$Coordinate.Coordinate(window.pageXOffset||c.scrollLeft,window.pageYOffset||c.scrollTop);b.x=a.left+c.x;b.y=a.top+c.y;return b}; @@ -149,7 +163,33 @@ module$exports$Blockly$utils$style.getViewportPageOffset=function(){var a=docume module$exports$Blockly$utils$style.getBorderBox=function(a){var b=(0,module$exports$Blockly$utils$style.getComputedStyle)(a,"borderLeftWidth"),c=(0,module$exports$Blockly$utils$style.getComputedStyle)(a,"borderRightWidth"),d=(0,module$exports$Blockly$utils$style.getComputedStyle)(a,"borderTopWidth");a=(0,module$exports$Blockly$utils$style.getComputedStyle)(a,"borderBottomWidth");return{top:parseFloat(d),right:parseFloat(c),bottom:parseFloat(a),left:parseFloat(b)}}; module$exports$Blockly$utils$style.scrollIntoContainerView=function(a,b,c){a=(0,module$exports$Blockly$utils$style.getContainerOffsetToScrollInto)(a,b,c);b.scrollLeft=a.x;b.scrollTop=a.y}; module$exports$Blockly$utils$style.getContainerOffsetToScrollInto=function(a,b,c){var d=(0,module$exports$Blockly$utils$style.getPageOffset)(a),e=(0,module$exports$Blockly$utils$style.getPageOffset)(b),f=(0,module$exports$Blockly$utils$style.getBorderBox)(b),g=d.x-e.x-f.left;d=d.y-e.y-f.top;e=module$contents$Blockly$utils$style_getSizeWithDisplay(a);a=b.clientWidth-e.width;e=b.clientHeight-e.height;f=b.scrollLeft;b=b.scrollTop;c?(f+=g-a/2,b+=d-e/2):(f+=Math.min(g,Math.max(g-a,0)),b+=Math.min(d,Math.max(d- -e,0)));return new module$exports$Blockly$utils$Coordinate.Coordinate(f,b)};var module$exports$Blockly$utils$Rect={Rect:function(a,b,c,d){this.top=a;this.bottom=b;this.left=c;this.right=d}};module$exports$Blockly$utils$Rect.Rect.prototype.contains=function(a,b){return a>=this.left&&a<=this.right&&b>=this.top&&b<=this.bottom};module$exports$Blockly$utils$Rect.Rect.prototype.intersects=function(a){return!(this.left>a.right||this.righta.bottom||this.bottom=this.left&&a<=this.right&&b>=this.top&&b<=this.bottom};module$exports$Blockly$utils$Rect.Rect.prototype.intersects=function(a){return!(this.left>a.right||this.righta.bottom||this.bottome.top?module$contents$Blockly$dropDownDiv_getPositionAboveMetrics(c,d,e,f):b+f.heightdocument.documentElement.clientTop?module$contents$Blockly$dropDownDiv_getPositionAboveMetrics(c,d,e,f):module$contents$Blockly$dropDownDiv_getPositionTopOfPageMetrics(a,e,f)}; +var module$contents$Blockly$dropDownDiv_getPositionBelowMetrics=function(a,b,c,d){a=(0,module$exports$Blockly$dropDownDiv.getPositionX)(a,c.left,c.right,d.width);return{initialX:a.divX,initialY:b,finalX:a.divX,finalY:b+module$exports$Blockly$dropDownDiv.PADDING_Y,arrowX:a.arrowX,arrowY:-(module$exports$Blockly$dropDownDiv.ARROW_SIZE/2+module$exports$Blockly$dropDownDiv.BORDER_SIZE),arrowAtTop:!0,arrowVisible:!0}},module$contents$Blockly$dropDownDiv_getPositionAboveMetrics=function(a,b,c,d){a=(0,module$exports$Blockly$dropDownDiv.getPositionX)(a, +c.left,c.right,d.width);return{initialX:a.divX,initialY:b-d.height,finalX:a.divX,finalY:b-d.height-module$exports$Blockly$dropDownDiv.PADDING_Y,arrowX:a.arrowX,arrowY:d.height-2*module$exports$Blockly$dropDownDiv.BORDER_SIZE-module$exports$Blockly$dropDownDiv.ARROW_SIZE/2,arrowAtTop:!1,arrowVisible:!0}},module$contents$Blockly$dropDownDiv_getPositionTopOfPageMetrics=function(a,b,c){a=(0,module$exports$Blockly$dropDownDiv.getPositionX)(a,b.left,b.right,c.width);return{initialX:a.divX,initialY:0,finalX:a.divX, +finalY:0,arrowAtTop:null,arrowX:null,arrowY:null,arrowVisible:!1}};module$exports$Blockly$dropDownDiv.getPositionX=function(a,b,c,d){b=(0,module$exports$Blockly$utils$math.clamp)(b,a-d/2,c-d);a=a-module$exports$Blockly$dropDownDiv.ARROW_SIZE/2-b;c=module$exports$Blockly$dropDownDiv.ARROW_HORIZONTAL_PADDING;a=(0,module$exports$Blockly$utils$math.clamp)(c,a,d-c-module$exports$Blockly$dropDownDiv.ARROW_SIZE);return{arrowX:a,divX:b}};module$exports$Blockly$dropDownDiv.isVisible=function(){return!!module$contents$Blockly$dropDownDiv_owner}; +module$exports$Blockly$dropDownDiv.hideIfOwner=function(a,b){return module$contents$Blockly$dropDownDiv_owner===a?(b?(0,module$exports$Blockly$dropDownDiv.hideWithoutAnimation)():(0,module$exports$Blockly$dropDownDiv.hide)(),!0):!1}; +module$exports$Blockly$dropDownDiv.hide=function(){module$contents$Blockly$dropDownDiv_div.style.transform="translate(0, 0)";module$contents$Blockly$dropDownDiv_div.style.opacity=0;module$contents$Blockly$dropDownDiv_animateOutTimer=setTimeout(function(){(0,module$exports$Blockly$dropDownDiv.hideWithoutAnimation)()},1E3*module$exports$Blockly$dropDownDiv.ANIMATION_TIME);module$contents$Blockly$dropDownDiv_onHide&&(module$contents$Blockly$dropDownDiv_onHide(),module$contents$Blockly$dropDownDiv_onHide= +null)}; +module$exports$Blockly$dropDownDiv.hideWithoutAnimation=function(){(0,module$exports$Blockly$dropDownDiv.isVisible)()&&(module$contents$Blockly$dropDownDiv_animateOutTimer&&clearTimeout(module$contents$Blockly$dropDownDiv_animateOutTimer),module$contents$Blockly$dropDownDiv_div.style.transform="",module$contents$Blockly$dropDownDiv_div.style.left="",module$contents$Blockly$dropDownDiv_div.style.top="",module$contents$Blockly$dropDownDiv_div.style.opacity=0,module$contents$Blockly$dropDownDiv_div.style.display="none", +module$contents$Blockly$dropDownDiv_div.style.backgroundColor="",module$contents$Blockly$dropDownDiv_div.style.borderColor="",module$contents$Blockly$dropDownDiv_onHide&&(module$contents$Blockly$dropDownDiv_onHide(),module$contents$Blockly$dropDownDiv_onHide=null),(0,module$exports$Blockly$dropDownDiv.clearContent)(),module$contents$Blockly$dropDownDiv_owner=null,module$contents$Blockly$dropDownDiv_renderedClassName&&((0,module$exports$Blockly$utils$dom.removeClass)(module$contents$Blockly$dropDownDiv_div, +module$contents$Blockly$dropDownDiv_renderedClassName),module$contents$Blockly$dropDownDiv_renderedClassName=""),module$contents$Blockly$dropDownDiv_themeClassName&&((0,module$exports$Blockly$utils$dom.removeClass)(module$contents$Blockly$dropDownDiv_div,module$contents$Blockly$dropDownDiv_themeClassName),module$contents$Blockly$dropDownDiv_themeClassName=""),(0,$.module$exports$Blockly$common.getMainWorkspace)().markFocused())}; +var module$contents$Blockly$dropDownDiv_positionInternal=function(a,b,c,d){a=module$exports$Blockly$dropDownDiv.TEST_ONLY.getPositionMetrics(a,b,c,d);a.arrowVisible?(module$contents$Blockly$dropDownDiv_arrow.style.display="",module$contents$Blockly$dropDownDiv_arrow.style.transform="translate("+a.arrowX+"px,"+a.arrowY+"px) rotate(45deg)",module$contents$Blockly$dropDownDiv_arrow.setAttribute("class",a.arrowAtTop?"blocklyDropDownArrow blocklyArrowTop":"blocklyDropDownArrow blocklyArrowBottom")):module$contents$Blockly$dropDownDiv_arrow.style.display= +"none";b=Math.floor(a.initialX);c=Math.floor(a.initialY);d=Math.floor(a.finalX);var e=Math.floor(a.finalY);module$contents$Blockly$dropDownDiv_div.style.left=b+"px";module$contents$Blockly$dropDownDiv_div.style.top=c+"px";module$contents$Blockly$dropDownDiv_div.style.display="block";module$contents$Blockly$dropDownDiv_div.style.opacity=1;module$contents$Blockly$dropDownDiv_div.style.transform="translate("+(d-b)+"px,"+(e-c)+"px)";return!!a.arrowAtTop}; +module$exports$Blockly$dropDownDiv.repositionForWindowResize=function(){if(module$contents$Blockly$dropDownDiv_owner){var a=module$contents$Blockly$dropDownDiv_owner,b=a.getSourceBlock();a=module$contents$Blockly$dropDownDiv_positionToField?module$contents$Blockly$dropDownDiv_getScaledBboxOfField(a):module$contents$Blockly$dropDownDiv_getScaledBboxOfBlock(b);b=a.left+(a.right-a.left)/2;module$contents$Blockly$dropDownDiv_positionInternal(b,a.bottom,b,a.top)}else(0,module$exports$Blockly$dropDownDiv.hide)()};var module$exports$Blockly$utils$svgMath={},module$contents$Blockly$utils$svgMath_XY_REGEX=/translate\(\s*([-+\d.e]+)([ ,]\s*([-+\d.e]+)\s*)?/,module$contents$Blockly$utils$svgMath_XY_STYLE_REGEX=/transform:\s*translate(?:3d)?\(\s*([-+\d.e]+)\s*px([ ,]\s*([-+\d.e]+)\s*px)?/; module$exports$Blockly$utils$svgMath.getRelativeXY=function(a){var b=new module$exports$Blockly$utils$Coordinate.Coordinate(0,0),c=a.getAttribute("x");c&&(b.x=parseInt(c,10));if(c=a.getAttribute("y"))b.y=parseInt(c,10);if(c=(c=a.getAttribute("transform"))&&c.match(module$contents$Blockly$utils$svgMath_XY_REGEX))b.x+=Number(c[1]),c[3]&&(b.y+=Number(c[3]));(a=a.getAttribute("style"))&&-1e.top?module$contents$Blockly$DropDownDiv_getPositionAboveMetrics(c,d,e,f):b+f.heightdocument.documentElement.clientTop?module$contents$Blockly$DropDownDiv_getPositionAboveMetrics(c,d,e,f):module$contents$Blockly$DropDownDiv_getPositionTopOfPageMetrics(a,e,f)}; -var module$contents$Blockly$DropDownDiv_getPositionBelowMetrics=function(a,b,c,d){a=module$exports$Blockly$DropDownDiv.DropDownDiv.getPositionX(a,c.left,c.right,d.width);return{initialX:a.divX,initialY:b,finalX:a.divX,finalY:b+module$exports$Blockly$DropDownDiv.DropDownDiv.PADDING_Y,arrowX:a.arrowX,arrowY:-(module$exports$Blockly$DropDownDiv.DropDownDiv.ARROW_SIZE/2+module$exports$Blockly$DropDownDiv.DropDownDiv.BORDER_SIZE),arrowAtTop:!0,arrowVisible:!0}},module$contents$Blockly$DropDownDiv_getPositionAboveMetrics= -function(a,b,c,d){a=module$exports$Blockly$DropDownDiv.DropDownDiv.getPositionX(a,c.left,c.right,d.width);return{initialX:a.divX,initialY:b-d.height,finalX:a.divX,finalY:b-d.height-module$exports$Blockly$DropDownDiv.DropDownDiv.PADDING_Y,arrowX:a.arrowX,arrowY:d.height-2*module$exports$Blockly$DropDownDiv.DropDownDiv.BORDER_SIZE-module$exports$Blockly$DropDownDiv.DropDownDiv.ARROW_SIZE/2,arrowAtTop:!1,arrowVisible:!0}},module$contents$Blockly$DropDownDiv_getPositionTopOfPageMetrics=function(a,b,c){a= -module$exports$Blockly$DropDownDiv.DropDownDiv.getPositionX(a,b.left,b.right,c.width);return{initialX:a.divX,initialY:0,finalX:a.divX,finalY:0,arrowAtTop:null,arrowX:null,arrowY:null,arrowVisible:!1}}; -module$exports$Blockly$DropDownDiv.DropDownDiv.getPositionX=function(a,b,c,d){b=(0,module$exports$Blockly$utils$math.clamp)(b,a-d/2,c-d);a=a-module$exports$Blockly$DropDownDiv.DropDownDiv.ARROW_SIZE/2-b;c=module$exports$Blockly$DropDownDiv.DropDownDiv.ARROW_HORIZONTAL_PADDING;a=(0,module$exports$Blockly$utils$math.clamp)(c,a,d-c-module$exports$Blockly$DropDownDiv.DropDownDiv.ARROW_SIZE);return{arrowX:a,divX:b}};module$exports$Blockly$DropDownDiv.DropDownDiv.isVisible=function(){return!!module$exports$Blockly$DropDownDiv.DropDownDiv.owner_}; -module$exports$Blockly$DropDownDiv.DropDownDiv.hideIfOwner=function(a,b){return module$exports$Blockly$DropDownDiv.DropDownDiv.owner_===a?(b?module$exports$Blockly$DropDownDiv.DropDownDiv.hideWithoutAnimation():module$exports$Blockly$DropDownDiv.DropDownDiv.hide(),!0):!1}; -module$exports$Blockly$DropDownDiv.DropDownDiv.hide=function(){module$exports$Blockly$DropDownDiv.DropDownDiv.DIV_.style.transform="translate(0, 0)";module$exports$Blockly$DropDownDiv.DropDownDiv.DIV_.style.opacity=0;module$exports$Blockly$DropDownDiv.DropDownDiv.animateOutTimer_=setTimeout(function(){module$exports$Blockly$DropDownDiv.DropDownDiv.hideWithoutAnimation()},1E3*module$exports$Blockly$DropDownDiv.DropDownDiv.ANIMATION_TIME);module$exports$Blockly$DropDownDiv.DropDownDiv.onHide_&&(module$exports$Blockly$DropDownDiv.DropDownDiv.onHide_(), -module$exports$Blockly$DropDownDiv.DropDownDiv.onHide_=null)}; -module$exports$Blockly$DropDownDiv.DropDownDiv.hideWithoutAnimation=function(){if(module$exports$Blockly$DropDownDiv.DropDownDiv.isVisible()){module$exports$Blockly$DropDownDiv.DropDownDiv.animateOutTimer_&&clearTimeout(module$exports$Blockly$DropDownDiv.DropDownDiv.animateOutTimer_);var a=module$exports$Blockly$DropDownDiv.DropDownDiv.DIV_;a.style.transform="";a.style.left="";a.style.top="";a.style.opacity=0;a.style.display="none";a.style.backgroundColor="";a.style.borderColor="";module$exports$Blockly$DropDownDiv.DropDownDiv.onHide_&& -(module$exports$Blockly$DropDownDiv.DropDownDiv.onHide_(),module$exports$Blockly$DropDownDiv.DropDownDiv.onHide_=null);module$exports$Blockly$DropDownDiv.DropDownDiv.clearContent();module$exports$Blockly$DropDownDiv.DropDownDiv.owner_=null;module$exports$Blockly$DropDownDiv.DropDownDiv.rendererClassName_&&((0,module$exports$Blockly$utils$dom.removeClass)(a,module$exports$Blockly$DropDownDiv.DropDownDiv.rendererClassName_),module$exports$Blockly$DropDownDiv.DropDownDiv.rendererClassName_="");module$exports$Blockly$DropDownDiv.DropDownDiv.themeClassName_&& -((0,module$exports$Blockly$utils$dom.removeClass)(a,module$exports$Blockly$DropDownDiv.DropDownDiv.themeClassName_),module$exports$Blockly$DropDownDiv.DropDownDiv.themeClassName_="");(0,$.module$exports$Blockly$common.getMainWorkspace)().markFocused()}}; -var module$contents$Blockly$DropDownDiv_positionInternal=function(a,b,c,d){a=module$contents$Blockly$DropDownDiv_internal.getPositionMetrics(a,b,c,d);a.arrowVisible?(module$exports$Blockly$DropDownDiv.DropDownDiv.arrow_.style.display="",module$exports$Blockly$DropDownDiv.DropDownDiv.arrow_.style.transform="translate("+a.arrowX+"px,"+a.arrowY+"px) rotate(45deg)",module$exports$Blockly$DropDownDiv.DropDownDiv.arrow_.setAttribute("class",a.arrowAtTop?"blocklyDropDownArrow blocklyArrowTop":"blocklyDropDownArrow blocklyArrowBottom")): -module$exports$Blockly$DropDownDiv.DropDownDiv.arrow_.style.display="none";b=Math.floor(a.initialX);c=Math.floor(a.initialY);d=Math.floor(a.finalX);var e=Math.floor(a.finalY),f=module$exports$Blockly$DropDownDiv.DropDownDiv.DIV_;f.style.left=b+"px";f.style.top=c+"px";f.style.display="block";f.style.opacity=1;f.style.transform="translate("+(d-b)+"px,"+(e-c)+"px)";return!!a.arrowAtTop}; -module$exports$Blockly$DropDownDiv.DropDownDiv.repositionForWindowResize=function(){if(module$exports$Blockly$DropDownDiv.DropDownDiv.owner_){var a=module$exports$Blockly$DropDownDiv.DropDownDiv.owner_,b=a.getSourceBlock();a=module$exports$Blockly$DropDownDiv.DropDownDiv.positionToField_?module$contents$Blockly$DropDownDiv_getScaledBboxOfField(a):module$contents$Blockly$DropDownDiv_getScaledBboxOfBlock(b);b=a.left+(a.right-a.left)/2;module$contents$Blockly$DropDownDiv_positionInternal(b,a.bottom, -b,a.top)}else module$exports$Blockly$DropDownDiv.DropDownDiv.hide()};module$exports$Blockly$DropDownDiv.DropDownDiv.TEST_ONLY=module$contents$Blockly$DropDownDiv_internal;var module$exports$Blockly$Grid={Grid:function(a,b){this.gridPattern_=a;this.spacing_=b.spacing;this.length_=b.length;this.line2_=(this.line1_=a.firstChild)&&this.line1_.nextSibling;this.snapToGrid_=b.snap}};module$exports$Blockly$Grid.Grid.prototype.scale_=1;module$exports$Blockly$Grid.Grid.prototype.dispose=function(){this.gridPattern_=null};module$exports$Blockly$Grid.Grid.prototype.shouldSnap=function(){return this.snapToGrid_};module$exports$Blockly$Grid.Grid.prototype.getSpacing=function(){return this.spacing_}; +module$exports$Blockly$BlockDragSurfaceSvg.BlockDragSurfaceSvg.prototype.clearAndHide=function(a){var b=this.getCurrentBlock();b&&(a?a.appendChild(b):this.dragGroup_.removeChild(b));this.SVG_.style.display="none";if(this.dragGroup_.childNodes.length)throw Error("Drag group was not cleared.");this.surfaceXY_=null};var module$exports$Blockly$Grid={Grid:function(a,b){this.scale_=1;this.gridPattern_=a;this.spacing_=b.spacing;this.length_=b.length;this.line2_=(this.line1_=a.firstChild)&&this.line1_.nextSibling;this.snapToGrid_=b.snap}};module$exports$Blockly$Grid.Grid.prototype.dispose=function(){this.gridPattern_=null};module$exports$Blockly$Grid.Grid.prototype.shouldSnap=function(){return this.snapToGrid_};module$exports$Blockly$Grid.Grid.prototype.getSpacing=function(){return this.spacing_}; module$exports$Blockly$Grid.Grid.prototype.getPatternId=function(){return this.gridPattern_.id};module$exports$Blockly$Grid.Grid.prototype.update=function(a){this.scale_=a;var b=this.spacing_*a||100;this.gridPattern_.setAttribute("width",b);this.gridPattern_.setAttribute("height",b);b=Math.floor(this.spacing_/2)+.5;var c=b-this.length_/2,d=b+this.length_/2;b*=a;c*=a;d*=a;this.setLineAttributes_(this.line1_,a,c,d,b,b);this.setLineAttributes_(this.line2_,a,b,b,c,d)}; module$exports$Blockly$Grid.Grid.prototype.setLineAttributes_=function(a,b,c,d,e,f){a&&(a.setAttribute("stroke-width",b),a.setAttribute("x1",c),a.setAttribute("y1",e),a.setAttribute("x2",d),a.setAttribute("y2",f))};module$exports$Blockly$Grid.Grid.prototype.moveTo=function(a,b){this.gridPattern_.setAttribute("x",a);this.gridPattern_.setAttribute("y",b);(module$exports$Blockly$utils$userAgent.IE||module$exports$Blockly$utils$userAgent.EDGE)&&this.update(this.scale_)}; module$exports$Blockly$Grid.Grid.createDom=function(a,b,c){a=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.PATTERN,{id:"blocklyGridPattern"+a,patternUnits:"userSpaceOnUse"},c);0f&&(f=k.x)}d=d-g+10;c=b.RTL?c-f:c-e;for(e=0;ef&&(f=k.x)}d=d-g+10;c=b.RTL?c-f:c-e;for(e=0;e=a||isNaN(a)?0:Math.min(a,this.scrollbarLength_)};module$exports$Blockly$Scrollbar.Scrollbar.prototype.setHandleLength_=function(a){this.handleLength_=a;this.svgHandle_.setAttribute(this.lengthAttribute_,this.handleLength_)}; module$exports$Blockly$Scrollbar.Scrollbar.prototype.constrainHandlePosition_=function(a){return a=0>=a||isNaN(a)?0:Math.min(a,this.scrollbarLength_-this.handleLength_)};module$exports$Blockly$Scrollbar.Scrollbar.prototype.setHandlePosition=function(a){this.handlePosition_=a;this.svgHandle_.setAttribute(this.positionAttribute_,this.handlePosition_)}; @@ -283,7 +292,9 @@ module$exports$Blockly$Scrollbar.Scrollbar.onMouseMoveWrapper_=(0,module$exports module$exports$Blockly$Scrollbar.Scrollbar.prototype.onMouseUpHandle_=function(){this.workspace_.resetDragSurface();(0,module$exports$Blockly$Touch.clearTouchIdentifier)();this.cleanUp_()}; module$exports$Blockly$Scrollbar.Scrollbar.prototype.cleanUp_=function(){this.workspace_.hideChaff(!0);module$exports$Blockly$Scrollbar.Scrollbar.onMouseUpWrapper_&&((0,module$exports$Blockly$browserEvents.unbind)(module$exports$Blockly$Scrollbar.Scrollbar.onMouseUpWrapper_),module$exports$Blockly$Scrollbar.Scrollbar.onMouseUpWrapper_=null);module$exports$Blockly$Scrollbar.Scrollbar.onMouseMoveWrapper_&&((0,module$exports$Blockly$browserEvents.unbind)(module$exports$Blockly$Scrollbar.Scrollbar.onMouseMoveWrapper_), module$exports$Blockly$Scrollbar.Scrollbar.onMouseMoveWrapper_=null)};module$exports$Blockly$Scrollbar.Scrollbar.prototype.getRatio_=function(){var a=this.handlePosition_/(this.scrollbarLength_-this.handleLength_);isNaN(a)&&(a=0);return a};module$exports$Blockly$Scrollbar.Scrollbar.prototype.updateMetrics_=function(){var a=this.getRatio_(),b={};this.horizontal_?b.x=a:b.y=a;this.workspace_.setMetrics(b)}; -module$exports$Blockly$Scrollbar.Scrollbar.prototype.set=function(a,b){this.setHandlePosition(this.constrainHandlePosition_(a*this.ratio));(b||void 0===b)&&this.updateMetrics_()};module$exports$Blockly$Scrollbar.Scrollbar.prototype.setOrigin=function(a,b){this.origin_=new module$exports$Blockly$utils$Coordinate.Coordinate(a,b)};var module$exports$Blockly$ScrollbarPair={ScrollbarPair:function(a,b,c,d,e){this.workspace_=a;b=void 0===b?!0:b;c=void 0===c?!0:c;var f=b&&c;b&&(this.hScroll=new module$exports$Blockly$Scrollbar.Scrollbar(a,!0,f,d,e));c&&(this.vScroll=new module$exports$Blockly$Scrollbar.Scrollbar(a,!1,f,d,e));f&&(this.corner_=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.RECT,{height:module$exports$Blockly$Scrollbar.Scrollbar.scrollbarThickness,width:module$exports$Blockly$Scrollbar.Scrollbar.scrollbarThickness, +module$exports$Blockly$Scrollbar.Scrollbar.prototype.set=function(a,b){this.setHandlePosition(this.constrainHandlePosition_(a*this.ratio));(b||void 0===b)&&this.updateMetrics_()};module$exports$Blockly$Scrollbar.Scrollbar.prototype.setOrigin=function(a,b){this.origin_=new module$exports$Blockly$utils$Coordinate.Coordinate(a,b)}; +module$exports$Blockly$Scrollbar.Scrollbar.metricsAreEquivalent_=function(a,b){return a.viewWidth===b.viewWidth&&a.viewHeight===b.viewHeight&&a.viewLeft===b.viewLeft&&a.viewTop===b.viewTop&&a.absoluteTop===b.absoluteTop&&a.absoluteLeft===b.absoluteLeft&&a.scrollWidth===b.scrollWidth&&a.scrollHeight===b.scrollHeight&&a.scrollLeft===b.scrollLeft&&a.scrollTop===b.scrollTop};module$exports$Blockly$Scrollbar.Scrollbar.scrollbarThickness=15; +module$exports$Blockly$Touch.TOUCH_ENABLED&&(module$exports$Blockly$Scrollbar.Scrollbar.scrollbarThickness=25);module$exports$Blockly$Scrollbar.Scrollbar.DEFAULT_SCROLLBAR_MARGIN=.5;var module$exports$Blockly$ScrollbarPair={ScrollbarPair:function(a,b,c,d,e){this.workspace_=a;b=void 0===b?!0:b;c=void 0===c?!0:c;var f=b&&c;b&&(this.hScroll=new module$exports$Blockly$Scrollbar.Scrollbar(a,!0,f,d,e));c&&(this.vScroll=new module$exports$Blockly$Scrollbar.Scrollbar(a,!1,f,d,e));f&&(this.corner_=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.RECT,{height:module$exports$Blockly$Scrollbar.Scrollbar.scrollbarThickness,width:module$exports$Blockly$Scrollbar.Scrollbar.scrollbarThickness, "class":"blocklyScrollbarBackground"},null),(0,module$exports$Blockly$utils$dom.insertAfter)(this.corner_,a.getBubbleCanvas()));this.oldHostMetrics_=null}};module$exports$Blockly$ScrollbarPair.ScrollbarPair.prototype.dispose=function(){(0,module$exports$Blockly$utils$dom.removeNode)(this.corner_);this.oldHostMetrics_=this.workspace_=this.corner_=null;this.hScroll&&(this.hScroll.dispose(),this.hScroll=null);this.vScroll&&(this.vScroll.dispose(),this.vScroll=null)}; module$exports$Blockly$ScrollbarPair.ScrollbarPair.prototype.resize=function(){var a=this.workspace_.getMetrics();if(a){var b=!1,c=!1;this.oldHostMetrics_&&this.oldHostMetrics_.viewWidth===a.viewWidth&&this.oldHostMetrics_.viewHeight===a.viewHeight&&this.oldHostMetrics_.absoluteTop===a.absoluteTop&&this.oldHostMetrics_.absoluteLeft===a.absoluteLeft?(this.oldHostMetrics_&&this.oldHostMetrics_.scrollWidth===a.scrollWidth&&this.oldHostMetrics_.viewLeft===a.viewLeft&&this.oldHostMetrics_.scrollLeft=== a.scrollLeft||(b=!0),this.oldHostMetrics_&&this.oldHostMetrics_.scrollHeight===a.scrollHeight&&this.oldHostMetrics_.viewTop===a.viewTop&&this.oldHostMetrics_.scrollTop===a.scrollTop||(c=!0)):c=b=!0;if(b||c){try{(0,module$exports$Blockly$Events$utils.disable)(),this.hScroll&&b&&this.hScroll.resize(a),this.vScroll&&c&&this.vScroll.resize(a)}finally{(0,module$exports$Blockly$Events$utils.enable)()}this.workspace_.maybeFireViewportChangeEvent()}this.hScroll&&this.vScroll&&(this.oldHostMetrics_&&this.oldHostMetrics_.viewWidth=== @@ -293,8 +304,8 @@ module$exports$Blockly$ScrollbarPair.ScrollbarPair.prototype.set=function(a,b,c) module$exports$Blockly$ScrollbarPair.ScrollbarPair.prototype.setY=function(a){this.vScroll&&this.vScroll.set(a,!0)};module$exports$Blockly$ScrollbarPair.ScrollbarPair.prototype.setContainerVisible=function(a){this.hScroll&&this.hScroll.setContainerVisible(a);this.vScroll&&this.vScroll.setContainerVisible(a)};module$exports$Blockly$ScrollbarPair.ScrollbarPair.prototype.isVisible=function(){var a=!1;this.hScroll&&(a=this.hScroll.isVisible());this.vScroll&&(a=a||this.vScroll.isVisible());return a}; module$exports$Blockly$ScrollbarPair.ScrollbarPair.prototype.resizeContent=function(a){this.hScroll&&this.hScroll.resizeContentHorizontal(a);this.vScroll&&this.vScroll.resizeContentVertical(a)};module$exports$Blockly$ScrollbarPair.ScrollbarPair.prototype.resizeView=function(a){this.hScroll&&this.hScroll.resizeViewHorizontal(a);this.vScroll&&this.vScroll.resizeViewVertical(a)};var module$exports$Blockly$utils$KeyCodes={KeyCodes:{WIN_KEY_FF_LINUX:0,MAC_ENTER:3,BACKSPACE:8,TAB:9,NUM_CENTER:12,ENTER:13,SHIFT:16,CTRL:17,ALT:18,PAUSE:19,CAPS_LOCK:20,ESC:27,SPACE:32,PAGE_UP:33,PAGE_DOWN:34,END:35,HOME:36,LEFT:37,UP:38,RIGHT:39,DOWN:40,PLUS_SIGN:43,PRINT_SCREEN:44,INSERT:45,DELETE:46,ZERO:48,ONE:49,TWO:50,THREE:51,FOUR:52,FIVE:53,SIX:54,SEVEN:55,EIGHT:56,NINE:57,FF_SEMICOLON:59,FF_EQUALS:61,FF_DASH:173,FF_HASH:163,QUESTION_MARK:63,AT_SIGN:64,A:65,B:66,C:67,D:68,E:69,F:70,G:71, H:72,I:73,J:74,K:75,L:76,M:77,N:78,O:79,P:80,Q:81,R:82,S:83,T:84,U:85,V:86,W:87,X:88,Y:89,Z:90,META:91,WIN_KEY_RIGHT:92,CONTEXT_MENU:93,NUM_ZERO:96,NUM_ONE:97,NUM_TWO:98,NUM_THREE:99,NUM_FOUR:100,NUM_FIVE:101,NUM_SIX:102,NUM_SEVEN:103,NUM_EIGHT:104,NUM_NINE:105,NUM_MULTIPLY:106,NUM_PLUS:107,NUM_MINUS:109,NUM_PERIOD:110,NUM_DIVISION:111,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,NUMLOCK:144,SCROLL_LOCK:145,FIRST_MEDIA_KEY:166,LAST_MEDIA_KEY:183,SEMICOLON:186, -DASH:189,EQUALS:187,COMMA:188,PERIOD:190,SLASH:191,APOSTROPHE:192,TILDE:192,SINGLE_QUOTE:222,OPEN_SQUARE_BRACKET:219,BACKSLASH:220,CLOSE_SQUARE_BRACKET:221,WIN_KEY:224,MAC_FF_META:224,MAC_WK_CMD_LEFT:91,MAC_WK_CMD_RIGHT:93,WIN_IME:229,VK_NONAME:252,PHANTOM:255}};var module$exports$Blockly$ShortcutRegistry={ShortcutRegistry:function(){module$exports$Blockly$ShortcutRegistry.ShortcutRegistry.registry=this;this.registry_=Object.create(null);this.keyMap_=Object.create(null)}};module$exports$Blockly$ShortcutRegistry.ShortcutRegistry.modifierKeys={Shift:module$exports$Blockly$utils$KeyCodes.KeyCodes.SHIFT,Control:module$exports$Blockly$utils$KeyCodes.KeyCodes.CTRL,Alt:module$exports$Blockly$utils$KeyCodes.KeyCodes.ALT,Meta:module$exports$Blockly$utils$KeyCodes.KeyCodes.META}; -module$exports$Blockly$ShortcutRegistry.ShortcutRegistry.prototype.register=function(a,b){if(this.registry_[a.name]&&!b)throw Error('Shortcut with name "'+a.name+'" already exists.');this.registry_[a.name]=a};module$exports$Blockly$ShortcutRegistry.ShortcutRegistry.prototype.unregister=function(a){if(!this.registry_[a])return console.warn('Keyboard shortcut with name "'+a+'" not found.'),!1;this.removeAllKeyMappings(a);delete this.registry_[a];return!0}; +DASH:189,EQUALS:187,COMMA:188,PERIOD:190,SLASH:191,APOSTROPHE:192,TILDE:192,SINGLE_QUOTE:222,OPEN_SQUARE_BRACKET:219,BACKSLASH:220,CLOSE_SQUARE_BRACKET:221,WIN_KEY:224,MAC_FF_META:224,MAC_WK_CMD_LEFT:91,MAC_WK_CMD_RIGHT:93,WIN_IME:229,VK_NONAME:252,PHANTOM:255}};var module$exports$Blockly$ShortcutRegistry={ShortcutRegistry:function(){this.reset()}};module$exports$Blockly$ShortcutRegistry.ShortcutRegistry.prototype.reset=function(){this.registry_=Object.create(null);this.keyMap_=Object.create(null)};module$exports$Blockly$ShortcutRegistry.ShortcutRegistry.prototype.register=function(a,b){if(this.registry_[a.name]&&!b)throw Error('Shortcut with name "'+a.name+'" already exists.');this.registry_[a.name]=a}; +module$exports$Blockly$ShortcutRegistry.ShortcutRegistry.prototype.unregister=function(a){if(!this.registry_[a])return console.warn('Keyboard shortcut with name "'+a+'" not found.'),!1;this.removeAllKeyMappings(a);delete this.registry_[a];return!0}; module$exports$Blockly$ShortcutRegistry.ShortcutRegistry.prototype.addKeyMapping=function(a,b,c){a=String(a);var d=this.keyMap_[a];if(d&&!c)throw Error('Shortcut with name "'+b+'" collides with shortcuts '+d.toString());d&&c?d.unshift(b):this.keyMap_[a]=[b]}; module$exports$Blockly$ShortcutRegistry.ShortcutRegistry.prototype.removeKeyMapping=function(a,b,c){var d=this.keyMap_[a];if(!d&&!c)return console.warn('No keyboard shortcut with name "'+b+'" registered with key code "'+a+'"'),!1;var e=d.indexOf(b);if(-1b.indexOf(d))throw Error(d+" is not a valid modifier key.");}; -module$exports$Blockly$ShortcutRegistry.ShortcutRegistry.prototype.createSerializedKey=function(a,b){var c="";if(b){this.checkModifiers_(b);for(var d in module$exports$Blockly$ShortcutRegistry.ShortcutRegistry.modifierKeys)-1a&&0<=b&&256>b&&0<=c&&256>c)? (0,module$exports$Blockly$utils$colour.rgbToHex)(a,b,c):null};module$exports$Blockly$utils$colour.rgbToHex=function(a,b,c){b=a<<16|b<<8|c;return 16>a?"#"+(16777216|b).toString(16).substr(1):"#"+b.toString(16)};module$exports$Blockly$utils$colour.hexToRgb=function(a){a=(0,module$exports$Blockly$utils$colour.parse)(a);if(!a)return[0,0,0];a=parseInt(a.substr(1),16);return[a>>16,a>>8&255,a&255]}; @@ -405,8 +417,8 @@ this.FIELD_COLOUR_FULL_BLOCK=this.FIELD_TEXTINPUT_BOX_SHADOW=!1;this.FIELD_COLOU "#4286f4";this.CURSOR_WS_WIDTH=100;this.WS_CURSOR_HEIGHT=5;this.CURSOR_STACK_PADDING=10;this.CURSOR_BLOCK_PADDING=2;this.CURSOR_STROKE_WIDTH=4;this.FULL_BLOCK_FIELDS=!1;this.INSERTION_MARKER_COLOUR="#000000";this.INSERTION_MARKER_OPACITY=.2;this.SHAPES={PUZZLE:1,NOTCH:2}}}; module$exports$Blockly$blockRendering$ConstantProvider.ConstantProvider.prototype.init=function(){this.JAGGED_TEETH=this.makeJaggedTeeth();this.NOTCH=this.makeNotch();this.START_HAT=this.makeStartHat();this.PUZZLE_TAB=this.makePuzzleTab();this.INSIDE_CORNERS=this.makeInsideCorners();this.OUTSIDE_CORNERS=this.makeOutsideCorners()}; module$exports$Blockly$blockRendering$ConstantProvider.ConstantProvider.prototype.setTheme=function(a){this.blockStyles=Object.create(null);var b=a.blockStyles,c;for(c in b)this.blockStyles[c]=this.validatedBlockStyle_(b[c]);this.setDynamicProperties_(a)};module$exports$Blockly$blockRendering$ConstantProvider.ConstantProvider.prototype.setDynamicProperties_=function(a){this.setFontConstants_(a);this.setComponentConstants_(a);this.ADD_START_HATS=null!==a.startHats?a.startHats:this.ADD_START_HATS}; -module$exports$Blockly$blockRendering$ConstantProvider.ConstantProvider.prototype.setFontConstants_=function(a){this.FIELD_TEXT_FONTFAMILY=a.fontStyle&&void 0!==a.fontStyle.family?a.fontStyle.family:this.FIELD_TEXT_FONTFAMILY;this.FIELD_TEXT_FONTWEIGHT=a.fontStyle&&void 0!==a.fontStyle.weight?a.fontStyle.weight:this.FIELD_TEXT_FONTWEIGHT;this.FIELD_TEXT_FONTSIZE=a.fontStyle&&void 0!==a.fontStyle.size?a.fontStyle.size:this.FIELD_TEXT_FONTSIZE;a=(0,module$exports$Blockly$utils$dom.measureFontMetrics)("Hg", -this.FIELD_TEXT_FONTSIZE+"pt",this.FIELD_TEXT_FONTWEIGHT,this.FIELD_TEXT_FONTFAMILY);this.FIELD_TEXT_HEIGHT=a.height;this.FIELD_TEXT_BASELINE=a.baseline}; +module$exports$Blockly$blockRendering$ConstantProvider.ConstantProvider.prototype.setFontConstants_=function(a){a.fontStyle&&a.fontStyle.family&&(this.FIELD_TEXT_FONTFAMILY=a.fontStyle.family);a.fontStyle&&a.fontStyle.weight&&(this.FIELD_TEXT_FONTWEIGHT=a.fontStyle.weight);a.fontStyle&&a.fontStyle.size&&(this.FIELD_TEXT_FONTSIZE=a.fontStyle.size);a=(0,module$exports$Blockly$utils$dom.measureFontMetrics)("Hg",this.FIELD_TEXT_FONTSIZE+"pt",this.FIELD_TEXT_FONTWEIGHT,this.FIELD_TEXT_FONTFAMILY);this.FIELD_TEXT_HEIGHT= +a.height;this.FIELD_TEXT_BASELINE=a.baseline}; module$exports$Blockly$blockRendering$ConstantProvider.ConstantProvider.prototype.setComponentConstants_=function(a){this.CURSOR_COLOUR=a.getComponentStyle("cursorColour")||this.CURSOR_COLOUR;this.MARKER_COLOUR=a.getComponentStyle("markerColour")||this.MARKER_COLOUR;this.INSERTION_MARKER_COLOUR=a.getComponentStyle("insertionMarkerColour")||this.INSERTION_MARKER_COLOUR;this.INSERTION_MARKER_OPACITY=Number(a.getComponentStyle("insertionMarkerOpacity"))||this.INSERTION_MARKER_OPACITY}; module$exports$Blockly$blockRendering$ConstantProvider.ConstantProvider.prototype.getBlockStyleForColour=function(a){var b="auto_"+a;this.blockStyles[b]||(this.blockStyles[b]=this.createBlockStyle_(a));return{style:this.blockStyles[b],name:b}};module$exports$Blockly$blockRendering$ConstantProvider.ConstantProvider.prototype.getBlockStyle=function(a){return this.blockStyles[a||""]||(a&&0===a.indexOf("auto_")?this.getBlockStyleForColour(a.substring(5)).style:this.createBlockStyle_("#000000"))}; module$exports$Blockly$blockRendering$ConstantProvider.ConstantProvider.prototype.createBlockStyle_=function(a){return this.validatedBlockStyle_({colourPrimary:a})}; @@ -432,15 +444,14 @@ module$exports$Blockly$blockRendering$ConstantProvider.ConstantProvider.prototyp module$exports$Blockly$blockRendering$ConstantProvider.ConstantProvider.prototype.injectCSS_=function(a,b){b=this.getCSS_(b);a="blockly-renderer-style-"+a;this.cssNode_=document.getElementById(a);var c=b.join("\n");this.cssNode_?this.cssNode_.firstChild.textContent=c:(b=document.createElement("style"),b.id=a,a=document.createTextNode(c),b.appendChild(a),document.head.insertBefore(b,document.head.firstChild),this.cssNode_=b)}; module$exports$Blockly$blockRendering$ConstantProvider.ConstantProvider.prototype.getCSS_=function(a){return[a+" .blocklyText, ",a+" .blocklyFlyoutLabelText {","font: "+this.FIELD_TEXT_FONTWEIGHT+" "+this.FIELD_TEXT_FONTSIZE+"pt "+this.FIELD_TEXT_FONTFAMILY+";","}",a+" .blocklyText {","fill: #fff;","}",a+" .blocklyNonEditableText>rect,",a+" .blocklyEditableText>rect {","fill: "+this.FIELD_BORDER_RECT_COLOUR+";","fill-opacity: .6;","stroke: none;","}",a+" .blocklyNonEditableText>text,",a+" .blocklyEditableText>text {", "fill: #000;","}",a+" .blocklyFlyoutLabelText {","fill: #000;","}",a+" .blocklyText.blocklyBubbleText {","fill: #000;","}",a+" .blocklyEditableText:not(.editing):hover>rect {","stroke: #fff;","stroke-width: 2;","}",a+" .blocklyHtmlInput {","font-family: "+this.FIELD_TEXT_FONTFAMILY+";","font-weight: "+this.FIELD_TEXT_FONTWEIGHT+";","}",a+" .blocklySelected>.blocklyPath {","stroke: #fc3;","stroke-width: 3px;","}",a+" .blocklyHighlightedConnectionPath {","stroke: #fc3;","}",a+" .blocklyReplaceable .blocklyPath {", -"fill-opacity: .5;","}",a+" .blocklyReplaceable .blocklyPathLight,",a+" .blocklyReplaceable .blocklyPathDark {","display: none;","}",a+" .blocklyInsertionMarker>.blocklyPath {","fill-opacity: "+this.INSERTION_MARKER_OPACITY+";","stroke: none;","}"]};var module$exports$Blockly$fieldRegistry={register:function(a,b){(0,module$exports$Blockly$registry.register)(module$exports$Blockly$registry.Type.FIELD,a,b)},unregister:function(a){(0,module$exports$Blockly$registry.unregister)(module$exports$Blockly$registry.Type.FIELD,a)},fromJson:function(a){var b=(0,module$exports$Blockly$registry.getObject)(module$exports$Blockly$registry.Type.FIELD,a.type);return b?b.fromJson(a):(console.warn("Blockly could not create a field of type "+a.type+". The field is probably not being registered. This could be because the file is not loaded, the field does not register itself (Issue #1584), or the registration is not being reached."), -null)}};var module$exports$Blockly$IASTNodeLocation={IASTNodeLocation:function(){}};var module$exports$Blockly$IASTNodeLocationSvg={IASTNodeLocationSvg:function(){}};var module$exports$Blockly$IASTNodeLocationWithBlock={IASTNodeLocationWithBlock:function(){}};var module$exports$Blockly$IKeyboardAccessible={IKeyboardAccessible:function(){}};var module$exports$Blockly$IRegistrable={IRegistrable:function(){}};var module$exports$Blockly$MarkerManager={MarkerManager:function(a){this.cursorSvg_=this.cursor_=null;this.markers_=Object.create(null);this.workspace_=a}};module$exports$Blockly$MarkerManager.MarkerManager.LOCAL_MARKER="local_marker_1"; -module$exports$Blockly$MarkerManager.MarkerManager.prototype.registerMarker=function(a,b){this.markers_[a]&&this.unregisterMarker(a);b.setDrawer(this.workspace_.getRenderer().makeMarkerDrawer(this.workspace_,b));this.setMarkerSvg(b.getDrawer().createDom());this.markers_[a]=b}; +"fill-opacity: .5;","}",a+" .blocklyReplaceable .blocklyPathLight,",a+" .blocklyReplaceable .blocklyPathDark {","display: none;","}",a+" .blocklyInsertionMarker>.blocklyPath {","fill-opacity: "+this.INSERTION_MARKER_OPACITY+";","stroke: none;","}"]};var module$exports$Blockly$blockRendering$Field={Field:function(a,b,c){module$exports$Blockly$blockRendering$Measurable.Measurable.call(this,a);this.field=b;this.isEditable=b.EDITABLE;this.flipRtl=b.getFlipRtl();this.type|=module$exports$Blockly$blockRendering$Types.Types.FIELD;a=this.field.getSize();this.height=a.height;this.width=a.width;this.parentInput=c}};$.$jscomp.inherits(module$exports$Blockly$blockRendering$Field.Field,module$exports$Blockly$blockRendering$Measurable.Measurable);var module$exports$Blockly$fieldRegistry={register:function(a,b){(0,module$exports$Blockly$registry.register)(module$exports$Blockly$registry.Type.FIELD,a,b)},unregister:function(a){(0,module$exports$Blockly$registry.unregister)(module$exports$Blockly$registry.Type.FIELD,a)},fromJson:function(a){var b=(0,module$exports$Blockly$registry.getObject)(module$exports$Blockly$registry.Type.FIELD,a.type);return b?b.fromJson(a):(console.warn("Blockly could not create a field of type "+a.type+". The field is probably not being registered. This could be because the file is not loaded, the field does not register itself (Issue #1584), or the registration is not being reached."), +null)}};var module$exports$Blockly$IASTNodeLocation={IASTNodeLocation:function(){}};var module$exports$Blockly$IASTNodeLocationSvg={IASTNodeLocationSvg:function(){}};var module$exports$Blockly$IASTNodeLocationWithBlock={IASTNodeLocationWithBlock:function(){}};var module$exports$Blockly$IKeyboardAccessible={IKeyboardAccessible:function(){}};var module$exports$Blockly$IRegistrable={IRegistrable:function(){}};var module$exports$Blockly$MarkerManager={MarkerManager:function(a){this.cursorSvg_=this.cursor_=null;this.markers_=Object.create(null);this.workspace_=a;this.markerSvg_=null}};module$exports$Blockly$MarkerManager.MarkerManager.prototype.registerMarker=function(a,b){this.markers_[a]&&this.unregisterMarker(a);b.setDrawer(this.workspace_.getRenderer().makeMarkerDrawer(this.workspace_,b));this.setMarkerSvg(b.getDrawer().createDom());this.markers_[a]=b}; module$exports$Blockly$MarkerManager.MarkerManager.prototype.unregisterMarker=function(a){var b=this.markers_[a];if(b)b.dispose(),delete this.markers_[a];else throw Error("Marker with ID "+a+" does not exist. Can only unregister markers that exist.");};module$exports$Blockly$MarkerManager.MarkerManager.prototype.getCursor=function(){return this.cursor_};module$exports$Blockly$MarkerManager.MarkerManager.prototype.getMarker=function(a){return this.markers_[a]||null}; module$exports$Blockly$MarkerManager.MarkerManager.prototype.setCursor=function(a){this.cursor_&&this.cursor_.getDrawer()&&this.cursor_.getDrawer().dispose();if(this.cursor_=a)a=this.workspace_.getRenderer().makeMarkerDrawer(this.workspace_,this.cursor_),this.cursor_.setDrawer(a),this.setCursorSvg(this.cursor_.getDrawer().createDom())}; module$exports$Blockly$MarkerManager.MarkerManager.prototype.setCursorSvg=function(a){a?(this.workspace_.getBlockCanvas().appendChild(a),this.cursorSvg_=a):this.cursorSvg_=null};module$exports$Blockly$MarkerManager.MarkerManager.prototype.setMarkerSvg=function(a){a?this.workspace_.getBlockCanvas()&&(this.cursorSvg_?this.workspace_.getBlockCanvas().insertBefore(a,this.cursorSvg_):this.workspace_.getBlockCanvas().appendChild(a)):this.markerSvg_=null}; -module$exports$Blockly$MarkerManager.MarkerManager.prototype.updateMarkers=function(){this.workspace_.keyboardAccessibilityMode&&this.cursorSvg_&&this.workspace_.getCursor().draw()};module$exports$Blockly$MarkerManager.MarkerManager.prototype.dispose=function(){for(var a=Object.keys(this.markers_),b=0,c;c=a[b];b++)this.unregisterMarker(c);this.markers_=null;this.cursor_&&(this.cursor_.dispose(),this.cursor_=null)};var module$exports$Blockly$Events$BlockChange={BlockChange:function(a,b,c,d,e){module$exports$Blockly$Events$BlockChange.BlockChange.superClass_.constructor.call(this,a);a&&(this.element="undefined"===typeof b?"":b,this.name="undefined"===typeof c?"":c,this.oldValue="undefined"===typeof d?"":d,this.newValue="undefined"===typeof e?"":e)}};(0,$.module$exports$Blockly$utils$object.inherits)(module$exports$Blockly$Events$BlockChange.BlockChange,module$exports$Blockly$Events$BlockBase.BlockBase); -module$exports$Blockly$Events$BlockChange.BlockChange.prototype.type=module$exports$Blockly$Events$utils.CHANGE;module$exports$Blockly$Events$BlockChange.BlockChange.prototype.toJson=function(){var a=module$exports$Blockly$Events$BlockChange.BlockChange.superClass_.toJson.call(this);a.element=this.element;this.name&&(a.name=this.name);a.oldValue=this.oldValue;a.newValue=this.newValue;return a}; -module$exports$Blockly$Events$BlockChange.BlockChange.prototype.fromJson=function(a){module$exports$Blockly$Events$BlockChange.BlockChange.superClass_.fromJson.call(this,a);this.element=a.element;this.name=a.name;this.oldValue=a.oldValue;this.newValue=a.newValue};module$exports$Blockly$Events$BlockChange.BlockChange.prototype.isNull=function(){return this.oldValue===this.newValue}; +module$exports$Blockly$MarkerManager.MarkerManager.prototype.updateMarkers=function(){this.workspace_.keyboardAccessibilityMode&&this.cursorSvg_&&this.workspace_.getCursor().draw()};module$exports$Blockly$MarkerManager.MarkerManager.prototype.dispose=function(){for(var a=Object.keys(this.markers_),b=0,c;c=a[b];b++)this.unregisterMarker(c);this.markers_=null;this.cursor_&&(this.cursor_.dispose(),this.cursor_=null)};module$exports$Blockly$MarkerManager.MarkerManager.LOCAL_MARKER="local_marker_1";var module$exports$Blockly$utils$Sentinel={Sentinel:function(){}};var module$exports$Blockly$Events$BlockChange={BlockChange:function(a,b,c,d,e){module$exports$Blockly$Events$BlockBase.BlockBase.call(this,a);this.type=module$exports$Blockly$Events$utils.CHANGE;a&&(this.element="undefined"===typeof b?"":b,this.name="undefined"===typeof c?"":c,this.oldValue="undefined"===typeof d?"":d,this.newValue="undefined"===typeof e?"":e)}};$.$jscomp.inherits(module$exports$Blockly$Events$BlockChange.BlockChange,module$exports$Blockly$Events$BlockBase.BlockBase); +module$exports$Blockly$Events$BlockChange.BlockChange.prototype.toJson=function(){var a=module$exports$Blockly$Events$BlockBase.BlockBase.prototype.toJson.call(this);a.element=this.element;this.name&&(a.name=this.name);a.oldValue=this.oldValue;a.newValue=this.newValue;return a}; +module$exports$Blockly$Events$BlockChange.BlockChange.prototype.fromJson=function(a){module$exports$Blockly$Events$BlockBase.BlockBase.prototype.fromJson.call(this,a);this.element=a.element;this.name=a.name;this.oldValue=a.oldValue;this.newValue=a.newValue};module$exports$Blockly$Events$BlockChange.BlockChange.prototype.isNull=function(){return this.oldValue===this.newValue}; module$exports$Blockly$Events$BlockChange.BlockChange.prototype.run=function(a){var b=this.getEventWorkspace_().getBlockById(this.blockId);if(b)switch(b.mutator&&b.mutator.setVisible(!1),a=a?this.newValue:this.oldValue,this.element){case "field":(b=b.getField(this.name))?b.setValue(a):console.warn("Can't set non-existent field: "+this.name);break;case "comment":b.setCommentText(a||null);break;case "collapsed":b.setCollapsed(!!a);break;case "disabled":b.setEnabled(!a);break;case "inline":b.setInputsInline(!!a); break;case "mutation":var c=module$exports$Blockly$Events$BlockChange.BlockChange.getExtraBlockState_(b);b.loadExtraState?b.loadExtraState(JSON.parse(a||"{}")):b.domToMutation&&b.domToMutation((0,$.module$exports$Blockly$Xml.textToDom)(a||""));(0,module$exports$Blockly$Events$utils.fire)(new module$exports$Blockly$Events$BlockChange.BlockChange(b,"mutation",null,c,a));break;default:console.warn("Unknown change type: "+this.element)}else console.warn("Can't change non-existent block: "+ this.blockId)};module$exports$Blockly$Events$BlockChange.BlockChange.getExtraBlockState_=function(a){return a.saveExtraState?(a=a.saveExtraState())?JSON.stringify(a):"":a.mutationToDom?(a=a.mutationToDom())?(0,$.module$exports$Blockly$Xml.domToText)(a):"":""};(0,module$exports$Blockly$registry.register)(module$exports$Blockly$registry.Type.EVENT,module$exports$Blockly$Events$utils.CHANGE,module$exports$Blockly$Events$BlockChange.BlockChange);var module$exports$Blockly$blockAnimations={},module$contents$Blockly$blockAnimations_disconnectPid=0,module$contents$Blockly$blockAnimations_disconnectGroup=null; @@ -450,7 +461,9 @@ module$exports$Blockly$blockAnimations.connectionUiEffect=function(a){var b=a.wo new Date,c)}};var module$contents$Blockly$blockAnimations_connectionUiStep=function(a,b,c){var d=(new Date-b)/150;1a.workspace.scale)){var b=a.getHeightWidth().height;b=Math.atan(10/b)/Math.PI*180;a.RTL||(b*=-1);module$contents$Blockly$blockAnimations_disconnectUiStep(a.getSvgRoot(),b,new Date)}}; var module$contents$Blockly$blockAnimations_disconnectUiStep=function(a,b,c){var d=(new Date-c)/200;11'),d.appendChild(c),b.push(d));if($.module$exports$Blockly$blocks.Blocks.variables_get)for(a.sort(module$exports$Blockly$VariableModel.VariableModel.compareByName), +$.module$exports$Blockly$Variables.flyoutCategoryBlocks=function(a){a=a.getVariablesOfType("");var b=[];if(01'),d.appendChild(c),b.push(d));if(module$exports$Blockly$blocks.Blocks.variables_get)for(a.sort(module$exports$Blockly$VariableModel.VariableModel.compareByName), c=0;d=a[c];c++){var e=(0,$.module$exports$Blockly$utils$xml.createElement)("block");e.setAttribute("type","variables_get");e.setAttribute("gap",8);e.appendChild((0,$.module$exports$Blockly$Variables.generateVariableFieldDom)(d));b.push(e)}}return b};$.module$exports$Blockly$Variables.VAR_LETTER_OPTIONS="ijkmnopqrstuvwxyzabcdefgh"; $.module$exports$Blockly$Variables.generateUniqueName=function(a){return(0,$.module$exports$Blockly$Variables.generateUniqueNameFromOptions)($.module$exports$Blockly$Variables.VAR_LETTER_OPTIONS.charAt(0),a.getAllVariableNames())}; $.module$exports$Blockly$Variables.generateUniqueNameFromOptions=function(a,b){if(!b.length)return a;for(var c=$.module$exports$Blockly$Variables.VAR_LETTER_OPTIONS,d="",e=c.indexOf(a);;){for(var f=!1,g=0;gc||b.getSourceBlock().isInsertionMarker())return!1;switch(b.type){case $.module$exports$Blockly$ConnectionType.ConnectionType.PREVIOUS_STATEMENT:return this.canConnectToPrevious_(a,b);case $.module$exports$Blockly$ConnectionType.ConnectionType.OUTPUT_VALUE:if(b.isConnected()&&!b.targetBlock().isInsertionMarker()||a.isConnected())return!1;break;case $.module$exports$Blockly$ConnectionType.ConnectionType.INPUT_VALUE:if(b.isConnected()&& !b.targetBlock().isMovable()&&!b.targetBlock().isShadow())return!1;break;case $.module$exports$Blockly$ConnectionType.ConnectionType.NEXT_STATEMENT:if(b.isConnected()&&!a.getSourceBlock().nextConnection&&!b.targetBlock().isShadow()&&b.targetBlock().nextConnection)return!1;break;default:return!1}return-1!==$.module$exports$Blockly$common.draggingConnections.indexOf(b)?!1:!0}; module$exports$Blockly$ConnectionChecker.ConnectionChecker.prototype.canConnectToPrevious_=function(a,b){if(a.targetConnection||-1!==$.module$exports$Blockly$common.draggingConnections.indexOf(b))return!1;if(!b.targetConnection)return!0;a=b.targetBlock();return a.isInsertionMarker()?!a.getPreviousBlock():!1};(0,module$exports$Blockly$registry.register)(module$exports$Blockly$registry.Type.CONNECTION_CHECKER,module$exports$Blockly$registry.DEFAULT,module$exports$Blockly$ConnectionChecker.ConnectionChecker);var module$exports$Blockly$Workspace={},module$contents$Blockly$Workspace_WorkspaceDB_=Object.create(null); -module$exports$Blockly$Workspace.Workspace=function(a){this.id=(0,module$exports$Blockly$utils$idGenerator.genUid)();module$contents$Blockly$Workspace_WorkspaceDB_[this.id]=this;this.options=a||new module$exports$Blockly$Options.Options({});this.RTL=!!this.options.RTL;this.horizontalLayout=!!this.options.horizontalLayout;this.toolboxPosition=this.options.toolboxPosition;this.connectionChecker=new ((0,module$exports$Blockly$registry.getClassFromOptions)(module$exports$Blockly$registry.Type.CONNECTION_CHECKER, -this.options,!0))(this);this.topBlocks_=[];this.topComments_=[];this.commentDB_=Object.create(null);this.listeners_=[];this.undoStack_=[];this.redoStack_=[];this.blockDB_=Object.create(null);this.typedBlocksDB_=Object.create(null);this.variableMap_=new module$exports$Blockly$VariableMap.VariableMap(this);this.potentialVariableMap_=null};module$exports$Blockly$Workspace.Workspace.prototype.rendered=!1;module$exports$Blockly$Workspace.Workspace.prototype.isClearing=!1; -module$exports$Blockly$Workspace.Workspace.prototype.MAX_UNDO=1024;module$exports$Blockly$Workspace.Workspace.prototype.connectionDBList=null;module$exports$Blockly$Workspace.Workspace.prototype.dispose=function(){this.listeners_.length=0;this.clear();delete module$contents$Blockly$Workspace_WorkspaceDB_[this.id]};module$exports$Blockly$Workspace.Workspace.SCAN_ANGLE=3; -module$exports$Blockly$Workspace.Workspace.prototype.sortObjects_=function(a,b){a=a.getRelativeToSurfaceXY();b=b.getRelativeToSurfaceXY();return a.y+module$exports$Blockly$Workspace.Workspace.prototype.sortObjects_.offset*a.x-(b.y+module$exports$Blockly$Workspace.Workspace.prototype.sortObjects_.offset*b.x)};module$exports$Blockly$Workspace.Workspace.prototype.addTopBlock=function(a){this.topBlocks_.push(a)}; -module$exports$Blockly$Workspace.Workspace.prototype.removeTopBlock=function(a){if(!(0,module$exports$Blockly$utils$array.removeElem)(this.topBlocks_,a))throw Error("Block not present in workspace's list of top-most blocks.");}; +module$exports$Blockly$Workspace.Workspace=function(a){this.id=(0,module$exports$Blockly$utils$idGenerator.genUid)();module$contents$Blockly$Workspace_WorkspaceDB_[this.id]=this;this.options=a||new module$exports$Blockly$Options.Options({});this.RTL=!!this.options.RTL;this.horizontalLayout=!!this.options.horizontalLayout;this.toolboxPosition=this.options.toolboxPosition;this.isClearing=this.isMutator=this.isFlyout=this.rendered=!1;this.MAX_UNDO=1024;this.connectionDBList=null;this.connectionChecker= +new ((0,module$exports$Blockly$registry.getClassFromOptions)(module$exports$Blockly$registry.Type.CONNECTION_CHECKER,this.options,!0))(this);this.topBlocks_=[];this.topComments_=[];this.commentDB_=Object.create(null);this.listeners_=[];this.undoStack_=[];this.redoStack_=[];this.blockDB_=Object.create(null);this.typedBlocksDB_=Object.create(null);this.variableMap_=new module$exports$Blockly$VariableMap.VariableMap(this);this.potentialVariableMap_=null}; +module$exports$Blockly$Workspace.Workspace.prototype.dispose=function(){this.listeners_.length=0;this.clear();delete module$contents$Blockly$Workspace_WorkspaceDB_[this.id]};module$exports$Blockly$Workspace.Workspace.prototype.sortObjects_=function(a,b){a=a.getRelativeToSurfaceXY();b=b.getRelativeToSurfaceXY();return a.y+module$exports$Blockly$Workspace.Workspace.prototype.sortObjects_.offset*a.x-(b.y+module$exports$Blockly$Workspace.Workspace.prototype.sortObjects_.offset*b.x)}; +module$exports$Blockly$Workspace.Workspace.prototype.addTopBlock=function(a){this.topBlocks_.push(a)};module$exports$Blockly$Workspace.Workspace.prototype.removeTopBlock=function(a){if(!(0,module$exports$Blockly$utils$array.removeElem)(this.topBlocks_,a))throw Error("Block not present in workspace's list of top-most blocks.");}; module$exports$Blockly$Workspace.Workspace.prototype.getTopBlocks=function(a){var b=[].concat(this.topBlocks_);a&&1this.MAX_UNDO&&0<=this.MAX_UNDO;)this.undoStack_.shift();for(var b=0;bb-$.module$exports$Blockly$internalConstants.CURRENT_CONNECTION_PREFERENCE)}if(this.localConnection_|| +module$exports$Blockly$InsertionMarkerManager.InsertionMarkerManager.prototype.shouldUpdatePreviews_=function(a,b){var c=a.local,d=a.closest;a=a.radius;if(c&&d){if(this.localConnection_&&this.closestConnection_){if(this.closestConnection_===d&&this.localConnection_===c)return!1;c=this.localConnection_.x+b.x-this.closestConnection_.x;b=this.localConnection_.y+b.y-this.closestConnection_.y;b=Math.sqrt(c*c+b*b);return!(d&&a>b-$.module$exports$Blockly$config.config.currentConnectionPreference)}if(this.localConnection_|| this.closestConnection_)console.error("Only one of localConnection_ and closestConnection_ was set.");else return!0}else return!(!this.localConnection_||!this.closestConnection_);console.error("Returning true from shouldUpdatePreviews, but it's not clear why.");return!0}; -module$exports$Blockly$InsertionMarkerManager.InsertionMarkerManager.prototype.getCandidate_=function(a){for(var b=this.getStartRadius_(),c=null,d=null,e=0;e(this.flyout_?$.module$exports$Blockly$internalConstants.FLYOUT_DRAG_RADIUS:$.module$exports$Blockly$internalConstants.DRAG_RADIUS)}; +module$exports$Blockly$Gesture.Gesture.prototype.updateDragDelta_=function(a){this.currentDragDeltaXY_=module$exports$Blockly$utils$Coordinate.Coordinate.difference(a,this.mouseDownXY_);return this.hasExceededDragRadius_?!1:this.hasExceededDragRadius_=module$exports$Blockly$utils$Coordinate.Coordinate.magnitude(this.currentDragDeltaXY_)>(this.flyout_?$.module$exports$Blockly$config.config.flyoutDragRadius:$.module$exports$Blockly$config.config.dragRadius)}; module$exports$Blockly$Gesture.Gesture.prototype.updateIsDraggingFromFlyout_=function(){return this.targetBlock_&&this.flyout_.isBlockCreatable_(this.targetBlock_)?!this.flyout_.isScrollable()||this.flyout_.isDragTowardWorkspace(this.currentDragDeltaXY_)?(this.startWorkspace_=this.flyout_.targetWorkspace,this.startWorkspace_.updateScreenCalculationsIfScrolled(),(0,module$exports$Blockly$Events$utils.getGroup)()||(0,module$exports$Blockly$Events$utils.setGroup)(!0),this.startBlock_=null,this.targetBlock_= this.flyout_.createBlock(this.targetBlock_),this.targetBlock_.select(),!0):!1:!1};module$exports$Blockly$Gesture.Gesture.prototype.updateIsDraggingBubble_=function(){if(!this.startBubble_)return!1;this.isDraggingBubble_=!0;this.startDraggingBubble_();return!0}; module$exports$Blockly$Gesture.Gesture.prototype.updateIsDraggingBlock_=function(){if(!this.targetBlock_)return!1;this.flyout_?this.isDraggingBlock_=this.updateIsDraggingFromFlyout_():this.targetBlock_.isMovable()&&(this.isDraggingBlock_=!0);return this.isDraggingBlock_?(this.startDraggingBlock_(),!0):!1}; @@ -691,9 +701,8 @@ module$exports$Blockly$Gesture.Gesture.prototype.setStartBlock=function(a){this. module$exports$Blockly$Gesture.Gesture.prototype.setStartWorkspace_=function(a){this.startWorkspace_||(this.startWorkspace_=a)};module$exports$Blockly$Gesture.Gesture.prototype.setStartFlyout_=function(a){this.flyout_||(this.flyout_=a)};module$exports$Blockly$Gesture.Gesture.prototype.isBubbleClick_=function(){return!!this.startBubble_&&!this.hasExceededDragRadius_};module$exports$Blockly$Gesture.Gesture.prototype.isBlockClick_=function(){return!!this.startBlock_&&!this.hasExceededDragRadius_&&!this.isFieldClick_()}; module$exports$Blockly$Gesture.Gesture.prototype.isFieldClick_=function(){return(this.startField_?this.startField_.isClickable():!1)&&!this.hasExceededDragRadius_&&(!this.flyout_||!this.flyout_.autoClose)};module$exports$Blockly$Gesture.Gesture.prototype.isWorkspaceClick_=function(){return!this.startBlock_&&!this.startBubble_&&!this.startField_&&!this.hasExceededDragRadius_}; module$exports$Blockly$Gesture.Gesture.prototype.isDragging=function(){return this.isDraggingWorkspace_||this.isDraggingBlock_||this.isDraggingBubble_};module$exports$Blockly$Gesture.Gesture.prototype.hasStarted=function(){return this.hasStarted_};module$exports$Blockly$Gesture.Gesture.prototype.getInsertionMarkers=function(){return this.blockDragger_?this.blockDragger_.getInsertionMarkers():[]}; -module$exports$Blockly$Gesture.Gesture.prototype.getCurrentDragger=function(){return this.isDraggingBlock_?this.blockDragger_:this.isDraggingWorkspace_?this.workspaceDragger_:this.isDraggingBubble_?this.bubbleDragger_:null};module$exports$Blockly$Gesture.Gesture.inProgress=function(){for(var a=module$exports$Blockly$Workspace.Workspace.getAll(),b=0,c;c=a[b];b++)if(c.currentGesture_)return!0;return!1};var module$exports$Blockly$Field={Field:function(a,b,c){this.value_=this.DEFAULT_VALUE;this.tooltip_=this.validator_=null;this.size_=new module$exports$Blockly$utils$Size.Size(0,0);this.constants_=this.mouseDownWrapper_=this.textContent_=this.textElement_=this.borderRect_=this.fieldGroup_=this.markerSvg_=this.cursorSvg_=null;c&&this.configure_(c);this.setValue(a);b&&this.setValidator(b)}};module$exports$Blockly$Field.Field.prototype.DEFAULT_VALUE=null; -module$exports$Blockly$Field.Field.prototype.name=void 0;module$exports$Blockly$Field.Field.prototype.disposed=!1;module$exports$Blockly$Field.Field.prototype.maxDisplayLength=50;module$exports$Blockly$Field.Field.prototype.sourceBlock_=null;module$exports$Blockly$Field.Field.prototype.isDirty_=!0;module$exports$Blockly$Field.Field.prototype.visible_=!0;module$exports$Blockly$Field.Field.prototype.enabled_=!0;module$exports$Blockly$Field.Field.prototype.clickTarget_=null; -module$exports$Blockly$Field.Field.NBSP="\u00a0";module$exports$Blockly$Field.Field.prototype.EDITABLE=!0;module$exports$Blockly$Field.Field.prototype.SERIALIZABLE=!1;module$exports$Blockly$Field.Field.prototype.configure_=function(a){var b=a.tooltip;"string"===typeof b&&(b=(0,module$exports$Blockly$utils$parsing.replaceMessageReferences)(a.tooltip));b&&this.setTooltip(b)}; +module$exports$Blockly$Gesture.Gesture.prototype.getCurrentDragger=function(){return this.isDraggingBlock_?this.blockDragger_:this.isDraggingWorkspace_?this.workspaceDragger_:this.isDraggingBubble_?this.bubbleDragger_:null};module$exports$Blockly$Gesture.Gesture.inProgress=function(){for(var a=module$exports$Blockly$Workspace.Workspace.getAll(),b=0,c;c=a[b];b++)if(c.currentGesture_)return!0;return!1};var module$exports$Blockly$Field={Field:function(a,b,c){this.name=void 0;this.value_=this.constructor.prototype.DEFAULT_VALUE;this.tooltip_=this.validator_=null;this.size_=new module$exports$Blockly$utils$Size.Size(0,0);this.constants_=this.mouseDownWrapper_=this.textContent_=this.textElement_=this.borderRect_=this.fieldGroup_=this.markerSvg_=this.cursorSvg_=null;this.disposed=!1;this.maxDisplayLength=50;this.sourceBlock_=null;this.enabled_=this.visible_=this.isDirty_=!0;this.suffixField=this.prefixField= +this.clickTarget_=null;this.EDITABLE=!0;this.SERIALIZABLE=!1;this.CURSOR="";a!==module$exports$Blockly$Field.Field.SKIP_SETUP&&(c&&this.configure_(c),this.setValue(a),b&&this.setValidator(b))}};module$exports$Blockly$Field.Field.prototype.configure_=function(a){var b=a.tooltip;"string"===typeof b&&(b=(0,module$exports$Blockly$utils$parsing.replaceMessageReferences)(a.tooltip));b&&this.setTooltip(b)}; module$exports$Blockly$Field.Field.prototype.setSourceBlock=function(a){if(this.sourceBlock_)throw Error("Field already bound to a block");this.sourceBlock_=a};module$exports$Blockly$Field.Field.prototype.getConstants=function(){!this.constants_&&this.sourceBlock_&&this.sourceBlock_.workspace&&this.sourceBlock_.workspace.rendered&&(this.constants_=this.sourceBlock_.workspace.getRenderer().getConstants());return this.constants_};module$exports$Blockly$Field.Field.prototype.getSourceBlock=function(){return this.sourceBlock_}; module$exports$Blockly$Field.Field.prototype.init=function(){this.fieldGroup_||(this.fieldGroup_=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.G,{},null),this.isVisible()||(this.fieldGroup_.style.display="none"),this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_),this.initView(),this.updateEditable(),this.setTooltip(this.tooltip_),this.bindEvents_(),this.initModel())}; module$exports$Blockly$Field.Field.prototype.initView=function(){this.createBorderRect_();this.createTextElement_()};module$exports$Blockly$Field.Field.prototype.initModel=function(){}; @@ -703,38 +712,39 @@ module$exports$Blockly$Field.Field.prototype.bindEvents_=function(){(0,module$ex module$exports$Blockly$Field.Field.prototype.saveState=function(a){a=this.saveLegacyState(module$exports$Blockly$Field.Field);return null!==a?a:this.getValue()};module$exports$Blockly$Field.Field.prototype.loadState=function(a){this.loadLegacyState(module$exports$Blockly$Field.Field,a)||this.setValue(a)}; module$exports$Blockly$Field.Field.prototype.saveLegacyState=function(a){return a.prototype.saveState===this.saveState&&a.prototype.toXml!==this.toXml?(a=(0,$.module$exports$Blockly$utils$xml.createElement)("field"),a.setAttribute("name",this.name||""),(0,$.module$exports$Blockly$Xml.domToText)(this.toXml(a)).replace(' xmlns="https://developers.google.com/blockly/xml"',"")):null}; module$exports$Blockly$Field.Field.prototype.loadLegacyState=function(a,b){return a.prototype.loadState===this.loadState&&a.prototype.fromXml!==this.fromXml?(this.fromXml((0,$.module$exports$Blockly$Xml.textToDom)(b)),!0):!1}; -module$exports$Blockly$Field.Field.prototype.dispose=function(){module$exports$Blockly$DropDownDiv.DropDownDiv.hideIfOwner(this);(0,module$exports$Blockly$WidgetDiv.hideIfOwner)(this);(0,module$exports$Blockly$Tooltip.unbindMouseEvents)(this.getClickTarget_());this.mouseDownWrapper_&&(0,module$exports$Blockly$browserEvents.unbind)(this.mouseDownWrapper_);(0,module$exports$Blockly$utils$dom.removeNode)(this.fieldGroup_);this.disposed=!0}; +module$exports$Blockly$Field.Field.prototype.dispose=function(){(0,module$exports$Blockly$dropDownDiv.hideIfOwner)(this);(0,module$exports$Blockly$WidgetDiv.hideIfOwner)(this);(0,module$exports$Blockly$Tooltip.unbindMouseEvents)(this.getClickTarget_());this.mouseDownWrapper_&&(0,module$exports$Blockly$browserEvents.unbind)(this.mouseDownWrapper_);(0,module$exports$Blockly$utils$dom.removeNode)(this.fieldGroup_);this.disposed=!0}; module$exports$Blockly$Field.Field.prototype.updateEditable=function(){var a=this.fieldGroup_;this.EDITABLE&&a&&(this.enabled_&&this.sourceBlock_.isEditable()?((0,module$exports$Blockly$utils$dom.addClass)(a,"blocklyEditableText"),(0,module$exports$Blockly$utils$dom.removeClass)(a,"blocklyNonEditableText"),a.style.cursor=this.CURSOR):((0,module$exports$Blockly$utils$dom.addClass)(a,"blocklyNonEditableText"),(0,module$exports$Blockly$utils$dom.removeClass)(a,"blocklyEditableText"),a.style.cursor=""))}; -module$exports$Blockly$Field.Field.prototype.setEnabled=function(a){this.enabled_=a;this.updateEditable()};module$exports$Blockly$Field.Field.prototype.isEnabled=function(){return this.enabled_};module$exports$Blockly$Field.Field.prototype.isClickable=function(){return this.enabled_&&!!this.sourceBlock_&&this.sourceBlock_.isEditable()&&!!this.showEditor_&&"function"===typeof this.showEditor_}; +module$exports$Blockly$Field.Field.prototype.setEnabled=function(a){this.enabled_=a;this.updateEditable()};module$exports$Blockly$Field.Field.prototype.isEnabled=function(){return this.enabled_};module$exports$Blockly$Field.Field.prototype.isClickable=function(){return this.enabled_&&!!this.sourceBlock_&&this.sourceBlock_.isEditable()&&this.showEditor_!==module$exports$Blockly$Field.Field.prototype.showEditor_}; module$exports$Blockly$Field.Field.prototype.isCurrentlyEditable=function(){return this.enabled_&&this.EDITABLE&&!!this.sourceBlock_&&this.sourceBlock_.isEditable()};module$exports$Blockly$Field.Field.prototype.isSerializable=function(){var a=!1;this.name&&(this.SERIALIZABLE?a=!0:this.EDITABLE&&(console.warn("Detected an editable field that was not serializable. Please define SERIALIZABLE property as true on all editable custom fields. Proceeding with serialization."),a=!0));return a}; module$exports$Blockly$Field.Field.prototype.isVisible=function(){return this.visible_};module$exports$Blockly$Field.Field.prototype.setVisible=function(a){if(this.visible_!==a){this.visible_=a;var b=this.getSvgRoot();b&&(b.style.display=a?"block":"none")}};module$exports$Blockly$Field.Field.prototype.setValidator=function(a){this.validator_=a};module$exports$Blockly$Field.Field.prototype.getValidator=function(){return this.validator_};module$exports$Blockly$Field.Field.prototype.getSvgRoot=function(){return this.fieldGroup_}; -module$exports$Blockly$Field.Field.prototype.applyColour=function(){};module$exports$Blockly$Field.Field.prototype.render_=function(){this.textContent_&&(this.textContent_.nodeValue=this.getDisplayText_());this.updateSize_()};module$exports$Blockly$Field.Field.prototype.showEditor=function(a){this.isClickable()&&this.showEditor_(a)}; +module$exports$Blockly$Field.Field.prototype.applyColour=function(){};module$exports$Blockly$Field.Field.prototype.render_=function(){this.textContent_&&(this.textContent_.nodeValue=this.getDisplayText_());this.updateSize_()};module$exports$Blockly$Field.Field.prototype.showEditor=function(a){this.isClickable()&&this.showEditor_(a)};module$exports$Blockly$Field.Field.prototype.showEditor_=function(a){}; module$exports$Blockly$Field.Field.prototype.updateSize_=function(a){var b=this.getConstants();a=void 0!==a?a:this.borderRect_?this.getConstants().FIELD_BORDER_RECT_X_PADDING:0;var c=2*a,d=b.FIELD_TEXT_HEIGHT,e=0;this.textElement_&&(e=(0,module$exports$Blockly$utils$dom.getFastTextWidth)(this.textElement_,b.FIELD_TEXT_FONTSIZE,b.FIELD_TEXT_FONTWEIGHT,b.FIELD_TEXT_FONTFAMILY),c+=e);this.borderRect_&&(d=Math.max(d,b.FIELD_BORDER_RECT_HEIGHT));this.size_.height=d;this.size_.width=c;this.positionTextElement_(a, e);this.positionBorderRect_()};module$exports$Blockly$Field.Field.prototype.positionTextElement_=function(a,b){if(this.textElement_){var c=this.getConstants(),d=this.size_.height/2;this.textElement_.setAttribute("x",this.sourceBlock_.RTL?this.size_.width-b-a:a);this.textElement_.setAttribute("y",c.FIELD_TEXT_BASELINE_CENTER?d:d-c.FIELD_TEXT_HEIGHT/2+c.FIELD_TEXT_BASELINE)}}; module$exports$Blockly$Field.Field.prototype.positionBorderRect_=function(){this.borderRect_&&(this.borderRect_.setAttribute("width",this.size_.width),this.borderRect_.setAttribute("height",this.size_.height),this.borderRect_.setAttribute("rx",this.getConstants().FIELD_BORDER_RECT_RADIUS),this.borderRect_.setAttribute("ry",this.getConstants().FIELD_BORDER_RECT_RADIUS))}; module$exports$Blockly$Field.Field.prototype.getSize=function(){if(!this.isVisible())return new module$exports$Blockly$utils$Size.Size(0,0);this.isDirty_?(this.render_(),this.isDirty_=!1):this.visible_&&0===this.size_.width&&(console.warn("Deprecated use of setting size_.width to 0 to rerender a field. Set field.isDirty_ to true instead."),this.render_());return this.size_}; module$exports$Blockly$Field.Field.prototype.getScaledBBox=function(){if(this.borderRect_){var a=this.borderRect_.getBoundingClientRect();var b=(0,module$exports$Blockly$utils$style.getPageOffset)(this.borderRect_);var c=a.width;var d=a.height}else d=this.sourceBlock_.getHeightWidth(),a=this.sourceBlock_.workspace.scale,b=this.getAbsoluteXY_(),c=d.width*a,d=d.height*a,module$exports$Blockly$utils$userAgent.GECKO?(b.x+=1.5*a,b.y+=1.5*a):module$exports$Blockly$utils$userAgent.EDGE||module$exports$Blockly$utils$userAgent.IE|| (b.x-=.5*a,b.y-=.5*a),c+=1*a,d+=1*a;return new module$exports$Blockly$utils$Rect.Rect(b.y,b.y+d,b.x,b.x+c)};module$exports$Blockly$Field.Field.prototype.getDisplayText_=function(){var a=this.getText();if(!a)return module$exports$Blockly$Field.Field.NBSP;a.length>this.maxDisplayLength&&(a=a.substring(0,this.maxDisplayLength-2)+"\u2026");a=a.replace(/\s/g,module$exports$Blockly$Field.Field.NBSP);this.sourceBlock_&&this.sourceBlock_.RTL&&(a+="\u200f");return a}; -module$exports$Blockly$Field.Field.prototype.getText=function(){if(this.getText_){var a=this.getText_.call(this);if(null!==a)return String(a)}return String(this.getValue())};module$exports$Blockly$Field.Field.prototype.markDirty=function(){this.isDirty_=!0;this.constants_=null};module$exports$Blockly$Field.Field.prototype.forceRerender=function(){this.isDirty_=!0;this.sourceBlock_&&this.sourceBlock_.rendered&&(this.sourceBlock_.render(),this.sourceBlock_.bumpNeighbours(),this.updateMarkers_())}; +module$exports$Blockly$Field.Field.prototype.getText=function(){var a=this.getText_();return null!==a?String(a):String(this.getValue())};module$exports$Blockly$Field.Field.prototype.getText_=function(){return null};module$exports$Blockly$Field.Field.prototype.markDirty=function(){this.isDirty_=!0;this.constants_=null}; +module$exports$Blockly$Field.Field.prototype.forceRerender=function(){this.isDirty_=!0;this.sourceBlock_&&this.sourceBlock_.rendered&&(this.sourceBlock_.render(),this.sourceBlock_.bumpNeighbours(),this.updateMarkers_())}; module$exports$Blockly$Field.Field.prototype.setValue=function(a){if(null!==a){var b=this.doClassValidation_(a);a=this.processValidation_(a,b);if(!(a instanceof Error)){if(b=this.getValidator())if(b=b.call(this,a),a=this.processValidation_(a,b),a instanceof Error)return;b=this.sourceBlock_;if(!b||!b.disposed){var c=this.getValue();c===a?this.doValueUpdate_(a):(this.doValueUpdate_(a),b&&(0,module$exports$Blockly$Events$utils.isEnabled)()&&(0,module$exports$Blockly$Events$utils.fire)(new ((0,module$exports$Blockly$Events$utils.get)(module$exports$Blockly$Events$utils.CHANGE))(b, "field",this.name||null,c,a)),this.isDirty_&&this.forceRerender())}}}};module$exports$Blockly$Field.Field.prototype.processValidation_=function(a,b){if(null===b)return this.doValueInvalid_(a),this.isDirty_&&this.forceRerender(),Error();void 0!==b&&(a=b);return a};module$exports$Blockly$Field.Field.prototype.getValue=function(){return this.value_};module$exports$Blockly$Field.Field.prototype.doClassValidation_=function(a){return null===a||void 0===a?null:a}; module$exports$Blockly$Field.Field.prototype.doValueUpdate_=function(a){this.value_=a;this.isDirty_=!0};module$exports$Blockly$Field.Field.prototype.doValueInvalid_=function(a){};module$exports$Blockly$Field.Field.prototype.onMouseDown_=function(a){this.sourceBlock_&&this.sourceBlock_.workspace&&(a=this.sourceBlock_.workspace.getGesture(a))&&a.setStartField(this)}; module$exports$Blockly$Field.Field.prototype.setTooltip=function(a){a||""===a||(a=this.sourceBlock_);var b=this.getClickTarget_();b?b.tooltip=a:this.tooltip_=a};module$exports$Blockly$Field.Field.prototype.getTooltip=function(){var a=this.getClickTarget_();return a?(0,module$exports$Blockly$Tooltip.getTooltipOfObject)(a):(0,module$exports$Blockly$Tooltip.getTooltipOfObject)({tooltip:this.tooltip_})}; -module$exports$Blockly$Field.Field.prototype.getClickTarget_=function(){return this.clickTarget_||this.getSvgRoot()};module$exports$Blockly$Field.Field.prototype.getAbsoluteXY_=function(){return(0,module$exports$Blockly$utils$style.getPageOffset)(this.getClickTarget_())};module$exports$Blockly$Field.Field.prototype.referencesVariables=function(){return!1}; +module$exports$Blockly$Field.Field.prototype.getClickTarget_=function(){return this.clickTarget_||this.getSvgRoot()};module$exports$Blockly$Field.Field.prototype.getAbsoluteXY_=function(){return(0,module$exports$Blockly$utils$style.getPageOffset)(this.getClickTarget_())};module$exports$Blockly$Field.Field.prototype.referencesVariables=function(){return!1};module$exports$Blockly$Field.Field.prototype.refreshVariableName=function(){}; module$exports$Blockly$Field.Field.prototype.getParentInput=function(){for(var a=null,b=this.sourceBlock_,c=b.inputList,d=0;da.height;e&&(b-=d);this.debugElements_.push((0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.RECT,{"class":"rowSpacerRect blockRenderDebug",x:c?-(a.xPos+a.width):a.xPos,y:b,width:a.width,height:d,stroke:e?"black":"blue",fill:"blue","fill-opacity":"0.5","stroke-width":"1px"}, this.svgRoot_))}}; module$exports$Blockly$blockRendering$Debug.Debug.prototype.drawSpacerElem=function(a,b,c){if(module$exports$Blockly$blockRendering$Debug.Debug.config.elemSpacers){b=Math.abs(a.width);var d=0>a.width,e=d?a.xPos-b:a.xPos;c&&(e=-(e+b));this.debugElements_.push((0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.RECT,{"class":"elemSpacerRect blockRenderDebug",x:e,y:a.centerline-a.height/2,width:b,height:a.height,stroke:"pink",fill:d?"black":"pink","fill-opacity":"0.5", "stroke-width":"1px"},this.svgRoot_))}}; module$exports$Blockly$blockRendering$Debug.Debug.prototype.drawRenderedElem=function(a,b){if(module$exports$Blockly$blockRendering$Debug.Debug.config.elems){var c=a.xPos;b&&(c=-(c+a.width));b=a.centerline-a.height/2;this.debugElements_.push((0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.RECT,{"class":"rowRenderingRect blockRenderDebug",x:c,y:b,width:a.width,height:a.height,stroke:"black",fill:"none","stroke-width":"1px"},this.svgRoot_));module$exports$Blockly$blockRendering$Types.Types.isField(a)&& -a.field instanceof $.module$exports$Blockly$FieldLabel.FieldLabel&&this.debugElements_.push((0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.RECT,{"class":"rowRenderingRect blockRenderDebug",x:c,y:b+this.constants_.FIELD_TEXT_BASELINE,width:a.width,height:"0.1px",stroke:"red",fill:"none","stroke-width":"0.5px"},this.svgRoot_))}module$exports$Blockly$blockRendering$Types.Types.isInput(a)&&module$exports$Blockly$blockRendering$Debug.Debug.config.connections&& -this.drawConnection(a.connectionModel)}; +a instanceof module$exports$Blockly$blockRendering$Field.Field&&a.field instanceof $.module$exports$Blockly$FieldLabel.FieldLabel&&this.debugElements_.push((0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.RECT,{"class":"rowRenderingRect blockRenderDebug",x:c,y:b+this.constants_.FIELD_TEXT_BASELINE,width:a.width,height:"0.1px",stroke:"red",fill:"none","stroke-width":"0.5px"},this.svgRoot_))}module$exports$Blockly$blockRendering$Types.Types.isInput(a)&&a instanceof +module$exports$Blockly$blockRendering$InputConnection.InputConnection&&module$exports$Blockly$blockRendering$Debug.Debug.config.connections&&this.drawConnection(a.connectionModel)}; module$exports$Blockly$blockRendering$Debug.Debug.prototype.drawConnection=function(a){if(module$exports$Blockly$blockRendering$Debug.Debug.config.connections){if(a.type===$.module$exports$Blockly$ConnectionType.ConnectionType.INPUT_VALUE){var b=4;var c="magenta";var d="none"}else a.type===$.module$exports$Blockly$ConnectionType.ConnectionType.OUTPUT_VALUE?(b=2,d=c="magenta"):a.type===$.module$exports$Blockly$ConnectionType.ConnectionType.NEXT_STATEMENT?(b=4,c="goldenrod",d="none"):a.type===$.module$exports$Blockly$ConnectionType.ConnectionType.PREVIOUS_STATEMENT&& (b=2,d=c="goldenrod");this.debugElements_.push((0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.CIRCLE,{"class":"blockRenderDebug",cx:a.offsetInBlock_.x,cy:a.offsetInBlock_.y,r:b,fill:d,stroke:c},this.svgRoot_))}}; module$exports$Blockly$blockRendering$Debug.Debug.prototype.drawRenderedRow=function(a,b,c){module$exports$Blockly$blockRendering$Debug.Debug.config.rows&&(this.debugElements_.push((0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.RECT,{"class":"elemRenderingRect blockRenderDebug",x:c?-(a.xPos+a.width):a.xPos,y:a.yPos,width:a.width,height:a.height,stroke:"red",fill:"none","stroke-width":"1px"},this.svgRoot_)),module$exports$Blockly$blockRendering$Types.Types.isTopOrBottomRow(a)|| @@ -743,19 +753,20 @@ module$exports$Blockly$blockRendering$Debug.Debug.prototype.drawRowWithElements= module$exports$Blockly$blockRendering$Debug.Debug.prototype.drawBoundingBox=function(a){if(module$exports$Blockly$blockRendering$Debug.Debug.config.blockBounds){var b=a.RTL?-a.width:0;this.debugElements_.push((0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.RECT,{"class":"blockBoundingBox blockRenderDebug",x:b,y:0,width:a.width,height:a.height,stroke:"black",fill:"none","stroke-width":"1px","stroke-dasharray":"5,5"},this.svgRoot_));module$exports$Blockly$blockRendering$Debug.Debug.config.connectedBlockBounds&& (b=a.RTL?-a.widthWithChildren:0,this.debugElements_.push((0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.RECT,{"class":"blockRenderDebug",x:b,y:0,width:a.widthWithChildren,height:a.height,stroke:"#DF57BC",fill:"none","stroke-width":"1px","stroke-dasharray":"3,3"},this.svgRoot_)))}}; module$exports$Blockly$blockRendering$Debug.Debug.prototype.drawDebug=function(a,b){this.clearElems();this.svgRoot_=a.getSvgRoot();this.randomColour_="#"+Math.floor(16777215*Math.random()).toString(16);for(var c=0,d=0;da||a>this.fieldRow.length)throw Error("index "+a+" out of bounds.");if(!(b||""===b&&c))return a;"string"===typeof b&&(b=(0,module$exports$Blockly$fieldRegistry.fromJson)({type:"field_label",text:b}));b.setSourceBlock(this.sourceBlock_);this.sourceBlock_.rendered&&(b.init(),b.applyColour());b.name=c;b.setVisible(this.isVisible());b.prefixField&&(a=this.insertFieldAt(a,b.prefixField));this.fieldRow.splice(a,0,b);a++;b.suffixField&& (a=this.insertFieldAt(a,b.suffixField));this.sourceBlock_.rendered&&(this.sourceBlock_=this.sourceBlock_,this.sourceBlock_.render(),this.sourceBlock_.bumpNeighbours());return a}; $.module$exports$Blockly$Input.Input.prototype.removeField=function(a,b){for(var c=0,d;d=this.fieldRow[c];c++)if(d.name===a)return d.dispose(),this.fieldRow.splice(c,1),this.sourceBlock_.rendered&&(this.sourceBlock_=this.sourceBlock_,this.sourceBlock_.render(),this.sourceBlock_.bumpNeighbours()),!0;if(b)return!1;throw Error('Field "'+a+'" not found.');};$.module$exports$Blockly$Input.Input.prototype.isVisible=function(){return this.visible_}; $.module$exports$Blockly$Input.Input.prototype.setVisible=function(a){var b=[];if(this.visible_===a)return b;this.visible_=a;for(var c=0,d;d=this.fieldRow[c];c++)d.setVisible(a);this.connection&&(this.connection=this.connection,a?b=this.connection.startTrackingAll():this.connection.stopTrackingAll(),c=this.connection.targetBlock())&&(c.getSvgRoot().style.display=a?"block":"none");return b};$.module$exports$Blockly$Input.Input.prototype.markDirty=function(){for(var a=0,b;b=this.fieldRow[a];a++)b.markDirty()}; $.module$exports$Blockly$Input.Input.prototype.setCheck=function(a){if(!this.connection)throw Error("This input does not have a connection.");this.connection.setCheck(a);return this};$.module$exports$Blockly$Input.Input.prototype.setAlign=function(a){this.align=a;this.sourceBlock_.rendered&&(this.sourceBlock_=this.sourceBlock_,this.sourceBlock_.render());return this}; $.module$exports$Blockly$Input.Input.prototype.setShadowDom=function(a){if(!this.connection)throw Error("This input does not have a connection.");this.connection.setShadowDom(a);return this};$.module$exports$Blockly$Input.Input.prototype.getShadowDom=function(){if(!this.connection)throw Error("This input does not have a connection.");return this.connection.getShadowDom()};$.module$exports$Blockly$Input.Input.prototype.init=function(){if(this.sourceBlock_.workspace.rendered)for(var a=0;aa.length)){b=[];for(c=0;c=this.connections_.length)return-1;b=a.y;for(var d=c;0<=d&&this.connections_[d].y===b;){if(this.connections_[d]===a)return d;d--}for(d=c;da)c=d;else{b=d;break}}return b}; module$exports$Blockly$ConnectionDB.ConnectionDB.prototype.removeConnection=function(a,b){a=this.findIndexOfConnection_(a,b);if(-1===a)throw Error("Unable to find connection in connectionDB.");this.connections_.splice(a,1)}; @@ -1100,28 +1141,28 @@ h,!0,g)&&(c=h,g=h.distanceFrom(a)),f++;a.x=e;a.y=d;return{connection:c,radius:g} module$exports$Blockly$ConnectionDB.ConnectionDB.init=function(a){var b=[];b[$.module$exports$Blockly$ConnectionType.ConnectionType.INPUT_VALUE]=new module$exports$Blockly$ConnectionDB.ConnectionDB(a);b[$.module$exports$Blockly$ConnectionType.ConnectionType.OUTPUT_VALUE]=new module$exports$Blockly$ConnectionDB.ConnectionDB(a);b[$.module$exports$Blockly$ConnectionType.ConnectionType.NEXT_STATEMENT]=new module$exports$Blockly$ConnectionDB.ConnectionDB(a);b[$.module$exports$Blockly$ConnectionType.ConnectionType.PREVIOUS_STATEMENT]= new module$exports$Blockly$ConnectionDB.ConnectionDB(a);return b};var module$exports$Blockly$ThemeManager={ThemeManager:function(a,b){this.workspace_=a;this.theme_=b;this.subscribedWorkspaces_=[];this.componentDB_=Object.create(null)}};module$exports$Blockly$ThemeManager.ThemeManager.prototype.getTheme=function(){return this.theme_}; module$exports$Blockly$ThemeManager.ThemeManager.prototype.setTheme=function(a){var b=this.theme_;this.theme_=a;if(a=this.workspace_.getInjectionDiv())b&&(0,module$exports$Blockly$utils$dom.removeClass)(a,b.getClassName()),(0,module$exports$Blockly$utils$dom.addClass)(a,this.theme_.getClassName());for(b=0;a=this.subscribedWorkspaces_[b];b++)a.refreshTheme();b=0;a=Object.keys(this.componentDB_);for(var c;c=a[b];b++)for(var d=0,e;e=this.componentDB_[c][d];d++){var f=e.element;e=e.propertyName;var g= -this.theme_&&this.theme_.getComponentStyle(c);f.style[e]=g||""}b=$jscomp.makeIterator(this.subscribedWorkspaces_);for(a=b.next();!a.done;a=b.next())a.value.hideChaff()};module$exports$Blockly$ThemeManager.ThemeManager.prototype.subscribeWorkspace=function(a){this.subscribedWorkspaces_.push(a)}; +this.theme_&&this.theme_.getComponentStyle(c);f.style[e]=g||""}b=$.$jscomp.makeIterator(this.subscribedWorkspaces_);for(a=b.next();!a.done;a=b.next())a.value.hideChaff()};module$exports$Blockly$ThemeManager.ThemeManager.prototype.subscribeWorkspace=function(a){this.subscribedWorkspaces_.push(a)}; module$exports$Blockly$ThemeManager.ThemeManager.prototype.unsubscribeWorkspace=function(a){if(!(0,module$exports$Blockly$utils$array.removeElem)(this.subscribedWorkspaces_,a))throw Error("Cannot unsubscribe a workspace that hasn't been subscribed.");}; module$exports$Blockly$ThemeManager.ThemeManager.prototype.subscribe=function(a,b,c){this.componentDB_[b]||(this.componentDB_[b]=[]);this.componentDB_[b].push({element:a,propertyName:c});b=this.theme_&&this.theme_.getComponentStyle(b);a.style[c]=b||""}; -module$exports$Blockly$ThemeManager.ThemeManager.prototype.unsubscribe=function(a){if(a)for(var b=Object.keys(this.componentDB_),c=0,d;d=b[c];c++){for(var e=this.componentDB_[d],f=e.length-1;0<=f;f--)e[f].element===a&&e.splice(f,1);this.componentDB_[d].length||delete this.componentDB_[d]}};module$exports$Blockly$ThemeManager.ThemeManager.prototype.dispose=function(){this.componentDB_=this.subscribedWorkspaces_=this.theme_=this.owner_=null};var module$exports$Blockly$TouchGesture={TouchGesture:function(a,b){module$exports$Blockly$TouchGesture.TouchGesture.superClass_.constructor.call(this,a,b);this.isMultiTouch_=!1;this.cachedPoints_=Object.create(null);this.startDistance_=this.previousScale_=0;this.isPinchZoomEnabled_=this.onStartWrapper_=null}};(0,$.module$exports$Blockly$utils$object.inherits)(module$exports$Blockly$TouchGesture.TouchGesture,module$exports$Blockly$Gesture.Gesture); -module$exports$Blockly$TouchGesture.TouchGesture.ZOOM_IN_MULTIPLIER=5;module$exports$Blockly$TouchGesture.TouchGesture.ZOOM_OUT_MULTIPLIER=6;module$exports$Blockly$TouchGesture.TouchGesture.prototype.doStart=function(a){this.isPinchZoomEnabled_=this.startWorkspace_.options.zoomOptions&&this.startWorkspace_.options.zoomOptions.pinch;module$exports$Blockly$TouchGesture.TouchGesture.superClass_.doStart.call(this,a);!this.isEnding_&&(0,module$exports$Blockly$Touch.isTouchEvent)(a)&&this.handleTouchStart(a)}; +module$exports$Blockly$ThemeManager.ThemeManager.prototype.unsubscribe=function(a){if(a)for(var b=Object.keys(this.componentDB_),c=0,d;d=b[c];c++){for(var e=this.componentDB_[d],f=e.length-1;0<=f;f--)e[f].element===a&&e.splice(f,1);this.componentDB_[d].length||delete this.componentDB_[d]}};module$exports$Blockly$ThemeManager.ThemeManager.prototype.dispose=function(){this.componentDB_=this.subscribedWorkspaces_=this.theme_=this.owner_=null};var module$exports$Blockly$TouchGesture={},module$contents$Blockly$TouchGesture_ZOOM_IN_MULTIPLIER=5,module$contents$Blockly$TouchGesture_ZOOM_OUT_MULTIPLIER=6;module$exports$Blockly$TouchGesture.TouchGesture=function(a,b){module$exports$Blockly$Gesture.Gesture.call(this,a,b);this.isMultiTouch_=!1;this.cachedPoints_=Object.create(null);this.startDistance_=this.previousScale_=0;this.isPinchZoomEnabled_=this.onStartWrapper_=null};$.$jscomp.inherits(module$exports$Blockly$TouchGesture.TouchGesture,module$exports$Blockly$Gesture.Gesture); +module$exports$Blockly$TouchGesture.TouchGesture.prototype.doStart=function(a){this.isPinchZoomEnabled_=this.startWorkspace_.options.zoomOptions&&this.startWorkspace_.options.zoomOptions.pinch;module$exports$Blockly$Gesture.Gesture.prototype.doStart.call(this,a);!this.isEnding_&&(0,module$exports$Blockly$Touch.isTouchEvent)(a)&&this.handleTouchStart(a)}; module$exports$Blockly$TouchGesture.TouchGesture.prototype.bindMouseEvents=function(a){this.onStartWrapper_=(0,module$exports$Blockly$browserEvents.conditionalBind)(document,"mousedown",null,this.handleStart.bind(this),!0);this.onMoveWrapper_=(0,module$exports$Blockly$browserEvents.conditionalBind)(document,"mousemove",null,this.handleMove.bind(this),!0);this.onUpWrapper_=(0,module$exports$Blockly$browserEvents.conditionalBind)(document,"mouseup",null,this.handleUp.bind(this),!0);a.preventDefault(); a.stopPropagation()};module$exports$Blockly$TouchGesture.TouchGesture.prototype.handleStart=function(a){!this.isDragging()&&(0,module$exports$Blockly$Touch.isTouchEvent)(a)&&(this.handleTouchStart(a),this.isMultiTouch()&&(0,module$exports$Blockly$Touch.longStop)())}; -module$exports$Blockly$TouchGesture.TouchGesture.prototype.handleMove=function(a){this.isDragging()?(0,module$exports$Blockly$Touch.shouldHandleEvent)(a)&&module$exports$Blockly$TouchGesture.TouchGesture.superClass_.handleMove.call(this,a):this.isMultiTouch()?((0,module$exports$Blockly$Touch.isTouchEvent)(a)&&this.handleTouchMove(a),(0,module$exports$Blockly$Touch.longStop)()):module$exports$Blockly$TouchGesture.TouchGesture.superClass_.handleMove.call(this,a)}; -module$exports$Blockly$TouchGesture.TouchGesture.prototype.handleUp=function(a){(0,module$exports$Blockly$Touch.isTouchEvent)(a)&&!this.isDragging()&&this.handleTouchEnd(a);!this.isMultiTouch()||this.isDragging()?(0,module$exports$Blockly$Touch.shouldHandleEvent)(a)&&module$exports$Blockly$TouchGesture.TouchGesture.superClass_.handleUp.call(this,a):(a.preventDefault(),a.stopPropagation(),this.dispose())};module$exports$Blockly$TouchGesture.TouchGesture.prototype.isMultiTouch=function(){return this.isMultiTouch_}; -module$exports$Blockly$TouchGesture.TouchGesture.prototype.dispose=function(){module$exports$Blockly$TouchGesture.TouchGesture.superClass_.dispose.call(this);this.onStartWrapper_&&(0,module$exports$Blockly$browserEvents.unbind)(this.onStartWrapper_)}; +module$exports$Blockly$TouchGesture.TouchGesture.prototype.handleMove=function(a){this.isDragging()?(0,module$exports$Blockly$Touch.shouldHandleEvent)(a)&&module$exports$Blockly$Gesture.Gesture.prototype.handleMove.call(this,a):this.isMultiTouch()?((0,module$exports$Blockly$Touch.isTouchEvent)(a)&&this.handleTouchMove(a),(0,module$exports$Blockly$Touch.longStop)()):module$exports$Blockly$Gesture.Gesture.prototype.handleMove.call(this,a)}; +module$exports$Blockly$TouchGesture.TouchGesture.prototype.handleUp=function(a){(0,module$exports$Blockly$Touch.isTouchEvent)(a)&&!this.isDragging()&&this.handleTouchEnd(a);!this.isMultiTouch()||this.isDragging()?(0,module$exports$Blockly$Touch.shouldHandleEvent)(a)&&module$exports$Blockly$Gesture.Gesture.prototype.handleUp.call(this,a):(a.preventDefault(),a.stopPropagation(),this.dispose())};module$exports$Blockly$TouchGesture.TouchGesture.prototype.isMultiTouch=function(){return this.isMultiTouch_}; +module$exports$Blockly$TouchGesture.TouchGesture.prototype.dispose=function(){module$exports$Blockly$Gesture.Gesture.prototype.dispose.call(this);this.onStartWrapper_&&(0,module$exports$Blockly$browserEvents.unbind)(this.onStartWrapper_)}; module$exports$Blockly$TouchGesture.TouchGesture.prototype.handleTouchStart=function(a){var b=(0,module$exports$Blockly$Touch.getTouchIdentifierFromEvent)(a);this.cachedPoints_[b]=this.getTouchPoint(a);b=Object.keys(this.cachedPoints_);2===b.length&&(this.startDistance_=module$exports$Blockly$utils$Coordinate.Coordinate.distance(this.cachedPoints_[b[0]],this.cachedPoints_[b[1]]),this.isMultiTouch_=!0,a.preventDefault())}; -module$exports$Blockly$TouchGesture.TouchGesture.prototype.handleTouchMove=function(a){var b=(0,module$exports$Blockly$Touch.getTouchIdentifierFromEvent)(a);this.cachedPoints_[b]=this.getTouchPoint(a);b=Object.keys(this.cachedPoints_);this.isPinchZoomEnabled_&&2===b.length?this.handlePinch_(a):module$exports$Blockly$TouchGesture.TouchGesture.superClass_.handleMove.call(this,a)}; -module$exports$Blockly$TouchGesture.TouchGesture.prototype.handlePinch_=function(a){var b=Object.keys(this.cachedPoints_);b=module$exports$Blockly$utils$Coordinate.Coordinate.distance(this.cachedPoints_[b[0]],this.cachedPoints_[b[1]])/this.startDistance_;if(0this.previousScale_){var c=b-this.previousScale_;c=0Object.keys(this.cachedPoints_).length&&(this.cachedPoints_=Object.create(null),this.previousScale_=0)}; -module$exports$Blockly$TouchGesture.TouchGesture.prototype.getTouchPoint=function(a){return this.startWorkspace_?new module$exports$Blockly$utils$Coordinate.Coordinate(a.changedTouches?a.changedTouches[0].pageX:a.pageX,a.changedTouches?a.changedTouches[0].pageY:a.pageY):null};var module$exports$Blockly$WorkspaceAudio={WorkspaceAudio:function(a){this.parentWorkspace_=a;this.SOUNDS_=Object.create(null)}};module$exports$Blockly$WorkspaceAudio.WorkspaceAudio.prototype.lastSound_=null;module$exports$Blockly$WorkspaceAudio.WorkspaceAudio.prototype.dispose=function(){this.SOUNDS_=this.parentWorkspace_=null}; +module$exports$Blockly$TouchGesture.TouchGesture.prototype.handleTouchMove=function(a){var b=(0,module$exports$Blockly$Touch.getTouchIdentifierFromEvent)(a);this.cachedPoints_[b]=this.getTouchPoint(a);b=Object.keys(this.cachedPoints_);this.isPinchZoomEnabled_&&2===b.length?this.handlePinch_(a):module$exports$Blockly$Gesture.Gesture.prototype.handleMove.call(this,a)}; +module$exports$Blockly$TouchGesture.TouchGesture.prototype.handlePinch_=function(a){var b=Object.keys(this.cachedPoints_);b=module$exports$Blockly$utils$Coordinate.Coordinate.distance(this.cachedPoints_[b[0]],this.cachedPoints_[b[1]])/this.startDistance_;if(0this.previousScale_){var c=b-this.previousScale_;c=0Object.keys(this.cachedPoints_).length&&(this.cachedPoints_=Object.create(null),this.previousScale_=0)}; +module$exports$Blockly$TouchGesture.TouchGesture.prototype.getTouchPoint=function(a){return this.startWorkspace_?new module$exports$Blockly$utils$Coordinate.Coordinate(a.changedTouches?a.changedTouches[0].pageX:a.pageX,a.changedTouches?a.changedTouches[0].pageY:a.pageY):null};var module$exports$Blockly$WorkspaceAudio={},module$contents$Blockly$WorkspaceAudio_SOUND_LIMIT=100;module$exports$Blockly$WorkspaceAudio.WorkspaceAudio=function(a){this.parentWorkspace_=a;this.SOUNDS_=Object.create(null);this.lastSound_=null};module$exports$Blockly$WorkspaceAudio.WorkspaceAudio.prototype.dispose=function(){this.SOUNDS_=this.parentWorkspace_=null}; module$exports$Blockly$WorkspaceAudio.WorkspaceAudio.prototype.load=function(a,b){if(a.length){try{var c=new $.module$exports$Blockly$utils$global.globalThis.Audio}catch(h){return}for(var d,e=0;eMath.abs(b-this.oldTop_)&&1>Math.abs(c-this.oldLeft_))){var d=new ((0,module$exports$Blockly$Events$utils.get)(module$exports$Blockly$Events$utils.VIEWPORT_CHANGE))(b,c,a,this.id,this.oldScale_);this.oldScale_=a;this.oldTop_=b;this.oldLeft_=c;(0,module$exports$Blockly$Events$utils.fire)(d)}}}; module$exports$Blockly$WorkspaceSvg.WorkspaceSvg.prototype.translate=function(a,b){if(this.useWorkspaceDragSurface_&&this.isDragSurfaceActive_)this.workspaceDragSurface_.translateSurface(a,b);else{var c="translate("+a+","+b+") scale("+this.scale+")";this.svgBlockCanvas_.setAttribute("transform",c);this.svgBubbleCanvas_.setAttribute("transform",c)}this.blockDragSurface_&&this.blockDragSurface_.translateAndScaleGroup(a,b,this.scale);this.grid_&&this.grid_.moveTo(a,b);this.maybeFireViewportChangeEvent()}; @@ -1176,13 +1213,13 @@ c,this.scale);this.workspaceDragSurface_.translateSurface(d.x,d.y)}};module$expo module$exports$Blockly$WorkspaceSvg.WorkspaceSvg.prototype.setVisible=function(a){this.isVisible_=a;if(this.svgGroup_)if(this.scrollbar&&this.scrollbar.setContainerVisible(a),this.getFlyout()&&this.getFlyout().setContainerVisible(a),this.getParentSvg().style.display=a?"block":"none",this.toolbox_&&this.toolbox_.setVisible(a),a){a=this.getAllBlocks(!1);for(var b=a.length-1;0<=b;b--)a[b].markDirty();this.render();this.toolbox_&&this.toolbox_.position()}else this.hideChaff(!0)}; module$exports$Blockly$WorkspaceSvg.WorkspaceSvg.prototype.render=function(){for(var a=this.getAllBlocks(!1),b=a.length-1;0<=b;b--)a[b].render(!1);if(this.currentGesture_)for(a=this.currentGesture_.getInsertionMarkers(),b=0;b=Math.abs(c-h.x)&&1>=Math.abs(d-h.y)){f=!0;break}}if(!f){var k=e.getConnections_(!1);a=0;for(b=void 0;b=k[a];a++)if(b.closest($.module$exports$Blockly$internalConstants.SNAP_RADIUS,new module$exports$Blockly$utils$Coordinate.Coordinate(c,d)).connection){f=!0;break}}f&&(c=this.RTL?c-$.module$exports$Blockly$internalConstants.SNAP_RADIUS:c+$.module$exports$Blockly$internalConstants.SNAP_RADIUS,d+=2*$.module$exports$Blockly$internalConstants.SNAP_RADIUS)}while(f); -e.moveTo(new module$exports$Blockly$utils$Coordinate.Coordinate(c,d))}}finally{(0,module$exports$Blockly$Events$utils.enable)()}(0,module$exports$Blockly$Events$utils.isEnabled)()&&!e.isShadow()&&(0,module$exports$Blockly$Events$utils.fire)(new ((0,module$exports$Blockly$Events$utils.get)(module$exports$Blockly$Events$utils.CREATE))(e));e.select()}; +void 0;b=g[a];a++){var h=b.getRelativeToSurfaceXY();if(1>=Math.abs(c-h.x)&&1>=Math.abs(d-h.y)){f=!0;break}}if(!f){var k=e.getConnections_(!1);a=0;for(b=void 0;b=k[a];a++)if(b.closest($.module$exports$Blockly$config.config.snapRadius,new module$exports$Blockly$utils$Coordinate.Coordinate(c,d)).connection){f=!0;break}}f&&(c=this.RTL?c-$.module$exports$Blockly$config.config.snapRadius:c+$.module$exports$Blockly$config.config.snapRadius,d+=2*$.module$exports$Blockly$config.config.snapRadius)}while(f); +e.moveTo(new module$exports$Blockly$utils$Coordinate.Coordinate(c,d))}}finally{(0,module$exports$Blockly$Events$utils.enable)()}(0,module$exports$Blockly$Events$utils.isEnabled)()&&!e.isShadow()&&(0,module$exports$Blockly$Events$utils.fire)(new ((0,module$exports$Blockly$Events$utils.get)(module$exports$Blockly$Events$utils.CREATE))(e));e.select();return e}; module$exports$Blockly$WorkspaceSvg.WorkspaceSvg.prototype.pasteWorkspaceComment_=function(a){(0,module$exports$Blockly$Events$utils.disable)();try{var b=module$exports$Blockly$WorkspaceCommentSvg.fromXml(a,this);var c=parseInt(a.getAttribute("x"),10),d=parseInt(a.getAttribute("y"),10);isNaN(c)||isNaN(d)||(this.RTL&&(c=-c),b.moveBy(c+50,d+50))}finally{(0,module$exports$Blockly$Events$utils.enable)()}(0,module$exports$Blockly$Events$utils.isEnabled)()&&module$exports$Blockly$WorkspaceComment.fireCreateEvent(b); -b.select()};module$exports$Blockly$WorkspaceSvg.WorkspaceSvg.prototype.refreshToolboxSelection=function(){var a=this.isFlyout?this.targetWorkspace:this;a&&!a.currentGesture_&&a.toolbox_&&a.toolbox_.getFlyout()&&a.toolbox_.refreshSelection()};module$exports$Blockly$WorkspaceSvg.WorkspaceSvg.prototype.renameVariableById=function(a,b){module$exports$Blockly$WorkspaceSvg.WorkspaceSvg.superClass_.renameVariableById.call(this,a,b);this.refreshToolboxSelection()}; -module$exports$Blockly$WorkspaceSvg.WorkspaceSvg.prototype.deleteVariableById=function(a){module$exports$Blockly$WorkspaceSvg.WorkspaceSvg.superClass_.deleteVariableById.call(this,a);this.refreshToolboxSelection()};module$exports$Blockly$WorkspaceSvg.WorkspaceSvg.prototype.createVariable=function(a,b,c){a=module$exports$Blockly$WorkspaceSvg.WorkspaceSvg.superClass_.createVariable.call(this,a,b,c);this.refreshToolboxSelection();return a}; +b.select();return b};module$exports$Blockly$WorkspaceSvg.WorkspaceSvg.prototype.refreshToolboxSelection=function(){var a=this.isFlyout?this.targetWorkspace:this;a&&!a.currentGesture_&&a.toolbox_&&a.toolbox_.getFlyout()&&a.toolbox_.refreshSelection()};module$exports$Blockly$WorkspaceSvg.WorkspaceSvg.prototype.renameVariableById=function(a,b){module$exports$Blockly$Workspace.Workspace.prototype.renameVariableById.call(this,a,b);this.refreshToolboxSelection()}; +module$exports$Blockly$WorkspaceSvg.WorkspaceSvg.prototype.deleteVariableById=function(a){module$exports$Blockly$Workspace.Workspace.prototype.deleteVariableById.call(this,a);this.refreshToolboxSelection()};module$exports$Blockly$WorkspaceSvg.WorkspaceSvg.prototype.createVariable=function(a,b,c){a=module$exports$Blockly$Workspace.Workspace.prototype.createVariable.call(this,a,b,c);this.refreshToolboxSelection();return a}; module$exports$Blockly$WorkspaceSvg.WorkspaceSvg.prototype.recordDeleteAreas=function(){module$exports$Blockly$utils.deprecation.warn("WorkspaceSvg.prototype.recordDeleteAreas","June 2021","June 2022","WorkspaceSvg.prototype.recordDragTargets");this.recordDragTargets()}; module$exports$Blockly$WorkspaceSvg.WorkspaceSvg.prototype.recordDragTargets=function(){var a=this.componentManager_.getComponents(module$exports$Blockly$ComponentManager.ComponentManager.Capability.DRAG_TARGET,!0);this.dragTargetAreas_=[];for(var b=0,c;c=a[b];b++){var d=c.getClientRect();d&&this.dragTargetAreas_.push({component:c,clientRect:d})}}; module$exports$Blockly$WorkspaceSvg.WorkspaceSvg.prototype.getDragTarget=function(a){for(var b=0,c;c=this.dragTargetAreas_[b];b++)if(c.clientRect.contains(a.clientX,a.clientY))return c.component;return null};module$exports$Blockly$WorkspaceSvg.WorkspaceSvg.prototype.onMouseDown_=function(a){var b=this.getGesture(a);b&&b.handleWsStart(a,this)}; @@ -1208,34 +1245,34 @@ module$exports$Blockly$WorkspaceSvg.WorkspaceSvg.prototype.centerOnBlock=functio module$exports$Blockly$WorkspaceSvg.WorkspaceSvg.prototype.setScale=function(a){this.options.zoomOptions.maxScale&&a>this.options.zoomOptions.maxScale?a=this.options.zoomOptions.maxScale:this.options.zoomOptions.minScale&&a-b||a<-180+b||a>180-b?!0:!1}; module$exports$Blockly$VerticalFlyout.VerticalFlyout.prototype.getClientRect=function(){if(!this.svgGroup_||this.autoClose||!this.isVisible())return null;var a=this.svgGroup_.getBoundingClientRect(),b=a.left;return this.toolboxPosition_===module$exports$Blockly$utils$toolbox.Position.LEFT?new module$exports$Blockly$utils$Rect.Rect(-1E9,1E9,-1E9,b+a.width):new module$exports$Blockly$utils$Rect.Rect(-1E9,1E9,b,1E9)}; module$exports$Blockly$VerticalFlyout.VerticalFlyout.prototype.reflowInternal_=function(){this.workspace_.scale=this.getFlyoutScale();for(var a=0,b=this.workspace_.getTopBlocks(!1),c=0,d;d=b[c];c++){var e=d.getHeightWidth().width;d.outputConnection&&(e-=this.tabWidth_);a=Math.max(a,e)}for(c=0;d=this.buttons_[c];c++)a=Math.max(a,d.width);a+=1.5*this.MARGIN+this.tabWidth_;a*=this.workspace_.scale;a+=module$exports$Blockly$Scrollbar.Scrollbar.scrollbarThickness;if(this.width_!==a){for(c=0;d=b[c];c++){if(this.RTL){e= -d.getRelativeToSurfaceXY().x;var f=a/this.workspace_.scale-this.MARGIN;d.outputConnection||(f-=this.tabWidth_);d.moveBy(f-e,0)}d.flyoutRect_&&this.moveRectToBlock_(d.flyoutRect_,d)}if(this.RTL)for(b=0;c=this.buttons_[b];b++)d=c.getPosition().y,c.moveTo(a/this.workspace_.scale-c.width-this.MARGIN-this.tabWidth_,d);this.targetWorkspace.toolboxPosition!==this.toolboxPosition_||this.toolboxPosition_!==module$exports$Blockly$utils$toolbox.Position.LEFT||this.targetWorkspace.getToolbox()||this.targetWorkspace.translate(this.targetWorkspace.scrollX+ -a,this.targetWorkspace.scrollY);this.width_=a;this.position();this.targetWorkspace.recordDragTargets()}};(0,module$exports$Blockly$registry.register)(module$exports$Blockly$registry.Type.FLYOUTS_VERTICAL_TOOLBOX,module$exports$Blockly$registry.DEFAULT,module$exports$Blockly$VerticalFlyout.VerticalFlyout);var module$exports$Blockly$IToolboxItem={IToolboxItem:function(){}};var module$exports$Blockly$ISelectableToolboxItem={ISelectableToolboxItem:function(){}};var module$exports$Blockly$ICollapsibleToolboxItem={ICollapsibleToolboxItem:function(){}};var module$exports$Blockly$ToolboxItem={ToolboxItem:function(a,b,c){this.id_=a.toolboxitemid||(0,module$exports$Blockly$utils$idGenerator.getNextUniqueId)();this.level_=(this.parent_=c||null)?this.parent_.getLevel()+1:0;this.toolboxItemDef_=a;this.parentToolbox_=b;this.workspace_=this.parentToolbox_.getWorkspace()}};module$exports$Blockly$ToolboxItem.ToolboxItem.prototype.init=function(){};module$exports$Blockly$ToolboxItem.ToolboxItem.prototype.getDiv=function(){return null}; -module$exports$Blockly$ToolboxItem.ToolboxItem.prototype.getId=function(){return this.id_};module$exports$Blockly$ToolboxItem.ToolboxItem.prototype.getParent=function(){return null};module$exports$Blockly$ToolboxItem.ToolboxItem.prototype.getLevel=function(){return this.level_};module$exports$Blockly$ToolboxItem.ToolboxItem.prototype.isSelectable=function(){return!1};module$exports$Blockly$ToolboxItem.ToolboxItem.prototype.isCollapsible=function(){return!1}; -module$exports$Blockly$ToolboxItem.ToolboxItem.prototype.dispose=function(){};var module$exports$Blockly$ToolboxCategory={ToolboxCategory:function(a,b,c){module$exports$Blockly$ToolboxCategory.ToolboxCategory.superClass_.constructor.call(this,a,b,c);this.name_=(0,module$exports$Blockly$utils$parsing.replaceMessageReferences)(a.name);this.colour_=this.getColour_(a);this.labelDom_=this.iconDom_=this.rowContents_=this.rowDiv_=this.htmlDiv_=null;this.cssConfig_=this.makeDefaultCssConfig_();(0,$.module$exports$Blockly$utils$object.mixin)(this.cssConfig_,a.cssconfig||a.cssConfig); -this.isDisabled_=this.isHidden_=!1;this.flyoutItems_=[];this.parseContents_(a)}};(0,$.module$exports$Blockly$utils$object.inherits)(module$exports$Blockly$ToolboxCategory.ToolboxCategory,module$exports$Blockly$ToolboxItem.ToolboxItem);module$exports$Blockly$ToolboxCategory.ToolboxCategory.registrationName="category";module$exports$Blockly$ToolboxCategory.ToolboxCategory.nestedPadding=19;module$exports$Blockly$ToolboxCategory.ToolboxCategory.borderWidth=8; -module$exports$Blockly$ToolboxCategory.ToolboxCategory.defaultBackgroundColour="#57e";module$exports$Blockly$ToolboxCategory.ToolboxCategory.prototype.makeDefaultCssConfig_=function(){return{container:"blocklyToolboxCategory",row:"blocklyTreeRow",rowcontentcontainer:"blocklyTreeRowContentContainer",icon:"blocklyTreeIcon",label:"blocklyTreeLabel",contents:"blocklyToolboxContents",selected:"blocklyTreeSelected",openicon:"blocklyTreeIconOpen",closedicon:"blocklyTreeIconClosed"}}; -module$exports$Blockly$ToolboxCategory.ToolboxCategory.prototype.parseContents_=function(a){var b=a.contents;if(a.custom)this.flyoutItems_=a.custom;else if(b)for(a=0;a>>/sprites.png);\n height: 16px;\n vertical-align: middle;\n visibility: hidden;\n width: 16px;\n }\n\n .blocklyTreeIconClosed {\n background-position: -32px -1px;\n }\n\n .blocklyToolboxDiv[dir="RTL"] .blocklyTreeIconClosed {\n background-position: 0 -1px;\n }\n\n .blocklyTreeSelected>.blocklyTreeIconClosed {\n background-position: -32px -17px;\n }\n\n .blocklyToolboxDiv[dir="RTL"] .blocklyTreeSelected>.blocklyTreeIconClosed {\n background-position: 0 -17px;\n }\n\n .blocklyTreeIconOpen {\n background-position: -16px -1px;\n }\n\n .blocklyTreeSelected>.blocklyTreeIconOpen {\n background-position: -16px -17px;\n }\n\n .blocklyTreeLabel {\n cursor: default;\n font: 16px sans-serif;\n padding: 0 3px;\n vertical-align: middle;\n }\n\n .blocklyToolboxDelete .blocklyTreeLabel {\n cursor: url("<<>>/handdelete.cur"), auto;\n }\n\n .blocklyTreeSelected .blocklyTreeLabel {\n color: #fff;\n }\n'); -(0,module$exports$Blockly$registry.register)(module$exports$Blockly$registry.Type.TOOLBOX_ITEM,module$exports$Blockly$ToolboxCategory.ToolboxCategory.registrationName,module$exports$Blockly$ToolboxCategory.ToolboxCategory);var module$exports$Blockly$ToolboxSeparator={ToolboxSeparator:function(a,b){module$exports$Blockly$ToolboxSeparator.ToolboxSeparator.superClass_.constructor.call(this,a,b);this.cssConfig_={container:"blocklyTreeSeparator"};(0,$.module$exports$Blockly$utils$object.mixin)(this.cssConfig_,a.cssconfig||a.cssConfig)}};(0,$.module$exports$Blockly$utils$object.inherits)(module$exports$Blockly$ToolboxSeparator.ToolboxSeparator,module$exports$Blockly$ToolboxItem.ToolboxItem); -module$exports$Blockly$ToolboxSeparator.ToolboxSeparator.registrationName="sep";module$exports$Blockly$ToolboxSeparator.ToolboxSeparator.prototype.init=function(){this.createDom_()};module$exports$Blockly$ToolboxSeparator.ToolboxSeparator.prototype.createDom_=function(){var a=document.createElement("div");(0,module$exports$Blockly$utils$dom.addClass)(a,this.cssConfig_.container);return this.htmlDiv_=a};module$exports$Blockly$ToolboxSeparator.ToolboxSeparator.prototype.getDiv=function(){return this.htmlDiv_}; -module$exports$Blockly$ToolboxSeparator.ToolboxSeparator.prototype.dispose=function(){(0,module$exports$Blockly$utils$dom.removeNode)(this.htmlDiv_)};(0,module$exports$Blockly$Css.register)('\n .blocklyTreeSeparator {\n border-bottom: solid #e5e5e5 1px;\n height: 0;\n margin: 5px 0;\n }\n\n .blocklyToolboxDiv[layout="h"] .blocklyTreeSeparator {\n border-right: solid #e5e5e5 1px;\n border-bottom: none;\n height: auto;\n margin: 0 5px 0 5px;\n padding: 5px 0;\n width: 0;\n }\n'); -(0,module$exports$Blockly$registry.register)(module$exports$Blockly$registry.Type.TOOLBOX_ITEM,module$exports$Blockly$ToolboxSeparator.ToolboxSeparator.registrationName,module$exports$Blockly$ToolboxSeparator.ToolboxSeparator);var module$exports$Blockly$CollapsibleToolboxCategory={CollapsibleToolboxCategory:function(a,b,c){this.subcategoriesDiv_=null;this.expanded_=!1;this.toolboxItems_=[];module$exports$Blockly$CollapsibleToolboxCategory.CollapsibleToolboxCategory.superClass_.constructor.call(this,a,b,c)}};(0,$.module$exports$Blockly$utils$object.inherits)(module$exports$Blockly$CollapsibleToolboxCategory.CollapsibleToolboxCategory,module$exports$Blockly$ToolboxCategory.ToolboxCategory); -module$exports$Blockly$CollapsibleToolboxCategory.CollapsibleToolboxCategory.registrationName="collapsibleCategory";module$exports$Blockly$CollapsibleToolboxCategory.CollapsibleToolboxCategory.prototype.makeDefaultCssConfig_=function(){var a=module$exports$Blockly$CollapsibleToolboxCategory.CollapsibleToolboxCategory.superClass_.makeDefaultCssConfig_.call(this);a.contents="blocklyToolboxContents";return a}; +module$exports$Blockly$ToolboxCategory.ToolboxCategory.prototype.updateFlyoutContents=function(a){this.flyoutItems_=[];"string"===typeof a?this.toolboxItemDef_.custom=a:(delete this.toolboxItemDef_.custom,this.toolboxItemDef_.contents=(0,module$exports$Blockly$utils$toolbox.convertFlyoutDefToJsonArray)(a));this.parseContents_(this.toolboxItemDef_)};module$exports$Blockly$ToolboxCategory.ToolboxCategory.prototype.dispose=function(){(0,module$exports$Blockly$utils$dom.removeNode)(this.htmlDiv_)}; +module$exports$Blockly$ToolboxCategory.ToolboxCategory.registrationName="category";module$exports$Blockly$ToolboxCategory.ToolboxCategory.nestedPadding=19;module$exports$Blockly$ToolboxCategory.ToolboxCategory.borderWidth=8;module$exports$Blockly$ToolboxCategory.ToolboxCategory.defaultBackgroundColour="#57e";(0,module$exports$Blockly$Css.register)('\n.blocklyTreeRow:not(.blocklyTreeSelected):hover {\n background-color: rgba(255, 255, 255, .2);\n}\n\n.blocklyToolboxDiv[layout="h"] .blocklyToolboxCategory {\n margin: 1px 5px 1px 0;\n}\n\n.blocklyToolboxDiv[dir="RTL"][layout="h"] .blocklyToolboxCategory {\n margin: 1px 0 1px 5px;\n}\n\n.blocklyTreeRow {\n height: 22px;\n line-height: 22px;\n margin-bottom: 3px;\n padding-right: 8px;\n white-space: nowrap;\n}\n\n.blocklyToolboxDiv[dir="RTL"] .blocklyTreeRow {\n margin-left: 8px;\n padding-right: 0;\n}\n\n.blocklyTreeIcon {\n background-image: url(<<>>/sprites.png);\n height: 16px;\n vertical-align: middle;\n visibility: hidden;\n width: 16px;\n}\n\n.blocklyTreeIconClosed {\n background-position: -32px -1px;\n}\n\n.blocklyToolboxDiv[dir="RTL"] .blocklyTreeIconClosed {\n background-position: 0 -1px;\n}\n\n.blocklyTreeSelected>.blocklyTreeIconClosed {\n background-position: -32px -17px;\n}\n\n.blocklyToolboxDiv[dir="RTL"] .blocklyTreeSelected>.blocklyTreeIconClosed {\n background-position: 0 -17px;\n}\n\n.blocklyTreeIconOpen {\n background-position: -16px -1px;\n}\n\n.blocklyTreeSelected>.blocklyTreeIconOpen {\n background-position: -16px -17px;\n}\n\n.blocklyTreeLabel {\n cursor: default;\n font: 16px sans-serif;\n padding: 0 3px;\n vertical-align: middle;\n}\n\n.blocklyToolboxDelete .blocklyTreeLabel {\n cursor: url("<<>>/handdelete.cur"), auto;\n}\n\n.blocklyTreeSelected .blocklyTreeLabel {\n color: #fff;\n}\n'); +(0,module$exports$Blockly$registry.register)(module$exports$Blockly$registry.Type.TOOLBOX_ITEM,module$exports$Blockly$ToolboxCategory.ToolboxCategory.registrationName,module$exports$Blockly$ToolboxCategory.ToolboxCategory);var module$exports$Blockly$ToolboxSeparator={ToolboxSeparator:function(a,b){module$exports$Blockly$ToolboxItem.ToolboxItem.call(this,a,b);this.cssConfig_={container:"blocklyTreeSeparator"};this.htmlDiv_=null;(0,$.module$exports$Blockly$utils$object.mixin)(this.cssConfig_,a.cssconfig||a.cssConfig)}};$.$jscomp.inherits(module$exports$Blockly$ToolboxSeparator.ToolboxSeparator,module$exports$Blockly$ToolboxItem.ToolboxItem);module$exports$Blockly$ToolboxSeparator.ToolboxSeparator.prototype.init=function(){this.createDom_()}; +module$exports$Blockly$ToolboxSeparator.ToolboxSeparator.prototype.createDom_=function(){var a=document.createElement("div");(0,module$exports$Blockly$utils$dom.addClass)(a,this.cssConfig_.container);return this.htmlDiv_=a};module$exports$Blockly$ToolboxSeparator.ToolboxSeparator.prototype.getDiv=function(){return this.htmlDiv_};module$exports$Blockly$ToolboxSeparator.ToolboxSeparator.prototype.dispose=function(){(0,module$exports$Blockly$utils$dom.removeNode)(this.htmlDiv_)}; +module$exports$Blockly$ToolboxSeparator.ToolboxSeparator.registrationName="sep";(0,module$exports$Blockly$Css.register)('\n.blocklyTreeSeparator {\n border-bottom: solid #e5e5e5 1px;\n height: 0;\n margin: 5px 0;\n}\n\n.blocklyToolboxDiv[layout="h"] .blocklyTreeSeparator {\n border-right: solid #e5e5e5 1px;\n border-bottom: none;\n height: auto;\n margin: 0 5px 0 5px;\n padding: 5px 0;\n width: 0;\n}\n'); +(0,module$exports$Blockly$registry.register)(module$exports$Blockly$registry.Type.TOOLBOX_ITEM,module$exports$Blockly$ToolboxSeparator.ToolboxSeparator.registrationName,module$exports$Blockly$ToolboxSeparator.ToolboxSeparator);var module$exports$Blockly$CollapsibleToolboxCategory={CollapsibleToolboxCategory:function(a,b,c){module$exports$Blockly$ToolboxCategory.ToolboxCategory.call(this,a,b,c);this.subcategoriesDiv_=null;this.expanded_=!1;this.toolboxItems_=[]}};$.$jscomp.inherits(module$exports$Blockly$CollapsibleToolboxCategory.CollapsibleToolboxCategory,module$exports$Blockly$ToolboxCategory.ToolboxCategory); +module$exports$Blockly$CollapsibleToolboxCategory.CollapsibleToolboxCategory.prototype.makeDefaultCssConfig_=function(){var a=module$exports$Blockly$ToolboxCategory.ToolboxCategory.prototype.makeDefaultCssConfig_.call(this);a.contents="blocklyToolboxContents";return a}; module$exports$Blockly$CollapsibleToolboxCategory.CollapsibleToolboxCategory.prototype.parseContents_=function(a){var b=a.contents,c=!0;if(a.custom)this.flyoutItems_=a.custom;else if(b)for(a=0;a>>/handdelete.cur"), auto;\n }\n\n .blocklyToolboxGrab {\n cursor: url("<<>>/handclosed.cur"), auto;\n cursor: grabbing;\n cursor: -webkit-grabbing;\n }\n\n /* Category tree in Toolbox. */\n .blocklyToolboxDiv {\n background-color: #ddd;\n overflow-x: visible;\n overflow-y: auto;\n padding: 4px 0 4px 0;\n position: absolute;\n z-index: 70; /* so blocks go under toolbox when dragging */\n -webkit-tap-highlight-color: transparent; /* issue #1345 */\n }\n\n .blocklyToolboxContents {\n display: flex;\n flex-wrap: wrap;\n flex-direction: column;\n }\n\n .blocklyToolboxContents:focus {\n outline: none;\n }\n'); -(0,module$exports$Blockly$registry.register)(module$exports$Blockly$registry.Type.TOOLBOX,module$exports$Blockly$registry.DEFAULT,module$exports$Blockly$Toolbox.Toolbox);var module$exports$Blockly$HorizontalFlyout={HorizontalFlyout:function(a){module$exports$Blockly$HorizontalFlyout.HorizontalFlyout.superClass_.constructor.call(this,a);this.horizontalLayout=!0}};(0,$.module$exports$Blockly$utils$object.inherits)(module$exports$Blockly$HorizontalFlyout.HorizontalFlyout,module$exports$Blockly$Flyout.Flyout); +module$exports$Blockly$Toolbox.Toolbox.prototype.dispose=function(){this.workspace_.getComponentManager().removeComponent("toolbox");this.flyout_.dispose();for(var a=0;a>>/handdelete.cur"), auto;\n}\n\n.blocklyToolboxGrab {\n cursor: url("<<>>/handclosed.cur"), auto;\n cursor: grabbing;\n cursor: -webkit-grabbing;\n}\n\n/* Category tree in Toolbox. */\n.blocklyToolboxDiv {\n background-color: #ddd;\n overflow-x: visible;\n overflow-y: auto;\n padding: 4px 0 4px 0;\n position: absolute;\n z-index: 70; /* so blocks go under toolbox when dragging */\n -webkit-tap-highlight-color: transparent; /* issue #1345 */\n}\n\n.blocklyToolboxContents {\n display: flex;\n flex-wrap: wrap;\n flex-direction: column;\n}\n\n.blocklyToolboxContents:focus {\n outline: none;\n}\n'); +(0,module$exports$Blockly$registry.register)(module$exports$Blockly$registry.Type.TOOLBOX,module$exports$Blockly$registry.DEFAULT,module$exports$Blockly$Toolbox.Toolbox);var module$exports$Blockly$HorizontalFlyout={HorizontalFlyout:function(a){module$exports$Blockly$Flyout.Flyout.call(this,a);this.horizontalLayout=!0}};$.$jscomp.inherits(module$exports$Blockly$HorizontalFlyout.HorizontalFlyout,module$exports$Blockly$Flyout.Flyout); module$exports$Blockly$HorizontalFlyout.HorizontalFlyout.prototype.setMetrics_=function(a){if(this.isVisible()){var b=this.workspace_.getMetricsManager(),c=b.getScrollMetrics(),d=b.getViewMetrics();b=b.getAbsoluteMetrics();"number"===typeof a.x&&(this.workspace_.scrollX=-(c.left+(c.width-d.width)*a.x));this.workspace_.translate(this.workspace_.scrollX+b.left,this.workspace_.scrollY+b.top)}};module$exports$Blockly$HorizontalFlyout.HorizontalFlyout.prototype.getX=function(){return 0}; module$exports$Blockly$HorizontalFlyout.HorizontalFlyout.prototype.getY=function(){if(!this.isVisible())return 0;var a=this.targetWorkspace.getMetricsManager(),b=a.getAbsoluteMetrics(),c=a.getViewMetrics();a=a.getToolboxMetrics();var d=this.toolboxPosition_===module$exports$Blockly$utils$toolbox.Position.TOP;return this.targetWorkspace.toolboxPosition===this.toolboxPosition_?this.targetWorkspace.getToolbox()?d?a.height:c.height-this.height_:d?0:c.height:d?0:c.height+b.top-this.height_}; module$exports$Blockly$HorizontalFlyout.HorizontalFlyout.prototype.position=function(){if(this.isVisible()&&this.targetWorkspace.isVisible()){var a=this.targetWorkspace.getMetricsManager().getViewMetrics();this.width_=a.width;this.setBackgroundPath_(a.width-2*this.CORNER_RADIUS,this.height_-this.CORNER_RADIUS);a=this.getX();var b=this.getY();this.positionAt_(this.width_,this.height_,a,b)}}; module$exports$Blockly$HorizontalFlyout.HorizontalFlyout.prototype.setBackgroundPath_=function(a,b){var c=this.toolboxPosition_===module$exports$Blockly$utils$toolbox.Position.TOP,d=["M 0,"+(c?0:this.CORNER_RADIUS)];c?(d.push("h",a+2*this.CORNER_RADIUS),d.push("v",b),d.push("a",this.CORNER_RADIUS,this.CORNER_RADIUS,0,0,1,-this.CORNER_RADIUS,this.CORNER_RADIUS),d.push("h",-a),d.push("a",this.CORNER_RADIUS,this.CORNER_RADIUS,0,0,1,-this.CORNER_RADIUS,-this.CORNER_RADIUS)):(d.push("a",this.CORNER_RADIUS, this.CORNER_RADIUS,0,0,1,this.CORNER_RADIUS,-this.CORNER_RADIUS),d.push("h",a),d.push("a",this.CORNER_RADIUS,this.CORNER_RADIUS,0,0,1,this.CORNER_RADIUS,this.CORNER_RADIUS),d.push("v",b),d.push("h",-a-2*this.CORNER_RADIUS));d.push("z");this.svgBackground_.setAttribute("d",d.join(" "))};module$exports$Blockly$HorizontalFlyout.HorizontalFlyout.prototype.scrollToStart=function(){this.workspace_.scrollbar.setX(this.RTL?Infinity:0)}; -module$exports$Blockly$HorizontalFlyout.HorizontalFlyout.prototype.wheel_=function(a){var b=(0,module$exports$Blockly$browserEvents.getScrollDeltaPixels)(a);if(b=b.x||b.y){var c=this.workspace_.getMetricsManager(),d=c.getScrollMetrics();b=c.getViewMetrics().left-d.left+b;this.workspace_.scrollbar.setX(b);(0,module$exports$Blockly$WidgetDiv.hide)();module$exports$Blockly$DropDownDiv.DropDownDiv.hideWithoutAnimation()}a.preventDefault();a.stopPropagation()}; +module$exports$Blockly$HorizontalFlyout.HorizontalFlyout.prototype.wheel_=function(a){var b=(0,module$exports$Blockly$browserEvents.getScrollDeltaPixels)(a);if(b=b.x||b.y){var c=this.workspace_.getMetricsManager(),d=c.getScrollMetrics();b=c.getViewMetrics().left-d.left+b;this.workspace_.scrollbar.setX(b);(0,module$exports$Blockly$WidgetDiv.hide)();(0,module$exports$Blockly$dropDownDiv.hideWithoutAnimation)()}a.preventDefault();a.stopPropagation()}; module$exports$Blockly$HorizontalFlyout.HorizontalFlyout.prototype.layout_=function(a,b){this.workspace_.scale=this.targetWorkspace.scale;var c=this.MARGIN,d=c+this.tabWidth_;this.RTL&&(a=a.reverse());for(var e=0,f;f=a[e];e++)if("block"===f.type){f=f.block;for(var g=f.getDescendants(!1),h=0,k;k=g[h];h++)k.isInFlyout=!0;f.render();g=f.getSvgRoot();h=f.getHeightWidth();k=f.outputConnection?this.tabWidth_:0;k=this.RTL?d+h.width:d-k;f.moveBy(k,c);k=this.createRect_(f,k,c,h,e);d+=h.width+b[e];this.addBlockListeners_(g, f,k)}else"button"===f.type&&(this.initFlyoutButton_(f.button,d,c),d+=f.button.width+b[e])};module$exports$Blockly$HorizontalFlyout.HorizontalFlyout.prototype.isDragTowardWorkspace=function(a){a=Math.atan2(a.y,a.x)/Math.PI*180;var b=this.dragAngleRange_;return a<90+b&&a>90-b||a>-90-b&&a<-90+b?!0:!1}; module$exports$Blockly$HorizontalFlyout.HorizontalFlyout.prototype.getClientRect=function(){if(!this.svgGroup_||this.autoClose||!this.isVisible())return null;var a=this.svgGroup_.getBoundingClientRect(),b=a.top;return this.toolboxPosition_===module$exports$Blockly$utils$toolbox.Position.TOP?new module$exports$Blockly$utils$Rect.Rect(-1E9,b+a.height,-1E9,1E9):new module$exports$Blockly$utils$Rect.Rect(b,1E9,-1E9,1E9)}; -module$exports$Blockly$HorizontalFlyout.HorizontalFlyout.prototype.reflowInternal_=function(){this.workspace_.scale=this.getFlyoutScale();for(var a=0,b=this.workspace_.getTopBlocks(!1),c=0,d;d=b[c];c++)a=Math.max(a,d.getHeightWidth().height);c=this.buttons_;d=0;for(var e;e=c[d];d++)a=Math.max(a,e.height);a+=1.5*this.MARGIN;a*=this.workspace_.scale;a+=module$exports$Blockly$Scrollbar.Scrollbar.scrollbarThickness;if(this.height_!==a){for(c=0;d=b[c];c++)d.flyoutRect_&&this.moveRectToBlock_(d.flyoutRect_, -d);this.targetWorkspace.toolboxPosition!==this.toolboxPosition_||this.toolboxPosition_!==module$exports$Blockly$utils$toolbox.Position.TOP||this.targetWorkspace.getToolbox()||this.targetWorkspace.translate(this.targetWorkspace.scrollX,this.targetWorkspace.scrollY+a);this.height_=a;this.position();this.targetWorkspace.recordDragTargets()}};(0,module$exports$Blockly$registry.register)(module$exports$Blockly$registry.Type.FLYOUTS_HORIZONTAL_TOOLBOX,module$exports$Blockly$registry.DEFAULT,module$exports$Blockly$HorizontalFlyout.HorizontalFlyout);$.module$exports$Blockly$Generator={Generator:function(a){this.name_=a;this.FUNCTION_NAME_PLACEHOLDER_REGEXP_=new RegExp(this.FUNCTION_NAME_PLACEHOLDER_,"g")}};$.module$exports$Blockly$Generator.Generator.prototype.INFINITE_LOOP_TRAP=null;$.module$exports$Blockly$Generator.Generator.prototype.STATEMENT_PREFIX=null;$.module$exports$Blockly$Generator.Generator.prototype.STATEMENT_SUFFIX=null;$.module$exports$Blockly$Generator.Generator.prototype.INDENT=" "; -$.module$exports$Blockly$Generator.Generator.prototype.COMMENT_WRAP=60;$.module$exports$Blockly$Generator.Generator.prototype.ORDER_OVERRIDES=[];$.module$exports$Blockly$Generator.Generator.prototype.isInitialized=null; +module$exports$Blockly$HorizontalFlyout.HorizontalFlyout.prototype.reflowInternal_=function(){this.workspace_.scale=this.getFlyoutScale();for(var a=0,b=this.workspace_.getTopBlocks(!1),c=0,d;d=b[c];c++)a=Math.max(a,d.getHeightWidth().height);c=this.buttons_;d=0;for(var e;e=c[d];d++)a=Math.max(a,e.height);a+=1.5*this.MARGIN;a*=this.workspace_.scale;a+=module$exports$Blockly$Scrollbar.Scrollbar.scrollbarThickness;if(this.height_!==a){for(c=0;d=b[c];c++)this.rectMap_.has(d)&&this.moveRectToBlock_(this.rectMap_.get(d), +d);this.targetWorkspace.toolboxPosition!==this.toolboxPosition_||this.toolboxPosition_!==module$exports$Blockly$utils$toolbox.Position.TOP||this.targetWorkspace.getToolbox()||this.targetWorkspace.translate(this.targetWorkspace.scrollX,this.targetWorkspace.scrollY+a);this.height_=a;this.position();this.targetWorkspace.recordDragTargets()}};(0,module$exports$Blockly$registry.register)(module$exports$Blockly$registry.Type.FLYOUTS_HORIZONTAL_TOOLBOX,module$exports$Blockly$registry.DEFAULT,module$exports$Blockly$HorizontalFlyout.HorizontalFlyout);$.module$exports$Blockly$Generator={Generator:function(a){this.name_=a;this.FUNCTION_NAME_PLACEHOLDER_="{leCUI8hutHZI4480Dc}";this.FUNCTION_NAME_PLACEHOLDER_REGEXP_=new RegExp(this.FUNCTION_NAME_PLACEHOLDER_,"g");this.STATEMENT_SUFFIX=this.STATEMENT_PREFIX=this.INFINITE_LOOP_TRAP=null;this.INDENT=" ";this.COMMENT_WRAP=60;this.ORDER_OVERRIDES=[];this.isInitialized=null;this.RESERVED_WORDS_="";this.nameDB_=this.functionNames_=this.definitions_=void 0}}; $.module$exports$Blockly$Generator.Generator.prototype.workspaceToCode=function(a){a||(console.warn("No workspace specified in workspaceToCode call. Guessing."),a=(0,$.module$exports$Blockly$common.getMainWorkspace)());var b=[];this.init(a);a=a.getTopBlocks(!0);for(var c=0,d;d=a[c];c++){var e=this.blockToCode(d);Array.isArray(e)&&(e=e[0]);e&&(d.outputConnection&&(e=this.scrubNakedValue(e),this.STATEMENT_PREFIX&&!d.suppressPrefixSuffix&&(e=this.injectId(this.STATEMENT_PREFIX,d)+e),this.STATEMENT_SUFFIX&& !d.suppressPrefixSuffix&&(e+=this.injectId(this.STATEMENT_SUFFIX,d))),b.push(e))}b=b.join("\n");b=this.finish(b);b=b.replace(/^\s+\n/,"");b=b.replace(/\n\s+$/,"\n");return b=b.replace(/[ \t]+\n/g,"\n")};$.module$exports$Blockly$Generator.Generator.prototype.prefixLines=function(a,b){return b+a.replace(/(?!\n$)\n/g,"\n"+b)}; $.module$exports$Blockly$Generator.Generator.prototype.allNestedComments=function(a){var b=[];a=a.getDescendants(!0);for(var c=0;ca.length)){b=[];for(c=0;c=a&&this.sourceBlock_.outputConnection&&!b}else this.fullBlockClickTarget_=!1;this.fullBlockClickTarget_?this.clickTarget_=this.sourceBlock_.getSvgRoot():this.createBorderRect_();this.createTextElement_()}; $.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.doClassValidation_=function(a){return null===a||void 0===a?null:String(a)}; $.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.doValueInvalid_=function(a){this.isBeingEdited_&&(this.isTextValid_=!1,a=this.value_,this.value_=this.htmlInput_.untypedDefaultValue_,this.sourceBlock_&&(0,module$exports$Blockly$Events$utils.isEnabled)()&&(0,module$exports$Blockly$Events$utils.fire)(new ((0,module$exports$Blockly$Events$utils.get)(module$exports$Blockly$Events$utils.CHANGE))(this.sourceBlock_,"field",this.name||null,a,this.value_)))}; $.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.doValueUpdate_=function(a){this.isTextValid_=!0;this.value_=a;this.isBeingEdited_||(this.isDirty_=!0)};$.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.applyColour=function(){this.sourceBlock_&&this.getConstants().FULL_BLOCK_FIELDS&&(this.borderRect_?this.borderRect_.setAttribute("stroke",this.sourceBlock_.style.colourTertiary):this.sourceBlock_.pathObject.svgPath.setAttribute("fill",this.getConstants().FIELD_BORDER_RECT_COLOUR))}; -$.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.render_=function(){$.module$exports$Blockly$FieldTextInput.FieldTextInput.superClass_.render_.call(this);if(this.isBeingEdited_){this.resizeEditor_();var a=this.htmlInput_;this.isTextValid_?((0,module$exports$Blockly$utils$dom.removeClass)(a,"blocklyInvalidInput"),(0,module$exports$Blockly$utils$aria.setState)(a,module$exports$Blockly$utils$aria.State.INVALID,!1)):((0,module$exports$Blockly$utils$dom.addClass)(a,"blocklyInvalidInput"), -(0,module$exports$Blockly$utils$aria.setState)(a,module$exports$Blockly$utils$aria.State.INVALID,!0))}};$.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.setSpellcheck=function(a){a!==this.spellcheck_&&(this.spellcheck_=a,this.htmlInput_&&this.htmlInput_.setAttribute("spellcheck",this.spellcheck_))}; +$.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.render_=function(){module$exports$Blockly$Field.Field.prototype.render_.call(this);if(this.isBeingEdited_){this.resizeEditor_();var a=this.htmlInput_;this.isTextValid_?((0,module$exports$Blockly$utils$dom.removeClass)(a,"blocklyInvalidInput"),(0,module$exports$Blockly$utils$aria.setState)(a,module$exports$Blockly$utils$aria.State.INVALID,!1)):((0,module$exports$Blockly$utils$dom.addClass)(a,"blocklyInvalidInput"),(0,module$exports$Blockly$utils$aria.setState)(a, +module$exports$Blockly$utils$aria.State.INVALID,!0))}};$.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.setSpellcheck=function(a){a!==this.spellcheck_&&(this.spellcheck_=a,this.htmlInput_&&this.htmlInput_.setAttribute("spellcheck",this.spellcheck_))}; $.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.showEditor_=function(a,b){this.workspace_=this.sourceBlock_.workspace;a=b||!1;!a&&(module$exports$Blockly$utils$userAgent.MOBILE||module$exports$Blockly$utils$userAgent.ANDROID||module$exports$Blockly$utils$userAgent.IPAD)?this.showPromptEditor_():this.showInlineEditor_(a)}; $.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.showPromptEditor_=function(){(0,module$exports$Blockly$dialog.prompt)($.module$exports$Blockly$Msg.Msg.CHANGE_VALUE_TITLE,this.getText(),function(a){null!==a&&this.setValue(this.getValueFromEditorText_(a))}.bind(this))}; $.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.showInlineEditor_=function(a){(0,module$exports$Blockly$WidgetDiv.show)(this,this.sourceBlock_.RTL,this.widgetDispose_.bind(this));this.htmlInput_=this.widgetCreate_();this.isBeingEdited_=!0;a||(this.htmlInput_.focus({preventScroll:!0}),this.htmlInput_.select())}; $.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.widgetCreate_=function(){(0,module$exports$Blockly$Events$utils.setGroup)(!0);var a=(0,module$exports$Blockly$WidgetDiv.getDiv)();(0,module$exports$Blockly$utils$dom.addClass)(this.getClickTarget_(),"editing");var b=document.createElement("input");b.className="blocklyHtmlInput";b.setAttribute("spellcheck",this.spellcheck_);var c=this.workspace_.getScale(),d=this.getConstants().FIELD_TEXT_FONTSIZE*c+"pt";a.style.fontSize=d;b.style.fontSize= d;d=$.module$exports$Blockly$FieldTextInput.FieldTextInput.BORDERRADIUS*c+"px";if(this.fullBlockClickTarget_){d=this.getScaledBBox();d=(d.bottom-d.top)/2+"px";var e=this.sourceBlock_.getParent()?this.sourceBlock_.getParent().style.colourTertiary:this.sourceBlock_.style.colourTertiary;b.style.border=1*c+"px solid "+e;a.style.borderRadius=d;a.style.transition="box-shadow 0.25s ease 0s";this.getConstants().FIELD_TEXTINPUT_BOX_SHADOW&&(a.style.boxShadow="rgba(255, 255, 255, 0.3) 0 0 0 "+4*c+"px")}b.style.borderRadius= d;a.appendChild(b);b.value=b.defaultValue=this.getEditorText_(this.value_);b.untypedDefaultValue_=this.value_;b.oldValue_=null;this.resizeEditor_();this.bindInputEvents_(b);return b}; -$.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.widgetDispose_=function(){this.isBeingEdited_=!1;this.isTextValid_=!0;this.forceRerender();if(this.onFinishEditing_)this.onFinishEditing_(this.value_);(0,module$exports$Blockly$Events$utils.setGroup)(!1);this.unbindInputEvents_();var a=(0,module$exports$Blockly$WidgetDiv.getDiv)().style;a.width="auto";a.height="auto";a.fontSize="";a.transition="";a.boxShadow="";this.htmlInput_=null;(0,module$exports$Blockly$utils$dom.removeClass)(this.getClickTarget_(), -"editing")};$.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.bindInputEvents_=function(a){this.onKeyDownWrapper_=(0,module$exports$Blockly$browserEvents.conditionalBind)(a,"keydown",this,this.onHtmlInputKeyDown_);this.onKeyInputWrapper_=(0,module$exports$Blockly$browserEvents.conditionalBind)(a,"input",this,this.onHtmlInputChange_)}; +$.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.widgetDispose_=function(){this.isBeingEdited_=!1;this.isTextValid_=!0;this.forceRerender();this.onFinishEditing_(this.value_);(0,module$exports$Blockly$Events$utils.setGroup)(!1);this.unbindInputEvents_();var a=(0,module$exports$Blockly$WidgetDiv.getDiv)().style;a.width="auto";a.height="auto";a.fontSize="";a.transition="";a.boxShadow="";this.htmlInput_=null;(0,module$exports$Blockly$utils$dom.removeClass)(this.getClickTarget_(),"editing")}; +$.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.onFinishEditing_=function(a){};$.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.bindInputEvents_=function(a){this.onKeyDownWrapper_=(0,module$exports$Blockly$browserEvents.conditionalBind)(a,"keydown",this,this.onHtmlInputKeyDown_);this.onKeyInputWrapper_=(0,module$exports$Blockly$browserEvents.conditionalBind)(a,"input",this,this.onHtmlInputChange_)}; $.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.unbindInputEvents_=function(){this.onKeyDownWrapper_&&((0,module$exports$Blockly$browserEvents.unbind)(this.onKeyDownWrapper_),this.onKeyDownWrapper_=null);this.onKeyInputWrapper_&&((0,module$exports$Blockly$browserEvents.unbind)(this.onKeyInputWrapper_),this.onKeyInputWrapper_=null)}; -$.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.onHtmlInputKeyDown_=function(a){a.keyCode===module$exports$Blockly$utils$KeyCodes.KeyCodes.ENTER?((0,module$exports$Blockly$WidgetDiv.hide)(),module$exports$Blockly$DropDownDiv.DropDownDiv.hideWithoutAnimation()):a.keyCode===module$exports$Blockly$utils$KeyCodes.KeyCodes.ESC?(this.setValue(this.htmlInput_.untypedDefaultValue_),(0,module$exports$Blockly$WidgetDiv.hide)(),module$exports$Blockly$DropDownDiv.DropDownDiv.hideWithoutAnimation()): -a.keyCode===module$exports$Blockly$utils$KeyCodes.KeyCodes.TAB&&((0,module$exports$Blockly$WidgetDiv.hide)(),module$exports$Blockly$DropDownDiv.DropDownDiv.hideWithoutAnimation(),this.sourceBlock_.tab(this,!a.shiftKey),a.preventDefault())};$.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.onHtmlInputChange_=function(a){a=this.htmlInput_.value;a!==this.htmlInput_.oldValue_&&(this.htmlInput_.oldValue_=a,a=this.getValueFromEditorText_(a),this.setValue(a),this.forceRerender(),this.resizeEditor_())}; +$.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.onHtmlInputKeyDown_=function(a){a.keyCode===module$exports$Blockly$utils$KeyCodes.KeyCodes.ENTER?((0,module$exports$Blockly$WidgetDiv.hide)(),(0,module$exports$Blockly$dropDownDiv.hideWithoutAnimation)()):a.keyCode===module$exports$Blockly$utils$KeyCodes.KeyCodes.ESC?(this.setValue(this.htmlInput_.untypedDefaultValue_),(0,module$exports$Blockly$WidgetDiv.hide)(),(0,module$exports$Blockly$dropDownDiv.hideWithoutAnimation)()):a.keyCode=== +module$exports$Blockly$utils$KeyCodes.KeyCodes.TAB&&((0,module$exports$Blockly$WidgetDiv.hide)(),(0,module$exports$Blockly$dropDownDiv.hideWithoutAnimation)(),this.sourceBlock_.tab(this,!a.shiftKey),a.preventDefault())};$.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.onHtmlInputChange_=function(a){a=this.htmlInput_.value;a!==this.htmlInput_.oldValue_&&(this.htmlInput_.oldValue_=a,a=this.getValueFromEditorText_(a),this.setValue(a),this.forceRerender(),this.resizeEditor_())}; $.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.setEditorValue_=function(a){this.isDirty_=!0;this.isBeingEdited_&&(this.htmlInput_.value=this.getEditorText_(a));this.setValue(a)}; $.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.resizeEditor_=function(){var a=(0,module$exports$Blockly$WidgetDiv.getDiv)(),b=this.getScaledBBox();a.style.width=b.right-b.left+"px";a.style.height=b.bottom-b.top+"px";b=new module$exports$Blockly$utils$Coordinate.Coordinate(this.sourceBlock_.RTL?b.right-a.offsetWidth:b.left,b.top);a.style.left=b.x+"px";a.style.top=b.y+"px"};$.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.isTabNavigable=function(){return!0}; -$.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.getText_=function(){return this.isBeingEdited_&&this.htmlInput_?this.htmlInput_.value:null};$.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.getEditorText_=function(a){return String(a)};$.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.getValueFromEditorText_=function(a){return a};(0,module$exports$Blockly$fieldRegistry.register)("field_input",$.module$exports$Blockly$FieldTextInput.FieldTextInput);var module$exports$Blockly$FieldNumber={FieldNumber:function(a,b,c,d,e,f){this.min_=-Infinity;this.max_=Infinity;this.precision_=0;this.decimalPlaces_=null;module$exports$Blockly$FieldNumber.FieldNumber.superClass_.constructor.call(this,a,e,f);f||this.setConstraints(b,c,d)}};(0,$.module$exports$Blockly$utils$object.inherits)(module$exports$Blockly$FieldNumber.FieldNumber,$.module$exports$Blockly$FieldTextInput.FieldTextInput); -module$exports$Blockly$FieldNumber.FieldNumber.prototype.DEFAULT_VALUE=0;module$exports$Blockly$FieldNumber.FieldNumber.fromJson=function(a){return new this(a.value,void 0,void 0,void 0,void 0,a)};module$exports$Blockly$FieldNumber.FieldNumber.prototype.SERIALIZABLE=!0;module$exports$Blockly$FieldNumber.FieldNumber.prototype.configure_=function(a){module$exports$Blockly$FieldNumber.FieldNumber.superClass_.configure_.call(this,a);this.setMinInternal_(a.min);this.setMaxInternal_(a.max);this.setPrecisionInternal_(a.precision)}; -module$exports$Blockly$FieldNumber.FieldNumber.prototype.setConstraints=function(a,b,c){this.setMinInternal_(a);this.setMaxInternal_(b);this.setPrecisionInternal_(c);this.setValue(this.getValue())};module$exports$Blockly$FieldNumber.FieldNumber.prototype.setMin=function(a){this.setMinInternal_(a);this.setValue(this.getValue())};module$exports$Blockly$FieldNumber.FieldNumber.prototype.setMinInternal_=function(a){null==a?this.min_=-Infinity:(a=Number(a),isNaN(a)||(this.min_=a))}; -module$exports$Blockly$FieldNumber.FieldNumber.prototype.getMin=function(){return this.min_};module$exports$Blockly$FieldNumber.FieldNumber.prototype.setMax=function(a){this.setMaxInternal_(a);this.setValue(this.getValue())};module$exports$Blockly$FieldNumber.FieldNumber.prototype.setMaxInternal_=function(a){null==a?this.max_=Infinity:(a=Number(a),isNaN(a)||(this.max_=a))};module$exports$Blockly$FieldNumber.FieldNumber.prototype.getMax=function(){return this.max_}; -module$exports$Blockly$FieldNumber.FieldNumber.prototype.setPrecision=function(a){this.setPrecisionInternal_(a);this.setValue(this.getValue())};module$exports$Blockly$FieldNumber.FieldNumber.prototype.setPrecisionInternal_=function(a){this.precision_=Number(a)||0;var b=String(this.precision_);-1!==b.indexOf("e")&&(b=this.precision_.toLocaleString("en-US",{maximumFractionDigits:20}));var c=b.indexOf(".");this.decimalPlaces_=-1===c?a?0:null:b.length-c-1}; -module$exports$Blockly$FieldNumber.FieldNumber.prototype.getPrecision=function(){return this.precision_}; +$.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.getText_=function(){return this.isBeingEdited_&&this.htmlInput_?this.htmlInput_.value:null};$.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.getEditorText_=function(a){return String(a)};$.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.getValueFromEditorText_=function(a){return a}; +$.module$exports$Blockly$FieldTextInput.FieldTextInput.fromJson=function(a){return new this((0,module$exports$Blockly$utils$parsing.replaceMessageReferences)(a.text),void 0,a)};$.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.DEFAULT_VALUE="";$.module$exports$Blockly$FieldTextInput.FieldTextInput.BORDERRADIUS=4;(0,module$exports$Blockly$fieldRegistry.register)("field_input",$.module$exports$Blockly$FieldTextInput.FieldTextInput);var module$exports$Blockly$FieldNumber={FieldNumber:function(a,b,c,d,e,f){$.module$exports$Blockly$FieldTextInput.FieldTextInput.call(this,module$exports$Blockly$Field.Field.SKIP_SETUP);this.min_=-Infinity;this.max_=Infinity;this.precision_=0;this.decimalPlaces_=null;this.SERIALIZABLE=!0;a!==module$exports$Blockly$Field.Field.SKIP_SETUP&&(f?this.configure_(f):this.setConstraints(b,c,d),this.setValue(a),e&&this.setValidator(e))}};$.$jscomp.inherits(module$exports$Blockly$FieldNumber.FieldNumber,$.module$exports$Blockly$FieldTextInput.FieldTextInput); +module$exports$Blockly$FieldNumber.FieldNumber.prototype.configure_=function(a){$.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.configure_.call(this,a);this.setMinInternal_(a.min);this.setMaxInternal_(a.max);this.setPrecisionInternal_(a.precision)};module$exports$Blockly$FieldNumber.FieldNumber.prototype.setConstraints=function(a,b,c){this.setMinInternal_(a);this.setMaxInternal_(b);this.setPrecisionInternal_(c);this.setValue(this.getValue())}; +module$exports$Blockly$FieldNumber.FieldNumber.prototype.setMin=function(a){this.setMinInternal_(a);this.setValue(this.getValue())};module$exports$Blockly$FieldNumber.FieldNumber.prototype.setMinInternal_=function(a){null==a?this.min_=-Infinity:(a=Number(a),isNaN(a)||(this.min_=a))};module$exports$Blockly$FieldNumber.FieldNumber.prototype.getMin=function(){return this.min_};module$exports$Blockly$FieldNumber.FieldNumber.prototype.setMax=function(a){this.setMaxInternal_(a);this.setValue(this.getValue())}; +module$exports$Blockly$FieldNumber.FieldNumber.prototype.setMaxInternal_=function(a){null==a?this.max_=Infinity:(a=Number(a),isNaN(a)||(this.max_=a))};module$exports$Blockly$FieldNumber.FieldNumber.prototype.getMax=function(){return this.max_};module$exports$Blockly$FieldNumber.FieldNumber.prototype.setPrecision=function(a){this.setPrecisionInternal_(a);this.setValue(this.getValue())}; +module$exports$Blockly$FieldNumber.FieldNumber.prototype.setPrecisionInternal_=function(a){this.precision_=Number(a)||0;var b=String(this.precision_);-1!==b.indexOf("e")&&(b=this.precision_.toLocaleString("en-US",{maximumFractionDigits:20}));var c=b.indexOf(".");this.decimalPlaces_=-1===c?a?0:null:b.length-c-1};module$exports$Blockly$FieldNumber.FieldNumber.prototype.getPrecision=function(){return this.precision_}; module$exports$Blockly$FieldNumber.FieldNumber.prototype.doClassValidation_=function(a){if(null===a)return null;a=String(a);a=a.replace(/O/ig,"0");a=a.replace(/,/g,"");a=a.replace(/infinity/i,"Infinity");a=Number(a||0);if(isNaN(a))return null;a=Math.min(Math.max(a,this.min_),this.max_);this.precision_&&isFinite(a)&&(a=Math.round(a/this.precision_)*this.precision_);null!==this.decimalPlaces_&&(a=Number(a.toFixed(this.decimalPlaces_)));return a}; -module$exports$Blockly$FieldNumber.FieldNumber.prototype.widgetCreate_=function(){var a=module$exports$Blockly$FieldNumber.FieldNumber.superClass_.widgetCreate_.call(this);-Infinitythis.max_&&(0,module$exports$Blockly$utils$aria.setState)(a,module$exports$Blockly$utils$aria.State.VALUEMAX,this.max_);return a}; -(0,module$exports$Blockly$fieldRegistry.register)("field_number",module$exports$Blockly$FieldNumber.FieldNumber);var module$exports$Blockly$FieldMultilineInput={FieldMultilineInput:function(a,b,c){module$exports$Blockly$FieldMultilineInput.FieldMultilineInput.superClass_.constructor.call(this,a,b,c);this.textGroup_=null;this.maxLines_=Infinity;this.isOverflowedY_=!1}};(0,$.module$exports$Blockly$utils$object.inherits)(module$exports$Blockly$FieldMultilineInput.FieldMultilineInput,$.module$exports$Blockly$FieldTextInput.FieldTextInput); -module$exports$Blockly$FieldMultilineInput.FieldMultilineInput.prototype.configure_=function(a){module$exports$Blockly$FieldMultilineInput.FieldMultilineInput.superClass_.configure_.call(this,a);a.maxLines&&this.setMaxLines(a.maxLines)};module$exports$Blockly$FieldMultilineInput.FieldMultilineInput.fromJson=function(a){return new this((0,module$exports$Blockly$utils$parsing.replaceMessageReferences)(a.text),void 0,a)}; -module$exports$Blockly$FieldMultilineInput.FieldMultilineInput.prototype.toXml=function(a){a.textContent=this.getValue().replace(/\n/g," ");return a};module$exports$Blockly$FieldMultilineInput.FieldMultilineInput.prototype.fromXml=function(a){this.setValue(a.textContent.replace(/ /g,"\n"))}; -module$exports$Blockly$FieldMultilineInput.FieldMultilineInput.prototype.saveState=function(){var a=this.saveLegacyState(module$exports$Blockly$FieldMultilineInput.FieldMultilineInput);return null!==a?a:this.getValue()};module$exports$Blockly$FieldMultilineInput.FieldMultilineInput.prototype.loadState=function(a){this.loadLegacyState(module$exports$Blockly$Field.Field,a)||this.setValue(a)}; -module$exports$Blockly$FieldMultilineInput.FieldMultilineInput.prototype.initView=function(){this.createBorderRect_();this.textGroup_=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.G,{"class":"blocklyEditableText"},this.fieldGroup_)}; +module$exports$Blockly$FieldNumber.FieldNumber.prototype.widgetCreate_=function(){var a=$.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.widgetCreate_.call(this);-Infinitythis.max_&&(0,module$exports$Blockly$utils$aria.setState)(a,module$exports$Blockly$utils$aria.State.VALUEMAX,this.max_);return a}; +module$exports$Blockly$FieldNumber.FieldNumber.fromJson=function(a){return new this(a.value,void 0,void 0,void 0,void 0,a)};module$exports$Blockly$FieldNumber.FieldNumber.prototype.DEFAULT_VALUE=0;(0,module$exports$Blockly$fieldRegistry.register)("field_number",module$exports$Blockly$FieldNumber.FieldNumber);var module$exports$Blockly$FieldMultilineInput={FieldMultilineInput:function(a,b,c){$.module$exports$Blockly$FieldTextInput.FieldTextInput.call(this,module$exports$Blockly$Field.Field.SKIP_SETUP);this.textGroup_=null;this.maxLines_=Infinity;this.isOverflowedY_=!1;a!==module$exports$Blockly$Field.Field.SKIP_SETUP&&(c&&this.configure_(c),this.setValue(a),b&&this.setValidator(b))}};$.$jscomp.inherits(module$exports$Blockly$FieldMultilineInput.FieldMultilineInput,$.module$exports$Blockly$FieldTextInput.FieldTextInput); +module$exports$Blockly$FieldMultilineInput.FieldMultilineInput.prototype.configure_=function(a){$.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.configure_.call(this,a);a.maxLines&&this.setMaxLines(a.maxLines)};module$exports$Blockly$FieldMultilineInput.FieldMultilineInput.prototype.toXml=function(a){a.textContent=this.getValue().replace(/\n/g," ");return a}; +module$exports$Blockly$FieldMultilineInput.FieldMultilineInput.prototype.fromXml=function(a){this.setValue(a.textContent.replace(/ /g,"\n"))};module$exports$Blockly$FieldMultilineInput.FieldMultilineInput.prototype.saveState=function(){var a=this.saveLegacyState(module$exports$Blockly$FieldMultilineInput.FieldMultilineInput);return null!==a?a:this.getValue()}; +module$exports$Blockly$FieldMultilineInput.FieldMultilineInput.prototype.loadState=function(a){this.loadLegacyState(module$exports$Blockly$Field.Field,a)||this.setValue(a)};module$exports$Blockly$FieldMultilineInput.FieldMultilineInput.prototype.initView=function(){this.createBorderRect_();this.textGroup_=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.G,{"class":"blocklyEditableText"},this.fieldGroup_)}; module$exports$Blockly$FieldMultilineInput.FieldMultilineInput.prototype.getDisplayText_=function(){var a=this.getText();if(!a)return module$exports$Blockly$Field.Field.NBSP;var b=a.split("\n");a="";for(var c=this.isOverflowedY_?this.maxLines_:b.length,d=0;dthis.maxDisplayLength?e=e.substring(0,this.maxDisplayLength-4)+"...":this.isOverflowedY_&&d===c-1&&(e=e.substring(0,e.length-3)+"...");e=e.replace(/\s/g,module$exports$Blockly$Field.Field.NBSP);a+=e;d!==c-1&&(a+="\n")}this.sourceBlock_.RTL&& -(a+="\u200f");return a};module$exports$Blockly$FieldMultilineInput.FieldMultilineInput.prototype.doValueUpdate_=function(a){module$exports$Blockly$FieldMultilineInput.FieldMultilineInput.superClass_.doValueUpdate_.call(this,a);this.isOverflowedY_=this.value_.split("\n").length>this.maxLines_}; +(a+="\u200f");return a};module$exports$Blockly$FieldMultilineInput.FieldMultilineInput.prototype.doValueUpdate_=function(a){$.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.doValueUpdate_.call(this,a);this.isOverflowedY_=this.value_.split("\n").length>this.maxLines_}; module$exports$Blockly$FieldMultilineInput.FieldMultilineInput.prototype.render_=function(){for(var a;a=this.textGroup_.firstChild;)this.textGroup_.removeChild(a);a=this.getDisplayText_().split("\n");for(var b=0,c=0;cb&&(b=e);c+=this.getConstants().FIELD_TEXT_HEIGHT+(0this.maxDisplayLength&&(a[h]=a[h].substring(0,this.maxDisplayLength));d.textContent=a[h];var k=(0,module$exports$Blockly$utils$dom.getFastTextWidth)(d,e,f,g);k>b&&(b=k)}b+=this.htmlInput_.offsetWidth-this.htmlInput_.clientWidth}this.borderRect_&&(c+=2*this.getConstants().FIELD_BORDER_RECT_Y_PADDING,b+=2*this.getConstants().FIELD_BORDER_RECT_X_PADDING, -this.borderRect_.setAttribute("width",b),this.borderRect_.setAttribute("height",c));this.size_.width=b;this.size_.height=c;this.positionBorderRect_()};module$exports$Blockly$FieldMultilineInput.FieldMultilineInput.prototype.showEditor_=function(a,b){module$exports$Blockly$FieldMultilineInput.FieldMultilineInput.superClass_.showEditor_.call(this,a,b);this.forceRerender()}; +this.borderRect_.setAttribute("width",b),this.borderRect_.setAttribute("height",c));this.size_.width=b;this.size_.height=c;this.positionBorderRect_()};module$exports$Blockly$FieldMultilineInput.FieldMultilineInput.prototype.showEditor_=function(a,b){$.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.showEditor_.call(this,a,b);this.forceRerender()}; module$exports$Blockly$FieldMultilineInput.FieldMultilineInput.prototype.widgetCreate_=function(){var a=(0,module$exports$Blockly$WidgetDiv.getDiv)(),b=this.workspace_.getScale(),c=document.createElement("textarea");c.className="blocklyHtmlInput blocklyHtmlTextAreaInput";c.setAttribute("spellcheck",this.spellcheck_);var d=this.getConstants().FIELD_TEXT_FONTSIZE*b+"pt";a.style.fontSize=d;c.style.fontSize=d;c.style.borderRadius=$.module$exports$Blockly$FieldTextInput.FieldTextInput.BORDERRADIUS*b+"px"; d=this.getConstants().FIELD_BORDER_RECT_X_PADDING*b;var e=this.getConstants().FIELD_BORDER_RECT_Y_PADDING*b/2;c.style.padding=e+"px "+d+"px "+e+"px "+d+"px";d=this.getConstants().FIELD_TEXT_HEIGHT+this.getConstants().FIELD_BORDER_RECT_Y_PADDING;c.style.lineHeight=d*b+"px";a.appendChild(c);c.value=c.defaultValue=this.getEditorText_(this.value_);c.untypedDefaultValue_=this.value_;c.oldValue_=null;module$exports$Blockly$utils$userAgent.GECKO?setTimeout(this.resizeEditor_.bind(this),0):this.resizeEditor_(); this.bindInputEvents_(c);return c};module$exports$Blockly$FieldMultilineInput.FieldMultilineInput.prototype.setMaxLines=function(a){"number"===typeof a&&0a?0>e&&0e&&(e=0):0d-1&&fd-1&&e--:0>b?0>f&&(f=0):0Math.floor(c.length/d)-1&&(f=Math.floor(c.length/d)-1);this.setHighlightedCell_(this.picker_.childNodes[f].childNodes[e], f*d+e)};module$exports$Blockly$FieldColour.FieldColour.prototype.onMouseMove_=function(a){var b=(a=a.target)&&Number(a.getAttribute("data-index"));null!==b&&b!==this.highlightedIndex_&&this.setHighlightedCell_(a,b)};module$exports$Blockly$FieldColour.FieldColour.prototype.onMouseEnter_=function(){this.picker_.focus({preventScroll:!0})}; module$exports$Blockly$FieldColour.FieldColour.prototype.onMouseLeave_=function(){this.picker_.blur();var a=this.getHighlighted_();a&&(0,module$exports$Blockly$utils$dom.removeClass)(a,"blocklyColourHighlighted")};module$exports$Blockly$FieldColour.FieldColour.prototype.getHighlighted_=function(){var a=this.columns_||module$exports$Blockly$FieldColour.FieldColour.COLUMNS,b=this.picker_.childNodes[Math.floor(this.highlightedIndex_/a)];return b?b.childNodes[this.highlightedIndex_%a]:null}; @@ -1496,51 +1500,48 @@ var h=document.createElement("td");f.appendChild(h);h.label=b[g];h.title=c[g]||b b[g];b[g]===d&&(h.className="blocklyColourSelected",this.highlightedIndex_=g)}this.onClickWrapper_=(0,module$exports$Blockly$browserEvents.conditionalBind)(e,"click",this,this.onClick_,!0);this.onMouseMoveWrapper_=(0,module$exports$Blockly$browserEvents.conditionalBind)(e,"mousemove",this,this.onMouseMove_,!0);this.onMouseEnterWrapper_=(0,module$exports$Blockly$browserEvents.conditionalBind)(e,"mouseenter",this,this.onMouseEnter_,!0);this.onMouseLeaveWrapper_=(0,module$exports$Blockly$browserEvents.conditionalBind)(e, "mouseleave",this,this.onMouseLeave_,!0);this.onKeyDownWrapper_=(0,module$exports$Blockly$browserEvents.conditionalBind)(e,"keydown",this,this.onKeyDown_);this.picker_=e}; module$exports$Blockly$FieldColour.FieldColour.prototype.dropdownDispose_=function(){this.onClickWrapper_&&((0,module$exports$Blockly$browserEvents.unbind)(this.onClickWrapper_),this.onClickWrapper_=null);this.onMouseMoveWrapper_&&((0,module$exports$Blockly$browserEvents.unbind)(this.onMouseMoveWrapper_),this.onMouseMoveWrapper_=null);this.onMouseEnterWrapper_&&((0,module$exports$Blockly$browserEvents.unbind)(this.onMouseEnterWrapper_),this.onMouseEnterWrapper_=null);this.onMouseLeaveWrapper_&&((0,module$exports$Blockly$browserEvents.unbind)(this.onMouseLeaveWrapper_), -this.onMouseLeaveWrapper_=null);this.onKeyDownWrapper_&&((0,module$exports$Blockly$browserEvents.unbind)(this.onKeyDownWrapper_),this.onKeyDownWrapper_=null);this.highlightedIndex_=this.picker_=null};(0,module$exports$Blockly$Css.register)("\n .blocklyColourTable {\n border-collapse: collapse;\n display: block;\n outline: none;\n padding: 1px;\n }\n\n .blocklyColourTable>tr>td {\n border: .5px solid #888;\n box-sizing: border-box;\n cursor: pointer;\n display: inline-block;\n height: 20px;\n padding: 0;\n width: 20px;\n }\n\n .blocklyColourTable>tr>td.blocklyColourHighlighted {\n border-color: #eee;\n box-shadow: 2px 2px 7px 2px rgba(0,0,0,.3);\n position: relative;\n }\n\n .blocklyColourSelected, .blocklyColourSelected:hover {\n border-color: #eee !important;\n outline: 1px solid #333;\n position: relative;\n }\n"); -(0,module$exports$Blockly$fieldRegistry.register)("field_colour",module$exports$Blockly$FieldColour.FieldColour);$.module$exports$Blockly$FieldCheckbox={FieldCheckbox:function(a,b,c){this.checkChar_=null;$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.superClass_.constructor.call(this,a,b,c)}};(0,$.module$exports$Blockly$utils$object.inherits)($.module$exports$Blockly$FieldCheckbox.FieldCheckbox,module$exports$Blockly$Field.Field);$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.prototype.DEFAULT_VALUE=!1; -$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.fromJson=function(a){return new this(a.checked,void 0,a)};$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.CHECK_CHAR="\u2713";$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.prototype.SERIALIZABLE=!0;$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.prototype.CURSOR="default"; -$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.prototype.configure_=function(a){$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.superClass_.configure_.call(this,a);a.checkCharacter&&(this.checkChar_=a.checkCharacter)};$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.prototype.saveState=function(){var a=this.saveLegacyState($.module$exports$Blockly$FieldCheckbox.FieldCheckbox);return null!==a?a:this.getValueBoolean()}; -$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.prototype.initView=function(){$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.superClass_.initView.call(this);(0,module$exports$Blockly$utils$dom.addClass)(this.textElement_,"blocklyCheckbox");this.textElement_.style.display=this.value_?"block":"none"};$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.prototype.render_=function(){this.textContent_&&(this.textContent_.nodeValue=this.getDisplayText_());this.updateSize_(this.getConstants().FIELD_CHECKBOX_X_OFFSET)}; -$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.prototype.getDisplayText_=function(){return this.checkChar_||$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.CHECK_CHAR};$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.prototype.setCheckCharacter=function(a){this.checkChar_=a;this.forceRerender()};$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.prototype.showEditor_=function(){this.setValue(!this.value_)}; +this.onMouseLeaveWrapper_=null);this.onKeyDownWrapper_&&((0,module$exports$Blockly$browserEvents.unbind)(this.onKeyDownWrapper_),this.onKeyDownWrapper_=null);this.highlightedIndex_=this.picker_=null};module$exports$Blockly$FieldColour.FieldColour.fromJson=function(a){return new this(a.colour,void 0,a)};module$exports$Blockly$FieldColour.FieldColour.COLOURS="#ffffff #cccccc #c0c0c0 #999999 #666666 #333333 #000000 #ffcccc #ff6666 #ff0000 #cc0000 #990000 #660000 #330000 #ffcc99 #ff9966 #ff9900 #ff6600 #cc6600 #993300 #663300 #ffff99 #ffff66 #ffcc66 #ffcc33 #cc9933 #996633 #663333 #ffffcc #ffff33 #ffff00 #ffcc00 #999900 #666600 #333300 #99ff99 #66ff99 #33ff33 #33cc00 #009900 #006600 #003300 #99ffff #33ffff #66cccc #00cccc #339999 #336666 #003333 #ccffff #66ffff #33ccff #3366ff #3333ff #000099 #000066 #ccccff #9999ff #6666cc #6633ff #6600cc #333399 #330099 #ffccff #ff99ff #cc66cc #cc33cc #993399 #663366 #330033".split(" "); +module$exports$Blockly$FieldColour.FieldColour.prototype.DEFAULT_VALUE=module$exports$Blockly$FieldColour.FieldColour.COLOURS[0];module$exports$Blockly$FieldColour.FieldColour.TITLES=[];module$exports$Blockly$FieldColour.FieldColour.COLUMNS=7;(0,module$exports$Blockly$Css.register)("\n.blocklyColourTable {\n border-collapse: collapse;\n display: block;\n outline: none;\n padding: 1px;\n}\n\n.blocklyColourTable>tr>td {\n border: .5px solid #888;\n box-sizing: border-box;\n cursor: pointer;\n display: inline-block;\n height: 20px;\n padding: 0;\n width: 20px;\n}\n\n.blocklyColourTable>tr>td.blocklyColourHighlighted {\n border-color: #eee;\n box-shadow: 2px 2px 7px 2px rgba(0,0,0,.3);\n position: relative;\n}\n\n.blocklyColourSelected, .blocklyColourSelected:hover {\n border-color: #eee !important;\n outline: 1px solid #333;\n position: relative;\n}\n"); +(0,module$exports$Blockly$fieldRegistry.register)("field_colour",module$exports$Blockly$FieldColour.FieldColour);$.module$exports$Blockly$FieldCheckbox={FieldCheckbox:function(a,b,c){module$exports$Blockly$Field.Field.call(this,module$exports$Blockly$Field.Field.SKIP_SETUP);this.checkChar_=$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.CHECK_CHAR;this.SERIALIZABLE=!0;this.CURSOR="default";a!==module$exports$Blockly$Field.Field.SKIP_SETUP&&(c&&this.configure_(c),this.setValue(a),b&&this.setValidator(b))}};$.$jscomp.inherits($.module$exports$Blockly$FieldCheckbox.FieldCheckbox,module$exports$Blockly$Field.Field); +$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.prototype.configure_=function(a){module$exports$Blockly$Field.Field.prototype.configure_.call(this,a);a.checkCharacter&&(this.checkChar_=a.checkCharacter)};$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.prototype.saveState=function(){var a=this.saveLegacyState($.module$exports$Blockly$FieldCheckbox.FieldCheckbox);return null!==a?a:this.getValueBoolean()}; +$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.prototype.initView=function(){module$exports$Blockly$Field.Field.prototype.initView.call(this);(0,module$exports$Blockly$utils$dom.addClass)(this.textElement_,"blocklyCheckbox");this.textElement_.style.display=this.value_?"block":"none"};$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.prototype.render_=function(){this.textContent_&&(this.textContent_.nodeValue=this.getDisplayText_());this.updateSize_(this.getConstants().FIELD_CHECKBOX_X_OFFSET)}; +$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.prototype.getDisplayText_=function(){return this.checkChar_};$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.prototype.setCheckCharacter=function(a){this.checkChar_=a||$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.CHECK_CHAR;this.forceRerender()};$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.prototype.showEditor_=function(){this.setValue(!this.value_)}; $.module$exports$Blockly$FieldCheckbox.FieldCheckbox.prototype.doClassValidation_=function(a){return!0===a||"TRUE"===a?"TRUE":!1===a||"FALSE"===a?"FALSE":null};$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.prototype.doValueUpdate_=function(a){this.value_=this.convertValueToBool_(a);this.textElement_&&(this.textElement_.style.display=this.value_?"block":"none")};$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.prototype.getValue=function(){return this.value_?"TRUE":"FALSE"}; -$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.prototype.getValueBoolean=function(){return this.value_};$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.prototype.getText=function(){return String(this.convertValueToBool_(this.value_))};$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.prototype.convertValueToBool_=function(a){return"string"===typeof a?"TRUE"===a:!!a};(0,module$exports$Blockly$fieldRegistry.register)("field_checkbox",$.module$exports$Blockly$FieldCheckbox.FieldCheckbox);var module$exports$Blockly$FieldAngle={FieldAngle:function(a,b,c){this.clockwise_=module$exports$Blockly$FieldAngle.FieldAngle.CLOCKWISE;this.offset_=module$exports$Blockly$FieldAngle.FieldAngle.OFFSET;this.wrap_=module$exports$Blockly$FieldAngle.FieldAngle.WRAP;this.round_=module$exports$Blockly$FieldAngle.FieldAngle.ROUND;module$exports$Blockly$FieldAngle.FieldAngle.superClass_.constructor.call(this,a,b,c);this.moveSurfaceWrapper_=this.clickSurfaceWrapper_=this.clickWrapper_=this.line_=this.gauge_= -this.editor_=null}};(0,$.module$exports$Blockly$utils$object.inherits)(module$exports$Blockly$FieldAngle.FieldAngle,$.module$exports$Blockly$FieldTextInput.FieldTextInput);module$exports$Blockly$FieldAngle.FieldAngle.prototype.DEFAULT_VALUE=0;module$exports$Blockly$FieldAngle.FieldAngle.fromJson=function(a){return new this(a.angle,void 0,a)};module$exports$Blockly$FieldAngle.FieldAngle.prototype.SERIALIZABLE=!0;module$exports$Blockly$FieldAngle.FieldAngle.ROUND=15; -module$exports$Blockly$FieldAngle.FieldAngle.HALF=50;module$exports$Blockly$FieldAngle.FieldAngle.CLOCKWISE=!1;module$exports$Blockly$FieldAngle.FieldAngle.OFFSET=0;module$exports$Blockly$FieldAngle.FieldAngle.WRAP=360;module$exports$Blockly$FieldAngle.FieldAngle.RADIUS=module$exports$Blockly$FieldAngle.FieldAngle.HALF-1; -module$exports$Blockly$FieldAngle.FieldAngle.prototype.configure_=function(a){module$exports$Blockly$FieldAngle.FieldAngle.superClass_.configure_.call(this,a);switch(a.mode){case "compass":this.clockwise_=!0;this.offset_=90;break;case "protractor":this.clockwise_=!1,this.offset_=0}var b=a.clockwise;"boolean"===typeof b&&(this.clockwise_=b);b=a.offset;null!==b&&(b=Number(b),isNaN(b)||(this.offset_=b));b=a.wrap;null!==b&&(b=Number(b),isNaN(b)||(this.wrap_=b));a=a.round;null!==a&&(a=Number(a),isNaN(a)|| -(this.round_=a))};module$exports$Blockly$FieldAngle.FieldAngle.prototype.initView=function(){module$exports$Blockly$FieldAngle.FieldAngle.superClass_.initView.call(this);this.symbol_=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.TSPAN,{},null);this.symbol_.appendChild(document.createTextNode("\u00b0"));this.textElement_.appendChild(this.symbol_)}; -module$exports$Blockly$FieldAngle.FieldAngle.prototype.render_=function(){module$exports$Blockly$FieldAngle.FieldAngle.superClass_.render_.call(this);this.updateGraph_()}; -module$exports$Blockly$FieldAngle.FieldAngle.prototype.showEditor_=function(a){module$exports$Blockly$FieldAngle.FieldAngle.superClass_.showEditor_.call(this,a,module$exports$Blockly$utils$userAgent.MOBILE||module$exports$Blockly$utils$userAgent.ANDROID||module$exports$Blockly$utils$userAgent.IPAD);this.dropdownCreate_();module$exports$Blockly$DropDownDiv.DropDownDiv.getContentDiv().appendChild(this.editor_);module$exports$Blockly$DropDownDiv.DropDownDiv.setColour(this.sourceBlock_.style.colourPrimary, -this.sourceBlock_.style.colourTertiary);module$exports$Blockly$DropDownDiv.DropDownDiv.showPositionedByField(this,this.dropdownDispose_.bind(this));this.updateGraph_()}; +$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.prototype.getValueBoolean=function(){return this.value_};$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.prototype.getText=function(){return String(this.convertValueToBool_(this.value_))};$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.prototype.convertValueToBool_=function(a){return"string"===typeof a?"TRUE"===a:!!a};$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.fromJson=function(a){return new this(a.checked,void 0,a)}; +$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.prototype.DEFAULT_VALUE=!1;$.module$exports$Blockly$FieldCheckbox.FieldCheckbox.CHECK_CHAR="\u2713";(0,module$exports$Blockly$fieldRegistry.register)("field_checkbox",$.module$exports$Blockly$FieldCheckbox.FieldCheckbox);var module$exports$Blockly$FieldAngle={FieldAngle:function(a,b,c){$.module$exports$Blockly$FieldTextInput.FieldTextInput.call(this,module$exports$Blockly$Field.Field.SKIP_SETUP);this.clockwise_=module$exports$Blockly$FieldAngle.FieldAngle.CLOCKWISE;this.offset_=module$exports$Blockly$FieldAngle.FieldAngle.OFFSET;this.wrap_=module$exports$Blockly$FieldAngle.FieldAngle.WRAP;this.round_=module$exports$Blockly$FieldAngle.FieldAngle.ROUND;this.moveSurfaceWrapper_=this.clickSurfaceWrapper_=this.clickWrapper_= +this.symbol_=this.line_=this.gauge_=this.editor_=null;this.SERIALIZABLE=!0;a!==module$exports$Blockly$Field.Field.SKIP_SETUP&&(c&&this.configure_(c),this.setValue(a),b&&this.setValidator(b))}};$.$jscomp.inherits(module$exports$Blockly$FieldAngle.FieldAngle,$.module$exports$Blockly$FieldTextInput.FieldTextInput); +module$exports$Blockly$FieldAngle.FieldAngle.prototype.configure_=function(a){$.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.configure_.call(this,a);switch(a.mode){case "compass":this.clockwise_=!0;this.offset_=90;break;case "protractor":this.clockwise_=!1,this.offset_=0}var b=a.clockwise;"boolean"===typeof b&&(this.clockwise_=b);b=a.offset;null!==b&&(b=Number(b),isNaN(b)||(this.offset_=b));b=a.wrap;null!==b&&(b=Number(b),isNaN(b)||(this.wrap_=b));a=a.round;null!==a&&(a=Number(a), +isNaN(a)||(this.round_=a))};module$exports$Blockly$FieldAngle.FieldAngle.prototype.initView=function(){$.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.initView.call(this);this.symbol_=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.TSPAN,{},null);this.symbol_.appendChild(document.createTextNode("\u00b0"));this.textElement_.appendChild(this.symbol_)}; +module$exports$Blockly$FieldAngle.FieldAngle.prototype.render_=function(){$.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.render_.call(this);this.updateGraph_()}; +module$exports$Blockly$FieldAngle.FieldAngle.prototype.showEditor_=function(a){$.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.showEditor_.call(this,a,module$exports$Blockly$utils$userAgent.MOBILE||module$exports$Blockly$utils$userAgent.ANDROID||module$exports$Blockly$utils$userAgent.IPAD);this.dropdownCreate_();(0,module$exports$Blockly$dropDownDiv.getContentDiv)().appendChild(this.editor_);(0,module$exports$Blockly$dropDownDiv.setColour)(this.sourceBlock_.style.colourPrimary,this.sourceBlock_.style.colourTertiary); +(0,module$exports$Blockly$dropDownDiv.showPositionedByField)(this,this.dropdownDispose_.bind(this));this.updateGraph_()}; module$exports$Blockly$FieldAngle.FieldAngle.prototype.dropdownCreate_=function(){var a=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.SVG,{xmlns:module$exports$Blockly$utils$dom.SVG_NS,"xmlns:html":module$exports$Blockly$utils$dom.HTML_NS,"xmlns:xlink":module$exports$Blockly$utils$dom.XLINK_NS,version:"1.1",height:2*module$exports$Blockly$FieldAngle.FieldAngle.HALF+"px",width:2*module$exports$Blockly$FieldAngle.FieldAngle.HALF+"px",style:"touch-action: none"}, null),b=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.CIRCLE,{cx:module$exports$Blockly$FieldAngle.FieldAngle.HALF,cy:module$exports$Blockly$FieldAngle.FieldAngle.HALF,r:module$exports$Blockly$FieldAngle.FieldAngle.RADIUS,"class":"blocklyAngleCircle"},a);this.gauge_=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.PATH,{"class":"blocklyAngleGauge"},a);this.line_=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.LINE, {x1:module$exports$Blockly$FieldAngle.FieldAngle.HALF,y1:module$exports$Blockly$FieldAngle.FieldAngle.HALF,"class":"blocklyAngleLine"},a);for(var c=0;360>c;c+=15)(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.LINE,{x1:module$exports$Blockly$FieldAngle.FieldAngle.HALF+module$exports$Blockly$FieldAngle.FieldAngle.RADIUS,y1:module$exports$Blockly$FieldAngle.FieldAngle.HALF,x2:module$exports$Blockly$FieldAngle.FieldAngle.HALF+module$exports$Blockly$FieldAngle.FieldAngle.RADIUS- (0===c%45?10:5),y2:module$exports$Blockly$FieldAngle.FieldAngle.HALF,"class":"blocklyAngleMarks",transform:"rotate("+c+","+module$exports$Blockly$FieldAngle.FieldAngle.HALF+","+module$exports$Blockly$FieldAngle.FieldAngle.HALF+")"},a);this.clickWrapper_=(0,module$exports$Blockly$browserEvents.conditionalBind)(a,"click",this,this.hide_);this.clickSurfaceWrapper_=(0,module$exports$Blockly$browserEvents.conditionalBind)(b,"click",this,this.onMouseMove_,!0,!0);this.moveSurfaceWrapper_=(0,module$exports$Blockly$browserEvents.conditionalBind)(b, "mousemove",this,this.onMouseMove_,!0,!0);this.editor_=a}; module$exports$Blockly$FieldAngle.FieldAngle.prototype.dropdownDispose_=function(){this.clickWrapper_&&((0,module$exports$Blockly$browserEvents.unbind)(this.clickWrapper_),this.clickWrapper_=null);this.clickSurfaceWrapper_&&((0,module$exports$Blockly$browserEvents.unbind)(this.clickSurfaceWrapper_),this.clickSurfaceWrapper_=null);this.moveSurfaceWrapper_&&((0,module$exports$Blockly$browserEvents.unbind)(this.moveSurfaceWrapper_),this.moveSurfaceWrapper_=null);this.line_=this.gauge_=null}; -module$exports$Blockly$FieldAngle.FieldAngle.prototype.hide_=function(){module$exports$Blockly$DropDownDiv.DropDownDiv.hideIfOwner(this);(0,module$exports$Blockly$WidgetDiv.hide)()}; +module$exports$Blockly$FieldAngle.FieldAngle.prototype.hide_=function(){(0,module$exports$Blockly$dropDownDiv.hideIfOwner)(this);(0,module$exports$Blockly$WidgetDiv.hide)()}; module$exports$Blockly$FieldAngle.FieldAngle.prototype.onMouseMove_=function(a){var b=this.gauge_.ownerSVGElement.getBoundingClientRect(),c=a.clientX-b.left-module$exports$Blockly$FieldAngle.FieldAngle.HALF;a=a.clientY-b.top-module$exports$Blockly$FieldAngle.FieldAngle.HALF;b=Math.atan(-a/c);isNaN(b)||(b=(0,module$exports$Blockly$utils$math.toDegrees)(b),0>c?b+=180:0a&&(a+=360);a>this.wrap_&&(a-=360);return a};(0,module$exports$Blockly$Css.register)("\n .blocklyAngleCircle {\n stroke: #444;\n stroke-width: 1;\n fill: #ddd;\n fill-opacity: .8;\n }\n\n .blocklyAngleMarks {\n stroke: #444;\n stroke-width: 1;\n }\n\n .blocklyAngleGauge {\n fill: #f88;\n fill-opacity: .8;\n pointer-events: none;\n }\n\n .blocklyAngleLine {\n stroke: #f00;\n stroke-width: 2;\n stroke-linecap: round;\n pointer-events: none;\n }\n"); -(0,module$exports$Blockly$fieldRegistry.register)("field_angle",module$exports$Blockly$FieldAngle.FieldAngle);var module$exports$Blockly$zelos$TopRow={TopRow:function(a){module$exports$Blockly$zelos$TopRow.TopRow.superClass_.constructor.call(this,a)}};(0,$.module$exports$Blockly$utils$object.inherits)(module$exports$Blockly$zelos$TopRow.TopRow,module$exports$Blockly$blockRendering$TopRow.TopRow);module$exports$Blockly$zelos$TopRow.TopRow.prototype.endsWithElemSpacer=function(){return!1}; -module$exports$Blockly$zelos$TopRow.TopRow.prototype.hasLeftSquareCorner=function(a){var b=(a.hat?"cap"===a.hat:this.constants_.ADD_START_HATS)&&!a.outputConnection&&!a.previousConnection;return!!a.outputConnection||b};module$exports$Blockly$zelos$TopRow.TopRow.prototype.hasRightSquareCorner=function(a){return!!a.outputConnection&&!a.statementInputCount&&!a.nextConnection};var module$exports$Blockly$zelos$StatementInput={StatementInput:function(a,b){module$exports$Blockly$zelos$StatementInput.StatementInput.superClass_.constructor.call(this,a,b);if(this.connectedBlock){for(a=this.connectedBlock;b=a.getNextBlock();)a=b;a.nextConnection||(this.height=this.connectedBlockHeight,this.connectedBottomNextConnection=!0)}}};(0,$.module$exports$Blockly$utils$object.inherits)(module$exports$Blockly$zelos$StatementInput.StatementInput,module$exports$Blockly$blockRendering$StatementInput.StatementInput);var module$exports$Blockly$zelos$RightConnectionShape={RightConnectionShape:function(a){module$exports$Blockly$zelos$RightConnectionShape.RightConnectionShape.superClass_.constructor.call(this,a);this.type|=module$exports$Blockly$blockRendering$Types.Types.getType("RIGHT_CONNECTION");this.width=this.height=0}};(0,$.module$exports$Blockly$utils$object.inherits)(module$exports$Blockly$zelos$RightConnectionShape.RightConnectionShape,module$exports$Blockly$blockRendering$Measurable.Measurable);var module$exports$Blockly$zelos$MarkerSvg={MarkerSvg:function(a,b,c){module$exports$Blockly$zelos$MarkerSvg.MarkerSvg.superClass_.constructor.call(this,a,b,c)}};(0,$.module$exports$Blockly$utils$object.inherits)(module$exports$Blockly$zelos$MarkerSvg.MarkerSvg,module$exports$Blockly$blockRendering$MarkerSvg.MarkerSvg); -module$exports$Blockly$zelos$MarkerSvg.MarkerSvg.prototype.showWithInputOutput_=function(a){var b=a.getSourceBlock();a=a.getLocation().getOffsetInBlock();this.positionCircle_(a.x,a.y);this.setParent_(b);this.showCurrent_()};module$exports$Blockly$zelos$MarkerSvg.MarkerSvg.prototype.showWithOutput_=function(a){this.showWithInputOutput_(a)};module$exports$Blockly$zelos$MarkerSvg.MarkerSvg.prototype.showWithInput_=function(a){this.showWithInputOutput_(a)}; -module$exports$Blockly$zelos$MarkerSvg.MarkerSvg.prototype.showWithBlock_=function(a){a=a.getLocation();var b=a.getHeightWidth();this.positionRect_(0,0,b.width,b.height);this.setParent_(a);this.showCurrent_()};module$exports$Blockly$zelos$MarkerSvg.MarkerSvg.prototype.positionCircle_=function(a,b){this.markerCircle_.setAttribute("cx",a);this.markerCircle_.setAttribute("cy",b);this.currentMarkerSvg=this.markerCircle_}; -module$exports$Blockly$zelos$MarkerSvg.MarkerSvg.prototype.hide=function(){module$exports$Blockly$zelos$MarkerSvg.MarkerSvg.superClass_.hide.call(this);this.markerCircle_.style.display="none"}; -module$exports$Blockly$zelos$MarkerSvg.MarkerSvg.prototype.createDomInternal_=function(){module$exports$Blockly$zelos$MarkerSvg.MarkerSvg.superClass_.createDomInternal_.call(this);this.markerCircle_=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.CIRCLE,{r:this.constants_.CURSOR_RADIUS,style:"display: none","stroke-width":this.constants_.CURSOR_STROKE_WIDTH},this.markerSvg_);if(this.isCursor()){var a=this.getBlinkProperties_();(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.ANIMATE, -a,this.markerCircle_)}return this.markerSvg_};module$exports$Blockly$zelos$MarkerSvg.MarkerSvg.prototype.applyColour_=function(a){module$exports$Blockly$zelos$MarkerSvg.MarkerSvg.superClass_.applyColour_.call(this,a);this.markerCircle_.setAttribute("fill",this.colour_);this.markerCircle_.setAttribute("stroke",this.colour_);this.isCursor()&&this.markerCircle_.firstChild.setAttribute("values",this.colour_+";transparent;transparent;")};var module$exports$Blockly$zelos$ConstantProvider={ConstantProvider:function(){module$exports$Blockly$zelos$ConstantProvider.ConstantProvider.superClass_.constructor.call(this);this.SMALL_PADDING=this.GRID_UNIT=4;this.MEDIUM_PADDING=2*this.GRID_UNIT;this.MEDIUM_LARGE_PADDING=3*this.GRID_UNIT;this.LARGE_PADDING=4*this.GRID_UNIT;this.CORNER_RADIUS=1*this.GRID_UNIT;this.NOTCH_WIDTH=9*this.GRID_UNIT;this.NOTCH_HEIGHT=2*this.GRID_UNIT;this.STATEMENT_INPUT_NOTCH_OFFSET=this.NOTCH_OFFSET_LEFT=3*this.GRID_UNIT; -this.MIN_BLOCK_WIDTH=2*this.GRID_UNIT;this.MIN_BLOCK_HEIGHT=12*this.GRID_UNIT;this.EMPTY_STATEMENT_INPUT_HEIGHT=6*this.GRID_UNIT;this.TAB_OFFSET_FROM_TOP=0;this.TOP_ROW_MIN_HEIGHT=this.CORNER_RADIUS;this.TOP_ROW_PRECEDES_STATEMENT_MIN_HEIGHT=this.LARGE_PADDING;this.BOTTOM_ROW_MIN_HEIGHT=this.CORNER_RADIUS;this.BOTTOM_ROW_AFTER_STATEMENT_MIN_HEIGHT=6*this.GRID_UNIT;this.STATEMENT_BOTTOM_SPACER=-this.NOTCH_HEIGHT;this.STATEMENT_INPUT_SPACER_MIN_WIDTH=40*this.GRID_UNIT;this.STATEMENT_INPUT_PADDING_LEFT= -4*this.GRID_UNIT;this.EMPTY_INLINE_INPUT_PADDING=4*this.GRID_UNIT;this.EMPTY_INLINE_INPUT_HEIGHT=8*this.GRID_UNIT;this.DUMMY_INPUT_MIN_HEIGHT=8*this.GRID_UNIT;this.DUMMY_INPUT_SHADOW_MIN_HEIGHT=6*this.GRID_UNIT;this.CURSOR_WS_WIDTH=20*this.GRID_UNIT;this.CURSOR_COLOUR="#ffa200";this.CURSOR_RADIUS=5;this.JAGGED_TEETH_WIDTH=this.JAGGED_TEETH_HEIGHT=0;this.START_HAT_HEIGHT=22;this.START_HAT_WIDTH=96;this.SHAPES={HEXAGONAL:1,ROUND:2,SQUARE:3,PUZZLE:4,NOTCH:5};this.SHAPE_IN_SHAPE_PADDING={1:{0:5*this.GRID_UNIT, -1:2*this.GRID_UNIT,2:5*this.GRID_UNIT,3:5*this.GRID_UNIT},2:{0:3*this.GRID_UNIT,1:3*this.GRID_UNIT,2:1*this.GRID_UNIT,3:2*this.GRID_UNIT},3:{0:2*this.GRID_UNIT,1:2*this.GRID_UNIT,2:2*this.GRID_UNIT,3:2*this.GRID_UNIT}};this.FULL_BLOCK_FIELDS=!0;this.FIELD_TEXT_FONTSIZE=3*this.GRID_UNIT;this.FIELD_TEXT_FONTWEIGHT="bold";this.FIELD_TEXT_FONTFAMILY='"Helvetica Neue", "Segoe UI", Helvetica, sans-serif';this.FIELD_BORDER_RECT_RADIUS=this.CORNER_RADIUS;this.FIELD_BORDER_RECT_X_PADDING=2*this.GRID_UNIT; -this.FIELD_BORDER_RECT_Y_PADDING=1.625*this.GRID_UNIT;this.FIELD_BORDER_RECT_HEIGHT=8*this.GRID_UNIT;this.FIELD_DROPDOWN_BORDER_RECT_HEIGHT=8*this.GRID_UNIT;this.FIELD_DROPDOWN_SVG_ARROW=this.FIELD_DROPDOWN_COLOURED_DIV=this.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW=!0;this.FIELD_DROPDOWN_SVG_ARROW_PADDING=this.FIELD_BORDER_RECT_X_PADDING;this.FIELD_COLOUR_FULL_BLOCK=this.FIELD_TEXTINPUT_BOX_SHADOW=!0;this.FIELD_COLOUR_DEFAULT_WIDTH=2*this.GRID_UNIT;this.FIELD_COLOUR_DEFAULT_HEIGHT=4*this.GRID_UNIT;this.FIELD_CHECKBOX_X_OFFSET= -1*this.GRID_UNIT;this.MAX_DYNAMIC_CONNECTION_SHAPE_WIDTH=12*this.GRID_UNIT;this.SELECTED_GLOW_COLOUR="#fff200";this.SELECTED_GLOW_SIZE=.5;this.REPLACEMENT_GLOW_COLOUR="#fff200";this.REPLACEMENT_GLOW_SIZE=2;this.selectedGlowFilterId="";this.selectedGlowFilter_=null;this.replacementGlowFilterId="";this.replacementGlowFilter_=null}};(0,$.module$exports$Blockly$utils$object.inherits)(module$exports$Blockly$zelos$ConstantProvider.ConstantProvider,module$exports$Blockly$blockRendering$ConstantProvider.ConstantProvider); -module$exports$Blockly$zelos$ConstantProvider.ConstantProvider.prototype.setFontConstants_=function(a){module$exports$Blockly$zelos$ConstantProvider.ConstantProvider.superClass_.setFontConstants_.call(this,a);this.FIELD_DROPDOWN_BORDER_RECT_HEIGHT=this.FIELD_BORDER_RECT_HEIGHT=this.FIELD_TEXT_HEIGHT+2*this.FIELD_BORDER_RECT_Y_PADDING}; -module$exports$Blockly$zelos$ConstantProvider.ConstantProvider.prototype.init=function(){module$exports$Blockly$zelos$ConstantProvider.ConstantProvider.superClass_.init.call(this);this.HEXAGONAL=this.makeHexagonal();this.ROUNDED=this.makeRounded();this.SQUARED=this.makeSquared();this.STATEMENT_INPUT_NOTCH_OFFSET=this.NOTCH_OFFSET_LEFT+this.INSIDE_CORNERS.rightWidth}; -module$exports$Blockly$zelos$ConstantProvider.ConstantProvider.prototype.setDynamicProperties_=function(a){module$exports$Blockly$zelos$ConstantProvider.ConstantProvider.superClass_.setDynamicProperties_.call(this,a);this.SELECTED_GLOW_COLOUR=a.getComponentStyle("selectedGlowColour")||this.SELECTED_GLOW_COLOUR;var b=Number(a.getComponentStyle("selectedGlowSize"));this.SELECTED_GLOW_SIZE=b&&!isNaN(b)?b:this.SELECTED_GLOW_SIZE;this.REPLACEMENT_GLOW_COLOUR=a.getComponentStyle("replacementGlowColour")|| -this.REPLACEMENT_GLOW_COLOUR;this.REPLACEMENT_GLOW_SIZE=(a=Number(a.getComponentStyle("replacementGlowSize")))&&!isNaN(a)?a:this.REPLACEMENT_GLOW_SIZE};module$exports$Blockly$zelos$ConstantProvider.ConstantProvider.prototype.dispose=function(){module$exports$Blockly$zelos$ConstantProvider.ConstantProvider.superClass_.dispose.call(this);this.selectedGlowFilter_&&(0,module$exports$Blockly$utils$dom.removeNode)(this.selectedGlowFilter_);this.replacementGlowFilter_&&(0,module$exports$Blockly$utils$dom.removeNode)(this.replacementGlowFilter_)}; +module$exports$Blockly$FieldAngle.FieldAngle.prototype.onHtmlInputKeyDown_=function(a){$.module$exports$Blockly$FieldTextInput.FieldTextInput.prototype.onHtmlInputKeyDown_.call(this,a);var b;a.keyCode===module$exports$Blockly$utils$KeyCodes.KeyCodes.LEFT?b=this.sourceBlock_.RTL?1:-1:a.keyCode===module$exports$Blockly$utils$KeyCodes.KeyCodes.RIGHT?b=this.sourceBlock_.RTL?-1:1:a.keyCode===module$exports$Blockly$utils$KeyCodes.KeyCodes.DOWN?b=-1:a.keyCode===module$exports$Blockly$utils$KeyCodes.KeyCodes.UP&& +(b=1);if(b){var c=this.getValue();this.displayMouseOrKeyboardValue_(c+b*this.round_);a.preventDefault();a.stopPropagation()}};module$exports$Blockly$FieldAngle.FieldAngle.prototype.doClassValidation_=function(a){a=Number(a);return isNaN(a)||!isFinite(a)?null:this.wrapValue_(a)};module$exports$Blockly$FieldAngle.FieldAngle.prototype.wrapValue_=function(a){a%=360;0>a&&(a+=360);a>this.wrap_&&(a-=360);return a}; +module$exports$Blockly$FieldAngle.FieldAngle.fromJson=function(a){return new this(a.angle,void 0,a)};module$exports$Blockly$FieldAngle.FieldAngle.prototype.DEFAULT_VALUE=0;module$exports$Blockly$FieldAngle.FieldAngle.ROUND=15;module$exports$Blockly$FieldAngle.FieldAngle.HALF=50;module$exports$Blockly$FieldAngle.FieldAngle.CLOCKWISE=!1;module$exports$Blockly$FieldAngle.FieldAngle.OFFSET=0;module$exports$Blockly$FieldAngle.FieldAngle.WRAP=360; +module$exports$Blockly$FieldAngle.FieldAngle.RADIUS=module$exports$Blockly$FieldAngle.FieldAngle.HALF-1;(0,module$exports$Blockly$Css.register)("\n.blocklyAngleCircle {\n stroke: #444;\n stroke-width: 1;\n fill: #ddd;\n fill-opacity: .8;\n}\n\n.blocklyAngleMarks {\n stroke: #444;\n stroke-width: 1;\n}\n\n.blocklyAngleGauge {\n fill: #f88;\n fill-opacity: .8;\n pointer-events: none;\n}\n\n.blocklyAngleLine {\n stroke: #f00;\n stroke-width: 2;\n stroke-linecap: round;\n pointer-events: none;\n}\n"); +(0,module$exports$Blockly$fieldRegistry.register)("field_angle",module$exports$Blockly$FieldAngle.FieldAngle);var module$exports$Blockly$zelos$BottomRow={BottomRow:function(a){module$exports$Blockly$blockRendering$BottomRow.BottomRow.call(this,a)}};$.$jscomp.inherits(module$exports$Blockly$zelos$BottomRow.BottomRow,module$exports$Blockly$blockRendering$BottomRow.BottomRow);module$exports$Blockly$zelos$BottomRow.BottomRow.prototype.endsWithElemSpacer=function(){return!1};module$exports$Blockly$zelos$BottomRow.BottomRow.prototype.hasLeftSquareCorner=function(a){return!!a.outputConnection}; +module$exports$Blockly$zelos$BottomRow.BottomRow.prototype.hasRightSquareCorner=function(a){return!!a.outputConnection&&!a.statementInputCount&&!a.nextConnection};var module$exports$Blockly$zelos$ConstantProvider={ConstantProvider:function(){module$exports$Blockly$blockRendering$ConstantProvider.ConstantProvider.call(this);this.SMALL_PADDING=this.GRID_UNIT=4;this.MEDIUM_PADDING=2*this.GRID_UNIT;this.MEDIUM_LARGE_PADDING=3*this.GRID_UNIT;this.LARGE_PADDING=4*this.GRID_UNIT;this.CORNER_RADIUS=1*this.GRID_UNIT;this.NOTCH_WIDTH=9*this.GRID_UNIT;this.NOTCH_HEIGHT=2*this.GRID_UNIT;this.STATEMENT_INPUT_NOTCH_OFFSET=this.NOTCH_OFFSET_LEFT=3*this.GRID_UNIT;this.MIN_BLOCK_WIDTH= +2*this.GRID_UNIT;this.MIN_BLOCK_HEIGHT=12*this.GRID_UNIT;this.EMPTY_STATEMENT_INPUT_HEIGHT=6*this.GRID_UNIT;this.TAB_OFFSET_FROM_TOP=0;this.TOP_ROW_MIN_HEIGHT=this.CORNER_RADIUS;this.TOP_ROW_PRECEDES_STATEMENT_MIN_HEIGHT=this.LARGE_PADDING;this.BOTTOM_ROW_MIN_HEIGHT=this.CORNER_RADIUS;this.BOTTOM_ROW_AFTER_STATEMENT_MIN_HEIGHT=6*this.GRID_UNIT;this.STATEMENT_BOTTOM_SPACER=-this.NOTCH_HEIGHT;this.STATEMENT_INPUT_SPACER_MIN_WIDTH=40*this.GRID_UNIT;this.STATEMENT_INPUT_PADDING_LEFT=4*this.GRID_UNIT; +this.EMPTY_INLINE_INPUT_PADDING=4*this.GRID_UNIT;this.EMPTY_INLINE_INPUT_HEIGHT=8*this.GRID_UNIT;this.DUMMY_INPUT_MIN_HEIGHT=8*this.GRID_UNIT;this.DUMMY_INPUT_SHADOW_MIN_HEIGHT=6*this.GRID_UNIT;this.CURSOR_WS_WIDTH=20*this.GRID_UNIT;this.CURSOR_COLOUR="#ffa200";this.CURSOR_RADIUS=5;this.JAGGED_TEETH_WIDTH=this.JAGGED_TEETH_HEIGHT=0;this.START_HAT_HEIGHT=22;this.START_HAT_WIDTH=96;this.SHAPES={HEXAGONAL:1,ROUND:2,SQUARE:3,PUZZLE:4,NOTCH:5};this.SHAPE_IN_SHAPE_PADDING={1:{0:5*this.GRID_UNIT,1:2*this.GRID_UNIT, +2:5*this.GRID_UNIT,3:5*this.GRID_UNIT},2:{0:3*this.GRID_UNIT,1:3*this.GRID_UNIT,2:1*this.GRID_UNIT,3:2*this.GRID_UNIT},3:{0:2*this.GRID_UNIT,1:2*this.GRID_UNIT,2:2*this.GRID_UNIT,3:2*this.GRID_UNIT}};this.FULL_BLOCK_FIELDS=!0;this.FIELD_TEXT_FONTSIZE=3*this.GRID_UNIT;this.FIELD_TEXT_FONTWEIGHT="bold";this.FIELD_TEXT_FONTFAMILY='"Helvetica Neue", "Segoe UI", Helvetica, sans-serif';this.FIELD_BORDER_RECT_RADIUS=this.CORNER_RADIUS;this.FIELD_BORDER_RECT_X_PADDING=2*this.GRID_UNIT;this.FIELD_BORDER_RECT_Y_PADDING= +1.625*this.GRID_UNIT;this.FIELD_BORDER_RECT_HEIGHT=8*this.GRID_UNIT;this.FIELD_DROPDOWN_BORDER_RECT_HEIGHT=8*this.GRID_UNIT;this.FIELD_DROPDOWN_SVG_ARROW=this.FIELD_DROPDOWN_COLOURED_DIV=this.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW=!0;this.FIELD_DROPDOWN_SVG_ARROW_PADDING=this.FIELD_BORDER_RECT_X_PADDING;this.FIELD_COLOUR_FULL_BLOCK=this.FIELD_TEXTINPUT_BOX_SHADOW=!0;this.FIELD_COLOUR_DEFAULT_WIDTH=2*this.GRID_UNIT;this.FIELD_COLOUR_DEFAULT_HEIGHT=4*this.GRID_UNIT;this.FIELD_CHECKBOX_X_OFFSET=1*this.GRID_UNIT; +this.MAX_DYNAMIC_CONNECTION_SHAPE_WIDTH=12*this.GRID_UNIT;this.SELECTED_GLOW_COLOUR="#fff200";this.SELECTED_GLOW_SIZE=.5;this.REPLACEMENT_GLOW_COLOUR="#fff200";this.REPLACEMENT_GLOW_SIZE=2;this.selectedGlowFilterId="";this.selectedGlowFilter_=null;this.replacementGlowFilterId="";this.SQUARED=this.ROUNDED=this.HEXAGONAL=this.replacementGlowFilter_=null}};$.$jscomp.inherits(module$exports$Blockly$zelos$ConstantProvider.ConstantProvider,module$exports$Blockly$blockRendering$ConstantProvider.ConstantProvider); +module$exports$Blockly$zelos$ConstantProvider.ConstantProvider.prototype.setFontConstants_=function(a){module$exports$Blockly$blockRendering$ConstantProvider.ConstantProvider.prototype.setFontConstants_.call(this,a);this.FIELD_DROPDOWN_BORDER_RECT_HEIGHT=this.FIELD_BORDER_RECT_HEIGHT=this.FIELD_TEXT_HEIGHT+2*this.FIELD_BORDER_RECT_Y_PADDING}; +module$exports$Blockly$zelos$ConstantProvider.ConstantProvider.prototype.init=function(){module$exports$Blockly$blockRendering$ConstantProvider.ConstantProvider.prototype.init.call(this);this.HEXAGONAL=this.makeHexagonal();this.ROUNDED=this.makeRounded();this.SQUARED=this.makeSquared();this.STATEMENT_INPUT_NOTCH_OFFSET=this.NOTCH_OFFSET_LEFT+this.INSIDE_CORNERS.rightWidth}; +module$exports$Blockly$zelos$ConstantProvider.ConstantProvider.prototype.setDynamicProperties_=function(a){module$exports$Blockly$blockRendering$ConstantProvider.ConstantProvider.prototype.setDynamicProperties_.call(this,a);this.SELECTED_GLOW_COLOUR=a.getComponentStyle("selectedGlowColour")||this.SELECTED_GLOW_COLOUR;var b=Number(a.getComponentStyle("selectedGlowSize"));this.SELECTED_GLOW_SIZE=b&&!isNaN(b)?b:this.SELECTED_GLOW_SIZE;this.REPLACEMENT_GLOW_COLOUR=a.getComponentStyle("replacementGlowColour")|| +this.REPLACEMENT_GLOW_COLOUR;this.REPLACEMENT_GLOW_SIZE=(a=Number(a.getComponentStyle("replacementGlowSize")))&&!isNaN(a)?a:this.REPLACEMENT_GLOW_SIZE};module$exports$Blockly$zelos$ConstantProvider.ConstantProvider.prototype.dispose=function(){module$exports$Blockly$blockRendering$ConstantProvider.ConstantProvider.prototype.dispose.call(this);this.selectedGlowFilter_&&(0,module$exports$Blockly$utils$dom.removeNode)(this.selectedGlowFilter_);this.replacementGlowFilter_&&(0,module$exports$Blockly$utils$dom.removeNode)(this.replacementGlowFilter_)}; module$exports$Blockly$zelos$ConstantProvider.ConstantProvider.prototype.makeStartHat=function(){var a=this.START_HAT_HEIGHT,b=this.START_HAT_WIDTH,c=(0,module$exports$Blockly$utils$svgPaths.curve)("c",[(0,module$exports$Blockly$utils$svgPaths.point)(25,-a),(0,module$exports$Blockly$utils$svgPaths.point)(71,-a),(0,module$exports$Blockly$utils$svgPaths.point)(b,0)]);return{height:a,width:b,path:c}}; module$exports$Blockly$zelos$ConstantProvider.ConstantProvider.prototype.makeHexagonal=function(){function a(c,d,e){var f=c/2;f=f>b?b:f;e=e?-1:1;c=(d?-1:1)*c/2;return(0,module$exports$Blockly$utils$svgPaths.lineTo)(-e*f,c)+(0,module$exports$Blockly$utils$svgPaths.lineTo)(e*f,c)}var b=this.MAX_DYNAMIC_CONNECTION_SHAPE_WIDTH;return{type:this.SHAPES.HEXAGONAL,isDynamic:!0,width:function(c){c/=2;return c>b?b:c},height:function(c){return c},connectionOffsetY:function(c){return c/2},connectionOffsetX:function(c){return-c}, pathDown:function(c){return a(c,!1,!1)},pathUp:function(c){return a(c,!0,!1)},pathRightDown:function(c){return a(c,!1,!0)},pathRightUp:function(c){return a(c,!1,!0)}}}; @@ -1556,76 +1557,83 @@ e,-f)])+(0,module$exports$Blockly$utils$svgPaths.curve)("c",[(0,module$exports$B module$exports$Blockly$zelos$ConstantProvider.ConstantProvider.prototype.makeInsideCorners=function(){var a=this.CORNER_RADIUS,b=(0,module$exports$Blockly$utils$svgPaths.arc)("a","0 0,0",a,(0,module$exports$Blockly$utils$svgPaths.point)(-a,a)),c=(0,module$exports$Blockly$utils$svgPaths.arc)("a","0 0,1",a,(0,module$exports$Blockly$utils$svgPaths.point)(-a,a)),d=(0,module$exports$Blockly$utils$svgPaths.arc)("a","0 0,0",a,(0,module$exports$Blockly$utils$svgPaths.point)(a,a)),e=(0,module$exports$Blockly$utils$svgPaths.arc)("a", "0 0,1",a,(0,module$exports$Blockly$utils$svgPaths.point)(a,a));return{width:a,height:a,pathTop:b,pathBottom:d,rightWidth:a,rightHeight:a,pathTopRight:c,pathBottomRight:e}};module$exports$Blockly$zelos$ConstantProvider.ConstantProvider.prototype.generateSecondaryColour_=function(a){return(0,module$exports$Blockly$utils$colour.blend)("#000",a,.15)||a}; module$exports$Blockly$zelos$ConstantProvider.ConstantProvider.prototype.generateTertiaryColour_=function(a){return(0,module$exports$Blockly$utils$colour.blend)("#000",a,.25)||a}; -module$exports$Blockly$zelos$ConstantProvider.ConstantProvider.prototype.createDom=function(a,b,c){module$exports$Blockly$zelos$ConstantProvider.ConstantProvider.superClass_.createDom.call(this,a,b,c);a=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.DEFS,{},a);b=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.FILTER,{id:"blocklySelectedGlowFilter"+this.randomIdentifier,height:"160%",width:"180%",y:"-30%",x:"-40%"}, -a);(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.FEGAUSSIANBLUR,{"in":"SourceGraphic",stdDeviation:this.SELECTED_GLOW_SIZE},b);c=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.FECOMPONENTTRANSFER,{result:"outBlur"},b);(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.FEFUNCA,{type:"table",tableValues:"0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1"},c);(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.FEFLOOD, +module$exports$Blockly$zelos$ConstantProvider.ConstantProvider.prototype.createDom=function(a,b,c){module$exports$Blockly$blockRendering$ConstantProvider.ConstantProvider.prototype.createDom.call(this,a,b,c);a=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.DEFS,{},a);b=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.FILTER,{id:"blocklySelectedGlowFilter"+this.randomIdentifier,height:"160%",width:"180%",y:"-30%", +x:"-40%"},a);(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.FEGAUSSIANBLUR,{"in":"SourceGraphic",stdDeviation:this.SELECTED_GLOW_SIZE},b);c=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.FECOMPONENTTRANSFER,{result:"outBlur"},b);(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.FEFUNCA,{type:"table",tableValues:"0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1"},c);(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.FEFLOOD, {"flood-color":this.SELECTED_GLOW_COLOUR,"flood-opacity":1,result:"outColor"},b);(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.FECOMPOSITE,{"in":"outColor",in2:"outBlur",operator:"in",result:"outGlow"},b);this.selectedGlowFilterId=b.id;this.selectedGlowFilter_=b;a=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.FILTER,{id:"blocklyReplacementGlowFilter"+this.randomIdentifier,height:"160%",width:"180%",y:"-30%", x:"-40%"},a);(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.FEGAUSSIANBLUR,{"in":"SourceGraphic",stdDeviation:this.REPLACEMENT_GLOW_SIZE},a);b=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.FECOMPONENTTRANSFER,{result:"outBlur"},a);(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.FEFUNCA,{type:"table",tableValues:"0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1"},b);(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.FEFLOOD, {"flood-color":this.REPLACEMENT_GLOW_COLOUR,"flood-opacity":1,result:"outColor"},a);(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.FECOMPOSITE,{"in":"outColor",in2:"outBlur",operator:"in",result:"outGlow"},a);(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.FECOMPOSITE,{"in":"SourceGraphic",in2:"outGlow",operator:"over"},a);this.replacementGlowFilterId=a.id;this.replacementGlowFilter_=a}; module$exports$Blockly$zelos$ConstantProvider.ConstantProvider.prototype.getCSS_=function(a){return[a+" .blocklyText,",a+" .blocklyFlyoutLabelText {","font: "+this.FIELD_TEXT_FONTWEIGHT+" "+this.FIELD_TEXT_FONTSIZE+"pt "+this.FIELD_TEXT_FONTFAMILY+";","}",a+" .blocklyText {","fill: #fff;","}",a+" .blocklyNonEditableText>rect:not(.blocklyDropdownRect),",a+" .blocklyEditableText>rect:not(.blocklyDropdownRect) {","fill: "+this.FIELD_BORDER_RECT_COLOUR+";","}",a+" .blocklyNonEditableText>text,",a+" .blocklyEditableText>text,", a+" .blocklyNonEditableText>g>text,",a+" .blocklyEditableText>g>text {","fill: #575E75;","}",a+" .blocklyFlyoutLabelText {","fill: #575E75;","}",a+" .blocklyText.blocklyBubbleText {","fill: #575E75;","}",a+" .blocklyDraggable:not(.blocklyDisabled)"," .blocklyEditableText:not(.editing):hover>rect,",a+" .blocklyDraggable:not(.blocklyDisabled)"," .blocklyEditableText:not(.editing):hover>.blocklyPath {","stroke: #fff;","stroke-width: 2;","}",a+" .blocklyHtmlInput {","font-family: "+this.FIELD_TEXT_FONTFAMILY+ ";","font-weight: "+this.FIELD_TEXT_FONTWEIGHT+";","color: #575E75;","}",a+" .blocklyDropdownText {","fill: #fff !important;","}",a+".blocklyWidgetDiv .goog-menuitem,",a+".blocklyDropDownDiv .goog-menuitem {","font-family: "+this.FIELD_TEXT_FONTFAMILY+";","}",a+".blocklyDropDownDiv .goog-menuitem-content {","color: #fff;","}",a+" .blocklyHighlightedConnectionPath {","stroke: "+this.SELECTED_GLOW_COLOUR+";","}",a+" .blocklyDisabled > .blocklyOutlinePath {","fill: url(#blocklyDisabledPattern"+this.randomIdentifier+ -")","}",a+" .blocklyInsertionMarker>.blocklyPath {","fill-opacity: "+this.INSERTION_MARKER_OPACITY+";","stroke: none;","}"]};var module$exports$Blockly$zelos$Drawer={Drawer:function(a,b){module$exports$Blockly$zelos$Drawer.Drawer.superClass_.constructor.call(this,a,b)}};(0,$.module$exports$Blockly$utils$object.inherits)(module$exports$Blockly$zelos$Drawer.Drawer,module$exports$Blockly$blockRendering$Drawer.Drawer); +")","}",a+" .blocklyInsertionMarker>.blocklyPath {","fill-opacity: "+this.INSERTION_MARKER_OPACITY+";","stroke: none;","}"]};var module$exports$Blockly$zelos$Drawer={Drawer:function(a,b){module$exports$Blockly$blockRendering$Drawer.Drawer.call(this,a,b)}};$.$jscomp.inherits(module$exports$Blockly$zelos$Drawer.Drawer,module$exports$Blockly$blockRendering$Drawer.Drawer); module$exports$Blockly$zelos$Drawer.Drawer.prototype.draw=function(){var a=this.block_.pathObject;a.beginDrawing();this.hideHiddenIcons_();this.drawOutline_();this.drawInternals_();a.setPath(this.outlinePath_+"\n"+this.inlinePath_);this.info_.RTL&&a.flipRTL();(0,module$exports$Blockly$blockRendering$debug.isDebuggerEnabled)()&&this.block_.renderingDebugger.drawDebug(this.block_,this.info_);this.recordSizeOnBlock_();this.info_.outputConnection&&(a.outputShapeType=this.info_.outputConnection.shape.type); -a.endDrawing()};module$exports$Blockly$zelos$Drawer.Drawer.prototype.drawOutline_=function(){this.info_.outputConnection&&this.info_.outputConnection.isDynamicShape&&!this.info_.hasStatementInput&&!this.info_.bottomRow.hasNextConnection?(this.drawFlatTop_(),this.drawRightDynamicConnection_(),this.drawFlatBottom_(),this.drawLeftDynamicConnection_()):module$exports$Blockly$zelos$Drawer.Drawer.superClass_.drawOutline_.call(this)}; -module$exports$Blockly$zelos$Drawer.Drawer.prototype.drawLeft_=function(){this.info_.outputConnection&&this.info_.outputConnection.isDynamicShape?this.drawLeftDynamicConnection_():module$exports$Blockly$zelos$Drawer.Drawer.superClass_.drawLeft_.call(this)}; -module$exports$Blockly$zelos$Drawer.Drawer.prototype.drawRightSideRow_=function(a){if(!(0>=a.height))if(a.precedesStatement||a.followsStatement){var b=this.constants_.INSIDE_CORNERS.rightHeight;b=a.height-(a.precedesStatement?b:0);this.outlinePath_+=(a.followsStatement?this.constants_.INSIDE_CORNERS.pathBottomRight:"")+(0=a.height))if(module$exports$Blockly$blockRendering$Types.Types.isSpacer(a)&&(a.precedesStatement||a.followsStatement)){var b=this.constants_.INSIDE_CORNERS.rightHeight;b=a.height-(a.precedesStatement?b:0);this.outlinePath_+=(a.followsStatement?this.constants_.INSIDE_CORNERS.pathBottomRight:"")+(0=c||0>=b)throw Error("Height and width values of an image field must be greater than 0."); -this.flipRtl_=!1;this.altText_="";$.module$exports$Blockly$FieldImage.FieldImage.superClass_.constructor.call(this,a,null,g);g||(this.flipRtl_=!!f,this.altText_=(0,module$exports$Blockly$utils$parsing.replaceMessageReferences)(d)||"");this.size_=new module$exports$Blockly$utils$Size.Size(b,c+$.module$exports$Blockly$FieldImage.FieldImage.Y_PADDING);this.imageHeight_=c;this.clickHandler_=null;"function"===typeof e&&(this.clickHandler_=e);this.imageElement_=null}}; -(0,$.module$exports$Blockly$utils$object.inherits)($.module$exports$Blockly$FieldImage.FieldImage,module$exports$Blockly$Field.Field);$.module$exports$Blockly$FieldImage.FieldImage.prototype.DEFAULT_VALUE="";$.module$exports$Blockly$FieldImage.FieldImage.fromJson=function(a){return new this(a.src,a.width,a.height,void 0,void 0,void 0,a)};$.module$exports$Blockly$FieldImage.FieldImage.Y_PADDING=1;$.module$exports$Blockly$FieldImage.FieldImage.prototype.EDITABLE=!1; -$.module$exports$Blockly$FieldImage.FieldImage.prototype.isDirty_=!1;$.module$exports$Blockly$FieldImage.FieldImage.prototype.configure_=function(a){$.module$exports$Blockly$FieldImage.FieldImage.superClass_.configure_.call(this,a);this.flipRtl_=!!a.flipRtl;this.altText_=(0,module$exports$Blockly$utils$parsing.replaceMessageReferences)(a.alt)||""}; +module$exports$Blockly$zelos$PathObject.PathObject.prototype.removeOutlinePath_=function(a){this.outlines_[a].parentNode.removeChild(this.outlines_[a]);delete this.outlines_[a]};$.module$exports$Blockly$FieldImage={FieldImage:function(a,b,c,d,e,f,g){module$exports$Blockly$Field.Field.call(this,module$exports$Blockly$Field.Field.SKIP_SETUP);if(!a)throw Error("Src value of an image field is required");c=Number((0,module$exports$Blockly$utils$parsing.replaceMessageReferences)(c));b=Number((0,module$exports$Blockly$utils$parsing.replaceMessageReferences)(b));if(isNaN(c)||isNaN(b))throw Error("Height and width values of an image field must cast to numbers.");if(0>=c||0>=b)throw Error("Height and width values of an image field must be greater than 0."); +this.size_=new module$exports$Blockly$utils$Size.Size(b,c+$.module$exports$Blockly$FieldImage.FieldImage.Y_PADDING);this.imageHeight_=c;this.clickHandler_=null;"function"===typeof e&&(this.clickHandler_=e);this.imageElement_=null;this.flipRtl_=this.isDirty_=this.EDITABLE=!1;this.altText_="";a!==module$exports$Blockly$Field.Field.SKIP_SETUP&&(g?this.configure_(g):(this.flipRtl_=!!f,this.altText_=(0,module$exports$Blockly$utils$parsing.replaceMessageReferences)(d)||""),this.setValue((0,module$exports$Blockly$utils$parsing.replaceMessageReferences)(a)))}}; +$.$jscomp.inherits($.module$exports$Blockly$FieldImage.FieldImage,module$exports$Blockly$Field.Field);$.module$exports$Blockly$FieldImage.FieldImage.prototype.configure_=function(a){module$exports$Blockly$Field.Field.prototype.configure_.call(this,a);this.flipRtl_=!!a.flipRtl;this.altText_=(0,module$exports$Blockly$utils$parsing.replaceMessageReferences)(a.alt)||""}; $.module$exports$Blockly$FieldImage.FieldImage.prototype.initView=function(){this.imageElement_=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.IMAGE,{height:this.imageHeight_+"px",width:this.size_.width+"px",alt:this.altText_},this.fieldGroup_);this.imageElement_.setAttributeNS(module$exports$Blockly$utils$dom.XLINK_NS,"xlink:href",this.value_);this.clickHandler_&&(this.imageElement_.style.cursor="pointer")}; $.module$exports$Blockly$FieldImage.FieldImage.prototype.updateSize_=function(){};$.module$exports$Blockly$FieldImage.FieldImage.prototype.doClassValidation_=function(a){return"string"!==typeof a?null:a};$.module$exports$Blockly$FieldImage.FieldImage.prototype.doValueUpdate_=function(a){this.value_=a;this.imageElement_&&this.imageElement_.setAttributeNS(module$exports$Blockly$utils$dom.XLINK_NS,"xlink:href",String(this.value_))}; $.module$exports$Blockly$FieldImage.FieldImage.prototype.getFlipRtl=function(){return this.flipRtl_};$.module$exports$Blockly$FieldImage.FieldImage.prototype.setAlt=function(a){a!==this.altText_&&(this.altText_=a||"",this.imageElement_&&this.imageElement_.setAttribute("alt",this.altText_))};$.module$exports$Blockly$FieldImage.FieldImage.prototype.showEditor_=function(){this.clickHandler_&&this.clickHandler_(this)}; -$.module$exports$Blockly$FieldImage.FieldImage.prototype.setOnClickHandler=function(a){this.clickHandler_=a};$.module$exports$Blockly$FieldImage.FieldImage.prototype.getText_=function(){return this.altText_};(0,module$exports$Blockly$fieldRegistry.register)("field_image",$.module$exports$Blockly$FieldImage.FieldImage);var module$exports$Blockly$zelos$RenderInfo={RenderInfo:function(a,b){module$exports$Blockly$zelos$RenderInfo.RenderInfo.superClass_.constructor.call(this,a,b);this.topRow=new module$exports$Blockly$zelos$TopRow.TopRow(this.constants_);this.bottomRow=new module$exports$Blockly$zelos$BottomRow.BottomRow(this.constants_);this.isInline=!0;this.isMultiRow=!b.getInputsInline()||b.isCollapsed();this.hasStatementInput=0=this.rows.length-1?!!this.bottomRow.hasNextConnection:!!f.precedesStatement;if(module$exports$Blockly$blockRendering$Types.Types.isInputRow(e)&&e.hasStatement)e.measure(),b=e.width- -e.getLastInput().width+a;else if(d&&(2===c||f)&&module$exports$Blockly$blockRendering$Types.Types.isInputRow(e)&&!e.hasStatement){f=e.xPos;d=null;for(var g=0;g=this.rows.length-1?!!this.bottomRow.hasNextConnection:!!f.precedesStatement;if(module$exports$Blockly$blockRendering$Types.Types.isInputRow(e)&&e.hasStatement)e.measure(),b=e.width-e.getLastInput().width+ +a;else if(d&&(2===c||f)&&module$exports$Blockly$blockRendering$Types.Types.isInputRow(e)&&!e.hasStatement){f=e.xPos;d=null;for(var g=0;gc?c:this.height/2,b-c*(1-Math.sin(Math.acos((c-this.constants_.SMALL_PADDING)/c)));default:return 0}if(module$exports$Blockly$blockRendering$Types.Types.isInlineInput(a)){var e=a.connectedBlock; -a=e?e.pathObject.outputShapeType:a.shape.type;return e&&e.outputConnection&&(e.statementInputCount||e.nextConnection)||c===d.SHAPES.HEXAGONAL&&c!==a?0:b-this.constants_.SHAPE_IN_SHAPE_PADDING[c][a]}return module$exports$Blockly$blockRendering$Types.Types.isField(a)?c===d.SHAPES.ROUND&&a.field instanceof $.module$exports$Blockly$FieldTextInput.FieldTextInput?b-2.75*d.GRID_UNIT:b-this.constants_.SHAPE_IN_SHAPE_PADDING[c][0]:module$exports$Blockly$blockRendering$Types.Types.isIcon(a)?this.constants_.SMALL_PADDING: -0}; -module$exports$Blockly$zelos$RenderInfo.RenderInfo.prototype.finalizeVerticalAlignment_=function(){if(!this.outputConnection)for(var a=2;a=this.rows.length-1?!!this.bottomRow.hasNextConnection:!!d.precedesStatement;if(e?this.topRow.hasPreviousConnection:b.followsStatement){var g=3===c.elements.length&&(c.elements[1].field instanceof $.module$exports$Blockly$FieldLabel.FieldLabel||c.elements[1].field instanceof $.module$exports$Blockly$FieldImage.FieldImage); -if(!e&&g)b.height-=this.constants_.SMALL_PADDING,d.height-=this.constants_.SMALL_PADDING,c.height-=this.constants_.MEDIUM_PADDING;else if(!e&&!f)b.height+=this.constants_.SMALL_PADDING;else if(f){e=!1;for(f=0;fc?c:this.height/2,b-c*(1-Math.sin(Math.acos((c-this.constants_.SMALL_PADDING)/c)));default:return 0}if(module$exports$Blockly$blockRendering$Types.Types.isInlineInput(a)&& +a instanceof module$exports$Blockly$blockRendering$InputConnection.InputConnection){var e=a.connectedBlock;a=e?e.pathObject.outputShapeType:a.shape.type;return e&&e.outputConnection&&(e.statementInputCount||e.nextConnection)||c===d.SHAPES.HEXAGONAL&&c!==a?0:b-this.constants_.SHAPE_IN_SHAPE_PADDING[c][a]}return module$exports$Blockly$blockRendering$Types.Types.isField(a)&&a instanceof module$exports$Blockly$blockRendering$Field.Field?c===d.SHAPES.ROUND&&a.field instanceof $.module$exports$Blockly$FieldTextInput.FieldTextInput? +b-2.75*d.GRID_UNIT:b-this.constants_.SHAPE_IN_SHAPE_PADDING[c][0]:module$exports$Blockly$blockRendering$Types.Types.isIcon(a)?this.constants_.SMALL_PADDING:0}; +module$exports$Blockly$zelos$RenderInfo.RenderInfo.prototype.finalizeVerticalAlignment_=function(){if(!this.outputConnection)for(var a=2;a=this.rows.length-1?!!this.bottomRow.hasNextConnection:!!d.precedesStatement;if(e?this.topRow.hasPreviousConnection:b.followsStatement){var g=c.elements[1];g=3===c.elements.length&&g instanceof module$exports$Blockly$blockRendering$Field.Field&&(g.field instanceof $.module$exports$Blockly$FieldLabel.FieldLabel|| +g.field instanceof $.module$exports$Blockly$FieldImage.FieldImage);if(!e&&g)b.height-=this.constants_.SMALL_PADDING,d.height-=this.constants_.SMALL_PADDING,c.height-=this.constants_.MEDIUM_PADDING;else if(!e&&!f)b.height+=this.constants_.SMALL_PADDING;else if(f){e=!1;for(f=0;f.blocklyPathLight,",a+" .blocklyInsertionMarker>.blocklyPathDark {","fill-opacity: "+this.INSERTION_MARKER_OPACITY+";","stroke: none;","}"])};var module$exports$Blockly$geras$Highlighter={Highlighter:function(a){this.info_=a;this.inlineSteps_=this.steps_="";this.RTL_=this.info_.RTL;a=a.getRenderer();this.constants_=a.getConstants();this.highlightConstants_=a.getHighlightConstants();this.highlightOffset_=this.highlightConstants_.OFFSET;this.outsideCornerPaths_=this.highlightConstants_.OUTSIDE_CORNER;this.insideCornerPaths_=this.highlightConstants_.INSIDE_CORNER;this.puzzleTabPaths_=this.highlightConstants_.PUZZLE_TAB;this.notchPaths_=this.highlightConstants_.NOTCH; +module$exports$Blockly$geras$PathObject.PathObject.prototype.updateDisabled_=function(a){module$exports$Blockly$blockRendering$PathObject.PathObject.prototype.updateDisabled_.call(this,a);a&&this.svgPath.setAttribute("stroke","none")};var module$exports$Blockly$geras$ConstantProvider={ConstantProvider:function(){module$exports$Blockly$blockRendering$ConstantProvider.ConstantProvider.call(this);this.FIELD_TEXT_BASELINE_CENTER=!1;this.DARK_PATH_OFFSET=1;this.MAX_BOTTOM_WIDTH=30;this.STATEMENT_BOTTOM_SPACER=-this.NOTCH_HEIGHT/2}};$.$jscomp.inherits(module$exports$Blockly$geras$ConstantProvider.ConstantProvider,module$exports$Blockly$blockRendering$ConstantProvider.ConstantProvider); +module$exports$Blockly$geras$ConstantProvider.ConstantProvider.prototype.getCSS_=function(a){return module$exports$Blockly$blockRendering$ConstantProvider.ConstantProvider.prototype.getCSS_.call(this,a).concat([a+" .blocklyInsertionMarker>.blocklyPathLight,",a+" .blocklyInsertionMarker>.blocklyPathDark {","fill-opacity: "+this.INSERTION_MARKER_OPACITY+";","stroke: none;","}"])};var module$exports$Blockly$geras$InlineInput={InlineInput:function(a,b){module$exports$Blockly$blockRendering$InlineInput.InlineInput.call(this,a,b);this.connectedBlock&&(this.width+=this.constants_.DARK_PATH_OFFSET,this.height+=this.constants_.DARK_PATH_OFFSET)}};$.$jscomp.inherits(module$exports$Blockly$geras$InlineInput.InlineInput,module$exports$Blockly$blockRendering$InlineInput.InlineInput);var module$exports$Blockly$geras$Highlighter={Highlighter:function(a){this.info_=a;this.inlineSteps_=this.steps_="";this.RTL_=this.info_.RTL;a=a.getRenderer();this.constants_=a.getConstants();this.highlightConstants_=a.getHighlightConstants();this.highlightOffset_=this.highlightConstants_.OFFSET;this.outsideCornerPaths_=this.highlightConstants_.OUTSIDE_CORNER;this.insideCornerPaths_=this.highlightConstants_.INSIDE_CORNER;this.puzzleTabPaths_=this.highlightConstants_.PUZZLE_TAB;this.notchPaths_=this.highlightConstants_.NOTCH; this.startPaths_=this.highlightConstants_.START_HAT;this.jaggedTeethPaths_=this.highlightConstants_.JAGGED_TEETH}};module$exports$Blockly$geras$Highlighter.Highlighter.prototype.getPath=function(){return this.steps_+"\n"+this.inlineSteps_}; module$exports$Blockly$geras$Highlighter.Highlighter.prototype.drawTopCorner=function(a){this.steps_+=(0,module$exports$Blockly$utils$svgPaths.moveBy)(a.xPos,this.info_.startY);for(var b=0,c;c=a.elements[b];b++)module$exports$Blockly$blockRendering$Types.Types.isLeftSquareCorner(c)?this.steps_+=this.highlightConstants_.START_POINT:module$exports$Blockly$blockRendering$Types.Types.isLeftRoundedCorner(c)?this.steps_+=this.outsideCornerPaths_.topLeft(this.RTL_):module$exports$Blockly$blockRendering$Types.Types.isPreviousConnection(c)? this.steps_+=this.notchPaths_.pathLeft:module$exports$Blockly$blockRendering$Types.Types.isHat(c)?this.steps_+=this.startPaths_.path(this.RTL_):module$exports$Blockly$blockRendering$Types.Types.isSpacer(c)&&0!==c.width&&(this.steps_+=(0,module$exports$Blockly$utils$svgPaths.lineOnAxis)("H",c.xPos+c.width-this.highlightOffset_));this.steps_+=(0,module$exports$Blockly$utils$svgPaths.lineOnAxis)("H",a.xPos+a.width-this.highlightOffset_)}; @@ -1660,18 +1668,19 @@ module$exports$Blockly$geras$Highlighter.Highlighter.prototype.drawJaggedEdge_=f module$exports$Blockly$geras$Highlighter.Highlighter.prototype.drawValueInput=function(a){var b=a.getLastInput();if(this.RTL_){var c=a.height-b.connectionHeight;this.steps_+=(0,module$exports$Blockly$utils$svgPaths.moveTo)(b.xPos+b.width-this.highlightOffset_,a.yPos)+this.puzzleTabPaths_.pathDown(this.RTL_)+(0,module$exports$Blockly$utils$svgPaths.lineOnAxis)("v",c)}else this.steps_+=(0,module$exports$Blockly$utils$svgPaths.moveTo)(b.xPos+b.width,a.yPos)+this.puzzleTabPaths_.pathDown(this.RTL_)}; module$exports$Blockly$geras$Highlighter.Highlighter.prototype.drawStatementInput=function(a){var b=a.getLastInput();if(this.RTL_){var c=a.height-2*this.insideCornerPaths_.height;this.steps_+=(0,module$exports$Blockly$utils$svgPaths.moveTo)(b.xPos,a.yPos)+this.insideCornerPaths_.pathTop(this.RTL_)+(0,module$exports$Blockly$utils$svgPaths.lineOnAxis)("v",c)+this.insideCornerPaths_.pathBottom(this.RTL_)+(0,module$exports$Blockly$utils$svgPaths.lineTo)(a.width-b.xPos-this.insideCornerPaths_.width,0)}else this.steps_+= (0,module$exports$Blockly$utils$svgPaths.moveTo)(b.xPos,a.yPos+a.height)+this.insideCornerPaths_.pathBottom(this.RTL_)+(0,module$exports$Blockly$utils$svgPaths.lineTo)(a.width-b.xPos-this.insideCornerPaths_.width,0)}; -module$exports$Blockly$geras$Highlighter.Highlighter.prototype.drawRightSideRow=function(a){var b=a.xPos+a.width-this.highlightOffset_;a.followsStatement&&(this.steps_+=(0,module$exports$Blockly$utils$svgPaths.lineOnAxis)("H",b));this.RTL_&&(this.steps_+=(0,module$exports$Blockly$utils$svgPaths.lineOnAxis)("H",b),a.height>this.highlightOffset_&&(this.steps_+=(0,module$exports$Blockly$utils$svgPaths.lineOnAxis)("V",a.yPos+a.height-this.highlightOffset_)))}; -module$exports$Blockly$geras$Highlighter.Highlighter.prototype.drawBottomRow=function(a){if(this.RTL_)this.steps_+=(0,module$exports$Blockly$utils$svgPaths.lineOnAxis)("V",a.baseline-this.highlightOffset_);else{var b=this.info_.bottomRow.elements[0];module$exports$Blockly$blockRendering$Types.Types.isLeftSquareCorner(b)?this.steps_+=(0,module$exports$Blockly$utils$svgPaths.moveTo)(a.xPos+this.highlightOffset_,a.baseline-this.highlightOffset_):module$exports$Blockly$blockRendering$Types.Types.isLeftRoundedCorner(b)&& -(this.steps_+=(0,module$exports$Blockly$utils$svgPaths.moveTo)(a.xPos,a.baseline),this.steps_+=this.outsideCornerPaths_.bottomLeft())}}; +module$exports$Blockly$geras$Highlighter.Highlighter.prototype.drawRightSideRow=function(a){var b=a.xPos+a.width-this.highlightOffset_;a instanceof module$exports$Blockly$blockRendering$SpacerRow.SpacerRow&&a.followsStatement&&(this.steps_+=(0,module$exports$Blockly$utils$svgPaths.lineOnAxis)("H",b));this.RTL_&&(this.steps_+=(0,module$exports$Blockly$utils$svgPaths.lineOnAxis)("H",b),a.height>this.highlightOffset_&&(this.steps_+=(0,module$exports$Blockly$utils$svgPaths.lineOnAxis)("V",a.yPos+a.height- +this.highlightOffset_)))}; +module$exports$Blockly$geras$Highlighter.Highlighter.prototype.drawBottomRow=function(a){if(this.RTL_)this.steps_+=(0,module$exports$Blockly$utils$svgPaths.lineOnAxis)("V",a.baseline-this.highlightOffset_);else{var b=this.info_.bottomRow.elements[0];module$exports$Blockly$blockRendering$Types.Types.isLeftSquareCorner(b)?this.steps_+=(0,module$exports$Blockly$utils$svgPaths.moveTo)(a.xPos+this.highlightOffset_,a.baseline-this.highlightOffset_):module$exports$Blockly$blockRendering$Types.Types.isLeftRoundedCorner(b)&&(this.steps_+= +(0,module$exports$Blockly$utils$svgPaths.moveTo)(a.xPos,a.baseline),this.steps_+=this.outsideCornerPaths_.bottomLeft())}}; module$exports$Blockly$geras$Highlighter.Highlighter.prototype.drawLeft=function(){var a=this.info_.outputConnection;a&&(a=a.connectionOffsetY+a.height,this.RTL_?this.steps_+=(0,module$exports$Blockly$utils$svgPaths.moveTo)(this.info_.startX,a):(this.steps_+=(0,module$exports$Blockly$utils$svgPaths.moveTo)(this.info_.startX+this.highlightOffset_,this.info_.bottomRow.baseline-this.highlightOffset_),this.steps_+=(0,module$exports$Blockly$utils$svgPaths.lineOnAxis)("V",a)),this.steps_+=this.puzzleTabPaths_.pathUp(this.RTL_)); this.RTL_||(a=this.info_.topRow,module$exports$Blockly$blockRendering$Types.Types.isLeftRoundedCorner(a.elements[0])?this.steps_+=(0,module$exports$Blockly$utils$svgPaths.lineOnAxis)("V",this.outsideCornerPaths_.height):this.steps_+=(0,module$exports$Blockly$utils$svgPaths.lineOnAxis)("V",a.capline+this.highlightOffset_))}; module$exports$Blockly$geras$Highlighter.Highlighter.prototype.drawInlineInput=function(a){var b=this.highlightOffset_,c=a.xPos+a.connectionWidth,d=a.centerline-a.height/2,e=a.width-a.connectionWidth,f=d+b;this.RTL_?(d=a.connectionOffsetY-b,a=a.height-(a.connectionOffsetY+a.connectionHeight)+b,this.inlineSteps_+=(0,module$exports$Blockly$utils$svgPaths.moveTo)(c-b,f)+(0,module$exports$Blockly$utils$svgPaths.lineOnAxis)("v",d)+this.puzzleTabPaths_.pathDown(this.RTL_)+(0,module$exports$Blockly$utils$svgPaths.lineOnAxis)("v", -a)+(0,module$exports$Blockly$utils$svgPaths.lineOnAxis)("h",e)):this.inlineSteps_+=(0,module$exports$Blockly$utils$svgPaths.moveTo)(a.xPos+a.width+b,f)+(0,module$exports$Blockly$utils$svgPaths.lineOnAxis)("v",a.height)+(0,module$exports$Blockly$utils$svgPaths.lineOnAxis)("h",-e)+(0,module$exports$Blockly$utils$svgPaths.moveTo)(c,d+a.connectionOffsetY)+this.puzzleTabPaths_.pathDown(this.RTL_)};var module$exports$Blockly$geras$Drawer={Drawer:function(a,b){module$exports$Blockly$geras$Drawer.Drawer.superClass_.constructor.call(this,a,b);this.highlighter_=new module$exports$Blockly$geras$Highlighter.Highlighter(b)}};(0,$.module$exports$Blockly$utils$object.inherits)(module$exports$Blockly$geras$Drawer.Drawer,module$exports$Blockly$blockRendering$Drawer.Drawer); +a)+(0,module$exports$Blockly$utils$svgPaths.lineOnAxis)("h",e)):this.inlineSteps_+=(0,module$exports$Blockly$utils$svgPaths.moveTo)(a.xPos+a.width+b,f)+(0,module$exports$Blockly$utils$svgPaths.lineOnAxis)("v",a.height)+(0,module$exports$Blockly$utils$svgPaths.lineOnAxis)("h",-e)+(0,module$exports$Blockly$utils$svgPaths.moveTo)(c,d+a.connectionOffsetY)+this.puzzleTabPaths_.pathDown(this.RTL_)};var module$exports$Blockly$geras$Drawer={Drawer:function(a,b){module$exports$Blockly$blockRendering$Drawer.Drawer.call(this,a,b);this.highlighter_=new module$exports$Blockly$geras$Highlighter.Highlighter(b)}};$.$jscomp.inherits(module$exports$Blockly$geras$Drawer.Drawer,module$exports$Blockly$blockRendering$Drawer.Drawer); module$exports$Blockly$geras$Drawer.Drawer.prototype.draw=function(){this.hideHiddenIcons_();this.drawOutline_();this.drawInternals_();var a=this.block_.pathObject;a.setPath(this.outlinePath_+"\n"+this.inlinePath_);a.setHighlightPath(this.highlighter_.getPath());this.info_.RTL&&a.flipRTL();(0,module$exports$Blockly$blockRendering$debug.isDebuggerEnabled)()&&this.block_.renderingDebugger.drawDebug(this.block_,this.info_);this.recordSizeOnBlock_()}; -module$exports$Blockly$geras$Drawer.Drawer.prototype.drawTop_=function(){this.highlighter_.drawTopCorner(this.info_.topRow);this.highlighter_.drawRightSideRow(this.info_.topRow);module$exports$Blockly$geras$Drawer.Drawer.superClass_.drawTop_.call(this)};module$exports$Blockly$geras$Drawer.Drawer.prototype.drawJaggedEdge_=function(a){this.highlighter_.drawJaggedEdge_(a);module$exports$Blockly$geras$Drawer.Drawer.superClass_.drawJaggedEdge_.call(this,a)}; -module$exports$Blockly$geras$Drawer.Drawer.prototype.drawValueInput_=function(a){this.highlighter_.drawValueInput(a);module$exports$Blockly$geras$Drawer.Drawer.superClass_.drawValueInput_.call(this,a)};module$exports$Blockly$geras$Drawer.Drawer.prototype.drawStatementInput_=function(a){this.highlighter_.drawStatementInput(a);module$exports$Blockly$geras$Drawer.Drawer.superClass_.drawStatementInput_.call(this,a)}; -module$exports$Blockly$geras$Drawer.Drawer.prototype.drawRightSideRow_=function(a){this.highlighter_.drawRightSideRow(a);this.outlinePath_+=(0,module$exports$Blockly$utils$svgPaths.lineOnAxis)("H",a.xPos+a.width)+(0,module$exports$Blockly$utils$svgPaths.lineOnAxis)("V",a.yPos+a.height)};module$exports$Blockly$geras$Drawer.Drawer.prototype.drawBottom_=function(){this.highlighter_.drawBottomRow(this.info_.bottomRow);module$exports$Blockly$geras$Drawer.Drawer.superClass_.drawBottom_.call(this)}; -module$exports$Blockly$geras$Drawer.Drawer.prototype.drawLeft_=function(){this.highlighter_.drawLeft();module$exports$Blockly$geras$Drawer.Drawer.superClass_.drawLeft_.call(this)};module$exports$Blockly$geras$Drawer.Drawer.prototype.drawInlineInput_=function(a){this.highlighter_.drawInlineInput(a);module$exports$Blockly$geras$Drawer.Drawer.superClass_.drawInlineInput_.call(this,a)}; +module$exports$Blockly$geras$Drawer.Drawer.prototype.drawTop_=function(){this.highlighter_.drawTopCorner(this.info_.topRow);this.highlighter_.drawRightSideRow(this.info_.topRow);module$exports$Blockly$blockRendering$Drawer.Drawer.prototype.drawTop_.call(this)};module$exports$Blockly$geras$Drawer.Drawer.prototype.drawJaggedEdge_=function(a){this.highlighter_.drawJaggedEdge_(a);module$exports$Blockly$blockRendering$Drawer.Drawer.prototype.drawJaggedEdge_.call(this,a)}; +module$exports$Blockly$geras$Drawer.Drawer.prototype.drawValueInput_=function(a){this.highlighter_.drawValueInput(a);module$exports$Blockly$blockRendering$Drawer.Drawer.prototype.drawValueInput_.call(this,a)};module$exports$Blockly$geras$Drawer.Drawer.prototype.drawStatementInput_=function(a){this.highlighter_.drawStatementInput(a);module$exports$Blockly$blockRendering$Drawer.Drawer.prototype.drawStatementInput_.call(this,a)}; +module$exports$Blockly$geras$Drawer.Drawer.prototype.drawRightSideRow_=function(a){this.highlighter_.drawRightSideRow(a);this.outlinePath_+=(0,module$exports$Blockly$utils$svgPaths.lineOnAxis)("H",a.xPos+a.width)+(0,module$exports$Blockly$utils$svgPaths.lineOnAxis)("V",a.yPos+a.height)};module$exports$Blockly$geras$Drawer.Drawer.prototype.drawBottom_=function(){this.highlighter_.drawBottomRow(this.info_.bottomRow);module$exports$Blockly$blockRendering$Drawer.Drawer.prototype.drawBottom_.call(this)}; +module$exports$Blockly$geras$Drawer.Drawer.prototype.drawLeft_=function(){this.highlighter_.drawLeft();module$exports$Blockly$blockRendering$Drawer.Drawer.prototype.drawLeft_.call(this)};module$exports$Blockly$geras$Drawer.Drawer.prototype.drawInlineInput_=function(a){this.highlighter_.drawInlineInput(a);module$exports$Blockly$blockRendering$Drawer.Drawer.prototype.drawInlineInput_.call(this,a)}; module$exports$Blockly$geras$Drawer.Drawer.prototype.positionInlineInputConnection_=function(a){var b=a.centerline-a.height/2;if(a.connectionModel){var c=a.xPos+a.connectionWidth+this.constants_.DARK_PATH_OFFSET;this.info_.RTL&&(c*=-1);a.connectionModel.setOffsetInBlock(c,b+a.connectionOffsetY+this.constants_.DARK_PATH_OFFSET)}}; module$exports$Blockly$geras$Drawer.Drawer.prototype.positionStatementInputConnection_=function(a){var b=a.getLastInput();if(b.connectionModel){var c=a.xPos+a.statementEdge+b.notchOffset;c=this.info_.RTL?-1*c:c+this.constants_.DARK_PATH_OFFSET;b.connectionModel.setOffsetInBlock(c,a.yPos+this.constants_.DARK_PATH_OFFSET)}}; module$exports$Blockly$geras$Drawer.Drawer.prototype.positionExternalValueConnection_=function(a){var b=a.getLastInput();if(b.connectionModel){var c=a.xPos+a.width+this.constants_.DARK_PATH_OFFSET;this.info_.RTL&&(c*=-1);b.connectionModel.setOffsetInBlock(c,a.yPos)}}; @@ -1686,8 +1695,8 @@ module$exports$Blockly$geras$HighlightConstantProvider.HighlightConstantProvider a,1),f=(0,module$exports$Blockly$utils$svgPaths.moveBy)(-5,b-.7)+(0,module$exports$Blockly$utils$svgPaths.lineTo)(.46*a,-2.1);return{width:a,height:b,pathUp:function(g){return g?c:e},pathDown:function(g){return g?d:f}}};module$exports$Blockly$geras$HighlightConstantProvider.HighlightConstantProvider.prototype.makeNotch=function(){return{pathLeft:(0,module$exports$Blockly$utils$svgPaths.lineOnAxis)("h",this.OFFSET)+this.constantProvider.NOTCH.pathLeft}}; module$exports$Blockly$geras$HighlightConstantProvider.HighlightConstantProvider.prototype.makeJaggedTeeth=function(){return{pathLeft:(0,module$exports$Blockly$utils$svgPaths.lineTo)(5.1,2.6)+(0,module$exports$Blockly$utils$svgPaths.moveBy)(-10.2,6.8)+(0,module$exports$Blockly$utils$svgPaths.lineTo)(5.1,2.6),height:12,width:10.2}}; module$exports$Blockly$geras$HighlightConstantProvider.HighlightConstantProvider.prototype.makeStartHat=function(){var a=this.constantProvider.START_HAT.height,b=(0,module$exports$Blockly$utils$svgPaths.moveBy)(25,-8.7)+(0,module$exports$Blockly$utils$svgPaths.curve)("c",[(0,module$exports$Blockly$utils$svgPaths.point)(29.7,-6.2),(0,module$exports$Blockly$utils$svgPaths.point)(57.2,-.5),(0,module$exports$Blockly$utils$svgPaths.point)(75,8.7)]),c=(0,module$exports$Blockly$utils$svgPaths.curve)("c", -[(0,module$exports$Blockly$utils$svgPaths.point)(17.8,-9.2),(0,module$exports$Blockly$utils$svgPaths.point)(45.3,-14.9),(0,module$exports$Blockly$utils$svgPaths.point)(75,-8.7)])+(0,module$exports$Blockly$utils$svgPaths.moveTo)(100.5,a+.5);return{path:function(d){return d?b:c}}};var module$exports$Blockly$geras$InlineInput={InlineInput:function(a,b){module$exports$Blockly$geras$InlineInput.InlineInput.superClass_.constructor.call(this,a,b);this.connectedBlock&&(this.width+=this.constants_.DARK_PATH_OFFSET,this.height+=this.constants_.DARK_PATH_OFFSET)}};(0,$.module$exports$Blockly$utils$object.inherits)(module$exports$Blockly$geras$InlineInput.InlineInput,module$exports$Blockly$blockRendering$InlineInput.InlineInput);var module$exports$Blockly$geras$RenderInfo={RenderInfo:function(a,b){module$exports$Blockly$geras$RenderInfo.RenderInfo.superClass_.constructor.call(this,a,b)}};(0,$.module$exports$Blockly$utils$object.inherits)(module$exports$Blockly$geras$RenderInfo.RenderInfo,module$exports$Blockly$blockRendering$RenderInfo.RenderInfo);module$exports$Blockly$geras$RenderInfo.RenderInfo.prototype.getRenderer=function(){return this.renderer_}; -module$exports$Blockly$geras$RenderInfo.RenderInfo.prototype.populateBottomRow_=function(){module$exports$Blockly$geras$RenderInfo.RenderInfo.superClass_.populateBottomRow_.call(this);this.block_.inputList.length&&this.block_.inputList[this.block_.inputList.length-1].type===$.module$exports$Blockly$inputTypes.inputTypes.STATEMENT||(this.bottomRow.minHeight=this.constants_.MEDIUM_PADDING-this.constants_.DARK_PATH_OFFSET)}; +[(0,module$exports$Blockly$utils$svgPaths.point)(17.8,-9.2),(0,module$exports$Blockly$utils$svgPaths.point)(45.3,-14.9),(0,module$exports$Blockly$utils$svgPaths.point)(75,-8.7)])+(0,module$exports$Blockly$utils$svgPaths.moveTo)(100.5,a+.5);return{path:function(d){return d?b:c}}};var module$exports$Blockly$geras$RenderInfo={RenderInfo:function(a,b){module$exports$Blockly$blockRendering$RenderInfo.RenderInfo.call(this,a,b)}};$.$jscomp.inherits(module$exports$Blockly$geras$RenderInfo.RenderInfo,module$exports$Blockly$blockRendering$RenderInfo.RenderInfo);module$exports$Blockly$geras$RenderInfo.RenderInfo.prototype.getRenderer=function(){return this.renderer_}; +module$exports$Blockly$geras$RenderInfo.RenderInfo.prototype.populateBottomRow_=function(){module$exports$Blockly$blockRendering$RenderInfo.RenderInfo.prototype.populateBottomRow_.call(this);this.block_.inputList.length&&this.block_.inputList[this.block_.inputList.length-1].type===$.module$exports$Blockly$inputTypes.inputTypes.STATEMENT||(this.bottomRow.minHeight=this.constants_.MEDIUM_PADDING-this.constants_.DARK_PATH_OFFSET)}; module$exports$Blockly$geras$RenderInfo.RenderInfo.prototype.addInput_=function(a,b){this.isInline&&a.type===$.module$exports$Blockly$inputTypes.inputTypes.VALUE?(b.elements.push(new module$exports$Blockly$geras$InlineInput.InlineInput(this.constants_,a)),b.hasInlineInput=!0):a.type===$.module$exports$Blockly$inputTypes.inputTypes.STATEMENT?(b.elements.push(new module$exports$Blockly$geras$StatementInput.StatementInput(this.constants_,a)),b.hasStatement=!0):a.type===$.module$exports$Blockly$inputTypes.inputTypes.VALUE? (b.elements.push(new module$exports$Blockly$blockRendering$ExternalValueInput.ExternalValueInput(this.constants_,a)),b.hasExternalInput=!0):a.type===$.module$exports$Blockly$inputTypes.inputTypes.DUMMY&&(b.minHeight=Math.max(b.minHeight,this.constants_.DUMMY_INPUT_MIN_HEIGHT),b.hasDummyInput=!0);this.isInline||null!==b.align||(b.align=a.align)}; module$exports$Blockly$geras$RenderInfo.RenderInfo.prototype.addElemSpacing_=function(){for(var a=!1,b=0,c;c=this.rows[b];b++)c.hasExternalInput&&(a=!0);for(b=0;c=this.rows[b];b++){var d=c.elements;c.elements=[];c.startsWithElemSpacer()&&c.elements.push(new module$exports$Blockly$blockRendering$InRowSpacer.InRowSpacer(this.constants_,this.getInRowSpacing_(null,d[0])));if(d.length){for(var e=0;ea||Math.abs(this.workspaceHeight_-d)>a)this.workspaceWidth_=c,this.workspaceHeight_=d,this.bubble_.setBubbleSize(c+ +a,d+a),this.svgDialog_.setAttribute("width",this.workspaceWidth_),this.svgDialog_.setAttribute("height",this.workspaceHeight_),this.workspace_.setCachedParentSvgSize(this.workspaceWidth_,this.workspaceHeight_);this.block_.RTL&&(a="translate("+this.workspaceWidth_+",0)",this.workspace_.getCanvas().setAttribute("transform",a));this.workspace_.resize()};$.module$exports$Blockly$Mutator.Mutator.prototype.onBubbleMove_=function(){this.workspace_&&this.workspace_.recordDragTargets()}; +$.module$exports$Blockly$Mutator.Mutator.prototype.setVisible=function(a){var b=this;if(a!==this.isVisible())if((0,module$exports$Blockly$Events$utils.fire)(new ((0,module$exports$Blockly$Events$utils.get)(module$exports$Blockly$Events$utils.BUBBLE_OPEN))(this.block_,a,"mutator")),a){this.bubble_=new module$exports$Blockly$Bubble.Bubble(this.block_.workspace,this.createEditor_(),this.block_.pathObject.svgPath,this.iconXY_,null,null);this.bubble_.setSvgId(this.block_.id);this.bubble_.registerMoveEvent(this.onBubbleMove_.bind(this)); +var c=this.workspace_.options.languageTree;a=this.workspace_.getFlyout();c&&(a.init(this.workspace_),a.show(c));this.rootBlock_=this.block_.decompose(this.workspace_);c=this.rootBlock_.getDescendants(!1);for(var d=0,e=void 0;e=c[d];d++)e.render();this.rootBlock_.setMovable(!1);this.rootBlock_.setDeletable(!1);a?(c=2*a.CORNER_RADIUS,a=this.rootBlock_.RTL?a.getWidth()+c:c):a=c=16;this.block_.RTL&&(a=-a);this.rootBlock_.moveBy(a,c);if(this.block_.saveConnections){var f=this.rootBlock_;this.block_.saveConnections(f); +this.sourceListener_=function(){b.block_&&b.block_.saveConnections(f)};this.block_.workspace.addChangeListener(this.sourceListener_)}this.resizeBubble_();this.workspace_.addChangeListener(this.workspaceChanged_.bind(this));this.updateWorkspace_();this.applyColour()}else this.svgDialog_=null,this.workspace_.dispose(),this.rootBlock_=this.workspace_=null,this.bubble_.dispose(),this.bubble_=null,this.workspaceHeight_=this.workspaceWidth_=0,this.sourceListener_&&(this.block_.workspace.removeChangeListener(this.sourceListener_), +this.sourceListener_=null)};$.module$exports$Blockly$Mutator.Mutator.prototype.workspaceChanged_=function(a){a.isUiEvent||a.type===module$exports$Blockly$Events$utils.CHANGE&&"disabled"===a.element||a.type===module$exports$Blockly$Events$utils.CREATE||this.updateWorkspace_()}; +$.module$exports$Blockly$Mutator.Mutator.prototype.updateWorkspace_=function(){if(!this.workspace_.isDragging())for(var a=this.workspace_.getTopBlocks(!1),b=0,c=void 0;c=a[b];b++){var d=c.getRelativeToSurfaceXY();20>d.y&&c.moveBy(0,20-d.y);if(c.RTL){var e=-20,f=this.workspace_.getFlyout();f&&(e-=f.getWidth());d.x>e&&c.moveBy(e-d.x,0)}else 20>d.x&&c.moveBy(20-d.x,0)}if(this.rootBlock_.workspace===this.workspace_){(a=(0,module$exports$Blockly$Events$utils.getGroup)())||(0,module$exports$Blockly$Events$utils.setGroup)(!0); +var g=this.block_;b=module$exports$Blockly$Events$BlockChange.BlockChange.getExtraBlockState_(g);c=g.rendered;g.rendered=!1;g.compose(this.rootBlock_);g.rendered=c;g.initSvg();g.rendered&&g.render();c=module$exports$Blockly$Events$BlockChange.BlockChange.getExtraBlockState_(g);if(b!==c){(0,module$exports$Blockly$Events$utils.fire)(new ((0,module$exports$Blockly$Events$utils.get)(module$exports$Blockly$Events$utils.CHANGE))(g,"mutation",null,b,c));var h=(0,module$exports$Blockly$Events$utils.getGroup)(); +setTimeout(function(){var k=(0,module$exports$Blockly$Events$utils.getGroup)();(0,module$exports$Blockly$Events$utils.setGroup)(h);g.bumpNeighbours();(0,module$exports$Blockly$Events$utils.setGroup)(k)},$.module$exports$Blockly$config.config.bumpDelay)}this.workspace_.isDragging()||this.resizeBubble_();(0,module$exports$Blockly$Events$utils.setGroup)(a)}};$.module$exports$Blockly$Mutator.Mutator.prototype.dispose=function(){this.block_.mutator=null;module$exports$Blockly$Icon.Icon.prototype.dispose.call(this)}; +$.module$exports$Blockly$Mutator.Mutator.prototype.updateBlockStyle=function(){var a=this.workspace_;if(a&&a.getAllBlocks(!1)){for(var b=a.getAllBlocks(!1),c=0,d;d=b[c];c++)d.setStyle(d.getStyleName());if(a=a.getFlyout())for(a=a.workspace_.getAllBlocks(!1),b=0;c=a[b];b++)c.setStyle(c.getStyleName())}}; +$.module$exports$Blockly$Mutator.Mutator.reconnect=function(a,b,c){if(!a||!a.getSourceBlock().workspace)return!1;c=b.getInput(c).connection;var d=a.targetBlock();return d&&d!==b||c.targetConnection===a?!1:(c.isConnected()&&c.disconnect(),c.connect(a),!0)};$.module$exports$Blockly$Mutator.Mutator.findParentWs=function(a){var b=null;if(a&&a.options){var c=a.options.parentWorkspace;a.isFlyout?c&&c.options&&(b=c.options.parentWorkspace):c&&(b=c)}return b};var module$exports$Blockly$Warning={Warning:function(a){module$exports$Blockly$Icon.Icon.call(this,a);this.createIcon();this.text_=Object.create(null);this.paragraphElement_=null;this.collapseHidden=!1}};$.$jscomp.inherits(module$exports$Blockly$Warning.Warning,module$exports$Blockly$Icon.Icon); module$exports$Blockly$Warning.Warning.prototype.drawIcon_=function(a){(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.PATH,{"class":"blocklyIconShape",d:"M2,15Q-1,15 0.5,12L6.5,1.7Q8,-1 9.5,1.7L15.5,12Q17,15 14,15z"},a);(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.PATH,{"class":"blocklyIconSymbol",d:"m7,4.8v3.16l0.27,2.27h1.46l0.27,-2.27v-3.16z"},a);(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.RECT, {"class":"blocklyIconSymbol",x:"7",y:"11",height:"2",width:"2"},a)};module$exports$Blockly$Warning.Warning.prototype.setVisible=function(a){a!==this.isVisible()&&((0,module$exports$Blockly$Events$utils.fire)(new ((0,module$exports$Blockly$Events$utils.get)(module$exports$Blockly$Events$utils.BUBBLE_OPEN))(this.block_,a,"warning")),a?this.createBubble_():this.disposeBubble_())}; module$exports$Blockly$Warning.Warning.prototype.createBubble_=function(){this.paragraphElement_=module$exports$Blockly$Bubble.Bubble.textToDom(this.getText());this.bubble_=module$exports$Blockly$Bubble.Bubble.createNonEditableBubble(this.paragraphElement_,this.block_,this.iconXY_);this.applyColour()};module$exports$Blockly$Warning.Warning.prototype.disposeBubble_=function(){this.bubble_.dispose();this.paragraphElement_=this.bubble_=null}; -module$exports$Blockly$Warning.Warning.prototype.setText=function(a,b){this.text_[b]!==a&&(a?this.text_[b]=a:delete this.text_[b],this.isVisible()&&(this.setVisible(!1),this.setVisible(!0)))};module$exports$Blockly$Warning.Warning.prototype.getText=function(){var a=[],b;for(b in this.text_)a.push(this.text_[b]);return a.join("\n")};module$exports$Blockly$Warning.Warning.prototype.dispose=function(){this.block_.warning=null;module$exports$Blockly$Icon.Icon.prototype.dispose.call(this)};var module$exports$Blockly$Comment={Comment:function(a){module$exports$Blockly$Comment.Comment.superClass_.constructor.call(this,a);this.model_=a.commentModel;this.model_.text=this.model_.text||"";this.cachedText_="";this.onInputWrapper_=this.onChangeWrapper_=this.onWheelWrapper_=this.onMouseUpWrapper_=null;this.createIcon()}};(0,$.module$exports$Blockly$utils$object.inherits)(module$exports$Blockly$Comment.Comment,module$exports$Blockly$Icon.Icon); +module$exports$Blockly$Warning.Warning.prototype.setText=function(a,b){this.text_[b]!==a&&(a?this.text_[b]=a:delete this.text_[b],this.isVisible()&&(this.setVisible(!1),this.setVisible(!0)))};module$exports$Blockly$Warning.Warning.prototype.getText=function(){var a=[],b;for(b in this.text_)a.push(this.text_[b]);return a.join("\n")};module$exports$Blockly$Warning.Warning.prototype.dispose=function(){this.block_.warning=null;module$exports$Blockly$Icon.Icon.prototype.dispose.call(this)};var module$exports$Blockly$Comment={Comment:function(a){module$exports$Blockly$Icon.Icon.call(this,a);this.model_=a.commentModel;this.model_.text=this.model_.text||"";this.cachedText_="";this.paragraphElement_=this.textarea_=this.foreignObject_=this.onInputWrapper_=this.onChangeWrapper_=this.onWheelWrapper_=this.onMouseUpWrapper_=null;this.createIcon()}};$.$jscomp.inherits(module$exports$Blockly$Comment.Comment,module$exports$Blockly$Icon.Icon); module$exports$Blockly$Comment.Comment.prototype.drawIcon_=function(a){(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.CIRCLE,{"class":"blocklyIconShape",r:"8",cx:"8",cy:"8"},a);(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.PATH,{"class":"blocklyIconSymbol",d:"m6.8,10h2c0.003,-0.617 0.271,-0.962 0.633,-1.266 2.875,-2.4050.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"}, a);(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.RECT,{"class":"blocklyIconSymbol",x:"6.8",y:"10.78",height:"2",width:"2"},a)}; module$exports$Blockly$Comment.Comment.prototype.createEditor_=function(){this.foreignObject_=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.FOREIGNOBJECT,{x:module$exports$Blockly$Bubble.Bubble.BORDER_WIDTH,y:module$exports$Blockly$Bubble.Bubble.BORDER_WIDTH},null);var a=document.createElementNS(module$exports$Blockly$utils$dom.HTML_NS,"body");a.setAttribute("xmlns",module$exports$Blockly$utils$dom.HTML_NS);a.className="blocklyMinimalBody";var b=this.textarea_= document.createElementNS(module$exports$Blockly$utils$dom.HTML_NS,"textarea");b.className="blocklyCommentTextarea";b.setAttribute("dir",this.block_.RTL?"RTL":"LTR");b.value=this.model_.text;this.resizeTextarea_();a.appendChild(b);this.foreignObject_.appendChild(a);this.onMouseUpWrapper_=(0,module$exports$Blockly$browserEvents.conditionalBind)(b,"mouseup",this,this.startEdit_,!0,!0);this.onWheelWrapper_=(0,module$exports$Blockly$browserEvents.conditionalBind)(b,"wheel",this,function(c){c.stopPropagation()}); this.onChangeWrapper_=(0,module$exports$Blockly$browserEvents.conditionalBind)(b,"change",this,function(c){this.cachedText_!==this.model_.text&&(0,module$exports$Blockly$Events$utils.fire)(new ((0,module$exports$Blockly$Events$utils.get)(module$exports$Blockly$Events$utils.CHANGE))(this.block_,"comment",null,this.cachedText_,this.model_.text))});this.onInputWrapper_=(0,module$exports$Blockly$browserEvents.conditionalBind)(b,"input",this,function(c){this.model_.text=b.value});setTimeout(b.focus.bind(b), -0);return this.foreignObject_};module$exports$Blockly$Comment.Comment.prototype.updateEditable=function(){module$exports$Blockly$Comment.Comment.superClass_.updateEditable.call(this);this.isVisible()&&(this.disposeBubble_(),this.createBubble_())};module$exports$Blockly$Comment.Comment.prototype.onBubbleResize_=function(){this.isVisible()&&(this.model_.size=this.bubble_.getBubbleSize(),this.resizeTextarea_())}; +0);return this.foreignObject_};module$exports$Blockly$Comment.Comment.prototype.updateEditable=function(){module$exports$Blockly$Icon.Icon.prototype.updateEditable.call(this);this.isVisible()&&(this.disposeBubble_(),this.createBubble_())};module$exports$Blockly$Comment.Comment.prototype.onBubbleResize_=function(){this.isVisible()&&(this.model_.size=this.bubble_.getBubbleSize(),this.resizeTextarea_())}; module$exports$Blockly$Comment.Comment.prototype.resizeTextarea_=function(){var a=this.model_.size,b=2*module$exports$Blockly$Bubble.Bubble.BORDER_WIDTH,c=a.width-b;a=a.height-b;this.foreignObject_.setAttribute("width",c);this.foreignObject_.setAttribute("height",a);this.textarea_.style.width=c-4+"px";this.textarea_.style.height=a-4+"px"}; module$exports$Blockly$Comment.Comment.prototype.setVisible=function(a){a!==this.isVisible()&&((0,module$exports$Blockly$Events$utils.fire)(new ((0,module$exports$Blockly$Events$utils.get)(module$exports$Blockly$Events$utils.BUBBLE_OPEN))(this.block_,a,"comment")),(this.model_.pinned=a)?this.createBubble_():this.disposeBubble_())}; module$exports$Blockly$Comment.Comment.prototype.createBubble_=function(){!this.block_.isEditable()||module$exports$Blockly$utils$userAgent.IE?this.createNonEditableBubble_():this.createEditableBubble_()}; @@ -1810,12 +1835,11 @@ module$exports$Blockly$Comment.Comment.prototype.createNonEditableBubble_=functi module$exports$Blockly$Comment.Comment.prototype.disposeBubble_=function(){this.onMouseUpWrapper_&&((0,module$exports$Blockly$browserEvents.unbind)(this.onMouseUpWrapper_),this.onMouseUpWrapper_=null);this.onWheelWrapper_&&((0,module$exports$Blockly$browserEvents.unbind)(this.onWheelWrapper_),this.onWheelWrapper_=null);this.onChangeWrapper_&&((0,module$exports$Blockly$browserEvents.unbind)(this.onChangeWrapper_),this.onChangeWrapper_=null);this.onInputWrapper_&&((0,module$exports$Blockly$browserEvents.unbind)(this.onInputWrapper_), this.onInputWrapper_=null);this.bubble_.dispose();this.paragraphElement_=this.foreignObject_=this.textarea_=this.bubble_=null};module$exports$Blockly$Comment.Comment.prototype.startEdit_=function(a){this.bubble_.promote()&&this.textarea_.focus();this.cachedText_=this.model_.text};module$exports$Blockly$Comment.Comment.prototype.getBubbleSize=function(){return this.model_.size}; module$exports$Blockly$Comment.Comment.prototype.setBubbleSize=function(a,b){this.bubble_?this.bubble_.setBubbleSize(a,b):(this.model_.size.width=a,this.model_.size.height=b)};module$exports$Blockly$Comment.Comment.prototype.updateText=function(){this.textarea_?this.textarea_.value=this.model_.text:this.paragraphElement_&&(this.paragraphElement_.firstChild.textContent=this.model_.text)};module$exports$Blockly$Comment.Comment.prototype.dispose=function(){this.block_.comment=null;module$exports$Blockly$Icon.Icon.prototype.dispose.call(this)}; -(0,module$exports$Blockly$Css.register)("\n .blocklyCommentTextarea {\n background-color: #fef49c;\n border: 0;\n display: block;\n margin: 0;\n outline: 0;\n padding: 3px;\n resize: none;\n text-overflow: hidden;\n }\n");var module$exports$Blockly$uiPosition={verticalPosition:{TOP:0,BOTTOM:1},horizontalPosition:{LEFT:0,RIGHT:1},bumpDirection:{UP:0,DOWN:1},getStartPositionRect:function(a,b,c,d,e,f){var g=f.scrollbar&&f.scrollbar.canScrollVertically();a.horizontal===module$exports$Blockly$uiPosition.horizontalPosition.LEFT?(c=e.absoluteMetrics.left+c,g&&f.RTL&&(c+=module$exports$Blockly$Scrollbar.Scrollbar.scrollbarThickness)):(c=e.absoluteMetrics.left+e.viewMetrics.width-b.width-c,g&&!f.RTL&&(c-=module$exports$Blockly$Scrollbar.Scrollbar.scrollbarThickness)); +(0,module$exports$Blockly$Css.register)("\n.blocklyCommentTextarea {\n background-color: #fef49c;\n border: 0;\n display: block;\n margin: 0;\n outline: 0;\n padding: 3px;\n resize: none;\n text-overflow: hidden;\n}\n");var module$exports$Blockly$sprite={SPRITE:{width:96,height:124,url:"sprites.png"}};var module$exports$Blockly$uiPosition={verticalPosition:{TOP:0,BOTTOM:1},horizontalPosition:{LEFT:0,RIGHT:1},bumpDirection:{UP:0,DOWN:1},getStartPositionRect:function(a,b,c,d,e,f){var g=f.scrollbar&&f.scrollbar.canScrollVertically();a.horizontal===module$exports$Blockly$uiPosition.horizontalPosition.LEFT?(c=e.absoluteMetrics.left+c,g&&f.RTL&&(c+=module$exports$Blockly$Scrollbar.Scrollbar.scrollbarThickness)):(c=e.absoluteMetrics.left+e.viewMetrics.width-b.width-c,g&&!f.RTL&&(c-=module$exports$Blockly$Scrollbar.Scrollbar.scrollbarThickness)); a.vertical===module$exports$Blockly$uiPosition.verticalPosition.TOP?a=e.absoluteMetrics.top+d:(a=e.absoluteMetrics.top+e.viewMetrics.height-b.height-d,f.scrollbar&&f.scrollbar.canScrollHorizontally()&&(a-=module$exports$Blockly$Scrollbar.Scrollbar.scrollbarThickness));return new module$exports$Blockly$utils$Rect.Rect(a,a+b.height,c,c+b.width)},getCornerOppositeToolbox:function(a,b){return{horizontal:b.toolboxMetrics.position===module$exports$Blockly$utils$toolbox.Position.LEFT||a.horizontalLayout&& !a.RTL?module$exports$Blockly$uiPosition.horizontalPosition.RIGHT:module$exports$Blockly$uiPosition.horizontalPosition.LEFT,vertical:b.toolboxMetrics.position===module$exports$Blockly$utils$toolbox.Position.BOTTOM?module$exports$Blockly$uiPosition.verticalPosition.TOP:module$exports$Blockly$uiPosition.verticalPosition.BOTTOM}},bumpPositionRect:function(a,b,c,d){for(var e=a.left,f=a.right-a.left,g=a.bottom-a.top,h=0;himage, .blocklyZoom>svg>image {\n opacity: .4;\n }\n\n .blocklyZoom>image:hover, .blocklyZoom>svg>image:hover {\n opacity: .6;\n }\n\n .blocklyZoom>image:active, .blocklyZoom>svg>image:active {\n opacity: .8;\n }\n");var module$exports$Blockly$WorkspaceComment={WorkspaceComment:function(a,b,c,d,e){this.id=e&&!a.getCommentById(e)?e:(0,module$exports$Blockly$utils$idGenerator.genUid)();a.addTopComment(this);this.xy_=new module$exports$Blockly$utils$Coordinate.Coordinate(0,0);this.height_=c;this.width_=d;this.workspace=a;this.RTL=a.RTL;this.editable_=this.movable_=this.deletable_=!0;this.content_=b;this.disposed_=!1;this.isComment=!0;module$exports$Blockly$WorkspaceComment.WorkspaceComment.fireCreateEvent(this)}}; +{width:32,height:32},b);(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.IMAGE,{width:module$exports$Blockly$sprite.SPRITE.width,height:module$exports$Blockly$sprite.SPRITE.height,y:-92,"clip-path":"url(#blocklyZoomresetClipPath"+a+")"},this.zoomResetGroup_).setAttributeNS(module$exports$Blockly$utils$dom.XLINK_NS,"xlink:href",this.workspace_.options.pathToMedia+module$exports$Blockly$sprite.SPRITE.url);this.onZoomResetWrapper_=(0,module$exports$Blockly$browserEvents.conditionalBind)(this.zoomResetGroup_, +"mousedown",null,this.resetZoom_.bind(this))}; +module$exports$Blockly$ZoomControls.ZoomControls.prototype.resetZoom_=function(a){this.workspace_.markFocused();var b=Math.log(this.workspace_.options.zoomOptions.startScale/this.workspace_.scale)/Math.log(this.workspace_.options.zoomOptions.scaleSpeed);this.workspace_.beginCanvasTransition();this.workspace_.zoomCenter(b);this.workspace_.scrollCenter();setTimeout(this.workspace_.endCanvasTransition.bind(this.workspace_),500);this.fireZoomEvent_();(0,module$exports$Blockly$Touch.clearTouchIdentifier)();a.stopPropagation(); +a.preventDefault()};module$exports$Blockly$ZoomControls.ZoomControls.prototype.fireZoomEvent_=function(){var a=new ((0,module$exports$Blockly$Events$utils.get)(module$exports$Blockly$Events$utils.CLICK))(null,this.workspace_.id,"zoom_controls");(0,module$exports$Blockly$Events$utils.fire)(a)};(0,module$exports$Blockly$Css.register)("\n.blocklyZoom>image, .blocklyZoom>svg>image {\n opacity: .4;\n}\n\n.blocklyZoom>image:hover, .blocklyZoom>svg>image:hover {\n opacity: .6;\n}\n\n.blocklyZoom>image:active, .blocklyZoom>svg>image:active {\n opacity: .8;\n}\n");var module$exports$Blockly$WorkspaceComment={WorkspaceComment:function(a,b,c,d,e){this.id=e&&!a.getCommentById(e)?e:(0,module$exports$Blockly$utils$idGenerator.genUid)();a.addTopComment(this);this.xy_=new module$exports$Blockly$utils$Coordinate.Coordinate(0,0);this.height_=c;this.width_=d;this.workspace=a;this.RTL=a.RTL;this.editable_=this.movable_=this.deletable_=!0;this.content_=b;this.disposed_=!1;this.isComment=!0;module$exports$Blockly$WorkspaceComment.WorkspaceComment.fireCreateEvent(this)}}; module$exports$Blockly$WorkspaceComment.WorkspaceComment.prototype.dispose=function(){this.disposed_||((0,module$exports$Blockly$Events$utils.isEnabled)()&&(0,module$exports$Blockly$Events$utils.fire)(new ((0,module$exports$Blockly$Events$utils.get)(module$exports$Blockly$Events$utils.COMMENT_DELETE))(this)),this.workspace.removeTopComment(this),this.disposed_=!0)};module$exports$Blockly$WorkspaceComment.WorkspaceComment.prototype.getHeight=function(){return this.height_}; module$exports$Blockly$WorkspaceComment.WorkspaceComment.prototype.setHeight=function(a){this.height_=a};module$exports$Blockly$WorkspaceComment.WorkspaceComment.prototype.getWidth=function(){return this.width_};module$exports$Blockly$WorkspaceComment.WorkspaceComment.prototype.setWidth=function(a){this.width_=a};module$exports$Blockly$WorkspaceComment.WorkspaceComment.prototype.getXY=function(){return new module$exports$Blockly$utils$Coordinate.Coordinate(this.xy_.x,this.xy_.y)}; module$exports$Blockly$WorkspaceComment.WorkspaceComment.prototype.moveBy=function(a,b){var c=new ((0,module$exports$Blockly$Events$utils.get)(module$exports$Blockly$Events$utils.COMMENT_MOVE))(this);this.xy_.translate(a,b);c.recordNew();(0,module$exports$Blockly$Events$utils.fire)(c)};module$exports$Blockly$WorkspaceComment.WorkspaceComment.prototype.isDeletable=function(){return this.deletable_&&!(this.workspace&&this.workspace.options.readOnly)}; @@ -1843,11 +1867,11 @@ module$exports$Blockly$WorkspaceComment.WorkspaceComment.prototype.toXmlWithXY=f module$exports$Blockly$WorkspaceComment.WorkspaceComment.fireCreateEvent=function(a){if((0,module$exports$Blockly$Events$utils.isEnabled)()){var b=(0,module$exports$Blockly$Events$utils.getGroup)();b||(0,module$exports$Blockly$Events$utils.setGroup)(!0);try{(0,module$exports$Blockly$Events$utils.fire)(new ((0,module$exports$Blockly$Events$utils.get)(module$exports$Blockly$Events$utils.COMMENT_CREATE))(a))}finally{b||(0,module$exports$Blockly$Events$utils.setGroup)(!1)}}}; module$exports$Blockly$WorkspaceComment.WorkspaceComment.fromXml=function(a,b){var c=module$exports$Blockly$WorkspaceComment.WorkspaceComment.parseAttributes(a);b=new module$exports$Blockly$WorkspaceComment.WorkspaceComment(b,c.content,c.h,c.w,c.id);c=parseInt(a.getAttribute("x"),10);a=parseInt(a.getAttribute("y"),10);isNaN(c)||isNaN(a)||b.moveBy(c,a);module$exports$Blockly$WorkspaceComment.WorkspaceComment.fireCreateEvent(b);return b}; module$exports$Blockly$WorkspaceComment.WorkspaceComment.parseAttributes=function(a){var b=a.getAttribute("h"),c=a.getAttribute("w");return{id:a.getAttribute("id"),h:b?parseInt(b,10):100,w:c?parseInt(c,10):100,x:parseInt(a.getAttribute("x"),10),y:parseInt(a.getAttribute("y"),10),content:a.textContent}};var module$exports$Blockly$WorkspaceCommentSvg={},module$contents$Blockly$WorkspaceCommentSvg_RESIZE_SIZE=8,module$contents$Blockly$WorkspaceCommentSvg_BORDER_RADIUS=3,module$contents$Blockly$WorkspaceCommentSvg_TEXTAREA_OFFSET=2; -module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg=function(a,b,c,d,e){this.onMouseMoveWrapper_=this.onMouseUpWrapper_=null;this.svgGroup_=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.G,{"class":"blocklyComment"},null);this.svgGroup_.translate_="";this.svgRect_=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.RECT,{"class":"blocklyCommentRect",x:0,y:0,rx:module$contents$Blockly$WorkspaceCommentSvg_BORDER_RADIUS, -ry:module$contents$Blockly$WorkspaceCommentSvg_BORDER_RADIUS});this.svgGroup_.appendChild(this.svgRect_);this.rendered_=!1;this.useDragSurface_=(0,module$exports$Blockly$utils$svgMath.is3dSupported)()&&!!a.getBlockDragSurface();module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.superClass_.constructor.call(this,a,b,c,d,e);this.render()};(0,$.module$exports$Blockly$utils$object.inherits)(module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg,module$exports$Blockly$WorkspaceComment.WorkspaceComment); -module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.DEFAULT_SIZE=100;module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.TOP_OFFSET=10; +module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg=function(a,b,c,d,e){module$exports$Blockly$WorkspaceComment.WorkspaceComment.call(this,a,b,c,d,e);this.onMouseMoveWrapper_=this.onMouseUpWrapper_=null;this.eventsInit_=!1;this.deleteIconBorder_=this.deleteGroup_=this.resizeGroup_=this.foreignObject_=this.svgHandleTarget_=this.svgRectTarget_=this.textarea_=null;this.autoLayout_=this.focused_=!1;this.svgGroup_=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.G, +{"class":"blocklyComment"},null);this.svgGroup_.translate_="";this.svgRect_=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.RECT,{"class":"blocklyCommentRect",x:0,y:0,rx:module$contents$Blockly$WorkspaceCommentSvg_BORDER_RADIUS,ry:module$contents$Blockly$WorkspaceCommentSvg_BORDER_RADIUS});this.svgGroup_.appendChild(this.svgRect_);this.rendered_=!1;this.useDragSurface_=(0,module$exports$Blockly$utils$svgMath.is3dSupported)()&&!!a.getBlockDragSurface();this.render()}; +$.$jscomp.inherits(module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg,module$exports$Blockly$WorkspaceComment.WorkspaceComment); module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.prototype.dispose=function(){this.disposed_||((0,$.module$exports$Blockly$common.getSelected)()===this&&(this.unselect(),this.workspace.cancelCurrentGesture()),(0,module$exports$Blockly$Events$utils.isEnabled)()&&(0,module$exports$Blockly$Events$utils.fire)(new ((0,module$exports$Blockly$Events$utils.get)(module$exports$Blockly$Events$utils.COMMENT_DELETE))(this)),(0,module$exports$Blockly$utils$dom.removeNode)(this.svgGroup_),this.disposeInternal_(), -(0,module$exports$Blockly$Events$utils.disable)(),module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.superClass_.dispose.call(this),(0,module$exports$Blockly$Events$utils.enable)())}; +(0,module$exports$Blockly$Events$utils.disable)(),module$exports$Blockly$WorkspaceComment.WorkspaceComment.prototype.dispose.call(this),(0,module$exports$Blockly$Events$utils.enable)())}; module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.prototype.initSvg=function(a){if(!this.workspace.rendered)throw TypeError("Workspace is headless.");this.workspace.options.readOnly||this.eventsInit_||((0,module$exports$Blockly$browserEvents.conditionalBind)(this.svgRectTarget_,"mousedown",this,this.pathMouseDown_),(0,module$exports$Blockly$browserEvents.conditionalBind)(this.svgHandleTarget_,"mousedown",this,this.pathMouseDown_));this.eventsInit_=!0;this.updateMovable();this.getSvgRoot().parentNode|| this.workspace.getBubbleCanvas().appendChild(this.getSvgRoot());!a&&this.textarea_&&this.textarea_.select()};module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.prototype.pathMouseDown_=function(a){var b=this.workspace.getGesture(a);b&&b.handleBubbleStart(a,this)}; module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.prototype.showContextMenu=function(a){if(!this.workspace.options.readOnly){var b=[];this.isDeletable()&&this.isMovable()&&(b.push((0,$.module$exports$Blockly$ContextMenu.commentDuplicateOption)(this)),b.push((0,$.module$exports$Blockly$ContextMenu.commentDeleteOption)(this)));(0,$.module$exports$Blockly$ContextMenu.show)(a,b,this.RTL)}}; @@ -1861,13 +1885,12 @@ d!==c)}return this.xy_=new module$exports$Blockly$utils$Coordinate.Coordinate(a, module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.prototype.translate=function(a,b){this.xy_=new module$exports$Blockly$utils$Coordinate.Coordinate(a,b);this.getSvgRoot().setAttribute("transform","translate("+a+","+b+")")};module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.prototype.moveToDragSurface=function(){if(this.useDragSurface_){var a=this.getRelativeToSurfaceXY();this.clearTransformAttributes_();this.workspace.getBlockDragSurface().translateSurface(a.x,a.y);this.workspace.getBlockDragSurface().setBlocksAndShow(this.getSvgRoot())}}; module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.prototype.moveDuringDrag=function(a,b){a?a.translateSurface(b.x,b.y):(this.svgGroup_.translate_="translate("+b.x+","+b.y+")",this.svgGroup_.setAttribute("transform",this.svgGroup_.translate_+this.svgGroup_.skew_))};module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.prototype.moveTo=function(a,b){this.translate(a,b)};module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.prototype.clearTransformAttributes_=function(){this.getSvgRoot().removeAttribute("transform")}; module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.prototype.getBoundingRectangle=function(){var a=this.getRelativeToSurfaceXY(),b=this.getHeightWidth(),c=a.y,d=a.y+b.height;if(this.RTL){var e=a.x-b.width;a=a.x}else e=a.x,a=a.x+b.width;return new module$exports$Blockly$utils$Rect.Rect(c,d,e,a)}; -module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.prototype.updateMovable=function(){this.isMovable()?(0,module$exports$Blockly$utils$dom.addClass)(this.svgGroup_,"blocklyDraggable"):(0,module$exports$Blockly$utils$dom.removeClass)(this.svgGroup_,"blocklyDraggable")};module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.prototype.setMovable=function(a){module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.superClass_.setMovable.call(this,a);this.updateMovable()}; -module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.prototype.setEditable=function(a){module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.superClass_.setEditable.call(this,a);this.textarea_&&(this.textarea_.readOnly=!a)}; +module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.prototype.updateMovable=function(){this.isMovable()?(0,module$exports$Blockly$utils$dom.addClass)(this.svgGroup_,"blocklyDraggable"):(0,module$exports$Blockly$utils$dom.removeClass)(this.svgGroup_,"blocklyDraggable")};module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.prototype.setMovable=function(a){module$exports$Blockly$WorkspaceComment.WorkspaceComment.prototype.setMovable.call(this,a);this.updateMovable()}; +module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.prototype.setEditable=function(a){module$exports$Blockly$WorkspaceComment.WorkspaceComment.prototype.setEditable.call(this,a);this.textarea_&&(this.textarea_.readOnly=!a)}; module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.prototype.setDragging=function(a){a?(a=this.getSvgRoot(),a.translate_="",a.skew_="",(0,module$exports$Blockly$utils$dom.addClass)(this.svgGroup_,"blocklyDragging")):(0,module$exports$Blockly$utils$dom.removeClass)(this.svgGroup_,"blocklyDragging")};module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.prototype.getSvgRoot=function(){return this.svgGroup_}; -module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.prototype.getContent=function(){return this.textarea_?this.textarea_.value:this.content_};module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.prototype.setContent=function(a){module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.superClass_.setContent.call(this,a);this.textarea_&&(this.textarea_.value=a)}; +module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.prototype.getContent=function(){return this.textarea_?this.textarea_.value:this.content_};module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.prototype.setContent=function(a){module$exports$Blockly$WorkspaceComment.WorkspaceComment.prototype.setContent.call(this,a);this.textarea_&&(this.textarea_.value=a)}; module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.prototype.setDeleteStyle=function(a){a?(0,module$exports$Blockly$utils$dom.addClass)(this.svgGroup_,"blocklyDraggingDelete"):(0,module$exports$Blockly$utils$dom.removeClass)(this.svgGroup_,"blocklyDraggingDelete")};module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.prototype.setAutoLayout=function(a){}; -module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.fromXml=function(a,b,c){(0,module$exports$Blockly$Events$utils.disable)();try{var d=module$exports$Blockly$WorkspaceComment.WorkspaceComment.parseAttributes(a);var e=new module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg(b,d.content,d.h,d.w,d.id);b.rendered&&(e.initSvg(!0),e.render());if(!isNaN(d.x)&&!isNaN(d.y))if(b.RTL){var f=c||b.getWidth();e.moveBy(f-d.x,d.y)}else e.moveBy(d.x,d.y)}finally{(0,module$exports$Blockly$Events$utils.enable)()}module$exports$Blockly$WorkspaceComment.WorkspaceComment.fireCreateEvent(e); -return e};module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.prototype.toXmlWithXY=function(a){var b;this.workspace.RTL&&(b=this.workspace.getWidth());a=this.toXml(a);var c=this.getRelativeToSurfaceXY();a.setAttribute("x",Math.round(this.workspace.RTL?b-c.x:c.x));a.setAttribute("y",Math.round(c.y));a.setAttribute("h",this.getHeight());a.setAttribute("w",this.getWidth());return a}; +module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.prototype.toXmlWithXY=function(a){var b;this.workspace.RTL&&(b=this.workspace.getWidth());a=this.toXml(a);var c=this.getRelativeToSurfaceXY();a.setAttribute("x",Math.round(this.workspace.RTL?b-c.x:c.x));a.setAttribute("y",Math.round(c.y));a.setAttribute("h",this.getHeight());a.setAttribute("w",this.getWidth());return a}; module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.prototype.toCopyData=function(){return{saveInfo:this.toXmlWithXY(),source:this.workspace,typeCounts:null}};module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.prototype.getHeightWidth=function(){return{width:this.getWidth(),height:this.getHeight()}}; module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.prototype.render=function(){if(!this.rendered_){var a=this.getHeightWidth();this.createEditor_();this.svgGroup_.appendChild(this.foreignObject_);this.svgHandleTarget_=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.RECT,{"class":"blocklyCommentHandleTarget",x:0,y:0});this.svgGroup_.appendChild(this.svgHandleTarget_);this.svgRectTarget_=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.RECT, {"class":"blocklyCommentTarget",x:0,y:0,rx:module$contents$Blockly$WorkspaceCommentSvg_BORDER_RADIUS,ry:module$contents$Blockly$WorkspaceCommentSvg_BORDER_RADIUS});this.svgGroup_.appendChild(this.svgRectTarget_);this.addResizeDom_();this.isDeletable()&&this.addDeleteDom_();this.setSize_(a.width,a.height);this.textarea_.value=this.content_;this.rendered_=!0;this.resizeGroup_&&(0,module$exports$Blockly$browserEvents.conditionalBind)(this.resizeGroup_,"mousedown",this,this.resizeMouseDown_);this.isDeletable()&& @@ -1891,17 +1914,17 @@ module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.prototype.setSize ") scale(-1 1)")):(this.resizeGroup_.setAttribute("transform","translate("+(a-module$contents$Blockly$WorkspaceCommentSvg_RESIZE_SIZE)+","+(b-module$contents$Blockly$WorkspaceCommentSvg_RESIZE_SIZE)+")"),this.deleteGroup_.setAttribute("transform","translate("+(a-module$contents$Blockly$WorkspaceCommentSvg_RESIZE_SIZE)+","+-module$contents$Blockly$WorkspaceCommentSvg_RESIZE_SIZE+")")));this.resizeComment_()}; module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.prototype.disposeInternal_=function(){this.svgHandleTarget_=this.svgRectTarget_=this.foreignObject_=this.textarea_=null;this.disposed_=!0}; module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.prototype.setFocus=function(){var a=this;this.focused_=!0;setTimeout(function(){a.disposed_||(a.textarea_.focus(),a.addFocus(),(0,module$exports$Blockly$utils$dom.addClass)(a.svgRectTarget_,"blocklyCommentTargetFocused"),(0,module$exports$Blockly$utils$dom.addClass)(a.svgHandleTarget_,"blocklyCommentHandleTargetFocused"))},0)}; -module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.prototype.blurFocus=function(){var a=this;this.focused_=!1;setTimeout(function(){a.disposed_||(a.textarea_.blur(),a.removeFocus(),(0,module$exports$Blockly$utils$dom.removeClass)(a.svgRectTarget_,"blocklyCommentTargetFocused"),(0,module$exports$Blockly$utils$dom.removeClass)(a.svgHandleTarget_,"blocklyCommentHandleTargetFocused"))},0)};(0,module$exports$Blockly$Css.register)("\n .blocklyCommentForeignObject {\n position: relative;\n z-index: 0;\n }\n\n .blocklyCommentRect {\n fill: #E7DE8E;\n stroke: #bcA903;\n stroke-width: 1px;\n }\n\n .blocklyCommentTarget {\n fill: transparent;\n stroke: #bcA903;\n }\n\n .blocklyCommentTargetFocused {\n fill: none;\n }\n\n .blocklyCommentHandleTarget {\n fill: none;\n }\n\n .blocklyCommentHandleTargetFocused {\n fill: transparent;\n }\n\n .blocklyFocused>.blocklyCommentRect {\n fill: #B9B272;\n stroke: #B9B272;\n }\n\n .blocklySelected>.blocklyCommentTarget {\n stroke: #fc3;\n stroke-width: 3px;\n }\n\n .blocklyCommentDeleteIcon {\n cursor: pointer;\n fill: #000;\n display: none;\n }\n\n .blocklySelected > .blocklyCommentDeleteIcon {\n display: block;\n }\n\n .blocklyDeleteIconShape {\n fill: #000;\n stroke: #000;\n stroke-width: 1px;\n }\n\n .blocklyDeleteIconShape.blocklyDeleteIconHighlighted {\n stroke: #fc3;\n }\n");var module$exports$Blockly$Trashcan={Trashcan:function(a){module$exports$Blockly$Trashcan.Trashcan.superClass_.constructor.call(this);this.workspace_=a;this.id="trashcan";this.contents_=[];this.flyout=null;0>=this.workspace_.options.maxTrashcanContents||(a=new module$exports$Blockly$Options.Options({scrollbars:!0,parentWorkspace:this.workspace_,rtl:this.workspace_.RTL,oneBasedIndex:this.workspace_.options.oneBasedIndex,renderer:this.workspace_.options.renderer,rendererOverrides:this.workspace_.options.rendererOverrides, -move:{scrollbars:!0}}),this.workspace_.horizontalLayout?(a.toolboxPosition=this.workspace_.toolboxPosition===module$exports$Blockly$utils$toolbox.Position.TOP?module$exports$Blockly$utils$toolbox.Position.BOTTOM:module$exports$Blockly$utils$toolbox.Position.TOP,this.flyout=new ((0,module$exports$Blockly$registry.getClassFromOptions)(module$exports$Blockly$registry.Type.FLYOUTS_HORIZONTAL_TOOLBOX,this.workspace_.options,!0))(a)):(a.toolboxPosition=this.workspace_.toolboxPosition===module$exports$Blockly$utils$toolbox.Position.RIGHT? -module$exports$Blockly$utils$toolbox.Position.LEFT:module$exports$Blockly$utils$toolbox.Position.RIGHT,this.flyout=new ((0,module$exports$Blockly$registry.getClassFromOptions)(module$exports$Blockly$registry.Type.FLYOUTS_VERTICAL_TOOLBOX,this.workspace_.options,!0))(a)),this.workspace_.addChangeListener(this.onDelete_.bind(this)))}};(0,$.module$exports$Blockly$utils$object.inherits)(module$exports$Blockly$Trashcan.Trashcan,module$exports$Blockly$DeleteArea.DeleteArea); -var module$contents$Blockly$Trashcan_WIDTH=47,module$contents$Blockly$Trashcan_BODY_HEIGHT=44,module$contents$Blockly$Trashcan_LID_HEIGHT=16,module$contents$Blockly$Trashcan_MARGIN_VERTICAL=20,module$contents$Blockly$Trashcan_MARGIN_HORIZONTAL=20,module$contents$Blockly$Trashcan_MARGIN_HOTSPOT=10,module$contents$Blockly$Trashcan_SPRITE_LEFT=0,module$contents$Blockly$Trashcan_SPRITE_TOP=32,module$contents$Blockly$Trashcan_HAS_BLOCKS_LID_ANGLE=.1,module$contents$Blockly$Trashcan_ANIMATION_LENGTH=80, -module$contents$Blockly$Trashcan_ANIMATION_FRAMES=4,module$contents$Blockly$Trashcan_OPACITY_MIN=.4,module$contents$Blockly$Trashcan_OPACITY_MAX=.8,module$contents$Blockly$Trashcan_MAX_LID_ANGLE=45;module$exports$Blockly$Trashcan.Trashcan.prototype.isLidOpen=!1;module$exports$Blockly$Trashcan.Trashcan.prototype.minOpenness_=0;module$exports$Blockly$Trashcan.Trashcan.prototype.svgGroup_=null;module$exports$Blockly$Trashcan.Trashcan.prototype.svgLid_=null; -module$exports$Blockly$Trashcan.Trashcan.prototype.lidTask_=0;module$exports$Blockly$Trashcan.Trashcan.prototype.lidOpen_=0;module$exports$Blockly$Trashcan.Trashcan.prototype.left_=0;module$exports$Blockly$Trashcan.Trashcan.prototype.top_=0;module$exports$Blockly$Trashcan.Trashcan.prototype.initialized_=!1; +module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.prototype.blurFocus=function(){var a=this;this.focused_=!1;setTimeout(function(){a.disposed_||(a.textarea_.blur(),a.removeFocus(),(0,module$exports$Blockly$utils$dom.removeClass)(a.svgRectTarget_,"blocklyCommentTargetFocused"),(0,module$exports$Blockly$utils$dom.removeClass)(a.svgHandleTarget_,"blocklyCommentHandleTargetFocused"))},0)}; +module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.fromXmlRendered=function(a,b,c){(0,module$exports$Blockly$Events$utils.disable)();try{var d=module$exports$Blockly$WorkspaceComment.WorkspaceComment.parseAttributes(a);var e=new module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg(b,d.content,d.h,d.w,d.id);b.rendered&&(e.initSvg(!0),e.render());if(!isNaN(d.x)&&!isNaN(d.y))if(b.RTL){var f=c||b.getWidth();e.moveBy(f-d.x,d.y)}else e.moveBy(d.x,d.y)}finally{(0,module$exports$Blockly$Events$utils.enable)()}module$exports$Blockly$WorkspaceComment.WorkspaceComment.fireCreateEvent(e); +return e};module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.DEFAULT_SIZE=100;module$exports$Blockly$WorkspaceCommentSvg.WorkspaceCommentSvg.TOP_OFFSET=10;(0,module$exports$Blockly$Css.register)("\n.blocklyCommentForeignObject {\n position: relative;\n z-index: 0;\n}\n\n.blocklyCommentRect {\n fill: #E7DE8E;\n stroke: #bcA903;\n stroke-width: 1px;\n}\n\n.blocklyCommentTarget {\n fill: transparent;\n stroke: #bcA903;\n}\n\n.blocklyCommentTargetFocused {\n fill: none;\n}\n\n.blocklyCommentHandleTarget {\n fill: none;\n}\n\n.blocklyCommentHandleTargetFocused {\n fill: transparent;\n}\n\n.blocklyFocused>.blocklyCommentRect {\n fill: #B9B272;\n stroke: #B9B272;\n}\n\n.blocklySelected>.blocklyCommentTarget {\n stroke: #fc3;\n stroke-width: 3px;\n}\n\n.blocklyCommentDeleteIcon {\n cursor: pointer;\n fill: #000;\n display: none;\n}\n\n.blocklySelected > .blocklyCommentDeleteIcon {\n display: block;\n}\n\n.blocklyDeleteIconShape {\n fill: #000;\n stroke: #000;\n stroke-width: 1px;\n}\n\n.blocklyDeleteIconShape.blocklyDeleteIconHighlighted {\n stroke: #fc3;\n}\n");var module$exports$Blockly$Trashcan={Trashcan:function(a){module$exports$Blockly$DeleteArea.DeleteArea.call(this);this.workspace_=a;this.id="trashcan";this.contents_=[];this.flyout=null;0>=this.workspace_.options.maxTrashcanContents||(this.isLidOpen=!1,this.minOpenness_=0,this.svgLid_=this.svgGroup_=null,this.top_=this.left_=this.lidOpen_=this.lidTask_=0,this.initialized_=!1,a=new module$exports$Blockly$Options.Options({scrollbars:!0,parentWorkspace:this.workspace_,rtl:this.workspace_.RTL,oneBasedIndex:this.workspace_.options.oneBasedIndex, +renderer:this.workspace_.options.renderer,rendererOverrides:this.workspace_.options.rendererOverrides,move:{scrollbars:!0}}),this.workspace_.horizontalLayout?(a.toolboxPosition=this.workspace_.toolboxPosition===module$exports$Blockly$utils$toolbox.Position.TOP?module$exports$Blockly$utils$toolbox.Position.BOTTOM:module$exports$Blockly$utils$toolbox.Position.TOP,this.flyout=new ((0,module$exports$Blockly$registry.getClassFromOptions)(module$exports$Blockly$registry.Type.FLYOUTS_HORIZONTAL_TOOLBOX, +this.workspace_.options,!0))(a)):(a.toolboxPosition=this.workspace_.toolboxPosition===module$exports$Blockly$utils$toolbox.Position.RIGHT?module$exports$Blockly$utils$toolbox.Position.LEFT:module$exports$Blockly$utils$toolbox.Position.RIGHT,this.flyout=new ((0,module$exports$Blockly$registry.getClassFromOptions)(module$exports$Blockly$registry.Type.FLYOUTS_VERTICAL_TOOLBOX,this.workspace_.options,!0))(a)),this.workspace_.addChangeListener(this.onDelete_.bind(this)))}}; +$.$jscomp.inherits(module$exports$Blockly$Trashcan.Trashcan,module$exports$Blockly$DeleteArea.DeleteArea); module$exports$Blockly$Trashcan.Trashcan.prototype.createDom=function(){this.svgGroup_=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.G,{"class":"blocklyTrash"},null);var a=String(Math.random()).substring(2);var b=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.CLIPPATH,{id:"blocklyTrashBodyClipPath"+a},this.svgGroup_);(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.RECT, -{width:module$contents$Blockly$Trashcan_WIDTH,height:module$contents$Blockly$Trashcan_BODY_HEIGHT,y:module$contents$Blockly$Trashcan_LID_HEIGHT},b);var c=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.IMAGE,{width:$.module$exports$Blockly$internalConstants.SPRITE.width,x:-module$contents$Blockly$Trashcan_SPRITE_LEFT,height:$.module$exports$Blockly$internalConstants.SPRITE.height,y:-module$contents$Blockly$Trashcan_SPRITE_TOP,"clip-path":"url(#blocklyTrashBodyClipPath"+ -a+")"},this.svgGroup_);c.setAttributeNS(module$exports$Blockly$utils$dom.XLINK_NS,"xlink:href",this.workspace_.options.pathToMedia+$.module$exports$Blockly$internalConstants.SPRITE.url);b=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.CLIPPATH,{id:"blocklyTrashLidClipPath"+a},this.svgGroup_);(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.RECT,{width:module$contents$Blockly$Trashcan_WIDTH,height:module$contents$Blockly$Trashcan_LID_HEIGHT}, -b);this.svgLid_=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.IMAGE,{width:$.module$exports$Blockly$internalConstants.SPRITE.width,x:-module$contents$Blockly$Trashcan_SPRITE_LEFT,height:$.module$exports$Blockly$internalConstants.SPRITE.height,y:-module$contents$Blockly$Trashcan_SPRITE_TOP,"clip-path":"url(#blocklyTrashLidClipPath"+a+")"},this.svgGroup_);this.svgLid_.setAttributeNS(module$exports$Blockly$utils$dom.XLINK_NS,"xlink:href",this.workspace_.options.pathToMedia+ -$.module$exports$Blockly$internalConstants.SPRITE.url);(0,module$exports$Blockly$browserEvents.bind)(this.svgGroup_,"mousedown",this,this.blockMouseDownWhenOpenable_);(0,module$exports$Blockly$browserEvents.bind)(this.svgGroup_,"mouseup",this,this.click);(0,module$exports$Blockly$browserEvents.bind)(c,"mouseover",this,this.mouseOver_);(0,module$exports$Blockly$browserEvents.bind)(c,"mouseout",this,this.mouseOut_);this.animateLid_();return this.svgGroup_}; +{width:module$contents$Blockly$Trashcan_WIDTH,height:module$contents$Blockly$Trashcan_BODY_HEIGHT,y:module$contents$Blockly$Trashcan_LID_HEIGHT},b);var c=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.IMAGE,{width:module$exports$Blockly$sprite.SPRITE.width,x:-module$contents$Blockly$Trashcan_SPRITE_LEFT,height:module$exports$Blockly$sprite.SPRITE.height,y:-module$contents$Blockly$Trashcan_SPRITE_TOP,"clip-path":"url(#blocklyTrashBodyClipPath"+a+")"},this.svgGroup_); +c.setAttributeNS(module$exports$Blockly$utils$dom.XLINK_NS,"xlink:href",this.workspace_.options.pathToMedia+module$exports$Blockly$sprite.SPRITE.url);b=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.CLIPPATH,{id:"blocklyTrashLidClipPath"+a},this.svgGroup_);(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.RECT,{width:module$contents$Blockly$Trashcan_WIDTH,height:module$contents$Blockly$Trashcan_LID_HEIGHT},b);this.svgLid_= +(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.IMAGE,{width:module$exports$Blockly$sprite.SPRITE.width,x:-module$contents$Blockly$Trashcan_SPRITE_LEFT,height:module$exports$Blockly$sprite.SPRITE.height,y:-module$contents$Blockly$Trashcan_SPRITE_TOP,"clip-path":"url(#blocklyTrashLidClipPath"+a+")"},this.svgGroup_);this.svgLid_.setAttributeNS(module$exports$Blockly$utils$dom.XLINK_NS,"xlink:href",this.workspace_.options.pathToMedia+module$exports$Blockly$sprite.SPRITE.url); +(0,module$exports$Blockly$browserEvents.bind)(this.svgGroup_,"mousedown",this,this.blockMouseDownWhenOpenable_);(0,module$exports$Blockly$browserEvents.bind)(this.svgGroup_,"mouseup",this,this.click);(0,module$exports$Blockly$browserEvents.bind)(c,"mouseover",this,this.mouseOver_);(0,module$exports$Blockly$browserEvents.bind)(c,"mouseout",this,this.mouseOut_);this.animateLid_();return this.svgGroup_}; module$exports$Blockly$Trashcan.Trashcan.prototype.init=function(){0=this.workspace_.options.maxTrashcanContents||a.type!==module$exports$Blockly$Events$utils.DELETE||a.wasShadow)&&(a=this.cleanBlockJson_(a.oldJson),-1===this.contents_.indexOf(a))){for(this.contents_.unshift(a);this.contents_.length>this.workspace_.options.maxTrashcanContents;)this.contents_.pop();this.setMinOpenness_(module$contents$Blockly$Trashcan_HAS_BLOCKS_LID_ANGLE)}}; -module$exports$Blockly$Trashcan.Trashcan.prototype.cleanBlockJson_=function(a){function b(c){if(c){delete c.id;delete c.x;delete c.y;delete c.enabled;if(c.icons&&c.icons.comment){var d=c.icons.comment;delete d.height;delete d.width;delete d.pinned}d=c.inputs;for(var e in d){var f=d[e];b(f.block);b(f.shadow)}c.next&&(c=c.next,b(c.block),b(c.shadow))}}a=JSON.parse(JSON.stringify(a));b(a);a.kind="BLOCK";return JSON.stringify(a)};var module$exports$Blockly$FlyoutButton={FlyoutButton:function(a,b,c,d){this.workspace_=a;this.targetWorkspace_=b;this.text_=c.text;this.position_=new module$exports$Blockly$utils$Coordinate.Coordinate(0,0);this.isLabel_=d;this.callbackKey_=c.callbackKey||c.callbackkey;this.cssClass_=c["web-class"]||null;this.onMouseUpWrapper_=null;this.info=c}};module$exports$Blockly$FlyoutButton.FlyoutButton.MARGIN_X=5;module$exports$Blockly$FlyoutButton.FlyoutButton.MARGIN_Y=2; -module$exports$Blockly$FlyoutButton.FlyoutButton.prototype.width=0;module$exports$Blockly$FlyoutButton.FlyoutButton.prototype.height=0; +module$exports$Blockly$Trashcan.Trashcan.prototype.onDelete_=function(a){if(!(0>=this.workspace_.options.maxTrashcanContents||a.type!==module$exports$Blockly$Events$utils.DELETE||a.type!==module$exports$Blockly$Events$utils.DELETE||a.wasShadow)&&(a=this.cleanBlockJson_(a.oldJson),-1===this.contents_.indexOf(a))){for(this.contents_.unshift(a);this.contents_.length>this.workspace_.options.maxTrashcanContents;)this.contents_.pop();this.setMinOpenness_(module$contents$Blockly$Trashcan_HAS_BLOCKS_LID_ANGLE)}}; +module$exports$Blockly$Trashcan.Trashcan.prototype.cleanBlockJson_=function(a){function b(c){if(c){delete c.id;delete c.x;delete c.y;delete c.enabled;if(c.icons&&c.icons.comment){var d=c.icons.comment;delete d.height;delete d.width;delete d.pinned}d=c.inputs;for(var e in d){var f=d[e];b(f.block);b(f.shadow)}c.next&&(c=c.next,b(c.block),b(c.shadow))}}a=JSON.parse(JSON.stringify(a));b(a);a.kind="BLOCK";return JSON.stringify(a)}; +var module$contents$Blockly$Trashcan_WIDTH=47,module$contents$Blockly$Trashcan_BODY_HEIGHT=44,module$contents$Blockly$Trashcan_LID_HEIGHT=16,module$contents$Blockly$Trashcan_MARGIN_VERTICAL=20,module$contents$Blockly$Trashcan_MARGIN_HORIZONTAL=20,module$contents$Blockly$Trashcan_MARGIN_HOTSPOT=10,module$contents$Blockly$Trashcan_SPRITE_LEFT=0,module$contents$Blockly$Trashcan_SPRITE_TOP=32,module$contents$Blockly$Trashcan_HAS_BLOCKS_LID_ANGLE=.1,module$contents$Blockly$Trashcan_ANIMATION_LENGTH=80, +module$contents$Blockly$Trashcan_ANIMATION_FRAMES=4,module$contents$Blockly$Trashcan_OPACITY_MIN=.4,module$contents$Blockly$Trashcan_OPACITY_MAX=.8,module$contents$Blockly$Trashcan_MAX_LID_ANGLE=45;var module$exports$Blockly$FlyoutButton={FlyoutButton:function(a,b,c,d){this.workspace_=a;this.targetWorkspace_=b;this.text_=c.text;this.position_=new module$exports$Blockly$utils$Coordinate.Coordinate(0,0);this.isLabel_=d;this.callbackKey_=c.callbackKey||c.callbackkey;this.cssClass_=c["web-class"]||null;this.onMouseUpWrapper_=null;this.info=c;this.height=this.width=0;this.svgText_=this.svgGroup_=null}}; module$exports$Blockly$FlyoutButton.FlyoutButton.prototype.createDom=function(){var a=this.isLabel_?"blocklyFlyoutLabel":"blocklyFlyoutButton";this.cssClass_&&(a+=" "+this.cssClass_);this.svgGroup_=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.G,{"class":a},this.workspace_.getCanvas());var b;this.isLabel_||(b=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.RECT,{"class":"blocklyFlyoutButtonShadow",rx:4,ry:4,x:1, y:1},this.svgGroup_));a=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.RECT,{"class":this.isLabel_?"blocklyFlyoutLabelBackground":"blocklyFlyoutButtonBackground",rx:4,ry:4},this.svgGroup_);var c=(0,module$exports$Blockly$utils$dom.createSvgElement)(module$exports$Blockly$utils$Svg.Svg.TEXT,{"class":this.isLabel_?"blocklyFlyoutLabelText":"blocklyText",x:0,y:0,"text-anchor":"middle"},this.svgGroup_),d=(0,module$exports$Blockly$utils$parsing.replaceMessageReferences)(this.text_); this.workspace_.RTL&&(d+="\u200f");c.textContent=d;this.isLabel_&&(this.svgText_=c,this.workspace_.getThemeManager().subscribe(this.svgText_,"flyoutForegroundColour","fill"));var e=(0,module$exports$Blockly$utils$style.getComputedStyle)(c,"fontSize"),f=(0,module$exports$Blockly$utils$style.getComputedStyle)(c,"fontWeight"),g=(0,module$exports$Blockly$utils$style.getComputedStyle)(c,"fontFamily");this.width=(0,module$exports$Blockly$utils$dom.getFastTextWidthWithSizeString)(c,e,f,g);d=(0,module$exports$Blockly$utils$dom.measureFontMetrics)(d, -e,f,g);this.height=d.height;this.isLabel_||(this.width+=2*module$exports$Blockly$FlyoutButton.FlyoutButton.MARGIN_X,this.height+=2*module$exports$Blockly$FlyoutButton.FlyoutButton.MARGIN_Y,b.setAttribute("width",this.width),b.setAttribute("height",this.height));a.setAttribute("width",this.width);a.setAttribute("height",this.height);c.setAttribute("x",this.width/2);c.setAttribute("y",this.height/2-d.height/2+d.baseline);this.updateTransform_();this.onMouseUpWrapper_=(0,module$exports$Blockly$browserEvents.conditionalBind)(this.svgGroup_, +e,f,g);this.height=d.height;this.isLabel_||(this.width+=2*module$exports$Blockly$FlyoutButton.FlyoutButton.TEXT_MARGIN_X,this.height+=2*module$exports$Blockly$FlyoutButton.FlyoutButton.TEXT_MARGIN_Y,b.setAttribute("width",this.width),b.setAttribute("height",this.height));a.setAttribute("width",this.width);a.setAttribute("height",this.height);c.setAttribute("x",this.width/2);c.setAttribute("y",this.height/2-d.height/2+d.baseline);this.updateTransform_();this.onMouseUpWrapper_=(0,module$exports$Blockly$browserEvents.conditionalBind)(this.svgGroup_, "mouseup",this,this.onMouseUp_);return this.svgGroup_};module$exports$Blockly$FlyoutButton.FlyoutButton.prototype.show=function(){this.updateTransform_();this.svgGroup_.setAttribute("display","block")};module$exports$Blockly$FlyoutButton.FlyoutButton.prototype.updateTransform_=function(){this.svgGroup_.setAttribute("transform","translate("+this.position_.x+","+this.position_.y+")")}; module$exports$Blockly$FlyoutButton.FlyoutButton.prototype.moveTo=function(a,b){this.position_.x=a;this.position_.y=b;this.updateTransform_()};module$exports$Blockly$FlyoutButton.FlyoutButton.prototype.isLabel=function(){return this.isLabel_};module$exports$Blockly$FlyoutButton.FlyoutButton.prototype.getPosition=function(){return this.position_};module$exports$Blockly$FlyoutButton.FlyoutButton.prototype.getButtonText=function(){return this.text_}; module$exports$Blockly$FlyoutButton.FlyoutButton.prototype.getTargetWorkspace=function(){return this.targetWorkspace_};module$exports$Blockly$FlyoutButton.FlyoutButton.prototype.dispose=function(){this.onMouseUpWrapper_&&(0,module$exports$Blockly$browserEvents.unbind)(this.onMouseUpWrapper_);this.svgGroup_&&(0,module$exports$Blockly$utils$dom.removeNode)(this.svgGroup_);this.svgText_&&this.workspace_.getThemeManager().unsubscribe(this.svgText_)}; -module$exports$Blockly$FlyoutButton.FlyoutButton.prototype.onMouseUp_=function(a){(a=this.targetWorkspace_.getGesture(a))&&a.cancel();this.isLabel_&&this.callbackKey_?console.warn("Labels should not have callbacks. Label text: "+this.text_):this.isLabel_||this.callbackKey_&&this.targetWorkspace_.getButtonCallback(this.callbackKey_)?this.isLabel_||this.targetWorkspace_.getButtonCallback(this.callbackKey_)(this):console.warn("Buttons should have callbacks. Button text: "+this.text_)};(0,module$exports$Blockly$Css.register)("\n .blocklyFlyoutButton {\n fill: #888;\n cursor: default;\n }\n\n .blocklyFlyoutButtonShadow {\n fill: #666;\n }\n\n .blocklyFlyoutButton:hover {\n fill: #aaa;\n }\n\n .blocklyFlyoutLabel {\n cursor: default;\n }\n\n .blocklyFlyoutLabelBackground {\n opacity: 0;\n }\n");var module$exports$Blockly$BlocklyOptions={BlocklyOptions:function(){}};var module$exports$Blockly$VariablesDynamic={CATEGORY_NAME:"VARIABLE_DYNAMIC",onCreateVariableButtonClick_String:function(a){(0,$.module$exports$Blockly$Variables.createVariableButtonHandler)(a.getTargetWorkspace(),void 0,"String")},onCreateVariableButtonClick_Number:function(a){(0,$.module$exports$Blockly$Variables.createVariableButtonHandler)(a.getTargetWorkspace(),void 0,"Number")},onCreateVariableButtonClick_Colour:function(a){(0,$.module$exports$Blockly$Variables.createVariableButtonHandler)(a.getTargetWorkspace(), +module$exports$Blockly$FlyoutButton.FlyoutButton.prototype.onMouseUp_=function(a){(a=this.targetWorkspace_.getGesture(a))&&a.cancel();this.isLabel_&&this.callbackKey_?console.warn("Labels should not have callbacks. Label text: "+this.text_):this.isLabel_||this.callbackKey_&&this.targetWorkspace_.getButtonCallback(this.callbackKey_)?this.isLabel_||this.targetWorkspace_.getButtonCallback(this.callbackKey_)(this):console.warn("Buttons should have callbacks. Button text: "+this.text_)}; +module$exports$Blockly$FlyoutButton.FlyoutButton.TEXT_MARGIN_X=5;module$exports$Blockly$FlyoutButton.FlyoutButton.TEXT_MARGIN_Y=2;(0,module$exports$Blockly$Css.register)("\n.blocklyFlyoutButton {\n fill: #888;\n cursor: default;\n}\n\n.blocklyFlyoutButtonShadow {\n fill: #666;\n}\n\n.blocklyFlyoutButton:hover {\n fill: #aaa;\n}\n\n.blocklyFlyoutLabel {\n cursor: default;\n}\n\n.blocklyFlyoutLabelBackground {\n opacity: 0;\n}\n");var module$exports$Blockly$BlocklyOptions={BlocklyOptions:function(){}};var module$exports$Blockly$VariablesDynamic={CATEGORY_NAME:"VARIABLE_DYNAMIC",onCreateVariableButtonClick_String:function(a){(0,$.module$exports$Blockly$Variables.createVariableButtonHandler)(a.getTargetWorkspace(),void 0,"String")},onCreateVariableButtonClick_Number:function(a){(0,$.module$exports$Blockly$Variables.createVariableButtonHandler)(a.getTargetWorkspace(),void 0,"Number")},onCreateVariableButtonClick_Colour:function(a){(0,$.module$exports$Blockly$Variables.createVariableButtonHandler)(a.getTargetWorkspace(), void 0,"Colour")},flyoutCategory:function(a){var b=[],c=document.createElement("button");c.setAttribute("text",$.module$exports$Blockly$Msg.Msg.NEW_STRING_VARIABLE);c.setAttribute("callbackKey","CREATE_VARIABLE_STRING");b.push(c);c=document.createElement("button");c.setAttribute("text",$.module$exports$Blockly$Msg.Msg.NEW_NUMBER_VARIABLE);c.setAttribute("callbackKey","CREATE_VARIABLE_NUMBER");b.push(c);c=document.createElement("button");c.setAttribute("text",$.module$exports$Blockly$Msg.Msg.NEW_COLOUR_VARIABLE); c.setAttribute("callbackKey","CREATE_VARIABLE_COLOUR");b.push(c);a.registerButtonCallback("CREATE_VARIABLE_STRING",module$exports$Blockly$VariablesDynamic.onCreateVariableButtonClick_String);a.registerButtonCallback("CREATE_VARIABLE_NUMBER",module$exports$Blockly$VariablesDynamic.onCreateVariableButtonClick_Number);a.registerButtonCallback("CREATE_VARIABLE_COLOUR",module$exports$Blockly$VariablesDynamic.onCreateVariableButtonClick_Colour);a=(0,module$exports$Blockly$VariablesDynamic.flyoutCategoryBlocks)(a); -return b=b.concat(a)},flyoutCategoryBlocks:function(a){a=a.getAllVariables();var b=[];if(0a||Math.abs(this.workspaceHeight_-d)>a)this.workspaceWidth_=c,this.workspaceHeight_=d,this.bubble_.setBubbleSize(c+ -a,d+a),this.svgDialog_.setAttribute("width",this.workspaceWidth_),this.svgDialog_.setAttribute("height",this.workspaceHeight_),this.workspace_.setCachedParentSvgSize(this.workspaceWidth_,this.workspaceHeight_);this.block_.RTL&&(a="translate("+this.workspaceWidth_+",0)",this.workspace_.getCanvas().setAttribute("transform",a));this.workspace_.resize()};$.module$exports$Blockly$Mutator.Mutator.prototype.onBubbleMove_=function(){this.workspace_&&this.workspace_.recordDragTargets()}; -$.module$exports$Blockly$Mutator.Mutator.prototype.setVisible=function(a){if(a!==this.isVisible())if((0,module$exports$Blockly$Events$utils.fire)(new ((0,module$exports$Blockly$Events$utils.get)(module$exports$Blockly$Events$utils.BUBBLE_OPEN))(this.block_,a,"mutator")),a){this.bubble_=new module$exports$Blockly$Bubble.Bubble(this.block_.workspace,this.createEditor_(),this.block_.pathObject.svgPath,this.iconXY_,null,null);this.bubble_.setSvgId(this.block_.id);this.bubble_.registerMoveEvent(this.onBubbleMove_.bind(this)); -var b=this.workspace_.options.languageTree;a=this.workspace_.getFlyout();b&&(a.init(this.workspace_),a.show(b));this.rootBlock_=this.block_.decompose(this.workspace_);b=this.rootBlock_.getDescendants(!1);for(var c=0,d=void 0;d=b[c];c++)d.render();this.rootBlock_.setMovable(!1);this.rootBlock_.setDeletable(!1);a?(b=2*a.CORNER_RADIUS,a=this.rootBlock_.RTL?a.getWidth()+b:b):a=b=16;this.block_.RTL&&(a=-a);this.rootBlock_.moveBy(a,b);if(this.block_.saveConnections){var e=this,f=this.block_;f.saveConnections(this.rootBlock_); -this.sourceListener_=function(){f.saveConnections(e.rootBlock_)};this.block_.workspace.addChangeListener(this.sourceListener_)}this.resizeBubble_();this.workspace_.addChangeListener(this.workspaceChanged_.bind(this));this.updateWorkspace_();this.applyColour()}else this.svgDialog_=null,this.workspace_.dispose(),this.rootBlock_=this.workspace_=null,this.bubble_.dispose(),this.bubble_=null,this.workspaceHeight_=this.workspaceWidth_=0,this.sourceListener_&&(this.block_.workspace.removeChangeListener(this.sourceListener_), -this.sourceListener_=null)};$.module$exports$Blockly$Mutator.Mutator.prototype.workspaceChanged_=function(a){a.isUiEvent||a.type===module$exports$Blockly$Events$utils.CHANGE&&"disabled"===a.element||a.type===module$exports$Blockly$Events$utils.CREATE||this.updateWorkspace_()}; -$.module$exports$Blockly$Mutator.Mutator.prototype.updateWorkspace_=function(){if(!this.workspace_.isDragging())for(var a=this.workspace_.getTopBlocks(!1),b=0,c=void 0;c=a[b];b++){var d=c.getRelativeToSurfaceXY();20>d.y&&c.moveBy(0,20-d.y);if(c.RTL){var e=-20,f=this.workspace_.getFlyout();f&&(e-=f.getWidth());d.x>e&&c.moveBy(e-d.x,0)}else 20>d.x&&c.moveBy(20-d.x,0)}if(this.rootBlock_.workspace===this.workspace_){(0,module$exports$Blockly$Events$utils.setGroup)(!0);var g=this.block_;a=module$exports$Blockly$Events$BlockChange.BlockChange.getExtraBlockState_(g); -b=g.rendered;g.rendered=!1;g.compose(this.rootBlock_);g.rendered=b;g.initSvg();g.rendered&&g.render();b=module$exports$Blockly$Events$BlockChange.BlockChange.getExtraBlockState_(g);if(a!==b){(0,module$exports$Blockly$Events$utils.fire)(new ((0,module$exports$Blockly$Events$utils.get)(module$exports$Blockly$Events$utils.CHANGE))(g,"mutation",null,a,b));var h=(0,module$exports$Blockly$Events$utils.getGroup)();setTimeout(function(){(0,module$exports$Blockly$Events$utils.setGroup)(h);g.bumpNeighbours(); -(0,module$exports$Blockly$Events$utils.setGroup)(!1)},$.module$exports$Blockly$internalConstants.BUMP_DELAY)}this.workspace_.isDragging()||this.resizeBubble_();(0,module$exports$Blockly$Events$utils.setGroup)(!1)}};$.module$exports$Blockly$Mutator.Mutator.prototype.dispose=function(){this.block_.mutator=null;module$exports$Blockly$Icon.Icon.prototype.dispose.call(this)}; -$.module$exports$Blockly$Mutator.Mutator.prototype.updateBlockStyle=function(){var a=this.workspace_;if(a&&a.getAllBlocks(!1)){for(var b=a.getAllBlocks(!1),c=0,d;d=b[c];c++)d.setStyle(d.getStyleName());if(a=a.getFlyout())for(a=a.workspace_.getAllBlocks(!1),b=0;c=a[b];b++)c.setStyle(c.getStyleName())}}; -$.module$exports$Blockly$Mutator.Mutator.reconnect=function(a,b,c){if(!a||!a.getSourceBlock().workspace)return!1;c=b.getInput(c).connection;var d=a.targetBlock();return d&&d!==b||c.targetConnection===a?!1:(c.isConnected()&&c.disconnect(),c.connect(a),!0)};$.module$exports$Blockly$Mutator.Mutator.findParentWs=function(a){var b=null;if(a&&a.options){var c=a.options.parentWorkspace;a.isFlyout?c&&c.options&&(b=c.options.parentWorkspace):c&&(b=c)}return b};$.Blockly={VERSION:"7.20211209.4"};$.Blockly.ALIGN_LEFT=$.module$exports$Blockly$Input.Align.LEFT;$.Blockly.ALIGN_CENTRE=$.module$exports$Blockly$Input.Align.CENTRE;$.Blockly.ALIGN_RIGHT=$.module$exports$Blockly$Input.Align.RIGHT;$.Blockly.INPUT_VALUE=$.module$exports$Blockly$ConnectionType.ConnectionType.INPUT_VALUE;$.Blockly.OUTPUT_VALUE=$.module$exports$Blockly$ConnectionType.ConnectionType.OUTPUT_VALUE;$.Blockly.NEXT_STATEMENT=$.module$exports$Blockly$ConnectionType.ConnectionType.NEXT_STATEMENT; +"mutation",null,f,g)),(0,module$exports$Blockly$Events$utils.setRecordUndo)(b))}};$.module$exports$Blockly$Procedures.getDefinition=function(a,b){b=b.getAllBlocks(!1);for(var c=0;cgoog.require(\'Blockly\');'); } + /* eslint-disable-next-line no-invalid-this */ })(this); diff --git a/blocks/all.js b/blocks/all.js deleted file mode 100644 index 17000f8c9..000000000 --- a/blocks/all.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview All the blocks. (Entry point for blocks_compressed.js.) - * @suppress {extraRequire} - */ -'use strict'; - -goog.module('Blockly.blocks.all'); - -goog.require('Blockly.blocks.colour'); -goog.require('Blockly.blocks.lists'); -goog.require('Blockly.blocks.logic'); -goog.require('Blockly.blocks.loops'); -goog.require('Blockly.blocks.math'); -goog.require('Blockly.blocks.procedures'); -goog.require('Blockly.blocks.texts'); -goog.require('Blockly.blocks.variables'); -goog.require('Blockly.blocks.variablesDynamic'); diff --git a/blocks/blocks.js b/blocks/blocks.js new file mode 100644 index 000000000..f6758c2c5 --- /dev/null +++ b/blocks/blocks.js @@ -0,0 +1,47 @@ +/** + * @license + * Copyright 2021 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview All the blocks. (Entry point for blocks_compressed.js.) + * @suppress {extraRequire} + */ +'use strict'; + +goog.module('Blockly.libraryBlocks'); +goog.module.declareLegacyNamespace(); + +const colour = goog.require('Blockly.libraryBlocks.colour'); +const lists = goog.require('Blockly.libraryBlocks.lists'); +const logic = goog.require('Blockly.libraryBlocks.logic'); +const loops = goog.require('Blockly.libraryBlocks.loops'); +const math = goog.require('Blockly.libraryBlocks.math'); +const procedures = goog.require('Blockly.libraryBlocks.procedures'); +const texts = goog.require('Blockly.libraryBlocks.texts'); +const variables = goog.require('Blockly.libraryBlocks.variables'); +const variablesDynamic = goog.require('Blockly.libraryBlocks.variablesDynamic'); +/* eslint-disable-next-line no-unused-vars */ +const {BlockDefinition} = goog.requireType('Blockly.blocks'); + + +exports.colour = colour; +exports.lists = lists; +exports.logic = logic; +exports.loops = loops; +exports.math = math; +exports.procedures = procedures; +exports.texts = texts; +exports.variables = variables; +exports.variablesDynamic = variablesDynamic; + +/** + * A dictionary of the block definitions provided by all the + * Blockly.libraryBlocks.* modules. + * @type {!Object} + */ +const blocks = Object.assign( + {}, colour.blocks, lists.blocks, logic.blocks, loops.blocks, math.blocks, + procedures.blocks, variables.blocks, variablesDynamic.blocks); +exports.blocks = blocks; diff --git a/blocks/colour.js b/blocks/colour.js index c2a8b6b4b..91b82a282 100644 --- a/blocks/colour.js +++ b/blocks/colour.js @@ -9,14 +9,20 @@ */ 'use strict'; -goog.module('Blockly.blocks.colour'); +goog.module('Blockly.libraryBlocks.colour'); -const {defineBlocksWithJsonArray} = goog.require('Blockly.common'); +/* eslint-disable-next-line no-unused-vars */ +const {BlockDefinition} = goog.requireType('Blockly.blocks'); +const {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common'); /** @suppress {extraRequire} */ goog.require('Blockly.FieldColour'); -defineBlocksWithJsonArray([ +/** + * A dictionary of the block definitions provided by this module. + * @type {!Object} + */ +const blocks = createBlockDefinitionsFromJsonArray([ // Block for colour picker. { 'type': 'colour_picker', @@ -107,3 +113,7 @@ defineBlocksWithJsonArray([ 'tooltip': '%{BKY_COLOUR_BLEND_TOOLTIP}', }, ]); +exports.blocks = blocks; + +// Register provided blocks. +defineBlocks(blocks); diff --git a/blocks/lists.js b/blocks/lists.js index 80400536d..46975d6cd 100644 --- a/blocks/lists.js +++ b/blocks/lists.js @@ -10,25 +10,30 @@ */ 'use strict'; -goog.module('Blockly.blocks.lists'); +goog.module('Blockly.libraryBlocks.lists'); const xmlUtils = goog.require('Blockly.utils.xml'); const {Align} = goog.require('Blockly.Input'); /* eslint-disable-next-line no-unused-vars */ const {Block} = goog.requireType('Blockly.Block'); -const {Blocks} = goog.require('Blockly.blocks'); +/* eslint-disable-next-line no-unused-vars */ +const {BlockDefinition} = goog.requireType('Blockly.blocks'); const {ConnectionType} = goog.require('Blockly.ConnectionType'); const {FieldDropdown} = goog.require('Blockly.FieldDropdown'); const {Msg} = goog.require('Blockly.Msg'); const {Mutator} = goog.require('Blockly.Mutator'); /* eslint-disable-next-line no-unused-vars */ const {Workspace} = goog.requireType('Blockly.Workspace'); -const {defineBlocksWithJsonArray} = goog.require('Blockly.common'); +const {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common'); /** @suppress {extraRequire} */ goog.require('Blockly.FieldDropdown'); -defineBlocksWithJsonArray([ +/** + * A dictionary of the block definitions provided by this module. + * @type {!Object} + */ +const blocks = createBlockDefinitionsFromJsonArray([ // Block for creating an empty list // The 'list_create_with' block is preferred as it is more flexible. // @@ -112,8 +117,9 @@ defineBlocksWithJsonArray([ 'helpUrl': '%{BKY_LISTS_LENGTH_HELPURL}', }, ]); +exports.blocks = blocks; -Blocks['lists_create_with'] = { +blocks['lists_create_with'] = { /** * Block for creating a list with any number of elements of any type. * @this {Block} @@ -255,7 +261,7 @@ Blocks['lists_create_with'] = { }, }; -Blocks['lists_create_with_container'] = { +blocks['lists_create_with_container'] = { /** * Mutator block for list container. * @this {Block} @@ -270,7 +276,7 @@ Blocks['lists_create_with_container'] = { }, }; -Blocks['lists_create_with_item'] = { +blocks['lists_create_with_item'] = { /** * Mutator block for adding items. * @this {Block} @@ -285,7 +291,7 @@ Blocks['lists_create_with_item'] = { }, }; -Blocks['lists_indexOf'] = { +blocks['lists_indexOf'] = { /** * Block for finding an item in the list. * @this {Block} @@ -312,7 +318,7 @@ Blocks['lists_indexOf'] = { }, }; -Blocks['lists_getIndex'] = { +blocks['lists_getIndex'] = { /** * Block for getting element at index. * @this {Block} @@ -516,7 +522,7 @@ Blocks['lists_getIndex'] = { }, }; -Blocks['lists_setIndex'] = { +blocks['lists_setIndex'] = { /** * Block for setting the element at index. * @this {Block} @@ -668,7 +674,7 @@ Blocks['lists_setIndex'] = { }, }; -Blocks['lists_getSublist'] = { +blocks['lists_getSublist'] = { /** * Block for getting sublist. * @this {Block} @@ -786,7 +792,7 @@ Blocks['lists_getSublist'] = { }, }; -Blocks['lists_sort'] = { +blocks['lists_sort'] = { /** * Block for sorting a list. * @this {Block} @@ -826,7 +832,7 @@ Blocks['lists_sort'] = { }, }; -Blocks['lists_split'] = { +blocks['lists_split'] = { /** * Block for splitting text into a list, or joining a list into text. * @this {Block} @@ -913,3 +919,6 @@ Blocks['lists_split'] = { // dropdown values. // XML hooks are kept for backwards compatibility. }; + +// Register provided blocks. +defineBlocks(blocks); diff --git a/blocks/logic.js b/blocks/logic.js index 8a79d7f94..04e1572be 100644 --- a/blocks/logic.js +++ b/blocks/logic.js @@ -10,7 +10,7 @@ */ 'use strict'; -goog.module('Blockly.blocks.logic'); +goog.module('Blockly.libraryBlocks.logic'); /* eslint-disable-next-line no-unused-vars */ const AbstractEvent = goog.requireType('Blockly.Events.Abstract'); @@ -19,20 +19,26 @@ const Extensions = goog.require('Blockly.Extensions'); const xmlUtils = goog.require('Blockly.utils.xml'); /* eslint-disable-next-line no-unused-vars */ const {Block} = goog.requireType('Blockly.Block'); +/* eslint-disable-next-line no-unused-vars */ +const {BlockDefinition} = goog.requireType('Blockly.blocks'); const {Msg} = goog.require('Blockly.Msg'); const {Mutator} = goog.require('Blockly.Mutator'); /* eslint-disable-next-line no-unused-vars */ const {RenderedConnection} = goog.requireType('Blockly.RenderedConnection'); /* eslint-disable-next-line no-unused-vars */ const {Workspace} = goog.requireType('Blockly.Workspace'); -const {defineBlocksWithJsonArray} = goog.require('Blockly.common'); +const {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common'); /** @suppress {extraRequire} */ goog.require('Blockly.FieldDropdown'); /** @suppress {extraRequire} */ goog.require('Blockly.FieldLabel'); -defineBlocksWithJsonArray([ +/** + * A dictionary of the block definitions provided by this module. + * @type {!Object} + */ +const blocks = createBlockDefinitionsFromJsonArray([ // Block for boolean data type: true and false. { 'type': 'logic_boolean', @@ -258,6 +264,7 @@ defineBlocksWithJsonArray([ 'tooltip': '%{BKY_CONTROLS_IF_ELSE_TOOLTIP}', }, ]); +exports.blocks = blocks; /** * Tooltip text, keyed by block OP value. Used by logic_compare and @@ -645,3 +652,6 @@ const LOGIC_TERNARY_ONCHANGE_MIXIN = { }; Extensions.registerMixin('logic_ternary', LOGIC_TERNARY_ONCHANGE_MIXIN); + +// Register provided blocks. +defineBlocks(blocks); diff --git a/blocks/loops.js b/blocks/loops.js index cc3c27d37..4578fc46f 100644 --- a/blocks/loops.js +++ b/blocks/loops.js @@ -10,7 +10,7 @@ */ 'use strict'; -goog.module('Blockly.blocks.loops'); +goog.module('Blockly.libraryBlocks.loops'); /* eslint-disable-next-line no-unused-vars */ const AbstractEvent = goog.requireType('Blockly.Events.Abstract'); @@ -18,11 +18,13 @@ const ContextMenu = goog.require('Blockly.ContextMenu'); const Events = goog.require('Blockly.Events'); const Extensions = goog.require('Blockly.Extensions'); const Variables = goog.require('Blockly.Variables'); -const common = goog.require('Blockly.common'); const xmlUtils = goog.require('Blockly.utils.xml'); /* eslint-disable-next-line no-unused-vars */ const {Block} = goog.requireType('Blockly.Block'); +/* eslint-disable-next-line no-unused-vars */ +const {BlockDefinition} = goog.requireType('Blockly.blocks'); const {Msg} = goog.require('Blockly.Msg'); +const {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common'); /** @suppress {extraRequire} */ goog.require('Blockly.FieldDropdown'); /** @suppress {extraRequire} */ @@ -35,7 +37,11 @@ goog.require('Blockly.FieldVariable'); goog.require('Blockly.Warning'); -common.defineBlocksWithJsonArray([ +/** + * A dictionary of the block definitions provided by this module. + * @type {!Object} + */ +const blocks = createBlockDefinitionsFromJsonArray([ // Block for repeat n times (external number). { 'type': 'controls_repeat_ext', @@ -205,6 +211,7 @@ common.defineBlocksWithJsonArray([ ], }, ]); +exports.blocks = blocks; /** * Tooltips for the 'controls_whileUntil' block, keyed by MODE value. @@ -287,21 +294,24 @@ Extensions.register( * * // If using the Blockly npm package and es6 import syntax: * import {loopTypes} from 'blockly/blocks'; - * loopTypes.push('custom_loop'); + * loopTypes.add('custom_loop'); * * // Else if using Closure Compiler and goog.modules: - * const {loopTypes} = goog.require('Blockly.blocks.loops'); - * loopTypes.push('custom_loop'); + * const {loopTypes} = goog.require('Blockly.libraryBlocks.loops'); + * loopTypes.add('custom_loop'); * - * @type {!Array} + * // Else if using blockly_compressed + blockss_compressed.js in browser: + * Blockly.libraryBlocks.loopTypes.add('custom_loop'); + * + * @type {!Set} */ -const loopTypes = [ +const loopTypes = new Set([ 'controls_repeat', 'controls_repeat_ext', 'controls_forEach', 'controls_for', 'controls_whileUntil', -]; +]); exports.loopTypes = loopTypes; /** @@ -321,7 +331,7 @@ const CONTROL_FLOW_IN_LOOP_CHECK_MIXIN = { getSurroundLoop: function() { let block = this; do { - if (loopTypes.includes(block.type)) { + if (loopTypes.has(block.type)) { return block; } block = block.getSurroundParent(); @@ -332,7 +342,7 @@ const CONTROL_FLOW_IN_LOOP_CHECK_MIXIN = { /** * Called whenever anything on the workspace changes. * Add warning if this flow block is not nested inside a loop. - * @param {!AbstractEvent} e Change event. + * @param {!AbstractEvent} e Move event. * @this {Block} */ onchange: function(e) { @@ -358,3 +368,6 @@ const CONTROL_FLOW_IN_LOOP_CHECK_MIXIN = { Extensions.registerMixin( 'controls_flow_in_loop_check', CONTROL_FLOW_IN_LOOP_CHECK_MIXIN); + +// Register provided blocks. +defineBlocks(blocks); diff --git a/blocks/math.js b/blocks/math.js index 003ff5bdc..bd5458fdd 100644 --- a/blocks/math.js +++ b/blocks/math.js @@ -10,7 +10,7 @@ */ 'use strict'; -goog.module('Blockly.blocks.math'); +goog.module('Blockly.libraryBlocks.math'); const Extensions = goog.require('Blockly.Extensions'); // N.B.: Blockly.FieldDropdown needed for type AND side-effects. @@ -20,7 +20,8 @@ const xmlUtils = goog.require('Blockly.utils.xml'); /* eslint-disable-next-line no-unused-vars */ const {Block} = goog.requireType('Blockly.Block'); /* eslint-disable-next-line no-unused-vars */ -const {defineBlocksWithJsonArray} = goog.require('Blockly.common'); +const {BlockDefinition} = goog.requireType('Blockly.blocks'); +const {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common'); /** @suppress {extraRequire} */ goog.require('Blockly.FieldLabel'); /** @suppress {extraRequire} */ @@ -29,7 +30,11 @@ goog.require('Blockly.FieldNumber'); goog.require('Blockly.FieldVariable'); -defineBlocksWithJsonArray([ +/** + * A dictionary of the block definitions provided by this module. + * @type {!Object} + */ +const blocks = createBlockDefinitionsFromJsonArray([ // Block for numeric value. { 'type': 'math_number', @@ -384,6 +389,7 @@ defineBlocksWithJsonArray([ 'helpUrl': '%{BKY_MATH_ATAN2_HELPURL}', }, ]); +exports.blocks = blocks; /** * Mapping of math block OP value to tooltip message for blocks @@ -581,3 +587,6 @@ const LIST_MODES_MUTATOR_EXTENSION = function() { Extensions.registerMutator( 'math_modes_of_list_mutator', LIST_MODES_MUTATOR_MIXIN, LIST_MODES_MUTATOR_EXTENSION); + +// Register provided blocks. +defineBlocks(blocks); diff --git a/blocks/procedures.js b/blocks/procedures.js index c41eb92db..8e652ed39 100644 --- a/blocks/procedures.js +++ b/blocks/procedures.js @@ -10,7 +10,7 @@ */ 'use strict'; -goog.module('Blockly.blocks.procedures'); +goog.module('Blockly.libraryBlocks.procedures'); /* eslint-disable-next-line no-unused-vars */ const AbstractEvent = goog.requireType('Blockly.Events.Abstract'); @@ -19,12 +19,13 @@ const Events = goog.require('Blockly.Events'); const Procedures = goog.require('Blockly.Procedures'); const Variables = goog.require('Blockly.Variables'); const Xml = goog.require('Blockly.Xml'); -const internalConstants = goog.require('Blockly.internalConstants'); const xmlUtils = goog.require('Blockly.utils.xml'); const {Align} = goog.require('Blockly.Input'); /* eslint-disable-next-line no-unused-vars */ const {Block} = goog.requireType('Blockly.Block'); -const {Blocks} = goog.require('Blockly.blocks'); +/* eslint-disable-next-line no-unused-vars */ +const {BlockDefinition} = goog.requireType('Blockly.blocks'); +const {config} = goog.require('Blockly.config'); /* eslint-disable-next-line no-unused-vars */ const {FieldCheckbox} = goog.require('Blockly.FieldCheckbox'); const {FieldLabel} = goog.require('Blockly.FieldLabel'); @@ -36,12 +37,20 @@ const {Names} = goog.require('Blockly.Names'); const {VariableModel} = goog.requireType('Blockly.VariableModel'); /* eslint-disable-next-line no-unused-vars */ const {Workspace} = goog.requireType('Blockly.Workspace'); +const {defineBlocks} = goog.require('Blockly.common'); /** @suppress {extraRequire} */ goog.require('Blockly.Comment'); /** @suppress {extraRequire} */ goog.require('Blockly.Warning'); +/** + * A dictionary of the block definitions provided by this module. + * @type {!Object} + */ +const blocks = {}; +exports.blocks = blocks; + /** * Common properties for the procedure_defnoreturn and * procedure_defreturn blocks. @@ -434,10 +443,9 @@ const PROCEDURE_DEF_COMMON = { } } }, - callType_: 'procedures_callnoreturn', }; -Blocks['procedures_defnoreturn'] = { +blocks['procedures_defnoreturn'] = { ...PROCEDURE_DEF_COMMON, /** * Block for defining a procedure with no return value. @@ -477,9 +485,10 @@ Blocks['procedures_defnoreturn'] = { getProcedureDef: function() { return [this.getFieldValue('NAME'), this.arguments_, false]; }, + callType_: 'procedures_callnoreturn', }; -Blocks['procedures_defreturn'] = { +blocks['procedures_defreturn'] = { ...PROCEDURE_DEF_COMMON, /** * Block for defining a procedure with a return value. @@ -522,9 +531,10 @@ Blocks['procedures_defreturn'] = { getProcedureDef: function() { return [this.getFieldValue('NAME'), this.arguments_, true]; }, + callType_: 'procedures_callreturn', }; -Blocks['procedures_mutatorcontainer'] = { +blocks['procedures_mutatorcontainer'] = { /** * Mutator block for procedure container. * @this {Block} @@ -542,7 +552,7 @@ Blocks['procedures_mutatorcontainer'] = { }, }; -Blocks['procedures_mutatorarg'] = { +blocks['procedures_mutatorarg'] = { /** * Mutator block for procedure argument. * @this {Block} @@ -950,19 +960,18 @@ const PROCEDURE_CALL_COMMON = { const block = xmlUtils.createElement('block'); block.setAttribute('type', this.defType_); const xy = this.getRelativeToSurfaceXY(); - const x = xy.x + internalConstants.SNAP_RADIUS * (this.RTL ? -1 : 1); - const y = xy.y + internalConstants.SNAP_RADIUS * 2; + const x = xy.x + config.snapRadius * (this.RTL ? -1 : 1); + const y = xy.y + config.snapRadius * 2; block.setAttribute('x', x); block.setAttribute('y', y); const mutation = this.mutationToDom(); block.appendChild(mutation); const field = xmlUtils.createElement('field'); field.setAttribute('name', 'NAME'); - let callName = this.getProcedureCall(); - if (!callName) { - // Rename if name is empty string. - callName = Procedures.findLegalName('', this); - this.renameProcedure('', callName); + const callName = this.getProcedureCall(); + const newName = Procedures.findLegalName(callName, this); + if (callName !== newName) { + this.renameProcedure(callName, newName); } field.appendChild(xmlUtils.createTextNode(callName)); block.appendChild(field); @@ -1033,7 +1042,7 @@ const PROCEDURE_CALL_COMMON = { }, }; -Blocks['procedures_callnoreturn'] = { +blocks['procedures_callnoreturn'] = { ...PROCEDURE_CALL_COMMON, /** * Block for calling a procedure with no return value. @@ -1056,7 +1065,7 @@ Blocks['procedures_callnoreturn'] = { defType_: 'procedures_defnoreturn', }; -Blocks['procedures_callreturn'] = { +blocks['procedures_callreturn'] = { ...PROCEDURE_CALL_COMMON, /** * Block for calling a procedure with a return value. @@ -1078,7 +1087,7 @@ Blocks['procedures_callreturn'] = { defType_: 'procedures_defreturn', }; -Blocks['procedures_ifreturn'] = { +blocks['procedures_ifreturn'] = { /** * Block for conditionally returning a value from a procedure. * @this {Block} @@ -1130,11 +1139,12 @@ Blocks['procedures_ifreturn'] = { /** * Called whenever anything on the workspace changes. * Add warning if this flow block is not nested inside a loop. - * @param {!AbstractEvent} _e Change event. + * @param {!AbstractEvent} e Move event. * @this {Block} */ - onchange: function(_e) { - if (this.workspace.isDragging && this.workspace.isDragging()) { + onchange: function(e) { + if (this.workspace.isDragging && this.workspace.isDragging() || + e.type !== Events.BLOCK_MOVE) { return; // Don't change state at the start of a drag. } let legal = false; @@ -1162,14 +1172,15 @@ Blocks['procedures_ifreturn'] = { this.hasReturnValue_ = true; } this.setWarningText(null); - if (!this.isInFlyout) { - this.setEnabled(true); - } } else { this.setWarningText(Msg['PROCEDURES_IFRETURN_WARNING']); - if (!this.isInFlyout && !this.getInheritedDisabled()) { - this.setEnabled(false); - } + } + if (!this.isInFlyout) { + const group = Events.getGroup(); + // Makes it so the move and the disable event get undone together. + Events.setGroup(e.group); + this.setEnabled(legal); + Events.setGroup(group); } }, /** @@ -1179,3 +1190,6 @@ Blocks['procedures_ifreturn'] = { */ FUNCTION_TYPES: ['procedures_defnoreturn', 'procedures_defreturn'], }; + +// Register provided blocks. +defineBlocks(blocks); diff --git a/blocks/text.js b/blocks/text.js index 9072c7fa1..efaebb500 100644 --- a/blocks/text.js +++ b/blocks/text.js @@ -10,7 +10,7 @@ */ 'use strict'; -goog.module('Blockly.blocks.texts'); +goog.module('Blockly.libraryBlocks.texts'); const Extensions = goog.require('Blockly.Extensions'); const {Msg} = goog.require('Blockly.Msg'); @@ -19,7 +19,8 @@ const xmlUtils = goog.require('Blockly.utils.xml'); const {Align} = goog.require('Blockly.Input'); /* eslint-disable-next-line no-unused-vars */ const {Block} = goog.requireType('Blockly.Block'); -const {Blocks} = goog.require('Blockly.blocks'); +/* eslint-disable-next-line no-unused-vars */ +const {BlockDefinition} = goog.requireType('Blockly.blocks'); const {ConnectionType} = goog.require('Blockly.ConnectionType'); const {FieldDropdown} = goog.require('Blockly.FieldDropdown'); const {FieldImage} = goog.require('Blockly.FieldImage'); @@ -27,14 +28,18 @@ const {FieldTextInput} = goog.require('Blockly.FieldTextInput'); const {Mutator} = goog.require('Blockly.Mutator'); /* eslint-disable-next-line no-unused-vars */ const {Workspace} = goog.requireType('Blockly.Workspace'); -const {defineBlocksWithJsonArray} = goog.require('Blockly.common'); +const {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common'); /** @suppress {extraRequire} */ goog.require('Blockly.FieldMultilineInput'); /** @suppress {extraRequire} */ goog.require('Blockly.FieldVariable'); -defineBlocksWithJsonArray([ +/** + * A dictionary of the block definitions provided by this module. + * @type {!Object} + */ +const blocks = createBlockDefinitionsFromJsonArray([ // Block for text value { 'type': 'text', @@ -238,8 +243,9 @@ defineBlocksWithJsonArray([ 'mutator': 'text_charAt_mutator', }, ]); +exports.blocks = blocks; -Blocks['text_getSubstring'] = { +blocks['text_getSubstring'] = { /** * Block for getting substring. * @this {Block} @@ -363,7 +369,7 @@ Blocks['text_getSubstring'] = { }, }; -Blocks['text_changeCase'] = { +blocks['text_changeCase'] = { /** * Block for changing capitalization. * @this {Block} @@ -383,7 +389,7 @@ Blocks['text_changeCase'] = { }, }; -Blocks['text_trim'] = { +blocks['text_trim'] = { /** * Block for trimming spaces. * @this {Block} @@ -403,7 +409,7 @@ Blocks['text_trim'] = { }, }; -Blocks['text_print'] = { +blocks['text_print'] = { /** * Block for print statement. * @this {Block} @@ -463,7 +469,7 @@ const TEXT_PROMPT_COMMON = { }, }; -Blocks['text_prompt_ext'] = { +blocks['text_prompt_ext'] = { ...TEXT_PROMPT_COMMON, /** * Block for prompt function (external message). @@ -496,7 +502,7 @@ Blocks['text_prompt_ext'] = { // XML hooks are kept for backwards compatibility. }; -Blocks['text_prompt'] = { +blocks['text_prompt'] = { ...TEXT_PROMPT_COMMON, /** * Block for prompt function (internal message). @@ -531,7 +537,7 @@ Blocks['text_prompt'] = { }, }; -Blocks['text_count'] = { +blocks['text_count'] = { /** * Block for counting how many times one string appears within another string. * @this {Block} @@ -560,7 +566,7 @@ Blocks['text_count'] = { }, }; -Blocks['text_replace'] = { +blocks['text_replace'] = { /** * Block for replacing one string with another in the text. * @this {Block} @@ -594,7 +600,7 @@ Blocks['text_replace'] = { }, }; -Blocks['text_reverse'] = { +blocks['text_reverse'] = { /** * Block for reversing a string. * @this {Block} @@ -981,3 +987,6 @@ Extensions.registerMutator( Extensions.registerMutator( 'text_charAt_mutator', TEXT_CHARAT_MUTATOR_MIXIN, TEXT_CHARAT_EXTENSION); + +// Register provided blocks. +defineBlocks(blocks); diff --git a/blocks/variables.js b/blocks/variables.js index c9e4720fa..56a7b5eb8 100644 --- a/blocks/variables.js +++ b/blocks/variables.js @@ -10,7 +10,7 @@ */ 'use strict'; -goog.module('Blockly.blocks.variables'); +goog.module('Blockly.libraryBlocks.variables'); const ContextMenu = goog.require('Blockly.ContextMenu'); const Extensions = goog.require('Blockly.Extensions'); @@ -18,15 +18,21 @@ const Variables = goog.require('Blockly.Variables'); const xmlUtils = goog.require('Blockly.utils.xml'); /* eslint-disable-next-line no-unused-vars */ const {Block} = goog.requireType('Blockly.Block'); +/* eslint-disable-next-line no-unused-vars */ +const {BlockDefinition} = goog.requireType('Blockly.blocks'); const {Msg} = goog.require('Blockly.Msg'); -const {defineBlocksWithJsonArray} = goog.require('Blockly.common'); +const {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common'); /** @suppress {extraRequire} */ goog.require('Blockly.FieldLabel'); /** @suppress {extraRequire} */ goog.require('Blockly.FieldVariable'); -defineBlocksWithJsonArray([ +/** + * A dictionary of the block definitions provided by this module. + * @type {!Object} + */ +const blocks = createBlockDefinitionsFromJsonArray([ // Block for variable getter. { 'type': 'variables_get', @@ -67,6 +73,8 @@ defineBlocksWithJsonArray([ 'extensions': ['contextMenu_variableSetterGetter'], }, ]); +exports.blocks = blocks; + /** * Mixin to add context menu items to create getter/setter blocks for this @@ -161,3 +169,6 @@ const deleteOptionCallbackFactory = function(block) { Extensions.registerMixin( 'contextMenu_variableSetterGetter', CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN); + +// Register provided blocks. +defineBlocks(blocks); diff --git a/blocks/variables_dynamic.js b/blocks/variables_dynamic.js index 26a91af1e..6020c7859 100644 --- a/blocks/variables_dynamic.js +++ b/blocks/variables_dynamic.js @@ -10,7 +10,7 @@ */ 'use strict'; -goog.module('Blockly.blocks.variablesDynamic'); +goog.module('Blockly.libraryBlocks.variablesDynamic'); /* eslint-disable-next-line no-unused-vars */ const AbstractEvent = goog.requireType('Blockly.Events.Abstract'); @@ -20,15 +20,21 @@ const Variables = goog.require('Blockly.Variables'); const xml = goog.require('Blockly.utils.xml'); /* eslint-disable-next-line no-unused-vars */ const {Block} = goog.requireType('Blockly.Block'); +/* eslint-disable-next-line no-unused-vars */ +const {BlockDefinition} = goog.requireType('Blockly.blocks'); const {Msg} = goog.require('Blockly.Msg'); -const {defineBlocksWithJsonArray} = goog.require('Blockly.common'); +const {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common'); /** @suppress {extraRequire} */ goog.require('Blockly.FieldLabel'); /** @suppress {extraRequire} */ goog.require('Blockly.FieldVariable'); -defineBlocksWithJsonArray([ +/** + * A dictionary of the block definitions provided by this module. + * @type {!Object} + */ +const blocks = createBlockDefinitionsFromJsonArray([ // Block for variable getter. { 'type': 'variables_get_dynamic', @@ -67,6 +73,7 @@ defineBlocksWithJsonArray([ 'extensions': ['contextMenu_variableDynamicSetterGetter'], }, ]); +exports.blocks = blocks; /** * Mixin to add context menu items to create getter/setter blocks for this @@ -178,3 +185,6 @@ const deleteOptionCallbackFactory = function(block) { Extensions.registerMixin( 'contextMenu_variableDynamicSetterGetter', CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN); + +// Register provided blocks. +defineBlocks(blocks); diff --git a/blocks_compressed.js b/blocks_compressed.js index d82acb831..ca767070a 100644 --- a/blocks_compressed.js +++ b/blocks_compressed.js @@ -7,60 +7,61 @@ } else if (typeof exports === 'object') { // Node.js module.exports = factory(require("./blockly_compressed.js")); } else { // Browser - root.Blockly.Blocks = factory(root.Blockly); + var factoryExports = factory(root.Blockly); + root.Blockly.libraryBlocks = factoryExports; } -}(this, function(Blockly) { -const $=Blockly.internal_; -var module$exports$Blockly$blocks$variablesDynamic={}; -(0,$.module$exports$Blockly$common.defineBlocksWithJsonArray)([{type:"variables_get_dynamic",message0:"%1",args0:[{type:"field_variable",name:"VAR",variable:"%{BKY_VARIABLES_DEFAULT_NAME}"}],output:null,style:"variable_dynamic_blocks",helpUrl:"%{BKY_VARIABLES_GET_HELPURL}",tooltip:"%{BKY_VARIABLES_GET_TOOLTIP}",extensions:["contextMenu_variableDynamicSetterGetter"]},{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,style:"variable_dynamic_blocks",tooltip:"%{BKY_VARIABLES_SET_TOOLTIP}",helpUrl:"%{BKY_VARIABLES_SET_HELPURL}",extensions:["contextMenu_variableDynamicSetterGetter"]}]); -var module$contents$Blockly$blocks$variablesDynamic_CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN={customContextMenu:function(a){if(!this.isInFlyout){var b=this.getFieldValue("VAR");var c=this.workspace.getVariableById(b).type;if("variables_get_dynamic"===this.type){b="variables_set_dynamic";var d=$.module$exports$Blockly$Msg.Msg.VARIABLES_GET_CREATE_SET}else b="variables_get_dynamic",d=$.module$exports$Blockly$Msg.Msg.VARIABLES_SET_CREATE_GET;var e={enabled:0","GT"],["\u200f\u2265","GTE"]]},{type:"input_value",name:"B"}],inputsInline:!0,output:"Boolean", -style:"logic_blocks",helpUrl:"%{BKY_LOGIC_COMPARE_HELPURL}",extensions:["logic_compare","logic_op_tooltip"]},{type:"logic_operation",message0:"%1 %2 %3",args0:[{type:"input_value",name:"A",check:"Boolean"},{type:"field_dropdown",name:"OP",options:[["%{BKY_LOGIC_OPERATION_AND}","AND"],["%{BKY_LOGIC_OPERATION_OR}","OR"]]},{type:"input_value",name:"B",check:"Boolean"}],inputsInline:!0,output:"Boolean",style:"logic_blocks",helpUrl:"%{BKY_LOGIC_OPERATION_HELPURL}",extensions:["logic_op_tooltip"]},{type:"logic_negate", -message0:"%{BKY_LOGIC_NEGATE_TITLE}",args0:[{type:"input_value",name:"BOOL",check:"Boolean"}],output:"Boolean",style:"logic_blocks",tooltip:"%{BKY_LOGIC_NEGATE_TOOLTIP}",helpUrl:"%{BKY_LOGIC_NEGATE_HELPURL}"},{type:"logic_null",message0:"%{BKY_LOGIC_NULL}",output:null,style:"logic_blocks",tooltip:"%{BKY_LOGIC_NULL_TOOLTIP}",helpUrl:"%{BKY_LOGIC_NULL_HELPURL}"},{type:"logic_ternary",message0:"%{BKY_LOGIC_TERNARY_CONDITION} %1",args0:[{type:"input_value",name:"IF",check:"Boolean"}],message1:"%{BKY_LOGIC_TERNARY_IF_TRUE} %1", -args1:[{type:"input_value",name:"THEN"}],message2:"%{BKY_LOGIC_TERNARY_IF_FALSE} %1",args2:[{type:"input_value",name:"ELSE"}],output:null,style:"logic_blocks",tooltip:"%{BKY_LOGIC_TERNARY_TOOLTIP}",helpUrl:"%{BKY_LOGIC_TERNARY_HELPURL}",extensions:["logic_ternary"]},{type:"controls_if_if",message0:"%{BKY_CONTROLS_IF_IF_TITLE_IF}",nextStatement:null,enableContextMenu:!1,style:"logic_blocks",tooltip:"%{BKY_CONTROLS_IF_IF_TOOLTIP}"},{type:"controls_if_elseif",message0:"%{BKY_CONTROLS_IF_ELSEIF_TITLE_ELSEIF}", -previousStatement:null,nextStatement:null,enableContextMenu:!1,style:"logic_blocks",tooltip:"%{BKY_CONTROLS_IF_ELSEIF_TOOLTIP}"},{type:"controls_if_else",message0:"%{BKY_CONTROLS_IF_ELSE_TITLE_ELSE}",previousStatement:null,enableContextMenu:!1,style:"logic_blocks",tooltip:"%{BKY_CONTROLS_IF_ELSE_TOOLTIP}"}]); -var module$contents$Blockly$blocks$logic_TOOLTIPS_BY_OP={EQ:"%{BKY_LOGIC_COMPARE_TOOLTIP_EQ}",NEQ:"%{BKY_LOGIC_COMPARE_TOOLTIP_NEQ}",LT:"%{BKY_LOGIC_COMPARE_TOOLTIP_LT}",LTE:"%{BKY_LOGIC_COMPARE_TOOLTIP_LTE}",GT:"%{BKY_LOGIC_COMPARE_TOOLTIP_GT}",GTE:"%{BKY_LOGIC_COMPARE_TOOLTIP_GTE}",AND:"%{BKY_LOGIC_OPERATION_TOOLTIP_AND}",OR:"%{BKY_LOGIC_OPERATION_TOOLTIP_OR}"}; -(0,$.module$exports$Blockly$Extensions.register)("logic_op_tooltip",(0,$.module$exports$Blockly$Extensions.buildTooltipForDropdown)("OP",module$contents$Blockly$blocks$logic_TOOLTIPS_BY_OP)); -var module$contents$Blockly$blocks$logic_CONTROLS_IF_MUTATOR_MIXIN={elseifCount_:0,elseCount_:0,mutationToDom:function(){if(!this.elseifCount_&&!this.elseCount_)return null;var a=(0,$.module$exports$Blockly$utils$xml.createElement)("mutation");this.elseifCount_&&a.setAttribute("elseif",this.elseifCount_);this.elseCount_&&a.setAttribute("else",1);return a},domToMutation:function(a){this.elseifCount_=parseInt(a.getAttribute("elseif"),10)||0;this.elseCount_=parseInt(a.getAttribute("else"),10)||0;this.rebuildShape_()}, -saveExtraState:function(){if(!this.elseifCount_&&!this.elseCount_)return null;var a=Object.create(null);this.elseifCount_&&(a.elseIfCount=this.elseifCount_);this.elseCount_&&(a.hasElse=!0);return a},loadExtraState:function(a){this.elseifCount_=a.elseIfCount||0;this.elseCount_=a.hasElse?1:0;this.updateShape_()},decompose:function(a){var b=a.newBlock("controls_if_if");b.initSvg();for(var c=b.nextConnection,d=1;d<=this.elseifCount_;d++){var e=a.newBlock("controls_if_elseif");e.initSvg();c.connect(e.previousConnection); -c=e.nextConnection}this.elseCount_&&(a=a.newBlock("controls_if_else"),a.initSvg(),c.connect(a.previousConnection));return b},compose:function(a){a=a.nextConnection.targetBlock();this.elseCount_=this.elseifCount_=0;for(var b=[null],c=[null],d=null;a&&!a.isInsertionMarker();){switch(a.type){case "controls_if_elseif":this.elseifCount_++;b.push(a.valueConnection_);c.push(a.statementConnection_);break;case "controls_if_else":this.elseCount_++;d=a.statementConnection_;break;default:throw TypeError("Unknown block type: "+ -a.type);}a=a.nextConnection&&a.nextConnection.targetBlock()}this.updateShape_();this.reconnectChildBlocks_(b,c,d)},saveConnections:function(a){a=a.nextConnection.targetBlock();for(var b=1;a;){switch(a.type){case "controls_if_elseif":var c=this.getInput("IF"+b),d=this.getInput("DO"+b);a.valueConnection_=c&&c.connection.targetConnection;a.statementConnection_=d&&d.connection.targetConnection;b++;break;case "controls_if_else":c=this.getInput("ELSE");a.statementConnection_=c&&c.connection.targetConnection; -break;default:throw TypeError("Unknown block type: "+a.type);}a=a.nextConnection&&a.nextConnection.targetBlock()}},rebuildShape_:function(){var a=[null],b=[null],c=null;this.getInput("ELSE")&&(c=this.getInput("ELSE").connection.targetConnection);for(var d=1;this.getInput("IF"+d);d++){var e=this.getInput("IF"+d),f=this.getInput("DO"+d);a.push(e.connection.targetConnection);b.push(f.connection.targetConnection)}this.updateShape_();this.reconnectChildBlocks_(a,b,c)},updateShape_:function(){this.getInput("ELSE")&& -this.removeInput("ELSE");for(var a=1;this.getInput("IF"+a);a++)this.removeInput("IF"+a),this.removeInput("DO"+a);for(a=1;a<=this.elseifCount_;a++)this.appendValueInput("IF"+a).setCheck("Boolean").appendField($.module$exports$Blockly$Msg.Msg.CONTROLS_IF_MSG_ELSEIF),this.appendStatementInput("DO"+a).appendField($.module$exports$Blockly$Msg.Msg.CONTROLS_IF_MSG_THEN);this.elseCount_&&this.appendStatementInput("ELSE").appendField($.module$exports$Blockly$Msg.Msg.CONTROLS_IF_MSG_ELSE)},reconnectChildBlocks_:function(a, -b,c){for(var d=1;d<=this.elseifCount_;d++)$.module$exports$Blockly$Mutator.Mutator.reconnect(a[d],this,"IF"+d),$.module$exports$Blockly$Mutator.Mutator.reconnect(b[d],this,"DO"+d);$.module$exports$Blockly$Mutator.Mutator.reconnect(c,this,"ELSE")}};(0,$.module$exports$Blockly$Extensions.registerMutator)("controls_if_mutator",module$contents$Blockly$blocks$logic_CONTROLS_IF_MUTATOR_MIXIN,null,["controls_if_elseif","controls_if_else"]); -var module$contents$Blockly$blocks$logic_CONTROLS_IF_TOOLTIP_EXTENSION=function(){this.setTooltip(function(){if(this.elseifCount_||this.elseCount_){if(!this.elseifCount_&&this.elseCount_)return $.module$exports$Blockly$Msg.Msg.CONTROLS_IF_TOOLTIP_2;if(this.elseifCount_&&!this.elseCount_)return $.module$exports$Blockly$Msg.Msg.CONTROLS_IF_TOOLTIP_3;if(this.elseifCount_&&this.elseCount_)return $.module$exports$Blockly$Msg.Msg.CONTROLS_IF_TOOLTIP_4}else return $.module$exports$Blockly$Msg.Msg.CONTROLS_IF_TOOLTIP_1; -return""}.bind(this))};(0,$.module$exports$Blockly$Extensions.register)("controls_if_tooltip",module$contents$Blockly$blocks$logic_CONTROLS_IF_TOOLTIP_EXTENSION); -var module$contents$Blockly$blocks$logic_LOGIC_COMPARE_ONCHANGE_MIXIN={onchange:function(a){this.prevBlocks_||(this.prevBlocks_=[null,null]);var b=this.getInputTargetBlock("A"),c=this.getInputTargetBlock("B");b&&c&&!this.workspace.connectionChecker.doTypeChecks(b.outputConnection,c.outputConnection)&&((0,$.module$exports$Blockly$Events.setGroup)(a.group),a=this.prevBlocks_[0],a!==b&&(b.unplug(),!a||a.isDisposed()||a.isShadow()||this.getInput("A").connection.connect(a.outputConnection)),b=this.prevBlocks_[1], -b!==c&&(c.unplug(),!b||b.isDisposed()||b.isShadow()||this.getInput("B").connection.connect(b.outputConnection)),this.bumpNeighbours(),(0,$.module$exports$Blockly$Events.setGroup)(!1));this.prevBlocks_[0]=this.getInputTargetBlock("A");this.prevBlocks_[1]=this.getInputTargetBlock("B")}},module$contents$Blockly$blocks$logic_LOGIC_COMPARE_EXTENSION=function(){this.mixin(module$contents$Blockly$blocks$logic_LOGIC_COMPARE_ONCHANGE_MIXIN)}; -(0,$.module$exports$Blockly$Extensions.register)("logic_compare",module$contents$Blockly$blocks$logic_LOGIC_COMPARE_EXTENSION); -var module$contents$Blockly$blocks$logic_LOGIC_TERNARY_ONCHANGE_MIXIN={prevParentConnection_:null,onchange:function(a){var b=this.getInputTargetBlock("THEN"),c=this.getInputTargetBlock("ELSE"),d=this.outputConnection.targetConnection;if((b||c)&&d)for(var e=0;2>e;e++){var f=1===e?b:c;f&&!f.workspace.connectionChecker.doTypeChecks(f.outputConnection,d)&&((0,$.module$exports$Blockly$Events.setGroup)(a.group),d===this.prevParentConnection_?(this.unplug(),d.getSourceBlock().bumpNeighbours()):(f.unplug(), -f.bumpNeighbours()),(0,$.module$exports$Blockly$Events.setGroup)(!1))}this.prevParentConnection_=d}};(0,$.module$exports$Blockly$Extensions.registerMixin)("logic_ternary",module$contents$Blockly$blocks$logic_LOGIC_TERNARY_ONCHANGE_MIXIN);var module$exports$Blockly$blocks$lists={}; -(0,$.module$exports$Blockly$common.defineBlocksWithJsonArray)([{type:"lists_create_empty",message0:"%{BKY_LISTS_CREATE_EMPTY_TITLE}",output:"Array",style:"list_blocks",tooltip:"%{BKY_LISTS_CREATE_EMPTY_TOOLTIP}",helpUrl:"%{BKY_LISTS_CREATE_EMPTY_HELPURL}"},{type:"lists_repeat",message0:"%{BKY_LISTS_REPEAT_TITLE}",args0:[{type:"input_value",name:"ITEM"},{type:"input_value",name:"NUM",check:"Number"}],output:"Array",style:"list_blocks",tooltip:"%{BKY_LISTS_REPEAT_TOOLTIP}",helpUrl:"%{BKY_LISTS_REPEAT_HELPURL}"}, -{type:"lists_reverse",message0:"%{BKY_LISTS_REVERSE_MESSAGE0}",args0:[{type:"input_value",name:"LIST",check:"Array"}],output:"Array",inputsInline:!0,style:"list_blocks",tooltip:"%{BKY_LISTS_REVERSE_TOOLTIP}",helpUrl:"%{BKY_LISTS_REVERSE_HELPURL}"},{type:"lists_isEmpty",message0:"%{BKY_LISTS_ISEMPTY_TITLE}",args0:[{type:"input_value",name:"VALUE",check:["String","Array"]}],output:"Boolean",style:"list_blocks",tooltip:"%{BKY_LISTS_ISEMPTY_TOOLTIP}",helpUrl:"%{BKY_LISTS_ISEMPTY_HELPURL}"},{type:"lists_length", -message0:"%{BKY_LISTS_LENGTH_TITLE}",args0:[{type:"input_value",name:"VALUE",check:["String","Array"]}],output:"Number",style:"list_blocks",tooltip:"%{BKY_LISTS_LENGTH_TOOLTIP}",helpUrl:"%{BKY_LISTS_LENGTH_HELPURL}"}]); -$.module$exports$Blockly$blocks.Blocks.lists_create_with={init:function(){this.setHelpUrl($.module$exports$Blockly$Msg.Msg.LISTS_CREATE_WITH_HELPURL);this.setStyle("list_blocks");this.itemCount_=3;this.updateShape_();this.setOutput(!0,"Array");this.setMutator(new $.module$exports$Blockly$Mutator.Mutator(["lists_create_with_item"]));this.setTooltip($.module$exports$Blockly$Msg.Msg.LISTS_CREATE_WITH_TOOLTIP)},mutationToDom:function(){var a=(0,$.module$exports$Blockly$utils$xml.createElement)("mutation"); +var module$contents$Blockly$libraryBlocks$math_LIST_MODES_MUTATOR_MIXIN={updateType_:function(a){"MODE"===a?this.outputConnection.setCheck("Array"):this.outputConnection.setCheck("Number")},mutationToDom:function(){var a=(0,$.module$exports$Blockly$utils$xml.createElement)("mutation");a.setAttribute("op",this.getFieldValue("OP"));return a},domToMutation:function(a){this.updateType_(a.getAttribute("op"))}},module$contents$Blockly$libraryBlocks$math_LIST_MODES_MUTATOR_EXTENSION=function(){this.getField("OP").setValidator(function(a){this.updateType_(a)}.bind(this))}; +(0,$.module$exports$Blockly$Extensions.registerMutator)("math_modes_of_list_mutator",module$contents$Blockly$libraryBlocks$math_LIST_MODES_MUTATOR_MIXIN,module$contents$Blockly$libraryBlocks$math_LIST_MODES_MUTATOR_EXTENSION);(0,$.module$exports$Blockly$common.defineBlocks)(module$exports$Blockly$libraryBlocks$math.blocks);var module$exports$Blockly$libraryBlocks$loops={}; +module$exports$Blockly$libraryBlocks$loops.blocks=(0,$.module$exports$Blockly$common.createBlockDefinitionsFromJsonArray)([{type:"controls_repeat_ext",message0:"%{BKY_CONTROLS_REPEAT_TITLE}",args0:[{type:"input_value",name:"TIMES",check:"Number"}],message1:"%{BKY_CONTROLS_REPEAT_INPUT_DO} %1",args1:[{type:"input_statement",name:"DO"}],previousStatement:null,nextStatement:null,style:"loop_blocks",tooltip:"%{BKY_CONTROLS_REPEAT_TOOLTIP}",helpUrl:"%{BKY_CONTROLS_REPEAT_HELPURL}"},{type:"controls_repeat", +message0:"%{BKY_CONTROLS_REPEAT_TITLE}",args0:[{type:"field_number",name:"TIMES",value:10,min:0,precision:1}],message1:"%{BKY_CONTROLS_REPEAT_INPUT_DO} %1",args1:[{type:"input_statement",name:"DO"}],previousStatement:null,nextStatement:null,style:"loop_blocks",tooltip:"%{BKY_CONTROLS_REPEAT_TOOLTIP}",helpUrl:"%{BKY_CONTROLS_REPEAT_HELPURL}"},{type:"controls_whileUntil",message0:"%1 %2",args0:[{type:"field_dropdown",name:"MODE",options:[["%{BKY_CONTROLS_WHILEUNTIL_OPERATOR_WHILE}","WHILE"],["%{BKY_CONTROLS_WHILEUNTIL_OPERATOR_UNTIL}", +"UNTIL"]]},{type:"input_value",name:"BOOL",check:"Boolean"}],message1:"%{BKY_CONTROLS_REPEAT_INPUT_DO} %1",args1:[{type:"input_statement",name:"DO"}],previousStatement:null,nextStatement:null,style:"loop_blocks",helpUrl:"%{BKY_CONTROLS_WHILEUNTIL_HELPURL}",extensions:["controls_whileUntil_tooltip"]},{type:"controls_for",message0:"%{BKY_CONTROLS_FOR_TITLE}",args0:[{type:"field_variable",name:"VAR",variable:null},{type:"input_value",name:"FROM",check:"Number",align:"RIGHT"},{type:"input_value",name:"TO", +check:"Number",align:"RIGHT"},{type:"input_value",name:"BY",check:"Number",align:"RIGHT"}],message1:"%{BKY_CONTROLS_REPEAT_INPUT_DO} %1",args1:[{type:"input_statement",name:"DO"}],inputsInline:!0,previousStatement:null,nextStatement:null,style:"loop_blocks",helpUrl:"%{BKY_CONTROLS_FOR_HELPURL}",extensions:["contextMenu_newGetVariableBlock","controls_for_tooltip"]},{type:"controls_forEach",message0:"%{BKY_CONTROLS_FOREACH_TITLE}",args0:[{type:"field_variable",name:"VAR",variable:null},{type:"input_value", +name:"LIST",check:"Array"}],message1:"%{BKY_CONTROLS_REPEAT_INPUT_DO} %1",args1:[{type:"input_statement",name:"DO"}],previousStatement:null,nextStatement:null,style:"loop_blocks",helpUrl:"%{BKY_CONTROLS_FOREACH_HELPURL}",extensions:["contextMenu_newGetVariableBlock","controls_forEach_tooltip"]},{type:"controls_flow_statements",message0:"%1",args0:[{type:"field_dropdown",name:"FLOW",options:[["%{BKY_CONTROLS_FLOW_STATEMENTS_OPERATOR_BREAK}","BREAK"],["%{BKY_CONTROLS_FLOW_STATEMENTS_OPERATOR_CONTINUE}", +"CONTINUE"]]}],previousStatement:null,style:"loop_blocks",helpUrl:"%{BKY_CONTROLS_FLOW_STATEMENTS_HELPURL}",suppressPrefixSuffix:!0,extensions:["controls_flow_tooltip","controls_flow_in_loop_check"]}]);var module$contents$Blockly$libraryBlocks$loops_WHILE_UNTIL_TOOLTIPS={WHILE:"%{BKY_CONTROLS_WHILEUNTIL_TOOLTIP_WHILE}",UNTIL:"%{BKY_CONTROLS_WHILEUNTIL_TOOLTIP_UNTIL}"}; +(0,$.module$exports$Blockly$Extensions.register)("controls_whileUntil_tooltip",(0,$.module$exports$Blockly$Extensions.buildTooltipForDropdown)("MODE",module$contents$Blockly$libraryBlocks$loops_WHILE_UNTIL_TOOLTIPS));var module$contents$Blockly$libraryBlocks$loops_BREAK_CONTINUE_TOOLTIPS={BREAK:"%{BKY_CONTROLS_FLOW_STATEMENTS_TOOLTIP_BREAK}",CONTINUE:"%{BKY_CONTROLS_FLOW_STATEMENTS_TOOLTIP_CONTINUE}"}; +(0,$.module$exports$Blockly$Extensions.register)("controls_flow_tooltip",(0,$.module$exports$Blockly$Extensions.buildTooltipForDropdown)("FLOW",module$contents$Blockly$libraryBlocks$loops_BREAK_CONTINUE_TOOLTIPS)); +var module$contents$Blockly$libraryBlocks$loops_CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN={customContextMenu:function(a){if(!this.isInFlyout){var b=this.getField("VAR").getVariable(),c=b.name;if(!this.isCollapsed()&&null!==c){var d={enabled:!0};d.text=$.module$exports$Blockly$Msg.Msg.VARIABLES_SET_CREATE_GET.replace("%1",c);b=(0,$.module$exports$Blockly$Variables.generateVariableFieldDom)(b);c=(0,$.module$exports$Blockly$utils$xml.createElement)("block");c.setAttribute("type","variables_get"); +c.appendChild(b);d.callback=(0,$.module$exports$Blockly$ContextMenu.callbackFactory)(this,c);a.push(d)}}}};(0,$.module$exports$Blockly$Extensions.registerMixin)("contextMenu_newGetVariableBlock",module$contents$Blockly$libraryBlocks$loops_CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN);(0,$.module$exports$Blockly$Extensions.register)("controls_for_tooltip",(0,$.module$exports$Blockly$Extensions.buildTooltipWithFieldText)("%{BKY_CONTROLS_FOR_TOOLTIP}","VAR")); +(0,$.module$exports$Blockly$Extensions.register)("controls_forEach_tooltip",(0,$.module$exports$Blockly$Extensions.buildTooltipWithFieldText)("%{BKY_CONTROLS_FOREACH_TOOLTIP}","VAR"));module$exports$Blockly$libraryBlocks$loops.loopTypes=new Set(["controls_repeat","controls_repeat_ext","controls_forEach","controls_for","controls_whileUntil"]); +var module$contents$Blockly$libraryBlocks$loops_CONTROL_FLOW_IN_LOOP_CHECK_MIXIN={getSurroundLoop:function(){var a=this;do{if(module$exports$Blockly$libraryBlocks$loops.loopTypes.has(a.type))return a;a=a.getSurroundParent()}while(a);return null},onchange:function(a){if(this.workspace.isDragging&&!this.workspace.isDragging()&&a.type===$.module$exports$Blockly$Events.BLOCK_MOVE){var b=this.getSurroundLoop(this);this.setWarningText(b?null:$.module$exports$Blockly$Msg.Msg.CONTROLS_FLOW_STATEMENTS_WARNING); +if(!this.isInFlyout){var c=(0,$.module$exports$Blockly$Events.getGroup)();(0,$.module$exports$Blockly$Events.setGroup)(a.group);this.setEnabled(b);(0,$.module$exports$Blockly$Events.setGroup)(c)}}}};(0,$.module$exports$Blockly$Extensions.registerMixin)("controls_flow_in_loop_check",module$contents$Blockly$libraryBlocks$loops_CONTROL_FLOW_IN_LOOP_CHECK_MIXIN);(0,$.module$exports$Blockly$common.defineBlocks)(module$exports$Blockly$libraryBlocks$loops.blocks);var module$exports$Blockly$libraryBlocks$logic={}; +module$exports$Blockly$libraryBlocks$logic.blocks=(0,$.module$exports$Blockly$common.createBlockDefinitionsFromJsonArray)([{type:"logic_boolean",message0:"%1",args0:[{type:"field_dropdown",name:"BOOL",options:[["%{BKY_LOGIC_BOOLEAN_TRUE}","TRUE"],["%{BKY_LOGIC_BOOLEAN_FALSE}","FALSE"]]}],output:"Boolean",style:"logic_blocks",tooltip:"%{BKY_LOGIC_BOOLEAN_TOOLTIP}",helpUrl:"%{BKY_LOGIC_BOOLEAN_HELPURL}"},{type:"controls_if",message0:"%{BKY_CONTROLS_IF_MSG_IF} %1",args0:[{type:"input_value",name:"IF0", +check:"Boolean"}],message1:"%{BKY_CONTROLS_IF_MSG_THEN} %1",args1:[{type:"input_statement",name:"DO0"}],previousStatement:null,nextStatement:null,style:"logic_blocks",helpUrl:"%{BKY_CONTROLS_IF_HELPURL}",suppressPrefixSuffix:!0,mutator:"controls_if_mutator",extensions:["controls_if_tooltip"]},{type:"controls_ifelse",message0:"%{BKY_CONTROLS_IF_MSG_IF} %1",args0:[{type:"input_value",name:"IF0",check:"Boolean"}],message1:"%{BKY_CONTROLS_IF_MSG_THEN} %1",args1:[{type:"input_statement",name:"DO0"}],message2:"%{BKY_CONTROLS_IF_MSG_ELSE} %1", +args2:[{type:"input_statement",name:"ELSE"}],previousStatement:null,nextStatement:null,style:"logic_blocks",tooltip:"%{BKYCONTROLS_IF_TOOLTIP_2}",helpUrl:"%{BKY_CONTROLS_IF_HELPURL}",suppressPrefixSuffix:!0,extensions:["controls_if_tooltip"]},{type:"logic_compare",message0:"%1 %2 %3",args0:[{type:"input_value",name:"A"},{type:"field_dropdown",name:"OP",options:[["=","EQ"],["\u2260","NEQ"],["\u200f<","LT"],["\u200f\u2264","LTE"],["\u200f>","GT"],["\u200f\u2265","GTE"]]},{type:"input_value",name:"B"}], +inputsInline:!0,output:"Boolean",style:"logic_blocks",helpUrl:"%{BKY_LOGIC_COMPARE_HELPURL}",extensions:["logic_compare","logic_op_tooltip"]},{type:"logic_operation",message0:"%1 %2 %3",args0:[{type:"input_value",name:"A",check:"Boolean"},{type:"field_dropdown",name:"OP",options:[["%{BKY_LOGIC_OPERATION_AND}","AND"],["%{BKY_LOGIC_OPERATION_OR}","OR"]]},{type:"input_value",name:"B",check:"Boolean"}],inputsInline:!0,output:"Boolean",style:"logic_blocks",helpUrl:"%{BKY_LOGIC_OPERATION_HELPURL}",extensions:["logic_op_tooltip"]}, +{type:"logic_negate",message0:"%{BKY_LOGIC_NEGATE_TITLE}",args0:[{type:"input_value",name:"BOOL",check:"Boolean"}],output:"Boolean",style:"logic_blocks",tooltip:"%{BKY_LOGIC_NEGATE_TOOLTIP}",helpUrl:"%{BKY_LOGIC_NEGATE_HELPURL}"},{type:"logic_null",message0:"%{BKY_LOGIC_NULL}",output:null,style:"logic_blocks",tooltip:"%{BKY_LOGIC_NULL_TOOLTIP}",helpUrl:"%{BKY_LOGIC_NULL_HELPURL}"},{type:"logic_ternary",message0:"%{BKY_LOGIC_TERNARY_CONDITION} %1",args0:[{type:"input_value",name:"IF",check:"Boolean"}], +message1:"%{BKY_LOGIC_TERNARY_IF_TRUE} %1",args1:[{type:"input_value",name:"THEN"}],message2:"%{BKY_LOGIC_TERNARY_IF_FALSE} %1",args2:[{type:"input_value",name:"ELSE"}],output:null,style:"logic_blocks",tooltip:"%{BKY_LOGIC_TERNARY_TOOLTIP}",helpUrl:"%{BKY_LOGIC_TERNARY_HELPURL}",extensions:["logic_ternary"]},{type:"controls_if_if",message0:"%{BKY_CONTROLS_IF_IF_TITLE_IF}",nextStatement:null,enableContextMenu:!1,style:"logic_blocks",tooltip:"%{BKY_CONTROLS_IF_IF_TOOLTIP}"},{type:"controls_if_elseif", +message0:"%{BKY_CONTROLS_IF_ELSEIF_TITLE_ELSEIF}",previousStatement:null,nextStatement:null,enableContextMenu:!1,style:"logic_blocks",tooltip:"%{BKY_CONTROLS_IF_ELSEIF_TOOLTIP}"},{type:"controls_if_else",message0:"%{BKY_CONTROLS_IF_ELSE_TITLE_ELSE}",previousStatement:null,enableContextMenu:!1,style:"logic_blocks",tooltip:"%{BKY_CONTROLS_IF_ELSE_TOOLTIP}"}]); +var module$contents$Blockly$libraryBlocks$logic_TOOLTIPS_BY_OP={EQ:"%{BKY_LOGIC_COMPARE_TOOLTIP_EQ}",NEQ:"%{BKY_LOGIC_COMPARE_TOOLTIP_NEQ}",LT:"%{BKY_LOGIC_COMPARE_TOOLTIP_LT}",LTE:"%{BKY_LOGIC_COMPARE_TOOLTIP_LTE}",GT:"%{BKY_LOGIC_COMPARE_TOOLTIP_GT}",GTE:"%{BKY_LOGIC_COMPARE_TOOLTIP_GTE}",AND:"%{BKY_LOGIC_OPERATION_TOOLTIP_AND}",OR:"%{BKY_LOGIC_OPERATION_TOOLTIP_OR}"}; +(0,$.module$exports$Blockly$Extensions.register)("logic_op_tooltip",(0,$.module$exports$Blockly$Extensions.buildTooltipForDropdown)("OP",module$contents$Blockly$libraryBlocks$logic_TOOLTIPS_BY_OP)); +var module$contents$Blockly$libraryBlocks$logic_CONTROLS_IF_MUTATOR_MIXIN={elseifCount_:0,elseCount_:0,mutationToDom:function(){if(!this.elseifCount_&&!this.elseCount_)return null;var a=(0,$.module$exports$Blockly$utils$xml.createElement)("mutation");this.elseifCount_&&a.setAttribute("elseif",this.elseifCount_);this.elseCount_&&a.setAttribute("else",1);return a},domToMutation:function(a){this.elseifCount_=parseInt(a.getAttribute("elseif"),10)||0;this.elseCount_=parseInt(a.getAttribute("else"),10)|| +0;this.rebuildShape_()},saveExtraState:function(){if(!this.elseifCount_&&!this.elseCount_)return null;var a=Object.create(null);this.elseifCount_&&(a.elseIfCount=this.elseifCount_);this.elseCount_&&(a.hasElse=!0);return a},loadExtraState:function(a){this.elseifCount_=a.elseIfCount||0;this.elseCount_=a.hasElse?1:0;this.updateShape_()},decompose:function(a){var b=a.newBlock("controls_if_if");b.initSvg();for(var c=b.nextConnection,d=1;d<=this.elseifCount_;d++){var e=a.newBlock("controls_if_elseif"); +e.initSvg();c.connect(e.previousConnection);c=e.nextConnection}this.elseCount_&&(a=a.newBlock("controls_if_else"),a.initSvg(),c.connect(a.previousConnection));return b},compose:function(a){a=a.nextConnection.targetBlock();this.elseCount_=this.elseifCount_=0;for(var b=[null],c=[null],d=null;a&&!a.isInsertionMarker();){switch(a.type){case "controls_if_elseif":this.elseifCount_++;b.push(a.valueConnection_);c.push(a.statementConnection_);break;case "controls_if_else":this.elseCount_++;d=a.statementConnection_; +break;default:throw TypeError("Unknown block type: "+a.type);}a=a.nextConnection&&a.nextConnection.targetBlock()}this.updateShape_();this.reconnectChildBlocks_(b,c,d)},saveConnections:function(a){a=a.nextConnection.targetBlock();for(var b=1;a;){switch(a.type){case "controls_if_elseif":var c=this.getInput("IF"+b),d=this.getInput("DO"+b);a.valueConnection_=c&&c.connection.targetConnection;a.statementConnection_=d&&d.connection.targetConnection;b++;break;case "controls_if_else":c=this.getInput("ELSE"); +a.statementConnection_=c&&c.connection.targetConnection;break;default:throw TypeError("Unknown block type: "+a.type);}a=a.nextConnection&&a.nextConnection.targetBlock()}},rebuildShape_:function(){var a=[null],b=[null],c=null;this.getInput("ELSE")&&(c=this.getInput("ELSE").connection.targetConnection);for(var d=1;this.getInput("IF"+d);d++){var e=this.getInput("IF"+d),f=this.getInput("DO"+d);a.push(e.connection.targetConnection);b.push(f.connection.targetConnection)}this.updateShape_();this.reconnectChildBlocks_(a, +b,c)},updateShape_:function(){this.getInput("ELSE")&&this.removeInput("ELSE");for(var a=1;this.getInput("IF"+a);a++)this.removeInput("IF"+a),this.removeInput("DO"+a);for(a=1;a<=this.elseifCount_;a++)this.appendValueInput("IF"+a).setCheck("Boolean").appendField($.module$exports$Blockly$Msg.Msg.CONTROLS_IF_MSG_ELSEIF),this.appendStatementInput("DO"+a).appendField($.module$exports$Blockly$Msg.Msg.CONTROLS_IF_MSG_THEN);this.elseCount_&&this.appendStatementInput("ELSE").appendField($.module$exports$Blockly$Msg.Msg.CONTROLS_IF_MSG_ELSE)}, +reconnectChildBlocks_:function(a,b,c){for(var d=1;d<=this.elseifCount_;d++)$.module$exports$Blockly$Mutator.Mutator.reconnect(a[d],this,"IF"+d),$.module$exports$Blockly$Mutator.Mutator.reconnect(b[d],this,"DO"+d);$.module$exports$Blockly$Mutator.Mutator.reconnect(c,this,"ELSE")}};(0,$.module$exports$Blockly$Extensions.registerMutator)("controls_if_mutator",module$contents$Blockly$libraryBlocks$logic_CONTROLS_IF_MUTATOR_MIXIN,null,["controls_if_elseif","controls_if_else"]); +var module$contents$Blockly$libraryBlocks$logic_CONTROLS_IF_TOOLTIP_EXTENSION=function(){this.setTooltip(function(){if(this.elseifCount_||this.elseCount_){if(!this.elseifCount_&&this.elseCount_)return $.module$exports$Blockly$Msg.Msg.CONTROLS_IF_TOOLTIP_2;if(this.elseifCount_&&!this.elseCount_)return $.module$exports$Blockly$Msg.Msg.CONTROLS_IF_TOOLTIP_3;if(this.elseifCount_&&this.elseCount_)return $.module$exports$Blockly$Msg.Msg.CONTROLS_IF_TOOLTIP_4}else return $.module$exports$Blockly$Msg.Msg.CONTROLS_IF_TOOLTIP_1; +return""}.bind(this))};(0,$.module$exports$Blockly$Extensions.register)("controls_if_tooltip",module$contents$Blockly$libraryBlocks$logic_CONTROLS_IF_TOOLTIP_EXTENSION); +var module$contents$Blockly$libraryBlocks$logic_LOGIC_COMPARE_ONCHANGE_MIXIN={onchange:function(a){this.prevBlocks_||(this.prevBlocks_=[null,null]);var b=this.getInputTargetBlock("A"),c=this.getInputTargetBlock("B");b&&c&&!this.workspace.connectionChecker.doTypeChecks(b.outputConnection,c.outputConnection)&&((0,$.module$exports$Blockly$Events.setGroup)(a.group),a=this.prevBlocks_[0],a!==b&&(b.unplug(),!a||a.isDisposed()||a.isShadow()||this.getInput("A").connection.connect(a.outputConnection)),b=this.prevBlocks_[1], +b!==c&&(c.unplug(),!b||b.isDisposed()||b.isShadow()||this.getInput("B").connection.connect(b.outputConnection)),this.bumpNeighbours(),(0,$.module$exports$Blockly$Events.setGroup)(!1));this.prevBlocks_[0]=this.getInputTargetBlock("A");this.prevBlocks_[1]=this.getInputTargetBlock("B")}},module$contents$Blockly$libraryBlocks$logic_LOGIC_COMPARE_EXTENSION=function(){this.mixin(module$contents$Blockly$libraryBlocks$logic_LOGIC_COMPARE_ONCHANGE_MIXIN)}; +(0,$.module$exports$Blockly$Extensions.register)("logic_compare",module$contents$Blockly$libraryBlocks$logic_LOGIC_COMPARE_EXTENSION); +var module$contents$Blockly$libraryBlocks$logic_LOGIC_TERNARY_ONCHANGE_MIXIN={prevParentConnection_:null,onchange:function(a){var b=this.getInputTargetBlock("THEN"),c=this.getInputTargetBlock("ELSE"),d=this.outputConnection.targetConnection;if((b||c)&&d)for(var e=0;2>e;e++){var f=1===e?b:c;f&&!f.workspace.connectionChecker.doTypeChecks(f.outputConnection,d)&&((0,$.module$exports$Blockly$Events.setGroup)(a.group),d===this.prevParentConnection_?(this.unplug(),d.getSourceBlock().bumpNeighbours()):(f.unplug(), +f.bumpNeighbours()),(0,$.module$exports$Blockly$Events.setGroup)(!1))}this.prevParentConnection_=d}};(0,$.module$exports$Blockly$Extensions.registerMixin)("logic_ternary",module$contents$Blockly$libraryBlocks$logic_LOGIC_TERNARY_ONCHANGE_MIXIN);(0,$.module$exports$Blockly$common.defineBlocks)(module$exports$Blockly$libraryBlocks$logic.blocks);var module$exports$Blockly$libraryBlocks$lists={}; +module$exports$Blockly$libraryBlocks$lists.blocks=(0,$.module$exports$Blockly$common.createBlockDefinitionsFromJsonArray)([{type:"lists_create_empty",message0:"%{BKY_LISTS_CREATE_EMPTY_TITLE}",output:"Array",style:"list_blocks",tooltip:"%{BKY_LISTS_CREATE_EMPTY_TOOLTIP}",helpUrl:"%{BKY_LISTS_CREATE_EMPTY_HELPURL}"},{type:"lists_repeat",message0:"%{BKY_LISTS_REPEAT_TITLE}",args0:[{type:"input_value",name:"ITEM"},{type:"input_value",name:"NUM",check:"Number"}],output:"Array",style:"list_blocks",tooltip:"%{BKY_LISTS_REPEAT_TOOLTIP}", +helpUrl:"%{BKY_LISTS_REPEAT_HELPURL}"},{type:"lists_reverse",message0:"%{BKY_LISTS_REVERSE_MESSAGE0}",args0:[{type:"input_value",name:"LIST",check:"Array"}],output:"Array",inputsInline:!0,style:"list_blocks",tooltip:"%{BKY_LISTS_REVERSE_TOOLTIP}",helpUrl:"%{BKY_LISTS_REVERSE_HELPURL}"},{type:"lists_isEmpty",message0:"%{BKY_LISTS_ISEMPTY_TITLE}",args0:[{type:"input_value",name:"VALUE",check:["String","Array"]}],output:"Boolean",style:"list_blocks",tooltip:"%{BKY_LISTS_ISEMPTY_TOOLTIP}",helpUrl:"%{BKY_LISTS_ISEMPTY_HELPURL}"}, +{type:"lists_length",message0:"%{BKY_LISTS_LENGTH_TITLE}",args0:[{type:"input_value",name:"VALUE",check:["String","Array"]}],output:"Number",style:"list_blocks",tooltip:"%{BKY_LISTS_LENGTH_TOOLTIP}",helpUrl:"%{BKY_LISTS_LENGTH_HELPURL}"}]); +module$exports$Blockly$libraryBlocks$lists.blocks.lists_create_with={init:function(){this.setHelpUrl($.module$exports$Blockly$Msg.Msg.LISTS_CREATE_WITH_HELPURL);this.setStyle("list_blocks");this.itemCount_=3;this.updateShape_();this.setOutput(!0,"Array");this.setMutator(new $.module$exports$Blockly$Mutator.Mutator(["lists_create_with_item"]));this.setTooltip($.module$exports$Blockly$Msg.Msg.LISTS_CREATE_WITH_TOOLTIP)},mutationToDom:function(){var a=(0,$.module$exports$Blockly$utils$xml.createElement)("mutation"); a.setAttribute("items",this.itemCount_);return a},domToMutation:function(a){this.itemCount_=parseInt(a.getAttribute("items"),10);this.updateShape_()},saveExtraState:function(){return{itemCount:this.itemCount_}},loadExtraState:function(a){this.itemCount_=a.itemCount;this.updateShape_()},decompose:function(a){var b=a.newBlock("lists_create_with_container");b.initSvg();for(var c=b.getInput("STACK").connection,d=0;d * - * @type {Object|undefined} + * Currently the Closure Compiler will only recognize very simple definitions of + * this value when looking for values to apply to compiled code and ignore all + * other references. Specifically, it looks the value defined at the variable + * declaration, as with the example above. + * + * TODO(user): Improve the recognized definitions. + * + * @type {!Object|null|undefined} */ goog.global.CLOSURE_DEFINES; @@ -3175,23 +3182,10 @@ if (!COMPILED && goog.DEPENDENCIES_ENABLED) { scriptEl.nonce = nonce; } - if (goog.DebugLoader_.IS_OLD_IE_) { - // Execution order is not guaranteed on old IE, halt loading and write - // these scripts one at a time, after each loads. - controller.pause(); - scriptEl.onreadystatechange = function() { - if (scriptEl.readyState == 'loaded' || - scriptEl.readyState == 'complete') { - controller.loaded(); - controller.resume(); - } - }; - } else { - scriptEl.onload = function() { - scriptEl.onload = null; - controller.loaded(); - }; - } + scriptEl.onload = function() { + scriptEl.onload = null; + controller.loaded(); + }; scriptEl.src = goog.TRUSTED_TYPES_POLICY_ ? goog.TRUSTED_TYPES_POLICY_.createScriptURL(this.path) : @@ -3502,13 +3496,6 @@ if (!COMPILED && goog.DEPENDENCIES_ENABLED) { // If one thing is pending it is this. var anythingElsePending = controller.pending().length > 1; - // If anything else is loading we need to lazy load due to bugs in old IE. - // Specifically script tags with src and script tags with contents could - // execute out of order if document.write is used, so we cannot use - // document.write. Do not pause here; it breaks old IE as well. - var useOldIeWorkAround = - anythingElsePending && goog.DebugLoader_.IS_OLD_IE_; - // Additionally if we are meant to defer scripts but the page is still // loading (e.g. an ES6 module is loading) then also defer. Or if we are // meant to defer and anything else is pending then defer (those may be @@ -3517,7 +3504,7 @@ if (!COMPILED && goog.DEPENDENCIES_ENABLED) { var needsAsyncLoading = goog.Dependency.defer_ && (anythingElsePending || goog.isDocumentLoading_()); - if (useOldIeWorkAround || needsAsyncLoading) { + if (needsAsyncLoading) { // Note that we only defer when we have to rather than 100% of the time. // Always defering would work, but then in theory the order of // goog.require calls would then matter. We want to enforce that most of @@ -3561,8 +3548,7 @@ if (!COMPILED && goog.DEPENDENCIES_ENABLED) { }; } else { // Always eval on old IE. - if (goog.DebugLoader_.IS_OLD_IE_ || !goog.inHtmlDocument_() || - !goog.isDocumentLoading_()) { + if (!goog.inHtmlDocument_() || !goog.isDocumentLoading_()) { load(); } else { fetchInOwnScriptThenLoad(); @@ -3706,15 +3692,6 @@ if (!COMPILED && goog.DEPENDENCIES_ENABLED) { }; - /** - * Whether the browser is IE9 or earlier, which needs special handling - * for deferred modules. - * @const @private {boolean} - */ - goog.DebugLoader_.IS_OLD_IE_ = !!( - !goog.global.atob && goog.global.document && goog.global.document['all']); - - /** * @param {string} relPath * @param {!Array|undefined} provides diff --git a/closure/goog/goog.js b/closure/goog/goog.js new file mode 100644 index 000000000..9c8d53e88 --- /dev/null +++ b/closure/goog/goog.js @@ -0,0 +1,99 @@ +// Copyright 2018 The Closure Library Authors. All Rights Reserved. +// +// 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 ES6 module that exports symbols from base.js so that ES6 + * modules do not need to use globals and so that is clear if a project is using + * Closure's base.js file. It is also a subset of properties in base.js, meaning + * it should be clearer what should not be used in ES6 modules + * (goog.module/provide are not exported here, for example). Though that is not + * to say that everything in this file should be used in an ES6 module; some + * depreciated functions are exported to make migration easier (e.g. + * goog.scope). + * + * Note that this does not load Closure's base.js file, it is still up to the + * programmer to include it. Nor does the fact that this is an ES6 module mean + * that projects no longer require deps.js files for debug loading - they do. + * Closure will need to load your ES6 modules for you if you have any Closure + * file (goog.provide/goog.module) dependencies, as they need to be available + * before the ES6 module evaluates. + * + * Also note that this file has special compiler handling! It is okay to export + * anything from this file, but the name also needs to exist on the global goog. + * This special compiler pass enforces that you always import this file as + * `import * as goog`, as many tools use regex based parsing to find + * goog.require calls. + */ + +export const global = goog.global; +export const require = goog.require; +export const define = goog.define; +export const DEBUG = goog.DEBUG; +export const LOCALE = goog.LOCALE; +export const TRUSTED_SITE = goog.TRUSTED_SITE; +export const DISALLOW_TEST_ONLY_CODE = goog.DISALLOW_TEST_ONLY_CODE; +export const getGoogModule = goog.module.get; +export const setTestOnly = goog.setTestOnly; +export const forwardDeclare = goog.forwardDeclare; +export const getObjectByName = goog.getObjectByName; +export const basePath = goog.basePath; +export const addSingletonGetter = goog.addSingletonGetter; +export const typeOf = goog.typeOf; +export const isArrayLike = goog.isArrayLike; +export const isDateLike = goog.isDateLike; +export const isObject = goog.isObject; +export const getUid = goog.getUid; +export const hasUid = goog.hasUid; +export const removeUid = goog.removeUid; +export const mixin = goog.mixin; +export const now = Date.now; +export const globalEval = goog.globalEval; +export const getCssName = goog.getCssName; +export const setCssNameMapping = goog.setCssNameMapping; +export const getMsg = goog.getMsg; +export const getMsgWithFallback = goog.getMsgWithFallback; +export const exportSymbol = goog.exportSymbol; +export const exportProperty = goog.exportProperty; +export const nullFunction = goog.nullFunction; +export const abstractMethod = goog.abstractMethod; +export const cloneObject = goog.cloneObject; +export const bind = goog.bind; +export const partial = goog.partial; +export const inherits = goog.inherits; +export const scope = goog.scope; +export const defineClass = goog.defineClass; +export const declareModuleId = goog.declareModuleId; + +// Export select properties of module. Do not export the function itself or +// goog.module.declareLegacyNamespace. +export const module = { + get: goog.module.get, +}; + +// Omissions include: +// goog.ENABLE_DEBUG_LOADER - define only used in base. +// goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING - define only used in base. +// goog.provide - ES6 modules do not provide anything. +// goog.module - ES6 modules cannot be goog.modules. +// goog.module.declareLegacyNamespace - ES6 modules cannot declare namespaces. +// goog.addDependency - meant to only be used by dependency files. +// goog.DEPENDENCIES_ENABLED - constant only used in base. +// goog.TRANSPILE - define only used in base. +// goog.TRANSPILER - define only used in base. +// goog.loadModule - should not be called by any ES6 module; exists for +// generated bundles. +// goog.LOAD_MODULE_USING_EVAL - define only used in base. +// goog.SEAL_MODULE_EXPORTS - define only used in base. +// goog.DebugLoader - used rarely, only outside of compiled code. +// goog.Transpiler - used rarely, only outside of compiled code. diff --git a/core/block.js b/core/block.js index cda2e39a7..ce8f274b6 100644 --- a/core/block.js +++ b/core/block.js @@ -15,8 +15,6 @@ */ goog.module('Blockly.Block'); -/* eslint-disable-next-line no-unused-vars */ -const Abstract = goog.requireType('Blockly.Events.Abstract'); const Extensions = goog.require('Blockly.Extensions'); const Tooltip = goog.require('Blockly.Tooltip'); const arrayUtils = goog.require('Blockly.utils.array'); @@ -27,8 +25,12 @@ const fieldRegistry = goog.require('Blockly.fieldRegistry'); const idGenerator = goog.require('Blockly.utils.idGenerator'); const object = goog.require('Blockly.utils.object'); const parsing = goog.require('Blockly.utils.parsing'); +/* eslint-disable-next-line no-unused-vars */ +const {Abstract} = goog.requireType('Blockly.Events.Abstract'); const {Align, Input} = goog.require('Blockly.Input'); const {ASTNode} = goog.require('Blockly.ASTNode'); +/* eslint-disable-next-line no-unused-vars */ +const {BlockMove} = goog.requireType('Blockly.Events.BlockMove'); const {Blocks} = goog.require('Blockly.blocks'); /* eslint-disable-next-line no-unused-vars */ const {Comment} = goog.requireType('Blockly.Comment'); @@ -62,205 +64,2120 @@ goog.require('Blockly.Events.BlockMove'); /** * Class for one block. * Not normally called directly, workspace.newBlock() is preferred. - * @param {!Workspace} workspace The block's workspace. - * @param {!string} prototypeName Name of the language object containing - * type-specific functions for this block. - * @param {string=} opt_id Optional ID. Use this ID if provided, otherwise - * create a new ID. - * @constructor * @implements {IASTNodeLocation} * @implements {IDeletable} - * @throws When the prototypeName is not valid or not allowed. + * @unrestricted * @alias Blockly.Block */ -const Block = function(workspace, prototypeName, opt_id) { - const {Generator} = goog.module.get('Blockly.Generator'); - if (Generator && typeof Generator.prototype[prototypeName] !== 'undefined') { - // Occluding Generator class members is not allowed. - throw Error( - 'Block prototypeName "' + prototypeName + - '" conflicts with Blockly.Generator members.'); - } - - /** @type {string} */ - this.id = (opt_id && !workspace.getBlockById(opt_id)) ? opt_id : - idGenerator.genUid(); - workspace.setBlockById(this.id, this); - /** @type {Connection} */ - this.outputConnection = null; - /** @type {Connection} */ - this.nextConnection = null; - /** @type {Connection} */ - this.previousConnection = null; - /** @type {!Array} */ - this.inputList = []; - /** @type {boolean|undefined} */ - this.inputsInline = undefined; +class Block { /** - * @type {boolean} - * @private + * @param {!Workspace} workspace The block's workspace. + * @param {!string} prototypeName Name of the language object containing + * type-specific functions for this block. + * @param {string=} opt_id Optional ID. Use this ID if provided, otherwise + * create a new ID. + * @throws When the prototypeName is not valid or not allowed. */ - this.disabled = false; - /** @type {!Tooltip.TipInfo} */ - this.tooltip = ''; - /** @type {boolean} */ - this.contextMenu = true; - - /** - * @type {Block} - * @protected - */ - this.parentBlock_ = null; - - /** - * @type {!Array} - * @protected - */ - this.childBlocks_ = []; - - /** - * @type {boolean} - * @private - */ - this.deletable_ = true; - - /** - * @type {boolean} - * @private - */ - this.movable_ = true; - - /** - * @type {boolean} - * @private - */ - this.editable_ = true; - - /** - * @type {boolean} - * @private - */ - this.isShadow_ = false; - - /** - * @type {boolean} - * @protected - */ - this.collapsed_ = false; - - /** - * @type {?number} - * @protected - */ - this.outputShape_ = null; - - /** - * A string representing the comment attached to this block. - * @type {string|Comment} - * @deprecated August 2019. Use getCommentText instead. - */ - this.comment = null; - - /** - * A model of the comment attached to this block. - * @type {!Block.CommentModel} - * @package - */ - this.commentModel = {text: null, pinned: false, size: new Size(160, 80)}; - - /** - * The block's position in workspace units. (0, 0) is at the workspace's - * origin; scale does not change this value. - * @type {!Coordinate} - * @private - */ - this.xy_ = new Coordinate(0, 0); - - /** @type {!Workspace} */ - this.workspace = workspace; - /** @type {boolean} */ - this.isInFlyout = workspace.isFlyout; - /** @type {boolean} */ - this.isInMutator = workspace.isMutator; - - /** @type {boolean} */ - this.RTL = workspace.RTL; - - /** - * True if this block is an insertion marker. - * @type {boolean} - * @protected - */ - this.isInsertionMarker_ = false; - - /** - * Name of the type of hat. - * @type {string|undefined} - */ - this.hat = undefined; - - /** @type {?boolean} */ - this.rendered = null; - - /** - * A count of statement inputs on the block. - * @type {number} - * @package - */ - this.statementInputCount = 0; - - // Copy the type-specific functions and data from the prototype. - if (prototypeName) { - /** @type {string} */ - this.type = prototypeName; - const prototype = Blocks[prototypeName]; - if (!prototype || typeof prototype !== 'object') { - throw TypeError('Unknown block type: ' + prototypeName); + constructor(workspace, prototypeName, opt_id) { + const {Generator} = goog.module.get('Blockly.Generator'); + if (Generator && + typeof Generator.prototype[prototypeName] !== 'undefined') { + // Occluding Generator class members is not allowed. + throw Error( + 'Block prototypeName "' + prototypeName + + '" conflicts with Blockly.Generator members.'); } - object.mixin(this, prototype); + + /** + * Optional text data that round-trips between blocks and XML. + * Has no effect. May be used by 3rd parties for meta information. + * @type {?string} + */ + this.data = null; + + /** + * Has this block been disposed of? + * @type {boolean} + * @package + */ + this.disposed = false; + + /** + * Colour of the block as HSV hue value (0-360) + * This may be null if the block colour was not set via a hue number. + * @type {?number} + * @private + */ + this.hue_ = null; + + /** + * Colour of the block in '#RRGGBB' format. + * @type {string} + * @protected + */ + this.colour_ = '#000000'; + + /** + * Name of the block style. + * @type {string} + * @protected + */ + this.styleName_ = ''; + + /** + * An optional method called during initialization. + * @type {undefined|?function()} + */ + this.init = undefined; + + /** + * An optional serialization method for defining how to serialize the + * mutation state to XML. This must be coupled with defining + * `domToMutation`. + * @type {undefined|?function(...):!Element} + */ + this.mutationToDom = undefined; + + /** + * An optional deserialization method for defining how to deserialize the + * mutation state from XML. This must be coupled with defining + * `mutationToDom`. + * @type {undefined|?function(!Element)} + */ + this.domToMutation = undefined; + + /** + * An optional serialization method for defining how to serialize the + * block's extra state (eg mutation state) to something JSON compatible. + * This must be coupled with defining `loadExtraState`. + * @type {undefined|?function(): *} + */ + this.saveExtraState = undefined; + + /** + * An optional serialization method for defining how to deserialize the + * block's extra state (eg mutation state) from something JSON compatible. + * This must be coupled with defining `saveExtraState`. + * @type {undefined|?function(*)} + */ + this.loadExtraState = undefined; + + + /** + * An optional property for suppressing adding STATEMENT_PREFIX and + * STATEMENT_SUFFIX to generated code. + * @type {?boolean} + */ + this.suppressPrefixSuffix = false; + + /** + * An optional property for declaring developer variables. Return a list of + * variable names for use by generators. Developer variables are never + * shown to the user, but are declared as global variables in the generated + * code. + * @type {undefined|?function():!Array} + */ + this.getDeveloperVariables = undefined; + + /** @type {string} */ + this.id = (opt_id && !workspace.getBlockById(opt_id)) ? + opt_id : + idGenerator.genUid(); + workspace.setBlockById(this.id, this); + /** @type {Connection} */ + this.outputConnection = null; + /** @type {Connection} */ + this.nextConnection = null; + /** @type {Connection} */ + this.previousConnection = null; + /** @type {!Array} */ + this.inputList = []; + /** @type {boolean|undefined} */ + this.inputsInline = undefined; + /** + * @type {boolean} + * @private + */ + this.disabled = false; + /** @type {!Tooltip.TipInfo} */ + this.tooltip = ''; + /** @type {boolean} */ + this.contextMenu = true; + + /** + * @type {Block} + * @protected + */ + this.parentBlock_ = null; + + /** + * @type {!Array} + * @protected + */ + this.childBlocks_ = []; + + /** + * @type {boolean} + * @private + */ + this.deletable_ = true; + + /** + * @type {boolean} + * @private + */ + this.movable_ = true; + + /** + * @type {boolean} + * @private + */ + this.editable_ = true; + + /** + * @type {boolean} + * @private + */ + this.isShadow_ = false; + + /** + * @type {boolean} + * @protected + */ + this.collapsed_ = false; + + /** + * @type {?number} + * @protected + */ + this.outputShape_ = null; + + /** + * A string representing the comment attached to this block. + * @type {string|Comment} + * @deprecated August 2019. Use getCommentText instead. + */ + this.comment = null; + + /** + * A model of the comment attached to this block. + * @type {!Block.CommentModel} + * @package + */ + this.commentModel = {text: null, pinned: false, size: new Size(160, 80)}; + + /** + * The block's position in workspace units. (0, 0) is at the workspace's + * origin; scale does not change this value. + * @type {!Coordinate} + * @private + */ + this.xy_ = new Coordinate(0, 0); + + /** @type {!Workspace} */ + this.workspace = workspace; + /** @type {boolean} */ + this.isInFlyout = workspace.isFlyout; + /** @type {boolean} */ + this.isInMutator = workspace.isMutator; + + /** @type {boolean} */ + this.RTL = workspace.RTL; + + /** + * True if this block is an insertion marker. + * @type {boolean} + * @protected + */ + this.isInsertionMarker_ = false; + + /** + * Name of the type of hat. + * @type {string|undefined} + */ + this.hat = undefined; + + /** @type {?boolean} */ + this.rendered = null; + + /** + * String for block help, or function that returns a URL. Null for no help. + * @type {string|Function} + */ + this.helpUrl = null; + + /** + * A bound callback function to use when the parent workspace changes. + * @type {?function(Abstract)} + * @private + */ + this.onchangeWrapper_ = null; + + /** + * A count of statement inputs on the block. + * @type {number} + * @package + */ + this.statementInputCount = 0; + + // Copy the type-specific functions and data from the prototype. + if (prototypeName) { + /** @type {string} */ + this.type = prototypeName; + const prototype = Blocks[prototypeName]; + if (!prototype || typeof prototype !== 'object') { + throw TypeError('Invalid block definition for type: ' + prototypeName); + } + object.mixin(this, prototype); + } + + workspace.addTopBlock(this); + workspace.addTypedBlock(this); + + if (new.target === Block) this.doInit_(); } - workspace.addTopBlock(this); - workspace.addTypedBlock(this); + /** + * Calls the init() function and handles associated event firing, etc. + * @protected + */ + doInit_() { + // All events fired should be part of the same group. + // Any events fired during init should not be undoable, + // so that block creation is atomic. + const existingGroup = eventUtils.getGroup(); + if (!existingGroup) { + eventUtils.setGroup(true); + } + const initialUndoFlag = eventUtils.getRecordUndo(); - // All events fired should be part of the same group. - // Any events fired during init should not be undoable, - // so that block creation is atomic. - const existingGroup = eventUtils.getGroup(); - if (!existingGroup) { - eventUtils.setGroup(true); - } - const initialUndoFlag = eventUtils.getRecordUndo(); + try { + // Call an initialization function, if it exists. + if (typeof this.init === 'function') { + eventUtils.setRecordUndo(false); + this.init(); + eventUtils.setRecordUndo(initialUndoFlag); + } - try { - // Call an initialization function, if it exists. - if (typeof this.init === 'function') { - eventUtils.setRecordUndo(false); - this.init(); + // Fire a create event. + if (eventUtils.isEnabled()) { + eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CREATE))(this)); + } + } finally { + if (!existingGroup) { + eventUtils.setGroup(false); + } + // In case init threw, recordUndo flag should still be reset. eventUtils.setRecordUndo(initialUndoFlag); } - // Fire a create event. + // Record initial inline state. + /** @type {boolean|undefined} */ + this.inputsInlineDefault = this.inputsInline; + + // Bind an onchange function, if it exists. + if (typeof this.onchange === 'function') { + this.setOnChange(this.onchange); + } + } + + /** + * Dispose of this block. + * @param {boolean} healStack If true, then try to heal any gap by connecting + * the next statement with the previous statement. Otherwise, dispose of + * all children of this block. + * @suppress {checkTypes} + */ + dispose(healStack) { + if (!this.workspace) { + // Already deleted. + return; + } + // Terminate onchange event calls. + if (this.onchangeWrapper_) { + this.workspace.removeChangeListener(this.onchangeWrapper_); + } + + this.unplug(healStack); if (eventUtils.isEnabled()) { - eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CREATE))(this)); + eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_DELETE))(this)); } - } finally { - if (!existingGroup) { - eventUtils.setGroup(false); + eventUtils.disable(); + + try { + // This block is now at the top of the workspace. + // Remove this block from the workspace's list of top-most blocks. + if (this.workspace) { + this.workspace.removeTopBlock(this); + this.workspace.removeTypedBlock(this); + // Remove from block database. + this.workspace.removeBlockById(this.id); + this.workspace = null; + } + + // Just deleting this block from the DOM would result in a memory leak as + // well as corruption of the connection database. Therefore we must + // methodically step through the blocks and carefully disassemble them. + + if (common.getSelected() === this) { + common.setSelected(null); + } + + // First, dispose of all my children. + for (let i = this.childBlocks_.length - 1; i >= 0; i--) { + this.childBlocks_[i].dispose(false); + } + // Then dispose of myself. + // Dispose of all inputs and their fields. + for (let i = 0, input; (input = this.inputList[i]); i++) { + input.dispose(); + } + this.inputList.length = 0; + // Dispose of any remaining connections (next/previous/output). + const connections = this.getConnections_(true); + for (let i = 0, connection; (connection = connections[i]); i++) { + connection.dispose(); + } + } finally { + eventUtils.enable(); + this.disposed = true; } - // In case init threw, recordUndo flag should still be reset. - eventUtils.setRecordUndo(initialUndoFlag); } - // Record initial inline state. - /** @type {boolean|undefined} */ - this.inputsInlineDefault = this.inputsInline; - - // Bind an onchange function, if it exists. - if (typeof this.onchange === 'function') { - this.setOnChange(this.onchange); + /** + * 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 + */ + initModel() { + for (let i = 0, input; (input = this.inputList[i]); i++) { + for (let 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. + * @param {boolean=} opt_healStack Disconnect child statement and reconnect + * stack. Defaults to false. + */ + unplug(opt_healStack) { + if (this.outputConnection) { + this.unplugFromRow_(opt_healStack); + } + if (this.previousConnection) { + this.unplugFromStack_(opt_healStack); + } + } + + /** + * Unplug this block's output from an input on another block. Optionally + * reconnect the block's parent to the only child block, if possible. + * @param {boolean=} opt_healStack Disconnect right-side block and connect to + * left-side block. Defaults to false. + * @private + */ + unplugFromRow_(opt_healStack) { + let parentConnection = null; + if (this.outputConnection.isConnected()) { + parentConnection = this.outputConnection.targetConnection; + // Disconnect from any superior block. + this.outputConnection.disconnect(); + } + + // Return early in obvious cases. + if (!parentConnection || !opt_healStack) { + return; + } + + const thisConnection = this.getOnlyValueConnection_(); + if (!thisConnection || !thisConnection.isConnected() || + thisConnection.targetBlock().isShadow()) { + // Too many or too few possible connections on this block, or there's + // nothing on the other side of this connection. + return; + } + + const childConnection = thisConnection.targetConnection; + // Disconnect the child block. + childConnection.disconnect(); + // Connect child to the parent if possible, otherwise bump away. + if (this.workspace.connectionChecker.canConnect( + childConnection, parentConnection, false)) { + parentConnection.connect(childConnection); + } else { + childConnection.onFailedConnect(parentConnection); + } + } + + /** + * Returns the connection on the value input that is connected to another + * block. When an insertion marker is connected to a connection with a block + * already attached, the connected block is attached to the insertion marker. + * Since only one block can be displaced and attached to the insertion marker + * this should only ever return one connection. + * + * @return {?Connection} The connection on the value input, or null. + * @private + */ + getOnlyValueConnection_() { + let connection = null; + for (let i = 0; i < this.inputList.length; i++) { + const thisConnection = this.inputList[i].connection; + if (thisConnection && + thisConnection.type === ConnectionType.INPUT_VALUE && + thisConnection.targetConnection) { + if (connection) { + return null; // More than one value input found. + } + connection = thisConnection; + } + } + return connection; + } + + /** + * Unplug this statement block from its superior block. Optionally reconnect + * the block underneath with the block on top. + * @param {boolean=} opt_healStack Disconnect child statement and reconnect + * stack. Defaults to false. + * @private + */ + unplugFromStack_(opt_healStack) { + let previousTarget = null; + if (this.previousConnection.isConnected()) { + // Remember the connection that any next statements need to connect to. + previousTarget = this.previousConnection.targetConnection; + // Detach this block from the parent's tree. + this.previousConnection.disconnect(); + } + const nextBlock = this.getNextBlock(); + if (opt_healStack && nextBlock && !nextBlock.isShadow()) { + // Disconnect the next statement. + const nextTarget = this.nextConnection.targetConnection; + nextTarget.disconnect(); + if (previousTarget && + this.workspace.connectionChecker.canConnect( + previousTarget, nextTarget, false)) { + // Attach the next statement to the previous statement. + previousTarget.connect(nextTarget); + } + } + } + + /** + * Returns all connections originating from this block. + * @param {boolean} _all If true, return all connections even hidden ones. + * @return {!Array} Array of connections. + * @package + */ + getConnections_(_all) { + const myConnections = []; + if (this.outputConnection) { + myConnections.push(this.outputConnection); + } + if (this.previousConnection) { + myConnections.push(this.previousConnection); + } + if (this.nextConnection) { + myConnections.push(this.nextConnection); + } + for (let i = 0, input; (input = this.inputList[i]); i++) { + if (input.connection) { + myConnections.push(input.connection); + } + } + return myConnections; + } + + /** + * Walks down a stack of blocks and finds the last next connection on the + * stack. + * @param {boolean} ignoreShadows If true,the last connection on a non-shadow + * block will be returned. If false, this will follow shadows to find the + * last connection. + * @return {?Connection} The last next connection on the stack, or null. + * @package + */ + lastConnectionInStack(ignoreShadows) { + let nextConnection = this.nextConnection; + while (nextConnection) { + const nextBlock = nextConnection.targetBlock(); + if (!nextBlock || (ignoreShadows && nextBlock.isShadow())) { + return nextConnection; + } + nextConnection = nextBlock.nextConnection; + } + return null; + } + + /** + * Bump unconnected blocks out of alignment. Two blocks which aren't actually + * connected should not coincidentally line up on screen. + */ + bumpNeighbours() { + // noop. + } + + /** + * Return the parent block or null if this block is at the top level. The + * parent block is either the block connected to the previous connection (for + * a statement block) or the block connected to the output connection (for a + * value block). + * @return {?Block} The block (if any) that holds the current block. + */ + getParent() { + return this.parentBlock_; + } + + /** + * Return the input that connects to the specified block. + * @param {!Block} block A block connected to an input on this block. + * @return {?Input} The input (if any) that connects to the specified + * block. + */ + getInputWithBlock(block) { + for (let i = 0, input; (input = this.inputList[i]); i++) { + if (input.connection && input.connection.targetBlock() === block) { + return input; + } + } + return null; + } + + /** + * Return the parent block that surrounds the current block, or null if this + * block has no surrounding block. A parent block might just be the previous + * statement, whereas the surrounding block is an if statement, while loop, + * etc. + * @return {?Block} The block (if any) that surrounds the current block. + */ + getSurroundParent() { + let block = this; + let prevBlock; + do { + prevBlock = block; + block = block.getParent(); + if (!block) { + // Ran off the top. + return null; + } + } while (block.getNextBlock() === prevBlock); + // This block is an enclosing parent, not just a statement in a stack. + return block; + } + + /** + * Return the next statement block directly connected to this block. + * @return {?Block} The next statement block or null. + */ + getNextBlock() { + return this.nextConnection && this.nextConnection.targetBlock(); + } + + /** + * Returns the block connected to the previous connection. + * @return {?Block} The previous statement block or null. + */ + getPreviousBlock() { + return this.previousConnection && this.previousConnection.targetBlock(); + } + + /** + * Return the connection on the first statement input on this block, or null + * if there are none. + * @return {?Connection} The first statement connection or null. + * @package + */ + getFirstStatementConnection() { + for (let i = 0, input; (input = this.inputList[i]); i++) { + if (input.connection && + input.connection.type === ConnectionType.NEXT_STATEMENT) { + return input.connection; + } + } + return null; + } + + /** + * Return the top-most block in this block's tree. + * This will return itself if this block is at the top level. + * @return {!Block} The root block. + */ + getRootBlock() { + let rootBlock; + let block = this; + do { + rootBlock = block; + block = rootBlock.parentBlock_; + } while (block); + return rootBlock; + } + + /** + * Walk up from the given block up through the stack of blocks to find + * the top block of the sub stack. If we are nested in a statement input only + * find the top-most nested block. Do not go all the way to the root block. + * @return {!Block} The top block in a stack. + * @package + */ + getTopStackBlock() { + let block = this; + let previous; + do { + previous = block.getPreviousBlock(); + } while (previous && previous.getNextBlock() === block && + (block = previous)); + return block; + } + + /** + * Find all the blocks that are directly nested inside this one. + * Includes value and statement inputs, as well as any following statement. + * Excludes any connection on an output tab or any preceding statement. + * Blocks are optionally sorted by position; top to bottom. + * @param {boolean} ordered Sort the list if true. + * @return {!Array} Array of blocks. + */ + getChildren(ordered) { + if (!ordered) { + return this.childBlocks_; + } + const blocks = []; + for (let i = 0, input; (input = this.inputList[i]); i++) { + if (input.connection) { + const child = input.connection.targetBlock(); + if (child) { + blocks.push(child); + } + } + } + const next = this.getNextBlock(); + if (next) { + blocks.push(next); + } + return blocks; + } + + /** + * Set parent of this block to be a new block or null. + * @param {Block} newParent New parent block. + * @package + */ + setParent(newParent) { + if (newParent === this.parentBlock_) { + return; + } + + // Check that block is connected to new parent if new parent is not null and + // that block is not connected to superior one if new parent is null. + const targetBlock = + (this.previousConnection && this.previousConnection.targetBlock()) || + (this.outputConnection && this.outputConnection.targetBlock()); + const isConnected = !!targetBlock; + + if (isConnected && newParent && targetBlock !== newParent) { + throw Error('Block connected to superior one that is not new parent.'); + } else if (!isConnected && newParent) { + throw Error('Block not connected to new parent.'); + } else if (isConnected && !newParent) { + throw Error( + 'Cannot set parent to null while block is still connected to' + + ' superior block.'); + } + + if (this.parentBlock_) { + // Remove this block from the old parent's child list. + arrayUtils.removeElem(this.parentBlock_.childBlocks_, this); + + // This block hasn't actually moved on-screen, so there's no need to + // update + // its connection locations. + } else { + // New parent must be non-null so remove this block from the workspace's + // list of top-most blocks. + this.workspace.removeTopBlock(this); + } + + this.parentBlock_ = newParent; + if (newParent) { + // Add this block to the new parent's child list. + newParent.childBlocks_.push(this); + } else { + this.workspace.addTopBlock(this); + } + } + + /** + * Find all the blocks that are directly or indirectly nested inside this one. + * Includes this block in the list. + * Includes value and statement inputs, as well as any following statements. + * Excludes any connection on an output tab or any preceding statements. + * Blocks are optionally sorted by position; top to bottom. + * @param {boolean} ordered Sort the list if true. + * @return {!Array} Flattened array of blocks. + */ + getDescendants(ordered) { + const blocks = [this]; + const childBlocks = this.getChildren(ordered); + for (let child, i = 0; (child = childBlocks[i]); i++) { + blocks.push.apply(blocks, child.getDescendants(ordered)); + } + return blocks; + } + + /** + * Get whether this block is deletable or not. + * @return {boolean} True if deletable. + */ + isDeletable() { + return this.deletable_ && !this.isShadow_ && + !(this.workspace && this.workspace.options.readOnly); + } + + /** + * Set whether this block is deletable or not. + * @param {boolean} deletable True if deletable. + */ + setDeletable(deletable) { + this.deletable_ = deletable; + } + + /** + * Get whether this block is movable or not. + * @return {boolean} True if movable. + */ + isMovable() { + return this.movable_ && !this.isShadow_ && + !(this.workspace && this.workspace.options.readOnly); + } + + /** + * Set whether this block is movable or not. + * @param {boolean} movable True if movable. + */ + setMovable(movable) { + this.movable_ = movable; + } + + /** + * Get whether is block is duplicatable or not. If duplicating this block and + * descendants will put this block over the workspace's capacity this block is + * not duplicatable. If duplicating this block and descendants will put any + * type over their maxInstances this block is not duplicatable. + * @return {boolean} True if duplicatable. + */ + isDuplicatable() { + if (!this.workspace.hasBlockLimits()) { + return true; + } + return this.workspace.isCapacityAvailable( + common.getBlockTypeCounts(this, true)); + } + + /** + * Get whether this block is a shadow block or not. + * @return {boolean} True if a shadow. + */ + isShadow() { + return this.isShadow_; + } + + /** + * Set whether this block is a shadow block or not. + * @param {boolean} shadow True if a shadow. + * @package + */ + setShadow(shadow) { + this.isShadow_ = shadow; + } + + /** + * Get whether this block is an insertion marker block or not. + * @return {boolean} True if an insertion marker. + */ + isInsertionMarker() { + return this.isInsertionMarker_; + } + + /** + * Set whether this block is an insertion marker block or not. + * Once set this cannot be unset. + * @param {boolean} insertionMarker True if an insertion marker. + * @package + */ + setInsertionMarker(insertionMarker) { + this.isInsertionMarker_ = insertionMarker; + } + + /** + * Get whether this block is editable or not. + * @return {boolean} True if editable. + */ + isEditable() { + return this.editable_ && + !(this.workspace && this.workspace.options.readOnly); + } + + /** + * Set whether this block is editable or not. + * @param {boolean} editable True if editable. + */ + setEditable(editable) { + this.editable_ = editable; + for (let i = 0, input; (input = this.inputList[i]); i++) { + for (let j = 0, field; (field = input.fieldRow[j]); j++) { + field.updateEditable(); + } + } + } + + /** + * Returns if this block has been disposed of / deleted. + * @return {boolean} True if this block has been disposed of / deleted. + */ + isDisposed() { + return this.disposed; + } + + /** + * Find the connection on this block that corresponds to the given connection + * on the other block. + * Used to match connections between a block and its insertion marker. + * @param {!Block} otherBlock The other block to match against. + * @param {!Connection} conn The other connection to match. + * @return {?Connection} The matching connection on this block, or null. + * @package + */ + getMatchingConnection(otherBlock, conn) { + const connections = this.getConnections_(true); + const otherConnections = otherBlock.getConnections_(true); + if (connections.length !== otherConnections.length) { + throw Error('Connection lists did not match in length.'); + } + for (let i = 0; i < otherConnections.length; i++) { + if (otherConnections[i] === conn) { + return connections[i]; + } + } + return null; + } + + /** + * Set the URL of this block's help page. + * @param {string|Function} url URL string for block help, or function that + * returns a URL. Null for no help. + */ + setHelpUrl(url) { + this.helpUrl = url; + } + + /** + * Sets the tooltip for this block. + * @param {!Tooltip.TipInfo} newTip The text for the tooltip, a function + * that returns the text for the tooltip, or a parent object whose tooltip + * will be used. To not display a tooltip pass the empty string. + */ + setTooltip(newTip) { + this.tooltip = newTip; + } + + /** + * Returns the tooltip text for this block. + * @return {!string} The tooltip text for this block. + */ + getTooltip() { + return Tooltip.getTooltipOfObject(this); + } + + /** + * Get the colour of a block. + * @return {string} #RRGGBB string. + */ + getColour() { + return this.colour_; + } + + /** + * Get the name of the block style. + * @return {string} Name of the block style. + */ + getStyleName() { + return this.styleName_; + } + + /** + * Get the HSV hue value of a block. Null if hue not set. + * @return {?number} Hue value (0-360). + */ + getHue() { + return this.hue_; + } + + /** + * Change the colour of a block. + * @param {number|string} colour HSV hue value (0 to 360), #RRGGBB string, + * or a message reference string pointing to one of those two values. + */ + setColour(colour) { + const parsed = parsing.parseBlockColour(colour); + this.hue_ = parsed.hue; + this.colour_ = parsed.hex; + } + + /** + * Set the style and colour values of a block. + * @param {string} blockStyleName Name of the block style. + */ + setStyle(blockStyleName) { + this.styleName_ = blockStyleName; + } + + /** + * Sets a callback function to use whenever the block's parent workspace + * changes, replacing any prior onchange handler. This is usually only called + * from the constructor, the block type initializer function, or an extension + * initializer function. + * @param {function(Abstract)} onchangeFn The callback to call + * when the block's workspace changes. + * @throws {Error} if onchangeFn is not falsey and not a function. + */ + setOnChange(onchangeFn) { + if (onchangeFn && typeof onchangeFn !== 'function') { + throw Error('onchange must be a function.'); + } + if (this.onchangeWrapper_) { + this.workspace.removeChangeListener(this.onchangeWrapper_); + } + this.onchange = onchangeFn; + if (this.onchange) { + this.onchangeWrapper_ = onchangeFn.bind(this); + this.workspace.addChangeListener(this.onchangeWrapper_); + } + } + + /** + * Returns the named field from a block. + * @param {string} name The name of the field. + * @return {?Field} Named field, or null if field does not exist. + */ + getField(name) { + if (typeof name !== 'string') { + throw TypeError( + 'Block.prototype.getField expects a string ' + + 'with the field name but received ' + + (name === undefined ? 'nothing' : name + ' of type ' + typeof name) + + ' instead'); + } + for (let i = 0, input; (input = this.inputList[i]); i++) { + for (let j = 0, field; (field = input.fieldRow[j]); j++) { + if (field.name === name) { + return field; + } + } + } + return null; + } + + /** + * Return all variables referenced by this block. + * @return {!Array} List of variable ids. + */ + getVars() { + const vars = []; + for (let i = 0, input; (input = this.inputList[i]); i++) { + for (let j = 0, field; (field = input.fieldRow[j]); j++) { + if (field.referencesVariables()) { + vars.push(field.getValue()); + } + } + } + return vars; + } + + /** + * Return all variables referenced by this block. + * @return {!Array} List of variable models. + * @package + */ + getVarModels() { + const vars = []; + for (let i = 0, input; (input = this.inputList[i]); i++) { + for (let j = 0, field; (field = input.fieldRow[j]); j++) { + if (field.referencesVariables()) { + const model = this.workspace.getVariableById( + /** @type {string} */ (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 {!VariableModel} variable The variable being renamed. + * @package + */ + updateVarName(variable) { + for (let i = 0, input; (input = this.inputList[i]); i++) { + for (let j = 0, field; (field = input.fieldRow[j]); j++) { + if (field.referencesVariables() && + variable.getId() === field.getValue()) { + field.refreshVariableName(); + } + } + } + } + + /** + * 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. + */ + renameVarById(oldId, newId) { + for (let i = 0, input; (input = this.inputList[i]); i++) { + for (let j = 0, field; (field = input.fieldRow[j]); j++) { + if (field.referencesVariables() && oldId === field.getValue()) { + field.setValue(newId); + } + } + } + } + + /** + * Returns the language-neutral value of the given field. + * @param {string} name The name of the field. + * @return {*} Value of the field or null if field does not exist. + */ + getFieldValue(name) { + const field = this.getField(name); + if (field) { + return field.getValue(); + } + return null; + } + + /** + * Sets the value of the given field for this block. + * @param {*} newValue The value to set. + * @param {string} name The name of the field to set the value of. + */ + setFieldValue(newValue, name) { + const field = this.getField(name); + if (!field) { + throw Error('Field "' + name + '" not found.'); + } + field.setValue(newValue); + } + + /** + * Set whether this block can chain onto the bottom of another block. + * @param {boolean} newBoolean True if there can be a previous statement. + * @param {(string|Array|null)=} opt_check Statement type or + * list of statement types. Null/undefined if any type could be + * connected. + */ + setPreviousStatement(newBoolean, opt_check) { + if (newBoolean) { + if (opt_check === undefined) { + opt_check = null; + } + if (!this.previousConnection) { + this.previousConnection = + this.makeConnection_(ConnectionType.PREVIOUS_STATEMENT); + } + this.previousConnection.setCheck(opt_check); + } else { + if (this.previousConnection) { + if (this.previousConnection.isConnected()) { + throw Error( + 'Must disconnect previous statement before removing ' + + 'connection.'); + } + this.previousConnection.dispose(); + this.previousConnection = null; + } + } + } + + /** + * Set whether another block can chain onto the bottom of this block. + * @param {boolean} newBoolean True if there can be a next statement. + * @param {(string|Array|null)=} opt_check Statement type or + * list of statement types. Null/undefined if any type could be + * connected. + */ + setNextStatement(newBoolean, opt_check) { + if (newBoolean) { + if (opt_check === undefined) { + opt_check = null; + } + if (!this.nextConnection) { + this.nextConnection = + this.makeConnection_(ConnectionType.NEXT_STATEMENT); + } + this.nextConnection.setCheck(opt_check); + } else { + if (this.nextConnection) { + if (this.nextConnection.isConnected()) { + throw Error( + 'Must disconnect next statement before removing ' + + 'connection.'); + } + this.nextConnection.dispose(); + this.nextConnection = null; + } + } + } + + /** + * Set whether this block returns a value. + * @param {boolean} newBoolean True if there is an output. + * @param {(string|Array|null)=} opt_check Returned type or list + * of returned types. Null or undefined if any type could be returned + * (e.g. variable get). + */ + setOutput(newBoolean, opt_check) { + if (newBoolean) { + if (opt_check === undefined) { + opt_check = null; + } + if (!this.outputConnection) { + this.outputConnection = + this.makeConnection_(ConnectionType.OUTPUT_VALUE); + } + this.outputConnection.setCheck(opt_check); + } else { + if (this.outputConnection) { + if (this.outputConnection.isConnected()) { + throw Error( + 'Must disconnect output value before removing connection.'); + } + this.outputConnection.dispose(); + this.outputConnection = null; + } + } + } + + /** + * Set whether value inputs are arranged horizontally or vertically. + * @param {boolean} newBoolean True if inputs are horizontal. + */ + setInputsInline(newBoolean) { + if (this.inputsInline !== newBoolean) { + eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))( + this, 'inline', null, this.inputsInline, newBoolean)); + this.inputsInline = newBoolean; + } + } + + /** + * Get whether value inputs are arranged horizontally or vertically. + * @return {boolean} True if inputs are horizontal. + */ + getInputsInline() { + if (this.inputsInline !== undefined) { + // Set explicitly. + return this.inputsInline; + } + // Not defined explicitly. Figure out what would look best. + for (let i = 1; i < this.inputList.length; i++) { + if (this.inputList[i - 1].type === inputTypes.DUMMY && + this.inputList[i].type === inputTypes.DUMMY) { + // Two dummy inputs in a row. Don't inline them. + return false; + } + } + for (let i = 1; i < this.inputList.length; i++) { + if (this.inputList[i - 1].type === inputTypes.VALUE && + this.inputList[i].type === inputTypes.DUMMY) { + // Dummy input after a value input. Inline them. + return true; + } + } + return false; + } + + /** + * Set the block's output shape. + * @param {?number} outputShape Value representing an output shape. + */ + setOutputShape(outputShape) { + this.outputShape_ = outputShape; + } + + /** + * Get the block's output shape. + * @return {?number} Value representing output shape if one exists. + */ + getOutputShape() { + return this.outputShape_; + } + + /** + * Get whether this block is enabled or not. + * @return {boolean} True if enabled. + */ + isEnabled() { + return !this.disabled; + } + + /** + * Set whether the block is enabled or not. + * @param {boolean} enabled True if enabled. + */ + setEnabled(enabled) { + if (this.isEnabled() !== enabled) { + const oldValue = this.disabled; + this.disabled = !enabled; + eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))( + this, 'disabled', null, oldValue, !enabled)); + } + } + + /** + * Get whether the block is disabled or not due to parents. + * The block's own disabled property is not considered. + * @return {boolean} True if disabled. + */ + getInheritedDisabled() { + let ancestor = this.getSurroundParent(); + while (ancestor) { + if (ancestor.disabled) { + return true; + } + ancestor = ancestor.getSurroundParent(); + } + // Ran off the top. + return false; + } + + /** + * Get whether the block is collapsed or not. + * @return {boolean} True if collapsed. + */ + isCollapsed() { + return this.collapsed_; + } + + /** + * Set whether the block is collapsed or not. + * @param {boolean} collapsed True if collapsed. + */ + setCollapsed(collapsed) { + if (this.collapsed_ !== collapsed) { + eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))( + this, 'collapsed', null, this.collapsed_, collapsed)); + this.collapsed_ = collapsed; + } + } + + /** + * Create a human-readable text representation of this block and any children. + * @param {number=} opt_maxLength Truncate the string to this length. + * @param {string=} opt_emptyToken The placeholder string used to denote an + * empty field. If not specified, '?' is used. + * @return {string} Text of block. + */ + toString(opt_maxLength, opt_emptyToken) { + let text = []; + const emptyFieldPlaceholder = opt_emptyToken || '?'; + + // Temporarily set flag to navigate to all fields. + const prevNavigateFields = ASTNode.NAVIGATE_ALL_FIELDS; + ASTNode.NAVIGATE_ALL_FIELDS = true; + + let node = ASTNode.createBlockNode(this); + const rootNode = node; + + /** + * Whether or not to add parentheses around an input. + * @param {!Connection} connection The connection. + * @return {boolean} True if we should add parentheses around the input. + */ + function shouldAddParentheses(connection) { + let checks = connection.getCheck(); + if (!checks && connection.targetConnection) { + checks = connection.targetConnection.getCheck(); + } + return !!checks && + (checks.indexOf('Boolean') !== -1 || checks.indexOf('Number') !== -1); + } + + /** + * Check that we haven't circled back to the original root node. + */ + function checkRoot() { + if (node && node.getType() === rootNode.getType() && + node.getLocation() === rootNode.getLocation()) { + node = null; + } + } + + // Traverse the AST building up our text string. + while (node) { + switch (node.getType()) { + case ASTNode.types.INPUT: { + const connection = /** @type {!Connection} */ (node.getLocation()); + if (!node.in()) { + text.push(emptyFieldPlaceholder); + } else if (shouldAddParentheses(connection)) { + text.push('('); + } + break; + } + case ASTNode.types.FIELD: { + const field = /** @type {Field} */ (node.getLocation()); + if (field.name !== constants.COLLAPSED_FIELD_NAME) { + text.push(field.getText()); + } + break; + } + } + + const current = node; + node = current.in() || current.next(); + if (!node) { + // Can't go in or next, keep going out until we can go next. + node = current.out(); + checkRoot(); + while (node && !node.next()) { + node = node.out(); + checkRoot(); + // If we hit an input on the way up, possibly close out parentheses. + if (node && node.getType() === ASTNode.types.INPUT && + shouldAddParentheses( + /** @type {!Connection} */ (node.getLocation()))) { + text.push(')'); + } + } + if (node) { + node = node.next(); + } + } + } + + // Restore state of NAVIGATE_ALL_FIELDS. + ASTNode.NAVIGATE_ALL_FIELDS = prevNavigateFields; + + // Run through our text array and simplify expression to remove parentheses + // around single field blocks. + // E.g. ['repeat', '(', '10', ')', 'times', 'do', '?'] + for (let i = 2; i < text.length; i++) { + if (text[i - 2] === '(' && text[i] === ')') { + text[i - 2] = text[i - 1]; + text.splice(i - 1, 2); + } + } + + // Join the text array, removing spaces around added parentheses. + text = text.reduce(function(acc, value) { + return acc + ((acc.substr(-1) === '(' || value === ')') ? '' : ' ') + + value; + }, ''); + text = text.trim() || '???'; + if (opt_maxLength) { + // TODO: Improve truncation so that text from this block is given + // priority. E.g. "1+2+3+4+5+6+7+8+9=0" should be "...6+7+8+9=0", not + // "1+2+3+4+5...". E.g. "1+2+3+4+5=6+7+8+9+0" should be "...4+5=6+7...". + if (text.length > opt_maxLength) { + text = text.substring(0, opt_maxLength - 3) + '...'; + } + } + return text; + } + + /** + * Shortcut for appending a value input row. + * @param {string} name Language-neutral identifier which may used to find + * this input again. Should be unique to this block. + * @return {!Input} The input object created. + */ + appendValueInput(name) { + return this.appendInput_(inputTypes.VALUE, name); + } + + /** + * Shortcut for appending a statement input row. + * @param {string} name Language-neutral identifier which may used to find + * this input again. Should be unique to this block. + * @return {!Input} The input object created. + */ + appendStatementInput(name) { + return this.appendInput_(inputTypes.STATEMENT, name); + } + + /** + * Shortcut for appending a dummy input row. + * @param {string=} opt_name Language-neutral identifier which may used to + * find this input again. Should be unique to this block. + * @return {!Input} The input object created. + */ + appendDummyInput(opt_name) { + return this.appendInput_(inputTypes.DUMMY, opt_name || ''); + } + + /** + * Initialize this block using a cross-platform, internationalization-friendly + * JSON description. + * @param {!Object} json Structured data describing the block. + */ + jsonInit(json) { + const warningPrefix = json['type'] ? 'Block "' + json['type'] + '": ' : ''; + + // Validate inputs. + if (json['output'] && json['previousStatement']) { + throw Error( + warningPrefix + + 'Must not have both an output and a previousStatement.'); + } + + // Set basic properties of block. + // Makes styles backward compatible with old way of defining hat style. + if (json['style'] && json['style'].hat) { + this.hat = json['style'].hat; + // Must set to null so it doesn't error when checking for style and + // colour. + json['style'] = null; + } + + if (json['style'] && json['colour']) { + throw Error(warningPrefix + 'Must not have both a colour and a style.'); + } else if (json['style']) { + this.jsonInitStyle_(json, warningPrefix); + } else { + this.jsonInitColour_(json, warningPrefix); + } + + // Interpolate the message blocks. + let i = 0; + while (json['message' + i] !== undefined) { + this.interpolate_( + json['message' + i], json['args' + i] || [], + json['lastDummyAlign' + i], warningPrefix); + i++; + } + + if (json['inputsInline'] !== undefined) { + this.setInputsInline(json['inputsInline']); + } + // Set output and previous/next connections. + if (json['output'] !== undefined) { + this.setOutput(true, json['output']); + } + if (json['outputShape'] !== undefined) { + this.setOutputShape(json['outputShape']); + } + if (json['previousStatement'] !== undefined) { + this.setPreviousStatement(true, json['previousStatement']); + } + if (json['nextStatement'] !== undefined) { + this.setNextStatement(true, json['nextStatement']); + } + if (json['tooltip'] !== undefined) { + const rawValue = json['tooltip']; + const localizedText = parsing.replaceMessageReferences(rawValue); + this.setTooltip(localizedText); + } + if (json['enableContextMenu'] !== undefined) { + this.contextMenu = !!json['enableContextMenu']; + } + if (json['suppressPrefixSuffix'] !== undefined) { + this.suppressPrefixSuffix = !!json['suppressPrefixSuffix']; + } + if (json['helpUrl'] !== undefined) { + const rawValue = json['helpUrl']; + const localizedValue = parsing.replaceMessageReferences(rawValue); + this.setHelpUrl(localizedValue); + } + if (typeof json['extensions'] === 'string') { + console.warn( + warningPrefix + + 'JSON attribute \'extensions\' should be an array of' + + ' strings. Found raw string in JSON for \'' + json['type'] + + '\' block.'); + json['extensions'] = [json['extensions']]; // Correct and continue. + } + + // Add the mutator to the block. + if (json['mutator'] !== undefined) { + Extensions.apply(json['mutator'], this, true); + } + + const extensionNames = json['extensions']; + if (Array.isArray(extensionNames)) { + for (let j = 0; j < extensionNames.length; j++) { + Extensions.apply(extensionNames[j], this, false); + } + } + } + + /** + * Initialize the colour of this block from the JSON description. + * @param {!Object} json Structured data describing the block. + * @param {string} warningPrefix Warning prefix string identifying block. + * @private + */ + jsonInitColour_(json, warningPrefix) { + if ('colour' in json) { + if (json['colour'] === undefined) { + console.warn(warningPrefix + 'Undefined colour value.'); + } else { + const rawValue = json['colour']; + try { + this.setColour(rawValue); + } catch (e) { + console.warn(warningPrefix + 'Illegal colour value: ', rawValue); + } + } + } + } + + /** + * Initialize the style of this block from the JSON description. + * @param {!Object} json Structured data describing the block. + * @param {string} warningPrefix Warning prefix string identifying block. + * @private + */ + jsonInitStyle_(json, warningPrefix) { + const blockStyleName = json['style']; + try { + this.setStyle(blockStyleName); + } catch (styleError) { + console.warn(warningPrefix + 'Style does not exist: ', blockStyleName); + } + } + + /** + * Add key/values from mixinObj to this block object. By default, this method + * will check that the keys in mixinObj will not overwrite existing values in + * the block, including prototype values. This provides some insurance against + * mixin / extension incompatibilities with future block features. This check + * can be disabled by passing true as the second argument. + * @param {!Object} mixinObj The key/values pairs to add to this block object. + * @param {boolean=} opt_disableCheck Option flag to disable overwrite checks. + */ + mixin(mixinObj, opt_disableCheck) { + if (opt_disableCheck !== undefined && + typeof opt_disableCheck !== 'boolean') { + throw Error('opt_disableCheck must be a boolean if provided'); + } + if (!opt_disableCheck) { + const overwrites = []; + for (const key in mixinObj) { + if (this[key] !== undefined) { + overwrites.push(key); + } + } + if (overwrites.length) { + throw Error( + 'Mixin will overwrite block members: ' + + JSON.stringify(overwrites)); + } + } + object.mixin(this, mixinObj); + } + + /** + * Interpolate a message description onto the block. + * @param {string} message Text contains interpolation tokens (%1, %2, ...) + * that match with fields or inputs defined in the args array. + * @param {!Array} args Array of arguments to be interpolated. + * @param {string|undefined} lastDummyAlign If a dummy input is added at the + * end, how should it be aligned? + * @param {string} warningPrefix Warning prefix string identifying block. + * @private + */ + interpolate_(message, args, lastDummyAlign, warningPrefix) { + const tokens = parsing.tokenizeInterpolation(message); + this.validateTokens_(tokens, args.length); + const elements = this.interpolateArguments_(tokens, args, lastDummyAlign); + + // An array of [field, fieldName] tuples. + const fieldStack = []; + for (let i = 0, element; (element = elements[i]); i++) { + if (this.isInputKeyword_(element['type'])) { + const input = this.inputFromJson_(element, warningPrefix); + // Should never be null, but just in case. + if (input) { + for (let j = 0, tuple; (tuple = fieldStack[j]); j++) { + input.appendField(tuple[0], tuple[1]); + } + fieldStack.length = 0; + } + } else { + // All other types, including ones starting with 'input_' get routed + // here. + const field = this.fieldFromJson_(element); + if (field) { + fieldStack.push([field, element['name']]); + } + } + } + } + + /** + * Validates that the tokens are within the correct bounds, with no + * duplicates, and that all of the arguments are referred to. Throws errors if + * any of these things are not true. + * @param {!Array} tokens An array of tokens to validate + * @param {number} argsCount The number of args that need to be referred to. + * @private + */ + validateTokens_(tokens, argsCount) { + const visitedArgsHash = []; + let visitedArgsCount = 0; + for (let i = 0; i < tokens.length; i++) { + const token = tokens[i]; + if (typeof token !== 'number') { + continue; + } + if (token < 1 || token > argsCount) { + throw Error( + 'Block "' + this.type + '": ' + + 'Message index %' + token + ' out of range.'); + } + if (visitedArgsHash[token]) { + throw Error( + 'Block "' + this.type + '": ' + + 'Message index %' + token + ' duplicated.'); + } + visitedArgsHash[token] = true; + visitedArgsCount++; + } + if (visitedArgsCount !== argsCount) { + throw Error( + 'Block "' + this.type + '": ' + + 'Message does not reference all ' + argsCount + ' arg(s).'); + } + } + + /** + * Inserts args in place of numerical tokens. String args are converted to + * JSON that defines a label field. If necessary an extra dummy input is added + * to the end of the elements. + * @param {!Array} tokens The tokens to interpolate + * @param {!Array} args The arguments to insert. + * @param {string|undefined} lastDummyAlign The alignment the added dummy + * input should have, if we are required to add one. + * @return {!Array} The JSON definitions of field and inputs to add + * to the block. + * @private + */ + interpolateArguments_(tokens, args, lastDummyAlign) { + const elements = []; + for (let i = 0; i < tokens.length; i++) { + let element = tokens[i]; + if (typeof element === 'number') { + element = args[element - 1]; + } + // Args can be strings, which is why this isn't elseif. + if (typeof element === 'string') { + element = this.stringToFieldJson_(element); + if (!element) { + continue; + } + } + elements.push(element); + } + + const length = elements.length; + if (length && !this.isInputKeyword_(elements[length - 1]['type'])) { + const dummyInput = {'type': 'input_dummy'}; + if (lastDummyAlign) { + dummyInput['align'] = lastDummyAlign; + } + elements.push(dummyInput); + } + + return elements; + } + + /** + * Creates a field from the JSON definition of a field. If a field with the + * given type cannot be found, this attempts to create a different field using + * the 'alt' property of the JSON definition (if it exists). + * @param {{alt:(string|undefined)}} element The element to try to turn into a + * field. + * @return {?Field} The field defined by the JSON, or null if one + * couldn't be created. + * @private + */ + fieldFromJson_(element) { + const field = fieldRegistry.fromJson(element); + if (!field && element['alt']) { + if (typeof element['alt'] === 'string') { + const json = this.stringToFieldJson_(element['alt']); + return json ? this.fieldFromJson_(json) : null; + } + return this.fieldFromJson_(element['alt']); + } + return field; + } + + /** + * Creates an input from the JSON definition of an input. Sets the input's + * check and alignment if they are provided. + * @param {!Object} element The JSON to turn into an input. + * @param {string} warningPrefix The prefix to add to warnings to help the + * developer debug. + * @return {?Input} The input that has been created, or null if one + * could not be created for some reason (should never happen). + * @private + */ + inputFromJson_(element, warningPrefix) { + const alignmentLookup = { + 'LEFT': Align.LEFT, + 'RIGHT': Align.RIGHT, + 'CENTRE': Align.CENTRE, + 'CENTER': Align.CENTRE, + }; + + let input = null; + switch (element['type']) { + case 'input_value': + input = this.appendValueInput(element['name']); + break; + case 'input_statement': + input = this.appendStatementInput(element['name']); + break; + case 'input_dummy': + input = this.appendDummyInput(element['name']); + break; + } + // Should never be hit because of interpolate_'s checks, but just in case. + if (!input) { + return null; + } + + if (element['check']) { + input.setCheck(element['check']); + } + if (element['align']) { + const alignment = alignmentLookup[element['align'].toUpperCase()]; + if (alignment === undefined) { + console.warn(warningPrefix + 'Illegal align value: ', element['align']); + } else { + input.setAlign(alignment); + } + } + return input; + } + + /** + * Returns true if the given string matches one of the input keywords. + * @param {string} str The string to check. + * @return {boolean} True if the given string matches one of the input + * keywords, false otherwise. + * @private + */ + isInputKeyword_(str) { + return str === 'input_value' || str === 'input_statement' || + str === 'input_dummy'; + } + + /** + * Turns a string into the JSON definition of a label field. If the string + * becomes an empty string when trimmed, this returns null. + * @param {string} str String to turn into the JSON definition of a label + * field. + * @return {?{text: string, type: string}} The JSON definition or null. + * @private + */ + stringToFieldJson_(str) { + str = str.trim(); + if (str) { + return { + 'type': 'field_label', + 'text': str, + }; + } + return null; + } + + /** + * Add a value input, statement input or local variable to this block. + * @param {number} type One of Blockly.inputTypes. + * @param {string} name Language-neutral identifier which may used to find + * this input again. Should be unique to this block. + * @return {!Input} The input object created. + * @protected + */ + appendInput_(type, name) { + let connection = null; + if (type === inputTypes.VALUE || type === inputTypes.STATEMENT) { + connection = this.makeConnection_(type); + } + if (type === inputTypes.STATEMENT) { + this.statementInputCount++; + } + const input = new Input(type, name, this, connection); + // Append input to list. + this.inputList.push(input); + return input; + } + + /** + * Move a named input to a different location on this block. + * @param {string} name The name of the input to move. + * @param {?string} refName Name of input that should be after the moved + * input, + * or null to be the input at the end. + */ + moveInputBefore(name, refName) { + if (name === refName) { + return; + } + // Find both inputs. + let inputIndex = -1; + let refIndex = refName ? -1 : this.inputList.length; + for (let i = 0, input; (input = this.inputList[i]); i++) { + if (input.name === name) { + inputIndex = i; + if (refIndex !== -1) { + break; + } + } else if (refName && input.name === refName) { + refIndex = i; + if (inputIndex !== -1) { + break; + } + } + } + if (inputIndex === -1) { + throw Error('Named input "' + name + '" not found.'); + } + if (refIndex === -1) { + throw Error('Reference input "' + refName + '" not found.'); + } + this.moveNumberedInputBefore(inputIndex, refIndex); + } + + /** + * Move a numbered input to a different location on this block. + * @param {number} inputIndex Index of the input to move. + * @param {number} refIndex Index of input that should be after the moved + * input. + */ + moveNumberedInputBefore(inputIndex, refIndex) { + // Validate arguments. + if (inputIndex === refIndex) { + throw Error('Can\'t move input to itself.'); + } + if (inputIndex >= this.inputList.length) { + throw RangeError('Input index ' + inputIndex + ' out of bounds.'); + } + if (refIndex > this.inputList.length) { + throw RangeError('Reference input ' + refIndex + ' out of bounds.'); + } + // Remove input. + const input = this.inputList[inputIndex]; + this.inputList.splice(inputIndex, 1); + if (inputIndex < refIndex) { + refIndex--; + } + // Reinsert input. + this.inputList.splice(refIndex, 0, input); + } + + /** + * Remove an input from this block. + * @param {string} name The name of the input. + * @param {boolean=} opt_quiet True to prevent an error if input is not + * present. + * @return {boolean} True if operation succeeds, false if input is not present + * and opt_quiet is true. + * @throws {Error} if the input is not present and opt_quiet is not true. + */ + removeInput(name, opt_quiet) { + for (let i = 0, input; (input = this.inputList[i]); i++) { + if (input.name === name) { + if (input.type === inputTypes.STATEMENT) { + this.statementInputCount--; + } + input.dispose(); + this.inputList.splice(i, 1); + return true; + } + } + if (opt_quiet) { + return false; + } + throw Error('Input not found: ' + name); + } + + /** + * Fetches the named input object. + * @param {string} name The name of the input. + * @return {?Input} The input object, or null if input does not exist. + */ + getInput(name) { + for (let i = 0, input; (input = this.inputList[i]); i++) { + if (input.name === name) { + return input; + } + } + // This input does not exist. + return null; + } + + /** + * Fetches the block attached to the named input. + * @param {string} name The name of the input. + * @return {?Block} The attached value block, or null if the input is + * either disconnected or if the input does not exist. + */ + getInputTargetBlock(name) { + const input = this.getInput(name); + return input && input.connection && input.connection.targetBlock(); + } + + /** + * Returns the comment on this block (or null if there is no comment). + * @return {?string} Block's comment. + */ + getCommentText() { + return this.commentModel.text; + } + + /** + * Set this block's comment text. + * @param {?string} text The text, or null to delete. + */ + setCommentText(text) { + if (this.commentModel.text === text) { + return; + } + eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))( + this, 'comment', null, this.commentModel.text, text)); + this.commentModel.text = text; + this.comment = text; // For backwards compatibility. + } + + /** + * Set this block's warning text. + * @param {?string} _text The text, or null to delete. + * @param {string=} _opt_id An optional ID for the warning text to be able to + * maintain multiple warnings. + */ + setWarningText(_text, _opt_id) { + // NOP. + } + + /** + * Give this block a mutator dialog. + * @param {Mutator} _mutator A mutator dialog instance or null to + * remove. + */ + setMutator(_mutator) { + // NOP. + } + + /** + * Return the coordinates of the top-left corner of this block relative to the + * drawing surface's origin (0,0), in workspace units. + * @return {!Coordinate} Object with .x and .y properties. + */ + getRelativeToSurfaceXY() { + return this.xy_; + } + + /** + * Move a block by a relative offset. + * @param {number} dx Horizontal offset, in workspace units. + * @param {number} dy Vertical offset, in workspace units. + */ + moveBy(dx, dy) { + if (this.parentBlock_) { + throw Error('Block has parent.'); + } + const event = /** @type {!BlockMove} */ ( + new (eventUtils.get(eventUtils.BLOCK_MOVE))(this)); + this.xy_.translate(dx, dy); + event.recordNew(); + eventUtils.fire(event); + } + + /** + * Create a connection of the specified type. + * @param {number} type The type of the connection to create. + * @return {!Connection} A new connection of the specified type. + * @protected + */ + makeConnection_(type) { + return new Connection(this, type); + } + + /** + * Recursively checks whether all statement and value inputs are filled with + * blocks. Also checks all following statement blocks in this stack. + * @param {boolean=} opt_shadowBlocksAreFilled An optional argument + * controlling whether shadow blocks are counted as filled. Defaults to + * true. + * @return {boolean} True if all inputs are filled, false otherwise. + */ + allInputsFilled(opt_shadowBlocksAreFilled) { + // Account for the shadow block filledness toggle. + if (opt_shadowBlocksAreFilled === undefined) { + opt_shadowBlocksAreFilled = true; + } + if (!opt_shadowBlocksAreFilled && this.isShadow()) { + return false; + } + + // Recursively check each input block of the current block. + for (let i = 0, input; (input = this.inputList[i]); i++) { + if (!input.connection) { + continue; + } + const target = input.connection.targetBlock(); + if (!target || !target.allInputsFilled(opt_shadowBlocksAreFilled)) { + return false; + } + } + + // Recursively check the next block after the current block. + const next = this.getNextBlock(); + if (next) { + return next.allInputsFilled(opt_shadowBlocksAreFilled); + } + + return true; + } + + /** + * This method returns a string describing this Block in developer terms (type + * name and ID; English only). + * + * Intended to on be used in console logs and errors. If you need a string + * that uses the user's native language (including block text, field values, + * and child blocks), use [toString()]{@link Block#toString}. + * @return {string} The description. + */ + toDevString() { + let msg = this.type ? '"' + this.type + '" block' : 'Block'; + if (this.id) { + msg += ' (id="' + this.id + '")'; + } + return msg; + } +} /** * @typedef {{ @@ -271,6 +2188,14 @@ const Block = function(workspace, prototypeName, opt_id) { */ Block.CommentModel; +/** + * An optional callback method to use whenever the block's parent workspace + * changes. This is usually only called from the constructor, the block type + * initializer function, or an extension initializer function. + * @type {undefined|?function(Abstract)} + */ +Block.prototype.onchange; + /** * The language-neutral ID given to the collapsed input. * @const {string} @@ -283,1874 +2208,4 @@ Block.COLLAPSED_INPUT_NAME = constants.COLLAPSED_INPUT_NAME; */ Block.COLLAPSED_FIELD_NAME = constants.COLLAPSED_FIELD_NAME; -/** - * Optional text data that round-trips between blocks and XML. - * Has no effect. May be used by 3rd parties for meta information. - * @type {?string} - */ -Block.prototype.data = null; - -/** - * Has this block been disposed of? - * @type {boolean} - * @package - */ -Block.prototype.disposed = false; - -/** - * Colour of the block as HSV hue value (0-360) - * This may be null if the block colour was not set via a hue number. - * @type {?number} - * @private - */ -Block.prototype.hue_ = null; - -/** - * Colour of the block in '#RRGGBB' format. - * @type {string} - * @protected - */ -Block.prototype.colour_ = '#000000'; - -/** - * Name of the block style. - * @type {string} - * @protected - */ -Block.prototype.styleName_ = ''; - -/** - * An optional method called during initialization. - * @type {?function()} - */ -Block.prototype.init; - -/** - * An optional callback method to use whenever the block's parent workspace - * changes. This is usually only called from the constructor, the block type - * initializer function, or an extension initializer function. - * @type {?function(Abstract)} - */ -Block.prototype.onchange; - -/** - * An optional serialization method for defining how to serialize the - * mutation state to XML. This must be coupled with defining `domToMutation`. - * @type {?function(...):!Element} - */ -Block.prototype.mutationToDom; - -/** - * An optional deserialization method for defining how to deserialize the - * mutation state from XML. This must be coupled with defining `mutationToDom`. - * @type {?function(!Element)} - */ -Block.prototype.domToMutation; - -/** - * An optional serialization method for defining how to serialize the block's - * extra state (eg mutation state) to something JSON compatible. This must be - * coupled with defining `loadExtraState`. - * @type {?function(): *} - */ -Block.prototype.saveExtraState; - -/** - * An optional serialization method for defining how to deserialize the block's - * extra state (eg mutation state) from something JSON compatible. This must be - * coupled with defining `saveExtraState`. - * @type {?function(*)} - */ -Block.prototype.loadExtraState; - -/** - * An optional property for suppressing adding STATEMENT_PREFIX and - * STATEMENT_SUFFIX to generated code. - * @type {?boolean} - */ -Block.prototype.suppressPrefixSuffix; - -/** - * An optional property for declaring developer variables. Return a list of - * variable names for use by generators. Developer variables are never shown to - * the user, but are declared as global variables in the generated code. - * @type {?function():!Array} - */ -Block.prototype.getDeveloperVariables; - -/** - * Dispose of this block. - * @param {boolean} healStack If true, then try to heal any gap by connecting - * the next statement with the previous statement. Otherwise, dispose of - * all children of this block. - * @suppress {checkTypes} - */ -Block.prototype.dispose = function(healStack) { - if (!this.workspace) { - // Already deleted. - return; - } - // Terminate onchange event calls. - if (this.onchangeWrapper_) { - this.workspace.removeChangeListener(this.onchangeWrapper_); - } - - this.unplug(healStack); - if (eventUtils.isEnabled()) { - eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_DELETE))(this)); - } - eventUtils.disable(); - - try { - // This block is now at the top of the workspace. - // Remove this block from the workspace's list of top-most blocks. - if (this.workspace) { - this.workspace.removeTopBlock(this); - this.workspace.removeTypedBlock(this); - // Remove from block database. - this.workspace.removeBlockById(this.id); - this.workspace = null; - } - - // Just deleting this block from the DOM would result in a memory leak as - // well as corruption of the connection database. Therefore we must - // methodically step through the blocks and carefully disassemble them. - - if (common.getSelected() === this) { - common.setSelected(null); - } - - // First, dispose of all my children. - for (let i = this.childBlocks_.length - 1; i >= 0; i--) { - this.childBlocks_[i].dispose(false); - } - // Then dispose of myself. - // Dispose of all inputs and their fields. - for (let i = 0, input; (input = this.inputList[i]); i++) { - input.dispose(); - } - this.inputList.length = 0; - // Dispose of any remaining connections (next/previous/output). - const connections = this.getConnections_(true); - for (let i = 0, connection; (connection = connections[i]); i++) { - connection.dispose(); - } - } finally { - eventUtils.enable(); - this.disposed = true; - } -}; - -/** - * 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 - */ -Block.prototype.initModel = function() { - for (let i = 0, input; (input = this.inputList[i]); i++) { - for (let 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. - * @param {boolean=} opt_healStack Disconnect child statement and reconnect - * stack. Defaults to false. - */ -Block.prototype.unplug = function(opt_healStack) { - if (this.outputConnection) { - this.unplugFromRow_(opt_healStack); - } - if (this.previousConnection) { - this.unplugFromStack_(opt_healStack); - } -}; - -/** - * Unplug this block's output from an input on another block. Optionally - * reconnect the block's parent to the only child block, if possible. - * @param {boolean=} opt_healStack Disconnect right-side block and connect to - * left-side block. Defaults to false. - * @private - */ -Block.prototype.unplugFromRow_ = function(opt_healStack) { - let parentConnection = null; - if (this.outputConnection.isConnected()) { - parentConnection = this.outputConnection.targetConnection; - // Disconnect from any superior block. - this.outputConnection.disconnect(); - } - - // Return early in obvious cases. - if (!parentConnection || !opt_healStack) { - return; - } - - const thisConnection = this.getOnlyValueConnection_(); - if (!thisConnection || !thisConnection.isConnected() || - thisConnection.targetBlock().isShadow()) { - // Too many or too few possible connections on this block, or there's - // nothing on the other side of this connection. - return; - } - - const childConnection = thisConnection.targetConnection; - // Disconnect the child block. - childConnection.disconnect(); - // Connect child to the parent if possible, otherwise bump away. - if (this.workspace.connectionChecker.canConnect( - childConnection, parentConnection, false)) { - parentConnection.connect(childConnection); - } else { - childConnection.onFailedConnect(parentConnection); - } -}; - -/** - * Returns the connection on the value input that is connected to another block. - * When an insertion marker is connected to a connection with a block already - * attached, the connected block is attached to the insertion marker. - * Since only one block can be displaced and attached to the insertion marker - * this should only ever return one connection. - * - * @return {?Connection} The connection on the value input, or null. - * @private - */ -Block.prototype.getOnlyValueConnection_ = function() { - let connection = null; - for (let i = 0; i < this.inputList.length; i++) { - const thisConnection = this.inputList[i].connection; - if (thisConnection && thisConnection.type === ConnectionType.INPUT_VALUE && - thisConnection.targetConnection) { - if (connection) { - return null; // More than one value input found. - } - connection = thisConnection; - } - } - return connection; -}; - -/** - * Unplug this statement block from its superior block. Optionally reconnect - * the block underneath with the block on top. - * @param {boolean=} opt_healStack Disconnect child statement and reconnect - * stack. Defaults to false. - * @private - */ -Block.prototype.unplugFromStack_ = function(opt_healStack) { - let previousTarget = null; - if (this.previousConnection.isConnected()) { - // Remember the connection that any next statements need to connect to. - previousTarget = this.previousConnection.targetConnection; - // Detach this block from the parent's tree. - this.previousConnection.disconnect(); - } - const nextBlock = this.getNextBlock(); - if (opt_healStack && nextBlock && !nextBlock.isShadow()) { - // Disconnect the next statement. - const nextTarget = this.nextConnection.targetConnection; - nextTarget.disconnect(); - if (previousTarget && - this.workspace.connectionChecker.canConnect( - previousTarget, nextTarget, false)) { - // Attach the next statement to the previous statement. - previousTarget.connect(nextTarget); - } - } -}; - -/** - * Returns all connections originating from this block. - * @param {boolean} _all If true, return all connections even hidden ones. - * @return {!Array} Array of connections. - * @package - */ -Block.prototype.getConnections_ = function(_all) { - const myConnections = []; - if (this.outputConnection) { - myConnections.push(this.outputConnection); - } - if (this.previousConnection) { - myConnections.push(this.previousConnection); - } - if (this.nextConnection) { - myConnections.push(this.nextConnection); - } - for (let i = 0, input; (input = this.inputList[i]); i++) { - if (input.connection) { - myConnections.push(input.connection); - } - } - return myConnections; -}; - -/** - * Walks down a stack of blocks and finds the last next connection on the stack. - * @param {boolean} ignoreShadows If true,the last connection on a non-shadow - * block will be returned. If false, this will follow shadows to find the - * last connection. - * @return {?Connection} The last next connection on the stack, or null. - * @package - */ -Block.prototype.lastConnectionInStack = function(ignoreShadows) { - let nextConnection = this.nextConnection; - while (nextConnection) { - const nextBlock = nextConnection.targetBlock(); - if (!nextBlock || (ignoreShadows && nextBlock.isShadow())) { - return nextConnection; - } - nextConnection = nextBlock.nextConnection; - } - return null; -}; - -/** - * Bump unconnected blocks out of alignment. Two blocks which aren't actually - * connected should not coincidentally line up on screen. - */ -Block.prototype.bumpNeighbours = function() { - // noop. -}; - -/** - * Return the parent block or null if this block is at the top level. The parent - * block is either the block connected to the previous connection (for a - * statement block) or the block connected to the output connection (for a value - * block). - * @return {?Block} The block (if any) that holds the current block. - */ -Block.prototype.getParent = function() { - return this.parentBlock_; -}; - -/** - * Return the input that connects to the specified block. - * @param {!Block} block A block connected to an input on this block. - * @return {?Input} The input (if any) that connects to the specified - * block. - */ -Block.prototype.getInputWithBlock = function(block) { - for (let i = 0, input; (input = this.inputList[i]); i++) { - if (input.connection && input.connection.targetBlock() === block) { - return input; - } - } - return null; -}; - -/** - * Return the parent block that surrounds the current block, or null if this - * block has no surrounding block. A parent block might just be the previous - * statement, whereas the surrounding block is an if statement, while loop, etc. - * @return {?Block} The block (if any) that surrounds the current block. - */ -Block.prototype.getSurroundParent = function() { - let block = this; - let prevBlock; - do { - prevBlock = block; - block = block.getParent(); - if (!block) { - // Ran off the top. - return null; - } - } while (block.getNextBlock() === prevBlock); - // This block is an enclosing parent, not just a statement in a stack. - return block; -}; - -/** - * Return the next statement block directly connected to this block. - * @return {?Block} The next statement block or null. - */ -Block.prototype.getNextBlock = function() { - return this.nextConnection && this.nextConnection.targetBlock(); -}; - -/** - * Returns the block connected to the previous connection. - * @return {?Block} The previous statement block or null. - */ -Block.prototype.getPreviousBlock = function() { - return this.previousConnection && this.previousConnection.targetBlock(); -}; - -/** - * Return the connection on the first statement input on this block, or null if - * there are none. - * @return {?Connection} The first statement connection or null. - * @package - */ -Block.prototype.getFirstStatementConnection = function() { - for (let i = 0, input; (input = this.inputList[i]); i++) { - if (input.connection && - input.connection.type === ConnectionType.NEXT_STATEMENT) { - return input.connection; - } - } - return null; -}; - -/** - * Return the top-most block in this block's tree. - * This will return itself if this block is at the top level. - * @return {!Block} The root block. - */ -Block.prototype.getRootBlock = function() { - let rootBlock; - let block = this; - do { - rootBlock = block; - block = rootBlock.parentBlock_; - } while (block); - return rootBlock; -}; - -/** - * Walk up from the given block up through the stack of blocks to find - * the top block of the sub stack. If we are nested in a statement input only - * find the top-most nested block. Do not go all the way to the root block. - * @return {!Block} The top block in a stack. - * @package - */ -Block.prototype.getTopStackBlock = function() { - let block = this; - let previous; - do { - previous = block.getPreviousBlock(); - } while (previous && previous.getNextBlock() === block && (block = previous)); - return block; -}; - -/** - * Find all the blocks that are directly nested inside this one. - * Includes value and statement inputs, as well as any following statement. - * Excludes any connection on an output tab or any preceding statement. - * Blocks are optionally sorted by position; top to bottom. - * @param {boolean} ordered Sort the list if true. - * @return {!Array} Array of blocks. - */ -Block.prototype.getChildren = function(ordered) { - if (!ordered) { - return this.childBlocks_; - } - const blocks = []; - for (let i = 0, input; (input = this.inputList[i]); i++) { - if (input.connection) { - const child = input.connection.targetBlock(); - if (child) { - blocks.push(child); - } - } - } - const next = this.getNextBlock(); - if (next) { - blocks.push(next); - } - return blocks; -}; - -/** - * Set parent of this block to be a new block or null. - * @param {Block} newParent New parent block. - * @package - */ -Block.prototype.setParent = function(newParent) { - if (newParent === this.parentBlock_) { - return; - } - - // Check that block is connected to new parent if new parent is not null and - // that block is not connected to superior one if new parent is null. - const targetBlock = - (this.previousConnection && this.previousConnection.targetBlock()) || - (this.outputConnection && this.outputConnection.targetBlock()); - const isConnected = !!targetBlock; - - if (isConnected && newParent && targetBlock !== newParent) { - throw Error('Block connected to superior one that is not new parent.'); - } else if (!isConnected && newParent) { - throw Error('Block not connected to new parent.'); - } else if (isConnected && !newParent) { - throw Error( - 'Cannot set parent to null while block is still connected to' + - ' superior block.'); - } - - if (this.parentBlock_) { - // Remove this block from the old parent's child list. - arrayUtils.removeElem(this.parentBlock_.childBlocks_, this); - - // This block hasn't actually moved on-screen, so there's no need to update - // its connection locations. - } else { - // New parent must be non-null so remove this block from the workspace's - // list of top-most blocks. - this.workspace.removeTopBlock(this); - } - - this.parentBlock_ = newParent; - if (newParent) { - // Add this block to the new parent's child list. - newParent.childBlocks_.push(this); - } else { - this.workspace.addTopBlock(this); - } -}; - -/** - * Find all the blocks that are directly or indirectly nested inside this one. - * Includes this block in the list. - * Includes value and statement inputs, as well as any following statements. - * Excludes any connection on an output tab or any preceding statements. - * Blocks are optionally sorted by position; top to bottom. - * @param {boolean} ordered Sort the list if true. - * @return {!Array} Flattened array of blocks. - */ -Block.prototype.getDescendants = function(ordered) { - const blocks = [this]; - const childBlocks = this.getChildren(ordered); - for (let child, i = 0; (child = childBlocks[i]); i++) { - blocks.push.apply(blocks, child.getDescendants(ordered)); - } - return blocks; -}; - -/** - * Get whether this block is deletable or not. - * @return {boolean} True if deletable. - */ -Block.prototype.isDeletable = function() { - return this.deletable_ && !this.isShadow_ && - !(this.workspace && this.workspace.options.readOnly); -}; - -/** - * Set whether this block is deletable or not. - * @param {boolean} deletable True if deletable. - */ -Block.prototype.setDeletable = function(deletable) { - this.deletable_ = deletable; -}; - -/** - * Get whether this block is movable or not. - * @return {boolean} True if movable. - */ -Block.prototype.isMovable = function() { - return this.movable_ && !this.isShadow_ && - !(this.workspace && this.workspace.options.readOnly); -}; - -/** - * Set whether this block is movable or not. - * @param {boolean} movable True if movable. - */ -Block.prototype.setMovable = function(movable) { - this.movable_ = movable; -}; - -/** - * Get whether is block is duplicatable or not. If duplicating this block and - * descendants will put this block over the workspace's capacity this block is - * not duplicatable. If duplicating this block and descendants will put any - * type over their maxInstances this block is not duplicatable. - * @return {boolean} True if duplicatable. - */ -Block.prototype.isDuplicatable = function() { - if (!this.workspace.hasBlockLimits()) { - return true; - } - return this.workspace.isCapacityAvailable( - common.getBlockTypeCounts(this, true)); -}; - -/** - * Get whether this block is a shadow block or not. - * @return {boolean} True if a shadow. - */ -Block.prototype.isShadow = function() { - return this.isShadow_; -}; - -/** - * Set whether this block is a shadow block or not. - * @param {boolean} shadow True if a shadow. - * @package - */ -Block.prototype.setShadow = function(shadow) { - this.isShadow_ = shadow; -}; - -/** - * Get whether this block is an insertion marker block or not. - * @return {boolean} True if an insertion marker. - */ -Block.prototype.isInsertionMarker = function() { - return this.isInsertionMarker_; -}; - -/** - * Set whether this block is an insertion marker block or not. - * Once set this cannot be unset. - * @param {boolean} insertionMarker True if an insertion marker. - * @package - */ -Block.prototype.setInsertionMarker = function(insertionMarker) { - this.isInsertionMarker_ = insertionMarker; -}; - -/** - * Get whether this block is editable or not. - * @return {boolean} True if editable. - */ -Block.prototype.isEditable = function() { - return this.editable_ && !(this.workspace && this.workspace.options.readOnly); -}; - -/** - * Set whether this block is editable or not. - * @param {boolean} editable True if editable. - */ -Block.prototype.setEditable = function(editable) { - this.editable_ = editable; - for (let i = 0, input; (input = this.inputList[i]); i++) { - for (let j = 0, field; (field = input.fieldRow[j]); j++) { - field.updateEditable(); - } - } -}; - -/** - * Returns if this block has been disposed of / deleted. - * @return {boolean} True if this block has been disposed of / deleted. - */ -Block.prototype.isDisposed = function() { - return this.disposed; -}; - -/** - * Find the connection on this block that corresponds to the given connection - * on the other block. - * Used to match connections between a block and its insertion marker. - * @param {!Block} otherBlock The other block to match against. - * @param {!Connection} conn The other connection to match. - * @return {?Connection} The matching connection on this block, or null. - * @package - */ -Block.prototype.getMatchingConnection = function(otherBlock, conn) { - const connections = this.getConnections_(true); - const otherConnections = otherBlock.getConnections_(true); - if (connections.length !== otherConnections.length) { - throw Error('Connection lists did not match in length.'); - } - for (let i = 0; i < otherConnections.length; i++) { - if (otherConnections[i] === conn) { - return connections[i]; - } - } - return null; -}; - -/** - * Set the URL of this block's help page. - * @param {string|Function} url URL string for block help, or function that - * returns a URL. Null for no help. - */ -Block.prototype.setHelpUrl = function(url) { - this.helpUrl = url; -}; - -/** - * Sets the tooltip for this block. - * @param {!Tooltip.TipInfo} newTip The text for the tooltip, a function - * that returns the text for the tooltip, or a parent object whose tooltip - * will be used. To not display a tooltip pass the empty string. - */ -Block.prototype.setTooltip = function(newTip) { - this.tooltip = newTip; -}; - -/** - * Returns the tooltip text for this block. - * @return {!string} The tooltip text for this block. - */ -Block.prototype.getTooltip = function() { - return Tooltip.getTooltipOfObject(this); -}; - -/** - * Get the colour of a block. - * @return {string} #RRGGBB string. - */ -Block.prototype.getColour = function() { - return this.colour_; -}; - -/** - * Get the name of the block style. - * @return {string} Name of the block style. - */ -Block.prototype.getStyleName = function() { - return this.styleName_; -}; - -/** - * Get the HSV hue value of a block. Null if hue not set. - * @return {?number} Hue value (0-360). - */ -Block.prototype.getHue = function() { - return this.hue_; -}; - -/** - * Change the colour of a block. - * @param {number|string} colour HSV hue value (0 to 360), #RRGGBB string, - * or a message reference string pointing to one of those two values. - */ -Block.prototype.setColour = function(colour) { - const parsed = parsing.parseBlockColour(colour); - this.hue_ = parsed.hue; - this.colour_ = parsed.hex; -}; - -/** - * Set the style and colour values of a block. - * @param {string} blockStyleName Name of the block style. - */ -Block.prototype.setStyle = function(blockStyleName) { - this.styleName_ = blockStyleName; -}; - -/** - * Sets a callback function to use whenever the block's parent workspace - * changes, replacing any prior onchange handler. This is usually only called - * from the constructor, the block type initializer function, or an extension - * initializer function. - * @param {function(Abstract)} onchangeFn The callback to call - * when the block's workspace changes. - * @throws {Error} if onchangeFn is not falsey and not a function. - */ -Block.prototype.setOnChange = function(onchangeFn) { - if (onchangeFn && typeof onchangeFn !== 'function') { - throw Error('onchange must be a function.'); - } - if (this.onchangeWrapper_) { - this.workspace.removeChangeListener(this.onchangeWrapper_); - } - this.onchange = onchangeFn; - if (this.onchange) { - this.onchangeWrapper_ = onchangeFn.bind(this); - this.workspace.addChangeListener(this.onchangeWrapper_); - } -}; - -/** - * Returns the named field from a block. - * @param {string} name The name of the field. - * @return {?Field} Named field, or null if field does not exist. - */ -Block.prototype.getField = function(name) { - if (typeof name !== 'string') { - throw TypeError( - 'Block.prototype.getField expects a string ' + - 'with the field name but received ' + - (name === undefined ? 'nothing' : name + ' of type ' + typeof name) + - ' instead'); - } - for (let i = 0, input; (input = this.inputList[i]); i++) { - for (let j = 0, field; (field = input.fieldRow[j]); j++) { - if (field.name === name) { - return field; - } - } - } - return null; -}; - -/** - * Return all variables referenced by this block. - * @return {!Array} List of variable ids. - */ -Block.prototype.getVars = function() { - const vars = []; - for (let i = 0, input; (input = this.inputList[i]); i++) { - for (let j = 0, field; (field = input.fieldRow[j]); j++) { - if (field.referencesVariables()) { - vars.push(field.getValue()); - } - } - } - return vars; -}; - -/** - * Return all variables referenced by this block. - * @return {!Array} List of variable models. - * @package - */ -Block.prototype.getVarModels = function() { - const vars = []; - for (let i = 0, input; (input = this.inputList[i]); i++) { - for (let j = 0, field; (field = input.fieldRow[j]); j++) { - if (field.referencesVariables()) { - const model = this.workspace.getVariableById( - /** @type {string} */ (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 {!VariableModel} variable The variable being renamed. - * @package - */ -Block.prototype.updateVarName = function(variable) { - for (let i = 0, input; (input = this.inputList[i]); i++) { - for (let j = 0, field; (field = input.fieldRow[j]); j++) { - if (field.referencesVariables() && - variable.getId() === field.getValue()) { - field.refreshVariableName(); - } - } - } -}; - -/** - * 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. - */ -Block.prototype.renameVarById = function(oldId, newId) { - for (let i = 0, input; (input = this.inputList[i]); i++) { - for (let j = 0, field; (field = input.fieldRow[j]); j++) { - if (field.referencesVariables() && oldId === field.getValue()) { - field.setValue(newId); - } - } - } -}; - -/** - * Returns the language-neutral value of the given field. - * @param {string} name The name of the field. - * @return {*} Value of the field or null if field does not exist. - */ -Block.prototype.getFieldValue = function(name) { - const field = this.getField(name); - if (field) { - return field.getValue(); - } - return null; -}; - -/** - * Sets the value of the given field for this block. - * @param {*} newValue The value to set. - * @param {string} name The name of the field to set the value of. - */ -Block.prototype.setFieldValue = function(newValue, name) { - const field = this.getField(name); - if (!field) { - throw Error('Field "' + name + '" not found.'); - } - field.setValue(newValue); -}; - -/** - * Set whether this block can chain onto the bottom of another block. - * @param {boolean} newBoolean True if there can be a previous statement. - * @param {(string|Array|null)=} opt_check Statement type or - * list of statement types. Null/undefined if any type could be connected. - */ -Block.prototype.setPreviousStatement = function(newBoolean, opt_check) { - if (newBoolean) { - if (opt_check === undefined) { - opt_check = null; - } - if (!this.previousConnection) { - this.previousConnection = - this.makeConnection_(ConnectionType.PREVIOUS_STATEMENT); - } - this.previousConnection.setCheck(opt_check); - } else { - if (this.previousConnection) { - if (this.previousConnection.isConnected()) { - throw Error( - 'Must disconnect previous statement before removing ' + - 'connection.'); - } - this.previousConnection.dispose(); - this.previousConnection = null; - } - } -}; - -/** - * Set whether another block can chain onto the bottom of this block. - * @param {boolean} newBoolean True if there can be a next statement. - * @param {(string|Array|null)=} opt_check Statement type or - * list of statement types. Null/undefined if any type could be connected. - */ -Block.prototype.setNextStatement = function(newBoolean, opt_check) { - if (newBoolean) { - if (opt_check === undefined) { - opt_check = null; - } - if (!this.nextConnection) { - this.nextConnection = this.makeConnection_(ConnectionType.NEXT_STATEMENT); - } - this.nextConnection.setCheck(opt_check); - } else { - if (this.nextConnection) { - if (this.nextConnection.isConnected()) { - throw Error( - 'Must disconnect next statement before removing ' + - 'connection.'); - } - this.nextConnection.dispose(); - this.nextConnection = null; - } - } -}; - -/** - * Set whether this block returns a value. - * @param {boolean} newBoolean True if there is an output. - * @param {(string|Array|null)=} opt_check Returned type or list - * of returned types. Null or undefined if any type could be returned - * (e.g. variable get). - */ -Block.prototype.setOutput = function(newBoolean, opt_check) { - if (newBoolean) { - if (opt_check === undefined) { - opt_check = null; - } - if (!this.outputConnection) { - this.outputConnection = this.makeConnection_(ConnectionType.OUTPUT_VALUE); - } - this.outputConnection.setCheck(opt_check); - } else { - if (this.outputConnection) { - if (this.outputConnection.isConnected()) { - throw Error('Must disconnect output value before removing connection.'); - } - this.outputConnection.dispose(); - this.outputConnection = null; - } - } -}; - -/** - * Set whether value inputs are arranged horizontally or vertically. - * @param {boolean} newBoolean True if inputs are horizontal. - */ -Block.prototype.setInputsInline = function(newBoolean) { - if (this.inputsInline !== newBoolean) { - eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))( - this, 'inline', null, this.inputsInline, newBoolean)); - this.inputsInline = newBoolean; - } -}; - -/** - * Get whether value inputs are arranged horizontally or vertically. - * @return {boolean} True if inputs are horizontal. - */ -Block.prototype.getInputsInline = function() { - if (this.inputsInline !== undefined) { - // Set explicitly. - return this.inputsInline; - } - // Not defined explicitly. Figure out what would look best. - for (let i = 1; i < this.inputList.length; i++) { - if (this.inputList[i - 1].type === inputTypes.DUMMY && - this.inputList[i].type === inputTypes.DUMMY) { - // Two dummy inputs in a row. Don't inline them. - return false; - } - } - for (let i = 1; i < this.inputList.length; i++) { - if (this.inputList[i - 1].type === inputTypes.VALUE && - this.inputList[i].type === inputTypes.DUMMY) { - // Dummy input after a value input. Inline them. - return true; - } - } - return false; -}; - -/** - * Set the block's output shape. - * @param {?number} outputShape Value representing an output shape. - */ -Block.prototype.setOutputShape = function(outputShape) { - this.outputShape_ = outputShape; -}; - -/** - * Get the block's output shape. - * @return {?number} Value representing output shape if one exists. - */ -Block.prototype.getOutputShape = function() { - return this.outputShape_; -}; - -/** - * Get whether this block is enabled or not. - * @return {boolean} True if enabled. - */ -Block.prototype.isEnabled = function() { - return !this.disabled; -}; - -/** - * Set whether the block is enabled or not. - * @param {boolean} enabled True if enabled. - */ -Block.prototype.setEnabled = function(enabled) { - if (this.isEnabled() !== enabled) { - const oldValue = this.disabled; - this.disabled = !enabled; - eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))( - this, 'disabled', null, oldValue, !enabled)); - } -}; - -/** - * Get whether the block is disabled or not due to parents. - * The block's own disabled property is not considered. - * @return {boolean} True if disabled. - */ -Block.prototype.getInheritedDisabled = function() { - let ancestor = this.getSurroundParent(); - while (ancestor) { - if (ancestor.disabled) { - return true; - } - ancestor = ancestor.getSurroundParent(); - } - // Ran off the top. - return false; -}; - -/** - * Get whether the block is collapsed or not. - * @return {boolean} True if collapsed. - */ -Block.prototype.isCollapsed = function() { - return this.collapsed_; -}; - -/** - * Set whether the block is collapsed or not. - * @param {boolean} collapsed True if collapsed. - */ -Block.prototype.setCollapsed = function(collapsed) { - if (this.collapsed_ !== collapsed) { - eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))( - this, 'collapsed', null, this.collapsed_, collapsed)); - this.collapsed_ = collapsed; - } -}; - -/** - * Create a human-readable text representation of this block and any children. - * @param {number=} opt_maxLength Truncate the string to this length. - * @param {string=} opt_emptyToken The placeholder string used to denote an - * empty field. If not specified, '?' is used. - * @return {string} Text of block. - */ -Block.prototype.toString = function(opt_maxLength, opt_emptyToken) { - let text = []; - const emptyFieldPlaceholder = opt_emptyToken || '?'; - - // Temporarily set flag to navigate to all fields. - const prevNavigateFields = ASTNode.NAVIGATE_ALL_FIELDS; - ASTNode.NAVIGATE_ALL_FIELDS = true; - - let node = ASTNode.createBlockNode(this); - const rootNode = node; - - /** - * Whether or not to add parentheses around an input. - * @param {!Connection} connection The connection. - * @return {boolean} True if we should add parentheses around the input. - */ - function shouldAddParentheses(connection) { - let checks = connection.getCheck(); - if (!checks && connection.targetConnection) { - checks = connection.targetConnection.getCheck(); - } - return !!checks && - (checks.indexOf('Boolean') !== -1 || checks.indexOf('Number') !== -1); - } - - /** - * Check that we haven't circled back to the original root node. - */ - function checkRoot() { - if (node && node.getType() === rootNode.getType() && - node.getLocation() === rootNode.getLocation()) { - node = null; - } - } - - // Traverse the AST building up our text string. - while (node) { - switch (node.getType()) { - case ASTNode.types.INPUT: { - const connection = /** @type {!Connection} */ (node.getLocation()); - if (!node.in()) { - text.push(emptyFieldPlaceholder); - } else if (shouldAddParentheses(connection)) { - text.push('('); - } - break; - } - case ASTNode.types.FIELD: { - const field = /** @type {Field} */ (node.getLocation()); - if (field.name !== constants.COLLAPSED_FIELD_NAME) { - text.push(field.getText()); - } - break; - } - } - - const current = node; - node = current.in() || current.next(); - if (!node) { - // Can't go in or next, keep going out until we can go next. - node = current.out(); - checkRoot(); - while (node && !node.next()) { - node = node.out(); - checkRoot(); - // If we hit an input on the way up, possibly close out parentheses. - if (node && node.getType() === ASTNode.types.INPUT && - shouldAddParentheses( - /** @type {!Connection} */ (node.getLocation()))) { - text.push(')'); - } - } - if (node) { - node = node.next(); - } - } - } - - // Restore state of NAVIGATE_ALL_FIELDS. - ASTNode.NAVIGATE_ALL_FIELDS = prevNavigateFields; - - // Run through our text array and simplify expression to remove parentheses - // around single field blocks. - // E.g. ['repeat', '(', '10', ')', 'times', 'do', '?'] - for (let i = 2; i < text.length; i++) { - if (text[i - 2] === '(' && text[i] === ')') { - text[i - 2] = text[i - 1]; - text.splice(i - 1, 2); - } - } - - // Join the text array, removing spaces around added parentheses. - text = text.reduce(function(acc, value) { - return acc + ((acc.substr(-1) === '(' || value === ')') ? '' : ' ') + value; - }, ''); - text = text.trim() || '???'; - if (opt_maxLength) { - // TODO: Improve truncation so that text from this block is given priority. - // E.g. "1+2+3+4+5+6+7+8+9=0" should be "...6+7+8+9=0", not "1+2+3+4+5...". - // E.g. "1+2+3+4+5=6+7+8+9+0" should be "...4+5=6+7...". - if (text.length > opt_maxLength) { - text = text.substring(0, opt_maxLength - 3) + '...'; - } - } - return text; -}; - -/** - * Shortcut for appending a value input row. - * @param {string} name Language-neutral identifier which may used to find this - * input again. Should be unique to this block. - * @return {!Input} The input object created. - */ -Block.prototype.appendValueInput = function(name) { - return this.appendInput_(inputTypes.VALUE, name); -}; - -/** - * Shortcut for appending a statement input row. - * @param {string} name Language-neutral identifier which may used to find this - * input again. Should be unique to this block. - * @return {!Input} The input object created. - */ -Block.prototype.appendStatementInput = function(name) { - return this.appendInput_(inputTypes.STATEMENT, name); -}; - -/** - * Shortcut for appending a dummy input row. - * @param {string=} opt_name Language-neutral identifier which may used to find - * this input again. Should be unique to this block. - * @return {!Input} The input object created. - */ -Block.prototype.appendDummyInput = function(opt_name) { - return this.appendInput_(inputTypes.DUMMY, opt_name || ''); -}; - -/** - * Initialize this block using a cross-platform, internationalization-friendly - * JSON description. - * @param {!Object} json Structured data describing the block. - */ -Block.prototype.jsonInit = function(json) { - const warningPrefix = json['type'] ? 'Block "' + json['type'] + '": ' : ''; - - // Validate inputs. - if (json['output'] && json['previousStatement']) { - throw Error( - warningPrefix + - 'Must not have both an output and a previousStatement.'); - } - - // Set basic properties of block. - // Makes styles backward compatible with old way of defining hat style. - if (json['style'] && json['style'].hat) { - this.hat = json['style'].hat; - // Must set to null so it doesn't error when checking for style and colour. - json['style'] = null; - } - - if (json['style'] && json['colour']) { - throw Error(warningPrefix + 'Must not have both a colour and a style.'); - } else if (json['style']) { - this.jsonInitStyle_(json, warningPrefix); - } else { - this.jsonInitColour_(json, warningPrefix); - } - - // Interpolate the message blocks. - let i = 0; - while (json['message' + i] !== undefined) { - this.interpolate_( - json['message' + i], json['args' + i] || [], json['lastDummyAlign' + i], - warningPrefix); - i++; - } - - if (json['inputsInline'] !== undefined) { - this.setInputsInline(json['inputsInline']); - } - // Set output and previous/next connections. - if (json['output'] !== undefined) { - this.setOutput(true, json['output']); - } - if (json['outputShape'] !== undefined) { - this.setOutputShape(json['outputShape']); - } - if (json['previousStatement'] !== undefined) { - this.setPreviousStatement(true, json['previousStatement']); - } - if (json['nextStatement'] !== undefined) { - this.setNextStatement(true, json['nextStatement']); - } - if (json['tooltip'] !== undefined) { - const rawValue = json['tooltip']; - const localizedText = parsing.replaceMessageReferences(rawValue); - this.setTooltip(localizedText); - } - if (json['enableContextMenu'] !== undefined) { - this.contextMenu = !!json['enableContextMenu']; - } - if (json['suppressPrefixSuffix'] !== undefined) { - this.suppressPrefixSuffix = !!json['suppressPrefixSuffix']; - } - if (json['helpUrl'] !== undefined) { - const rawValue = json['helpUrl']; - const localizedValue = parsing.replaceMessageReferences(rawValue); - this.setHelpUrl(localizedValue); - } - if (typeof json['extensions'] === 'string') { - console.warn( - warningPrefix + 'JSON attribute \'extensions\' should be an array of' + - ' strings. Found raw string in JSON for \'' + json['type'] + - '\' block.'); - json['extensions'] = [json['extensions']]; // Correct and continue. - } - - // Add the mutator to the block. - if (json['mutator'] !== undefined) { - Extensions.apply(json['mutator'], this, true); - } - - const extensionNames = json['extensions']; - if (Array.isArray(extensionNames)) { - for (let j = 0; j < extensionNames.length; j++) { - Extensions.apply(extensionNames[j], this, false); - } - } -}; - -/** - * Initialize the colour of this block from the JSON description. - * @param {!Object} json Structured data describing the block. - * @param {string} warningPrefix Warning prefix string identifying block. - * @private - */ -Block.prototype.jsonInitColour_ = function(json, warningPrefix) { - if ('colour' in json) { - if (json['colour'] === undefined) { - console.warn(warningPrefix + 'Undefined colour value.'); - } else { - const rawValue = json['colour']; - try { - this.setColour(rawValue); - } catch (e) { - console.warn(warningPrefix + 'Illegal colour value: ', rawValue); - } - } - } -}; - -/** - * Initialize the style of this block from the JSON description. - * @param {!Object} json Structured data describing the block. - * @param {string} warningPrefix Warning prefix string identifying block. - * @private - */ -Block.prototype.jsonInitStyle_ = function(json, warningPrefix) { - const blockStyleName = json['style']; - try { - this.setStyle(blockStyleName); - } catch (styleError) { - console.warn(warningPrefix + 'Style does not exist: ', blockStyleName); - } -}; - -/** - * Add key/values from mixinObj to this block object. By default, this method - * will check that the keys in mixinObj will not overwrite existing values in - * the block, including prototype values. This provides some insurance against - * mixin / extension incompatibilities with future block features. This check - * can be disabled by passing true as the second argument. - * @param {!Object} mixinObj The key/values pairs to add to this block object. - * @param {boolean=} opt_disableCheck Option flag to disable overwrite checks. - */ -Block.prototype.mixin = function(mixinObj, opt_disableCheck) { - if (opt_disableCheck !== undefined && typeof opt_disableCheck !== 'boolean') { - throw Error('opt_disableCheck must be a boolean if provided'); - } - if (!opt_disableCheck) { - const overwrites = []; - for (const key in mixinObj) { - if (this[key] !== undefined) { - overwrites.push(key); - } - } - if (overwrites.length) { - throw Error( - 'Mixin will overwrite block members: ' + JSON.stringify(overwrites)); - } - } - object.mixin(this, mixinObj); -}; - -/** - * Interpolate a message description onto the block. - * @param {string} message Text contains interpolation tokens (%1, %2, ...) - * that match with fields or inputs defined in the args array. - * @param {!Array} args Array of arguments to be interpolated. - * @param {string|undefined} lastDummyAlign If a dummy input is added at the - * end, how should it be aligned? - * @param {string} warningPrefix Warning prefix string identifying block. - * @private - */ -Block.prototype.interpolate_ = function( - message, args, lastDummyAlign, warningPrefix) { - const tokens = parsing.tokenizeInterpolation(message); - this.validateTokens_(tokens, args.length); - const elements = this.interpolateArguments_(tokens, args, lastDummyAlign); - - // An array of [field, fieldName] tuples. - const fieldStack = []; - for (let i = 0, element; (element = elements[i]); i++) { - if (this.isInputKeyword_(element['type'])) { - const input = this.inputFromJson_(element, warningPrefix); - // Should never be null, but just in case. - if (input) { - for (let j = 0, tuple; (tuple = fieldStack[j]); j++) { - input.appendField(tuple[0], tuple[1]); - } - fieldStack.length = 0; - } - } else { - // All other types, including ones starting with 'input_' get routed here. - const field = this.fieldFromJson_(element); - if (field) { - fieldStack.push([field, element['name']]); - } - } - } -}; - -/** - * Validates that the tokens are within the correct bounds, with no duplicates, - * and that all of the arguments are referred to. Throws errors if any of these - * things are not true. - * @param {!Array} tokens An array of tokens to validate - * @param {number} argsCount The number of args that need to be referred to. - * @private - */ -Block.prototype.validateTokens_ = function(tokens, argsCount) { - const visitedArgsHash = []; - let visitedArgsCount = 0; - for (let i = 0; i < tokens.length; i++) { - const token = tokens[i]; - if (typeof token !== 'number') { - continue; - } - if (token < 1 || token > argsCount) { - throw Error( - 'Block "' + this.type + '": ' + - 'Message index %' + token + ' out of range.'); - } - if (visitedArgsHash[token]) { - throw Error( - 'Block "' + this.type + '": ' + - 'Message index %' + token + ' duplicated.'); - } - visitedArgsHash[token] = true; - visitedArgsCount++; - } - if (visitedArgsCount !== argsCount) { - throw Error( - 'Block "' + this.type + '": ' + - 'Message does not reference all ' + argsCount + ' arg(s).'); - } -}; - -/** - * Inserts args in place of numerical tokens. String args are converted to JSON - * that defines a label field. If necessary an extra dummy input is added to - * the end of the elements. - * @param {!Array} tokens The tokens to interpolate - * @param {!Array} args The arguments to insert. - * @param {string|undefined} lastDummyAlign The alignment the added dummy input - * should have, if we are required to add one. - * @return {!Array} The JSON definitions of field and inputs to add - * to the block. - * @private - */ -Block.prototype.interpolateArguments_ = function(tokens, args, lastDummyAlign) { - const elements = []; - for (let i = 0; i < tokens.length; i++) { - let element = tokens[i]; - if (typeof element === 'number') { - element = args[element - 1]; - } - // Args can be strings, which is why this isn't elseif. - if (typeof element === 'string') { - element = this.stringToFieldJson_(element); - if (!element) { - continue; - } - } - elements.push(element); - } - - const length = elements.length; - if (length && !this.isInputKeyword_(elements[length - 1]['type'])) { - const dummyInput = {'type': 'input_dummy'}; - if (lastDummyAlign) { - dummyInput['align'] = lastDummyAlign; - } - elements.push(dummyInput); - } - - return elements; -}; - -/** - * Creates a field from the JSON definition of a field. If a field with the - * given type cannot be found, this attempts to create a different field using - * the 'alt' property of the JSON definition (if it exists). - * @param {{alt:(string|undefined)}} element The element to try to turn into a - * field. - * @return {?Field} The field defined by the JSON, or null if one - * couldn't be created. - * @private - */ -Block.prototype.fieldFromJson_ = function(element) { - const field = fieldRegistry.fromJson(element); - if (!field && element['alt']) { - if (typeof element['alt'] === 'string') { - const json = this.stringToFieldJson_(element['alt']); - return json ? this.fieldFromJson_(json) : null; - } - return this.fieldFromJson_(element['alt']); - } - return field; -}; - -/** - * Creates an input from the JSON definition of an input. Sets the input's check - * and alignment if they are provided. - * @param {!Object} element The JSON to turn into an input. - * @param {string} warningPrefix The prefix to add to warnings to help the - * developer debug. - * @return {?Input} The input that has been created, or null if one - * could not be created for some reason (should never happen). - * @private - */ -Block.prototype.inputFromJson_ = function(element, warningPrefix) { - const alignmentLookup = { - 'LEFT': Align.LEFT, - 'RIGHT': Align.RIGHT, - 'CENTRE': Align.CENTRE, - 'CENTER': Align.CENTRE, - }; - - let input = null; - switch (element['type']) { - case 'input_value': - input = this.appendValueInput(element['name']); - break; - case 'input_statement': - input = this.appendStatementInput(element['name']); - break; - case 'input_dummy': - input = this.appendDummyInput(element['name']); - break; - } - // Should never be hit because of interpolate_'s checks, but just in case. - if (!input) { - return null; - } - - if (element['check']) { - input.setCheck(element['check']); - } - if (element['align']) { - const alignment = alignmentLookup[element['align'].toUpperCase()]; - if (alignment === undefined) { - console.warn(warningPrefix + 'Illegal align value: ', element['align']); - } else { - input.setAlign(alignment); - } - } - return input; -}; - -/** - * Returns true if the given string matches one of the input keywords. - * @param {string} str The string to check. - * @return {boolean} True if the given string matches one of the input keywords, - * false otherwise. - * @private - */ -Block.prototype.isInputKeyword_ = function(str) { - return str === 'input_value' || str === 'input_statement' || - str === 'input_dummy'; -}; - -/** - * Turns a string into the JSON definition of a label field. If the string - * becomes an empty string when trimmed, this returns null. - * @param {string} str String to turn into the JSON definition of a label field. - * @return {?{text: string, type: string}} The JSON definition or null. - * @private - */ -Block.prototype.stringToFieldJson_ = function(str) { - str = str.trim(); - if (str) { - return { - 'type': 'field_label', - 'text': str, - }; - } - return null; -}; - -/** - * Add a value input, statement input or local variable to this block. - * @param {number} type One of Blockly.inputTypes. - * @param {string} name Language-neutral identifier which may used to find this - * input again. Should be unique to this block. - * @return {!Input} The input object created. - * @protected - */ -Block.prototype.appendInput_ = function(type, name) { - let connection = null; - if (type === inputTypes.VALUE || type === inputTypes.STATEMENT) { - connection = this.makeConnection_(type); - } - if (type === inputTypes.STATEMENT) { - this.statementInputCount++; - } - const input = new Input(type, name, this, connection); - // Append input to list. - this.inputList.push(input); - return input; -}; - -/** - * Move a named input to a different location on this block. - * @param {string} name The name of the input to move. - * @param {?string} refName Name of input that should be after the moved input, - * or null to be the input at the end. - */ -Block.prototype.moveInputBefore = function(name, refName) { - if (name === refName) { - return; - } - // Find both inputs. - let inputIndex = -1; - let refIndex = refName ? -1 : this.inputList.length; - for (let i = 0, input; (input = this.inputList[i]); i++) { - if (input.name === name) { - inputIndex = i; - if (refIndex !== -1) { - break; - } - } else if (refName && input.name === refName) { - refIndex = i; - if (inputIndex !== -1) { - break; - } - } - } - if (inputIndex === -1) { - throw Error('Named input "' + name + '" not found.'); - } - if (refIndex === -1) { - throw Error('Reference input "' + refName + '" not found.'); - } - this.moveNumberedInputBefore(inputIndex, refIndex); -}; - -/** - * Move a numbered input to a different location on this block. - * @param {number} inputIndex Index of the input to move. - * @param {number} refIndex Index of input that should be after the moved input. - */ -Block.prototype.moveNumberedInputBefore = function(inputIndex, refIndex) { - // Validate arguments. - if (inputIndex === refIndex) { - throw Error('Can\'t move input to itself.'); - } - if (inputIndex >= this.inputList.length) { - throw RangeError('Input index ' + inputIndex + ' out of bounds.'); - } - if (refIndex > this.inputList.length) { - throw RangeError('Reference input ' + refIndex + ' out of bounds.'); - } - // Remove input. - const input = this.inputList[inputIndex]; - this.inputList.splice(inputIndex, 1); - if (inputIndex < refIndex) { - refIndex--; - } - // Reinsert input. - this.inputList.splice(refIndex, 0, input); -}; - -/** - * Remove an input from this block. - * @param {string} name The name of the input. - * @param {boolean=} opt_quiet True to prevent an error if input is not present. - * @return {boolean} True if operation succeeds, false if input is not present - * and opt_quiet is true. - * @throws {Error} if the input is not present and opt_quiet is not true. - */ -Block.prototype.removeInput = function(name, opt_quiet) { - for (let i = 0, input; (input = this.inputList[i]); i++) { - if (input.name === name) { - if (input.type === inputTypes.STATEMENT) { - this.statementInputCount--; - } - input.dispose(); - this.inputList.splice(i, 1); - return true; - } - } - if (opt_quiet) { - return false; - } - throw Error('Input not found: ' + name); -}; - -/** - * Fetches the named input object. - * @param {string} name The name of the input. - * @return {?Input} The input object, or null if input does not exist. - */ -Block.prototype.getInput = function(name) { - for (let i = 0, input; (input = this.inputList[i]); i++) { - if (input.name === name) { - return input; - } - } - // This input does not exist. - return null; -}; - -/** - * Fetches the block attached to the named input. - * @param {string} name The name of the input. - * @return {?Block} The attached value block, or null if the input is - * either disconnected or if the input does not exist. - */ -Block.prototype.getInputTargetBlock = function(name) { - const input = this.getInput(name); - return input && input.connection && input.connection.targetBlock(); -}; - -/** - * Returns the comment on this block (or null if there is no comment). - * @return {?string} Block's comment. - */ -Block.prototype.getCommentText = function() { - return this.commentModel.text; -}; - -/** - * Set this block's comment text. - * @param {?string} text The text, or null to delete. - */ -Block.prototype.setCommentText = function(text) { - if (this.commentModel.text === text) { - return; - } - eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))( - this, 'comment', null, this.commentModel.text, text)); - this.commentModel.text = text; - this.comment = text; // For backwards compatibility. -}; - -/** - * Set this block's warning text. - * @param {?string} _text The text, or null to delete. - * @param {string=} _opt_id An optional ID for the warning text to be able to - * maintain multiple warnings. - */ -Block.prototype.setWarningText = function(_text, _opt_id) { - // NOP. -}; - -/** - * Give this block a mutator dialog. - * @param {Mutator} _mutator A mutator dialog instance or null to - * remove. - */ -Block.prototype.setMutator = function(_mutator) { - // NOP. -}; - -/** - * Return the coordinates of the top-left corner of this block relative to the - * drawing surface's origin (0,0), in workspace units. - * @return {!Coordinate} Object with .x and .y properties. - */ -Block.prototype.getRelativeToSurfaceXY = function() { - return this.xy_; -}; - -/** - * Move a block by a relative offset. - * @param {number} dx Horizontal offset, in workspace units. - * @param {number} dy Vertical offset, in workspace units. - */ -Block.prototype.moveBy = function(dx, dy) { - if (this.parentBlock_) { - throw Error('Block has parent.'); - } - const event = new (eventUtils.get(eventUtils.BLOCK_MOVE))(this); - this.xy_.translate(dx, dy); - event.recordNew(); - eventUtils.fire(event); -}; - -/** - * Create a connection of the specified type. - * @param {number} type The type of the connection to create. - * @return {!Connection} A new connection of the specified type. - * @protected - */ -Block.prototype.makeConnection_ = function(type) { - return new Connection(this, type); -}; - -/** - * Recursively checks whether all statement and value inputs are filled with - * blocks. Also checks all following statement blocks in this stack. - * @param {boolean=} opt_shadowBlocksAreFilled An optional argument controlling - * whether shadow blocks are counted as filled. Defaults to true. - * @return {boolean} True if all inputs are filled, false otherwise. - */ -Block.prototype.allInputsFilled = function(opt_shadowBlocksAreFilled) { - // Account for the shadow block filledness toggle. - if (opt_shadowBlocksAreFilled === undefined) { - opt_shadowBlocksAreFilled = true; - } - if (!opt_shadowBlocksAreFilled && this.isShadow()) { - return false; - } - - // Recursively check each input block of the current block. - for (let i = 0, input; (input = this.inputList[i]); i++) { - if (!input.connection) { - continue; - } - const target = input.connection.targetBlock(); - if (!target || !target.allInputsFilled(opt_shadowBlocksAreFilled)) { - return false; - } - } - - // Recursively check the next block after the current block. - const next = this.getNextBlock(); - if (next) { - return next.allInputsFilled(opt_shadowBlocksAreFilled); - } - - return true; -}; - -/** - * This method returns a string describing this Block in developer terms (type - * name and ID; English only). - * - * Intended to on be used in console logs and errors. If you need a string that - * uses the user's native language (including block text, field values, and - * child blocks), use [toString()]{@link Block#toString}. - * @return {string} The description. - */ -Block.prototype.toDevString = function() { - let msg = this.type ? '"' + this.type + '" block' : 'Block'; - if (this.id) { - msg += ' (id="' + this.id + '")'; - } - return msg; -}; - exports.Block = Block; diff --git a/core/block_drag_surface.js b/core/block_drag_surface.js index 2ff685cde..c0b01094a 100644 --- a/core/block_drag_surface.js +++ b/core/block_drag_surface.js @@ -35,234 +35,235 @@ const {Svg} = goog.require('Blockly.utils.Svg'); /** * Class for a drag surface for the currently dragged block. This is a separate * SVG that contains only the currently moving block, or nothing. - * @param {!Element} container Containing element. - * @constructor * @alias Blockly.BlockDragSurfaceSvg */ -const BlockDragSurfaceSvg = function(container) { +const BlockDragSurfaceSvg = class { /** - * @type {!Element} + * @param {!Element} container Containing element. + */ + constructor(container) { + /** + * The SVG drag surface. Set once by BlockDragSurfaceSvg.createDom. + * @type {?SVGElement} + * @private + */ + this.SVG_ = null; + + /** + * This is where blocks live while they are being dragged if the drag + * surface is enabled. + * @type {?SVGElement} + * @private + */ + this.dragGroup_ = null; + + /** + * Containing HTML element; parent of the workspace and the drag surface. + * @type {!Element} + * @private + */ + this.container_ = container; + + /** + * Cached value for the scale of the drag surface. + * Used to set/get the correct translation during and after a drag. + * @type {number} + * @private + */ + this.scale_ = 1; + + /** + * Cached value for the translation of the drag surface. + * This translation is in pixel units, because the scale is applied to the + * drag group rather than the top-level SVG. + * @type {?Coordinate} + * @private + */ + this.surfaceXY_ = null; + + /** + * Cached value for the translation of the child drag surface in pixel + * units. Since the child drag surface tracks the translation of the + * workspace this is ultimately the translation of the workspace. + * @type {!Coordinate} + * @private + */ + this.childSurfaceXY_ = new Coordinate(0, 0); + + this.createDom(); + } + + /** + * Create the drag surface and inject it into the container. + */ + createDom() { + if (this.SVG_) { + return; // Already created. + } + this.SVG_ = dom.createSvgElement( + Svg.SVG, { + 'xmlns': dom.SVG_NS, + 'xmlns:html': dom.HTML_NS, + 'xmlns:xlink': dom.XLINK_NS, + 'version': '1.1', + 'class': 'blocklyBlockDragSurface', + }, + this.container_); + this.dragGroup_ = dom.createSvgElement(Svg.G, {}, this.SVG_); + } + + /** + * Set the SVG blocks on the drag surface's group and show the surface. + * Only one block group should be on the drag surface at a time. + * @param {!SVGElement} blocks Block or group of blocks to place on the drag + * surface. + */ + setBlocksAndShow(blocks) { + if (this.dragGroup_.childNodes.length) { + throw Error('Already dragging a block.'); + } + // appendChild removes the blocks from the previous parent + this.dragGroup_.appendChild(blocks); + this.SVG_.style.display = 'block'; + this.surfaceXY_ = new Coordinate(0, 0); + } + + /** + * Translate and scale the entire drag surface group to the given position, to + * keep in sync with the workspace. + * @param {number} x X translation in pixel coordinates. + * @param {number} y Y translation in pixel coordinates. + * @param {number} scale Scale of the group. + */ + translateAndScaleGroup(x, y, scale) { + this.scale_ = scale; + // This is a work-around to prevent a the blocks from rendering + // fuzzy while they are being dragged on the drag surface. + const fixedX = x.toFixed(0); + const fixedY = y.toFixed(0); + + this.childSurfaceXY_.x = parseInt(fixedX, 10); + this.childSurfaceXY_.y = parseInt(fixedY, 10); + + this.dragGroup_.setAttribute( + 'transform', + 'translate(' + fixedX + ',' + fixedY + ') scale(' + scale + ')'); + } + + /** + * Translate the drag surface's SVG based on its internal state. * @private */ - this.container_ = container; - this.createDom(); -}; + translateSurfaceInternal_() { + let x = this.surfaceXY_.x; + let y = this.surfaceXY_.y; + // This is a work-around to prevent a the blocks from rendering + // fuzzy while they are being dragged on the drag surface. + x = x.toFixed(0); + y = y.toFixed(0); + this.SVG_.style.display = 'block'; -/** - * The SVG drag surface. Set once by BlockDragSurfaceSvg.createDom. - * @type {?SVGElement} - * @private - */ -BlockDragSurfaceSvg.prototype.SVG_ = null; - -/** - * This is where blocks live while they are being dragged if the drag surface - * is enabled. - * @type {?SVGElement} - * @private - */ -BlockDragSurfaceSvg.prototype.dragGroup_ = null; - -/** - * Containing HTML element; parent of the workspace and the drag surface. - * @type {?Element} - * @private - */ -BlockDragSurfaceSvg.prototype.container_ = null; - -/** - * Cached value for the scale of the drag surface. - * Used to set/get the correct translation during and after a drag. - * @type {number} - * @private - */ -BlockDragSurfaceSvg.prototype.scale_ = 1; - -/** - * Cached value for the translation of the drag surface. - * This translation is in pixel units, because the scale is applied to the - * drag group rather than the top-level SVG. - * @type {?Coordinate} - * @private - */ -BlockDragSurfaceSvg.prototype.surfaceXY_ = null; - -/** - * Cached value for the translation of the child drag surface in pixel units. - * Since the child drag surface tracks the translation of the workspace this is - * ultimately the translation of the workspace. - * @type {!Coordinate} - * @private - */ -BlockDragSurfaceSvg.prototype.childSurfaceXY_ = new Coordinate(0, 0); - -/** - * Create the drag surface and inject it into the container. - */ -BlockDragSurfaceSvg.prototype.createDom = function() { - if (this.SVG_) { - return; // Already created. + dom.setCssTransform(this.SVG_, 'translate3d(' + x + 'px, ' + y + 'px, 0)'); } - this.SVG_ = dom.createSvgElement( - Svg.SVG, { - 'xmlns': dom.SVG_NS, - 'xmlns:html': dom.HTML_NS, - 'xmlns:xlink': dom.XLINK_NS, - 'version': '1.1', - 'class': 'blocklyBlockDragSurface', - }, - this.container_); - this.dragGroup_ = dom.createSvgElement(Svg.G, {}, this.SVG_); -}; -/** - * Set the SVG blocks on the drag surface's group and show the surface. - * Only one block group should be on the drag surface at a time. - * @param {!SVGElement} blocks Block or group of blocks to place on the drag - * surface. - */ -BlockDragSurfaceSvg.prototype.setBlocksAndShow = function(blocks) { - if (this.dragGroup_.childNodes.length) { - throw Error('Already dragging a block.'); + /** + * Translates the entire surface by a relative offset. + * @param {number} deltaX Horizontal offset in pixel units. + * @param {number} deltaY Vertical offset in pixel units. + */ + translateBy(deltaX, deltaY) { + const x = this.surfaceXY_.x + deltaX; + const y = this.surfaceXY_.y + deltaY; + this.surfaceXY_ = new Coordinate(x, y); + this.translateSurfaceInternal_(); } - // appendChild removes the blocks from the previous parent - this.dragGroup_.appendChild(blocks); - this.SVG_.style.display = 'block'; - this.surfaceXY_ = new Coordinate(0, 0); -}; -/** - * Translate and scale the entire drag surface group to the given position, to - * keep in sync with the workspace. - * @param {number} x X translation in pixel coordinates. - * @param {number} y Y translation in pixel coordinates. - * @param {number} scale Scale of the group. - */ -BlockDragSurfaceSvg.prototype.translateAndScaleGroup = function(x, y, scale) { - this.scale_ = scale; - // This is a work-around to prevent a the blocks from rendering - // fuzzy while they are being dragged on the drag surface. - const fixedX = x.toFixed(0); - const fixedY = y.toFixed(0); - - this.childSurfaceXY_.x = parseInt(fixedX, 10); - this.childSurfaceXY_.y = parseInt(fixedY, 10); - - this.dragGroup_.setAttribute( - 'transform', - 'translate(' + fixedX + ',' + fixedY + ') scale(' + scale + ')'); -}; - -/** - * Translate the drag surface's SVG based on its internal state. - * @private - */ -BlockDragSurfaceSvg.prototype.translateSurfaceInternal_ = function() { - let x = this.surfaceXY_.x; - let y = this.surfaceXY_.y; - // This is a work-around to prevent a the blocks from rendering - // fuzzy while they are being dragged on the drag surface. - x = x.toFixed(0); - y = y.toFixed(0); - this.SVG_.style.display = 'block'; - - dom.setCssTransform(this.SVG_, 'translate3d(' + x + 'px, ' + y + 'px, 0)'); -}; - -/** - * Translates the entire surface by a relative offset. - * @param {number} deltaX Horizontal offset in pixel units. - * @param {number} deltaY Vertical offset in pixel units. - */ -BlockDragSurfaceSvg.prototype.translateBy = function(deltaX, deltaY) { - const x = this.surfaceXY_.x + deltaX; - const y = this.surfaceXY_.y + deltaY; - this.surfaceXY_ = new Coordinate(x, y); - this.translateSurfaceInternal_(); -}; - -/** - * Translate the entire drag surface during a drag. - * We translate the drag surface instead of the blocks inside the surface - * so that the browser avoids repainting the SVG. - * Because of this, the drag coordinates must be adjusted by scale. - * @param {number} x X translation for the entire surface. - * @param {number} y Y translation for the entire surface. - */ -BlockDragSurfaceSvg.prototype.translateSurface = function(x, y) { - this.surfaceXY_ = new Coordinate(x * this.scale_, y * this.scale_); - this.translateSurfaceInternal_(); -}; - -/** - * Reports the surface translation in scaled workspace coordinates. - * Use this when finishing a drag to return blocks to the correct position. - * @return {!Coordinate} Current translation of the surface. - */ -BlockDragSurfaceSvg.prototype.getSurfaceTranslation = function() { - const xy = svgMath.getRelativeXY(/** @type {!SVGElement} */ (this.SVG_)); - return new Coordinate(xy.x / this.scale_, xy.y / this.scale_); -}; - -/** - * Provide a reference to the drag group (primarily for - * BlockSvg.getRelativeToSurfaceXY). - * @return {?SVGElement} Drag surface group element. - */ -BlockDragSurfaceSvg.prototype.getGroup = function() { - return this.dragGroup_; -}; - -/** - * Returns the SVG drag surface. - * @returns {?SVGElement} The SVG drag surface. - */ -BlockDragSurfaceSvg.prototype.getSvgRoot = function() { - return this.SVG_; -}; - -/** - * Get the current blocks on the drag surface, if any (primarily - * for BlockSvg.getRelativeToSurfaceXY). - * @return {?Element} Drag surface block DOM element, or null if no blocks - * exist. - */ -BlockDragSurfaceSvg.prototype.getCurrentBlock = function() { - return /** @type {Element} */ (this.dragGroup_.firstChild); -}; - -/** - * Gets the translation of the child block surface - * This surface is in charge of keeping track of how much the workspace has - * moved. - * @return {!Coordinate} The amount the workspace has been moved. - */ -BlockDragSurfaceSvg.prototype.getWsTranslation = function() { - // Returning a copy so the coordinate can not be changed outside this class. - return this.childSurfaceXY_.clone(); -}; - -/** - * Clear the group and hide the surface; move the blocks off onto the provided - * element. - * If the block is being deleted it doesn't need to go back to the original - * surface, since it would be removed immediately during dispose. - * @param {Element=} opt_newSurface Surface the dragging blocks should be moved - * to, or null if the blocks should be removed from this surface without - * being moved to a different surface. - */ -BlockDragSurfaceSvg.prototype.clearAndHide = function(opt_newSurface) { - if (opt_newSurface) { - // appendChild removes the node from this.dragGroup_ - opt_newSurface.appendChild(this.getCurrentBlock()); - } else { - this.dragGroup_.removeChild(this.getCurrentBlock()); + /** + * Translate the entire drag surface during a drag. + * We translate the drag surface instead of the blocks inside the surface + * so that the browser avoids repainting the SVG. + * Because of this, the drag coordinates must be adjusted by scale. + * @param {number} x X translation for the entire surface. + * @param {number} y Y translation for the entire surface. + */ + translateSurface(x, y) { + this.surfaceXY_ = new Coordinate(x * this.scale_, y * this.scale_); + this.translateSurfaceInternal_(); } - this.SVG_.style.display = 'none'; - if (this.dragGroup_.childNodes.length) { - throw Error('Drag group was not cleared.'); + + /** + * Reports the surface translation in scaled workspace coordinates. + * Use this when finishing a drag to return blocks to the correct position. + * @return {!Coordinate} Current translation of the surface. + */ + getSurfaceTranslation() { + const xy = svgMath.getRelativeXY(/** @type {!SVGElement} */ (this.SVG_)); + return new Coordinate(xy.x / this.scale_, xy.y / this.scale_); + } + + /** + * Provide a reference to the drag group (primarily for + * BlockSvg.getRelativeToSurfaceXY). + * @return {?SVGElement} Drag surface group element. + */ + getGroup() { + return this.dragGroup_; + } + + /** + * Returns the SVG drag surface. + * @returns {?SVGElement} The SVG drag surface. + */ + getSvgRoot() { + return this.SVG_; + } + + /** + * Get the current blocks on the drag surface, if any (primarily + * for BlockSvg.getRelativeToSurfaceXY). + * @return {?Element} Drag surface block DOM element, or null if no blocks + * exist. + */ + getCurrentBlock() { + return /** @type {Element} */ (this.dragGroup_.firstChild); + } + + /** + * Gets the translation of the child block surface + * This surface is in charge of keeping track of how much the workspace has + * moved. + * @return {!Coordinate} The amount the workspace has been moved. + */ + getWsTranslation() { + // Returning a copy so the coordinate can not be changed outside this class. + return this.childSurfaceXY_.clone(); + } + + /** + * Clear the group and hide the surface; move the blocks off onto the provided + * element. + * If the block is being deleted it doesn't need to go back to the original + * surface, since it would be removed immediately during dispose. + * @param {Element=} opt_newSurface Surface the dragging blocks should be + * moved to, or null if the blocks should be removed from this surface + * without being moved to a different surface. + */ + clearAndHide(opt_newSurface) { + const currentBlockElement = this.getCurrentBlock(); + if (currentBlockElement) { + if (opt_newSurface) { + // appendChild removes the node from this.dragGroup_ + opt_newSurface.appendChild(currentBlockElement); + } else { + this.dragGroup_.removeChild(currentBlockElement); + } + } + this.SVG_.style.display = 'none'; + if (this.dragGroup_.childNodes.length) { + throw Error('Drag group was not cleared.'); + } + this.surfaceXY_ = null; } - this.surfaceXY_ = null; }; exports.BlockDragSurfaceSvg = BlockDragSurfaceSvg; diff --git a/core/block_dragger.js b/core/block_dragger.js index 817fa10fe..cc7648e7c 100644 --- a/core/block_dragger.js +++ b/core/block_dragger.js @@ -22,6 +22,8 @@ const dom = goog.require('Blockly.utils.dom'); const eventUtils = goog.require('Blockly.Events.utils'); const registry = goog.require('Blockly.registry'); /* eslint-disable-next-line no-unused-vars */ +const {BlockMove} = goog.requireType('Blockly.Events.BlockMove'); +/* eslint-disable-next-line no-unused-vars */ const {BlockSvg} = goog.requireType('Blockly.BlockSvg'); const {Coordinate} = goog.require('Blockly.utils.Coordinate'); /* eslint-disable-next-line no-unused-vars */ @@ -40,76 +42,411 @@ goog.require('Blockly.Events.BlockMove'); /** * Class for a block dragger. It moves blocks around the workspace when they * are being dragged by a mouse or touch. - * @param {!BlockSvg} block The block to drag. - * @param {!WorkspaceSvg} workspace The workspace to drag on. - * @constructor * @implements {IBlockDragger} * @alias Blockly.BlockDragger */ -const BlockDragger = function(block, workspace) { +const BlockDragger = class { /** - * The top block in the stack that is being dragged. - * @type {!BlockSvg} + * @param {!BlockSvg} block The block to drag. + * @param {!WorkspaceSvg} workspace The workspace to drag on. + */ + constructor(block, workspace) { + /** + * The top block in the stack that is being dragged. + * @type {!BlockSvg} + * @protected + */ + this.draggingBlock_ = block; + + /** + * The workspace on which the block is being dragged. + * @type {!WorkspaceSvg} + * @protected + */ + this.workspace_ = workspace; + + /** + * Object that keeps track of connections on dragged blocks. + * @type {!InsertionMarkerManager} + * @protected + */ + this.draggedConnectionManager_ = + new InsertionMarkerManager(this.draggingBlock_); + + /** + * Which drag area the mouse pointer is over, if any. + * @type {?IDragTarget} + * @private + */ + this.dragTarget_ = null; + + /** + * Whether the block would be deleted if dropped immediately. + * @type {boolean} + * @protected + */ + this.wouldDeleteBlock_ = false; + + /** + * The location of the top left corner of the dragging block at the + * beginning of the drag in workspace coordinates. + * @type {!Coordinate} + * @protected + */ + this.startXY_ = this.draggingBlock_.getRelativeToSurfaceXY(); + + /** + * A list of all of the icons (comment, warning, and mutator) that are + * on this block and its descendants. Moving an icon moves the bubble that + * extends from it if that bubble is open. + * @type {Array} + * @protected + */ + this.dragIconData_ = initIconData(block); + } + + /** + * Sever all links from this object. + * @package + */ + dispose() { + this.dragIconData_.length = 0; + + if (this.draggedConnectionManager_) { + this.draggedConnectionManager_.dispose(); + } + } + + /** + * Start dragging a block. This includes moving it to the drag surface. + * @param {!Coordinate} currentDragDeltaXY How far the pointer has + * moved from the position at mouse down, in pixel units. + * @param {boolean} healStack Whether or not to heal the stack after + * disconnecting. + * @public + */ + startDrag(currentDragDeltaXY, healStack) { + if (!eventUtils.getGroup()) { + eventUtils.setGroup(true); + } + this.fireDragStartEvent_(); + + // Mutators don't have the same type of z-ordering as the normal workspace + // during a drag. They have to rely on the order of the blocks in the SVG. + // For performance reasons that usually happens at the end of a drag, + // but do it at the beginning for mutators. + if (this.workspace_.isMutator) { + this.draggingBlock_.bringToFront(); + } + + // During a drag there may be a lot of rerenders, but not field changes. + // Turn the cache on so we don't do spurious remeasures during the drag. + dom.startTextWidthCache(); + this.workspace_.setResizesEnabled(false); + blockAnimation.disconnectUiStop(); + + if (this.shouldDisconnect_(healStack)) { + this.disconnectBlock_(healStack, currentDragDeltaXY); + } + this.draggingBlock_.setDragging(true); + // For future consideration: we may be able to put moveToDragSurface inside + // the block dragger, which would also let the block not track the block + // drag surface. + this.draggingBlock_.moveToDragSurface(); + } + + /** + * Whether or not we should disconnect the block when a drag is started. + * @param {boolean} healStack Whether or not to heal the stack after + * disconnecting. + * @return {boolean} True to disconnect the block, false otherwise. * @protected */ - this.draggingBlock_ = block; + shouldDisconnect_(healStack) { + return !!( + this.draggingBlock_.getParent() || + (healStack && this.draggingBlock_.nextConnection && + this.draggingBlock_.nextConnection.targetBlock())); + } /** - * The workspace on which the block is being dragged. - * @type {!WorkspaceSvg} + * Disconnects the block and moves it to a new location. + * @param {boolean} healStack Whether or not to heal the stack after + * disconnecting. + * @param {!Coordinate} currentDragDeltaXY How far the pointer has + * moved from the position at mouse down, in pixel units. * @protected */ - this.workspace_ = workspace; + disconnectBlock_(healStack, currentDragDeltaXY) { + this.draggingBlock_.unplug(healStack); + const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY); + const newLoc = Coordinate.sum(this.startXY_, delta); + + this.draggingBlock_.translate(newLoc.x, newLoc.y); + blockAnimation.disconnectUiEffect(this.draggingBlock_); + this.draggedConnectionManager_.updateAvailableConnections(); + } /** - * Object that keeps track of connections on dragged blocks. - * @type {!InsertionMarkerManager} + * Fire a UI event at the start of a block drag. * @protected */ - this.draggedConnectionManager_ = - new InsertionMarkerManager(this.draggingBlock_); + fireDragStartEvent_() { + const event = new (eventUtils.get(eventUtils.BLOCK_DRAG))( + this.draggingBlock_, true, this.draggingBlock_.getDescendants(false)); + eventUtils.fire(event); + } /** - * Which drag area the mouse pointer is over, if any. - * @type {?IDragTarget} - * @private + * Execute a step of block dragging, based on the given event. Update the + * display accordingly. + * @param {!Event} e The most recent move event. + * @param {!Coordinate} currentDragDeltaXY How far the pointer has + * moved from the position at the start of the drag, in pixel units. + * @public */ - this.dragTarget_ = null; + drag(e, currentDragDeltaXY) { + const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY); + const newLoc = Coordinate.sum(this.startXY_, delta); + this.draggingBlock_.moveDuringDrag(newLoc); + this.dragIcons_(delta); + + const oldDragTarget = this.dragTarget_; + this.dragTarget_ = this.workspace_.getDragTarget(e); + + this.draggedConnectionManager_.update(delta, this.dragTarget_); + const oldWouldDeleteBlock = this.wouldDeleteBlock_; + this.wouldDeleteBlock_ = this.draggedConnectionManager_.wouldDeleteBlock(); + if (oldWouldDeleteBlock !== this.wouldDeleteBlock_) { + // Prevent unnecessary add/remove class calls. + this.updateCursorDuringBlockDrag_(); + } + + // Call drag enter/exit/over after wouldDeleteBlock is called in + // InsertionMarkerManager.update. + if (this.dragTarget_ !== oldDragTarget) { + oldDragTarget && oldDragTarget.onDragExit(this.draggingBlock_); + this.dragTarget_ && this.dragTarget_.onDragEnter(this.draggingBlock_); + } + this.dragTarget_ && this.dragTarget_.onDragOver(this.draggingBlock_); + } /** - * Whether the block would be deleted if dropped immediately. - * @type {boolean} + * Finish a block drag and put the block back on the workspace. + * @param {!Event} e The mouseup/touchend event. + * @param {!Coordinate} currentDragDeltaXY How far the pointer has + * moved from the position at the start of the drag, in pixel units. + * @public + */ + endDrag(e, currentDragDeltaXY) { + // Make sure internal state is fresh. + this.drag(e, currentDragDeltaXY); + this.dragIconData_ = []; + this.fireDragEndEvent_(); + + dom.stopTextWidthCache(); + + blockAnimation.disconnectUiStop(); + + const preventMove = !!this.dragTarget_ && + this.dragTarget_.shouldPreventMove(this.draggingBlock_); + /** @type {Coordinate} */ + let newLoc; + /** @type {Coordinate} */ + let delta; + if (preventMove) { + newLoc = this.startXY_; + } else { + const newValues = this.getNewLocationAfterDrag_(currentDragDeltaXY); + delta = newValues.delta; + newLoc = newValues.newLocation; + } + this.draggingBlock_.moveOffDragSurface(newLoc); + + if (this.dragTarget_) { + this.dragTarget_.onDrop(this.draggingBlock_); + } + + const deleted = this.maybeDeleteBlock_(); + if (!deleted) { + // These are expensive and don't need to be done if we're deleting. + this.draggingBlock_.setDragging(false); + if (delta) { // !preventMove + this.updateBlockAfterMove_(delta); + } else { + // Blocks dragged directly from a flyout may need to be bumped into + // bounds. + bumpObjects.bumpIntoBounds( + this.draggingBlock_.workspace, + this.workspace_.getMetricsManager().getScrollMetrics(true), + this.draggingBlock_); + } + } + this.workspace_.setResizesEnabled(true); + + eventUtils.setGroup(false); + } + + /** + * Calculates the drag delta and new location values after a block is dragged. + * @param {!Coordinate} currentDragDeltaXY How far the pointer has + * moved from the start of the drag, in pixel units. + * @return {{delta: !Coordinate, newLocation: + * !Coordinate}} New location after drag. delta is in + * workspace units. newLocation is the new coordinate where the block + * should end up. * @protected */ - this.wouldDeleteBlock_ = false; + getNewLocationAfterDrag_(currentDragDeltaXY) { + const newValues = {}; + newValues.delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY); + newValues.newLocation = Coordinate.sum(this.startXY_, newValues.delta); + return newValues; + } /** - * The location of the top left corner of the dragging block at the beginning - * of the drag in workspace coordinates. - * @type {!Coordinate} + * May delete the dragging block, if allowed. If `this.wouldDeleteBlock_` is + * not true, the block will not be deleted. This should be called at the end + * of a block drag. + * @return {boolean} True if the block was deleted. * @protected */ - this.startXY_ = this.draggingBlock_.getRelativeToSurfaceXY(); + maybeDeleteBlock_() { + if (this.wouldDeleteBlock_) { + // Fire a move event, so we know where to go back to for an undo. + this.fireMoveEvent_(); + this.draggingBlock_.dispose(false, true); + common.draggingConnections.length = 0; + return true; + } + return false; + } /** - * A list of all of the icons (comment, warning, and mutator) that are - * on this block and its descendants. Moving an icon moves the bubble that - * extends from it if that bubble is open. - * @type {Array} + * Updates the necessary information to place a block at a certain location. + * @param {!Coordinate} delta The change in location from where + * the block started the drag to where it ended the drag. * @protected */ - this.dragIconData_ = initIconData(block); -}; + updateBlockAfterMove_(delta) { + this.draggingBlock_.moveConnections(delta.x, delta.y); + this.fireMoveEvent_(); + if (this.draggedConnectionManager_.wouldConnectBlock()) { + // Applying connections also rerenders the relevant blocks. + this.draggedConnectionManager_.applyConnections(); + } else { + this.draggingBlock_.render(); + } + this.draggingBlock_.scheduleSnapAndBump(); + } -/** - * Sever all links from this object. - * @package - */ -BlockDragger.prototype.dispose = function() { - this.dragIconData_.length = 0; + /** + * Fire a UI event at the end of a block drag. + * @protected + */ + fireDragEndEvent_() { + const event = new (eventUtils.get(eventUtils.BLOCK_DRAG))( + this.draggingBlock_, false, this.draggingBlock_.getDescendants(false)); + eventUtils.fire(event); + } - if (this.draggedConnectionManager_) { - this.draggedConnectionManager_.dispose(); + /** + * Adds or removes the style of the cursor for the toolbox. + * This is what changes the cursor to display an x when a deletable block is + * held over the toolbox. + * @param {boolean} isEnd True if we are at the end of a drag, false + * otherwise. + * @protected + */ + updateToolboxStyle_(isEnd) { + const toolbox = this.workspace_.getToolbox(); + + if (toolbox) { + const style = this.draggingBlock_.isDeletable() ? 'blocklyToolboxDelete' : + 'blocklyToolboxGrab'; + + if (isEnd && typeof toolbox.removeStyle === 'function') { + toolbox.removeStyle(style); + } else if (!isEnd && typeof toolbox.addStyle === 'function') { + toolbox.addStyle(style); + } + } + } + + /** + * Fire a move event at the end of a block drag. + * @protected + */ + fireMoveEvent_() { + const event = /** @type {!BlockMove} */ + (new (eventUtils.get(eventUtils.BLOCK_MOVE))(this.draggingBlock_)); + event.oldCoordinate = this.startXY_; + event.recordNew(); + eventUtils.fire(event); + } + + /** + * Update the cursor (and possibly the trash can lid) to reflect whether the + * dragging block would be deleted if released immediately. + * @protected + */ + updateCursorDuringBlockDrag_() { + this.draggingBlock_.setDeleteStyle(this.wouldDeleteBlock_); + } + + /** + * 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 {!Coordinate} pixelCoord A coordinate with x and y + * values in CSS pixel units. + * @return {!Coordinate} The input coordinate divided by the + * workspace scale. + * @protected + */ + pixelsToWorkspaceUnits_(pixelCoord) { + const result = new 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. + const mainScale = this.workspace_.options.parentWorkspace.scale; + result.scale(1 / mainScale); + } + return result; + } + + /** + * Move all of the icons connected to this drag. + * @param {!Coordinate} dxy How far to move the icons from their + * original positions, in workspace units. + * @protected + */ + dragIcons_(dxy) { + // Moving icons moves their associated bubbles. + for (let i = 0; i < this.dragIconData_.length; i++) { + const data = this.dragIconData_[i]; + data.icon.setIconLocation(Coordinate.sum(data.location, dxy)); + } + } + + /** + * Get a list of the insertion markers that currently exist. Drags have 0, 1, + * or 2 insertion markers. + * @return {!Array} A possibly empty list of insertion + * marker blocks. + * @public + */ + getInsertionMarkers() { + // No insertion markers with the old style of dragged connection managers. + if (this.draggedConnectionManager_ && + this.draggedConnectionManager_.getInsertionMarkers) { + return this.draggedConnectionManager_.getInsertionMarkers(); + } + return []; } }; @@ -123,7 +460,8 @@ BlockDragger.prototype.dispose = function() { const initIconData = function(block) { // Build a list of icons that need to be moved and where they started. const dragIconData = []; - const descendants = block.getDescendants(false); + const descendants = + /** @type {!Array} */ (block.getDescendants(false)); for (let i = 0, descendant; (descendant = descendants[i]); i++) { const icons = descendant.getIcons(); @@ -141,340 +479,6 @@ const initIconData = function(block) { return dragIconData; }; -/** - * Start dragging a block. This includes moving it to the drag surface. - * @param {!Coordinate} currentDragDeltaXY How far the pointer has - * moved from the position at mouse down, in pixel units. - * @param {boolean} healStack Whether or not to heal the stack after - * disconnecting. - * @public - */ -BlockDragger.prototype.startDrag = function(currentDragDeltaXY, healStack) { - if (!eventUtils.getGroup()) { - eventUtils.setGroup(true); - } - this.fireDragStartEvent_(); - - // Mutators don't have the same type of z-ordering as the normal workspace - // during a drag. They have to rely on the order of the blocks in the SVG. - // For performance reasons that usually happens at the end of a drag, - // but do it at the beginning for mutators. - if (this.workspace_.isMutator) { - this.draggingBlock_.bringToFront(); - } - - // During a drag there may be a lot of rerenders, but not field changes. - // Turn the cache on so we don't do spurious remeasures during the drag. - dom.startTextWidthCache(); - this.workspace_.setResizesEnabled(false); - blockAnimation.disconnectUiStop(); - - if (this.shouldDisconnect_(healStack)) { - this.disconnectBlock_(healStack, currentDragDeltaXY); - } - this.draggingBlock_.setDragging(true); - // For future consideration: we may be able to put moveToDragSurface inside - // the block dragger, which would also let the block not track the block drag - // surface. - this.draggingBlock_.moveToDragSurface(); -}; - -/** - * Whether or not we should disconnect the block when a drag is started. - * @param {boolean} healStack Whether or not to heal the stack after - * disconnecting. - * @return {boolean} True to disconnect the block, false otherwise. - * @protected - */ -BlockDragger.prototype.shouldDisconnect_ = function(healStack) { - return !!( - this.draggingBlock_.getParent() || - (healStack && this.draggingBlock_.nextConnection && - this.draggingBlock_.nextConnection.targetBlock())); -}; - -/** - * Disconnects the block and moves it to a new location. - * @param {boolean} healStack Whether or not to heal the stack after - * disconnecting. - * @param {!Coordinate} currentDragDeltaXY How far the pointer has - * moved from the position at mouse down, in pixel units. - * @protected - */ -BlockDragger.prototype.disconnectBlock_ = function( - healStack, currentDragDeltaXY) { - this.draggingBlock_.unplug(healStack); - const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY); - const newLoc = Coordinate.sum(this.startXY_, delta); - - this.draggingBlock_.translate(newLoc.x, newLoc.y); - blockAnimation.disconnectUiEffect(this.draggingBlock_); - this.draggedConnectionManager_.updateAvailableConnections(); -}; - -/** - * Fire a UI event at the start of a block drag. - * @protected - */ -BlockDragger.prototype.fireDragStartEvent_ = function() { - const event = new (eventUtils.get(eventUtils.BLOCK_DRAG))( - this.draggingBlock_, true, this.draggingBlock_.getDescendants(false)); - eventUtils.fire(event); -}; - -/** - * Execute a step of block dragging, based on the given event. Update the - * display accordingly. - * @param {!Event} e The most recent move event. - * @param {!Coordinate} currentDragDeltaXY How far the pointer has - * moved from the position at the start of the drag, in pixel units. - * @public - */ -BlockDragger.prototype.drag = function(e, currentDragDeltaXY) { - const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY); - const newLoc = Coordinate.sum(this.startXY_, delta); - this.draggingBlock_.moveDuringDrag(newLoc); - this.dragIcons_(delta); - - const oldDragTarget = this.dragTarget_; - this.dragTarget_ = this.workspace_.getDragTarget(e); - - this.draggedConnectionManager_.update(delta, this.dragTarget_); - const oldWouldDeleteBlock = this.wouldDeleteBlock_; - this.wouldDeleteBlock_ = this.draggedConnectionManager_.wouldDeleteBlock(); - if (oldWouldDeleteBlock !== this.wouldDeleteBlock_) { - // Prevent unnecessary add/remove class calls. - this.updateCursorDuringBlockDrag_(); - } - - // Call drag enter/exit/over after wouldDeleteBlock is called in - // InsertionMarkerManager.update. - if (this.dragTarget_ !== oldDragTarget) { - oldDragTarget && oldDragTarget.onDragExit(this.draggingBlock_); - this.dragTarget_ && this.dragTarget_.onDragEnter(this.draggingBlock_); - } - this.dragTarget_ && this.dragTarget_.onDragOver(this.draggingBlock_); -}; - -/** - * Finish a block drag and put the block back on the workspace. - * @param {!Event} e The mouseup/touchend event. - * @param {!Coordinate} currentDragDeltaXY How far the pointer has - * moved from the position at the start of the drag, in pixel units. - * @public - */ -BlockDragger.prototype.endDrag = function(e, currentDragDeltaXY) { - // Make sure internal state is fresh. - this.drag(e, currentDragDeltaXY); - this.dragIconData_ = []; - this.fireDragEndEvent_(); - - dom.stopTextWidthCache(); - - blockAnimation.disconnectUiStop(); - - const preventMove = !!this.dragTarget_ && - this.dragTarget_.shouldPreventMove(this.draggingBlock_); - /** @type {Coordinate} */ - let newLoc; - /** @type {Coordinate} */ - let delta; - if (preventMove) { - newLoc = this.startXY_; - } else { - const newValues = this.getNewLocationAfterDrag_(currentDragDeltaXY); - delta = newValues.delta; - newLoc = newValues.newLocation; - } - this.draggingBlock_.moveOffDragSurface(newLoc); - - if (this.dragTarget_) { - this.dragTarget_.onDrop(this.draggingBlock_); - } - - const deleted = this.maybeDeleteBlock_(); - if (!deleted) { - // These are expensive and don't need to be done if we're deleting. - this.draggingBlock_.setDragging(false); - if (delta) { // !preventMove - this.updateBlockAfterMove_(delta); - } else { - // Blocks dragged directly from a flyout may need to be bumped into - // bounds. - bumpObjects.bumpIntoBounds( - this.draggingBlock_.workspace, - this.workspace_.getMetricsManager().getScrollMetrics(true), - this.draggingBlock_); - } - } - this.workspace_.setResizesEnabled(true); - - eventUtils.setGroup(false); -}; - -/** - * Calculates the drag delta and new location values after a block is dragged. - * @param {!Coordinate} currentDragDeltaXY How far the pointer has - * moved from the start of the drag, in pixel units. - * @return {{delta: !Coordinate, newLocation: - * !Coordinate}} New location after drag. delta is in - * workspace units. newLocation is the new coordinate where the block should - * end up. - * @protected - */ -BlockDragger.prototype.getNewLocationAfterDrag_ = function(currentDragDeltaXY) { - const newValues = {}; - newValues.delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY); - newValues.newLocation = Coordinate.sum(this.startXY_, newValues.delta); - return newValues; -}; - -/** - * May delete the dragging block, if allowed. If `this.wouldDeleteBlock_` is not - * true, the block will not be deleted. This should be called at the end of a - * block drag. - * @return {boolean} True if the block was deleted. - * @protected - */ -BlockDragger.prototype.maybeDeleteBlock_ = function() { - if (this.wouldDeleteBlock_) { - // Fire a move event, so we know where to go back to for an undo. - this.fireMoveEvent_(); - this.draggingBlock_.dispose(false, true); - common.draggingConnections.length = 0; - return true; - } - return false; -}; - -/** - * Updates the necessary information to place a block at a certain location. - * @param {!Coordinate} delta The change in location from where - * the block started the drag to where it ended the drag. - * @protected - */ -BlockDragger.prototype.updateBlockAfterMove_ = function(delta) { - this.draggingBlock_.moveConnections(delta.x, delta.y); - this.fireMoveEvent_(); - if (this.draggedConnectionManager_.wouldConnectBlock()) { - // Applying connections also rerenders the relevant blocks. - this.draggedConnectionManager_.applyConnections(); - } else { - this.draggingBlock_.render(); - } - this.draggingBlock_.scheduleSnapAndBump(); -}; - -/** - * Fire a UI event at the end of a block drag. - * @protected - */ -BlockDragger.prototype.fireDragEndEvent_ = function() { - const event = new (eventUtils.get(eventUtils.BLOCK_DRAG))( - this.draggingBlock_, false, this.draggingBlock_.getDescendants(false)); - eventUtils.fire(event); -}; - -/** - * Adds or removes the style of the cursor for the toolbox. - * This is what changes the cursor to display an x when a deletable block is - * held over the toolbox. - * @param {boolean} isEnd True if we are at the end of a drag, false otherwise. - * @protected - */ -BlockDragger.prototype.updateToolboxStyle_ = function(isEnd) { - const toolbox = this.workspace_.getToolbox(); - - if (toolbox) { - const style = this.draggingBlock_.isDeletable() ? 'blocklyToolboxDelete' : - 'blocklyToolboxGrab'; - - if (isEnd && typeof toolbox.removeStyle === 'function') { - toolbox.removeStyle(style); - } else if (!isEnd && typeof toolbox.addStyle === 'function') { - toolbox.addStyle(style); - } - } -}; - - -/** - * Fire a move event at the end of a block drag. - * @protected - */ -BlockDragger.prototype.fireMoveEvent_ = function() { - const event = - new (eventUtils.get(eventUtils.BLOCK_MOVE))(this.draggingBlock_); - event.oldCoordinate = this.startXY_; - event.recordNew(); - eventUtils.fire(event); -}; - -/** - * Update the cursor (and possibly the trash can lid) to reflect whether the - * dragging block would be deleted if released immediately. - * @protected - */ -BlockDragger.prototype.updateCursorDuringBlockDrag_ = function() { - this.draggingBlock_.setDeleteStyle(this.wouldDeleteBlock_); -}; - -/** - * 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 {!Coordinate} pixelCoord A coordinate with x and y - * values in CSS pixel units. - * @return {!Coordinate} The input coordinate divided by the - * workspace scale. - * @protected - */ -BlockDragger.prototype.pixelsToWorkspaceUnits_ = function(pixelCoord) { - const result = new 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. - const mainScale = this.workspace_.options.parentWorkspace.scale; - result.scale(1 / mainScale); - } - return result; -}; - -/** - * Move all of the icons connected to this drag. - * @param {!Coordinate} dxy How far to move the icons from their - * original positions, in workspace units. - * @protected - */ -BlockDragger.prototype.dragIcons_ = function(dxy) { - // Moving icons moves their associated bubbles. - for (let i = 0; i < this.dragIconData_.length; i++) { - const data = this.dragIconData_[i]; - data.icon.setIconLocation(Coordinate.sum(data.location, dxy)); - } -}; - -/** - * Get a list of the insertion markers that currently exist. Drags have 0, 1, - * or 2 insertion markers. - * @return {!Array} A possibly empty list of insertion - * marker blocks. - * @public - */ -BlockDragger.prototype.getInsertionMarkers = function() { - // No insertion markers with the old style of dragged connection managers. - if (this.draggedConnectionManager_ && - this.draggedConnectionManager_.getInsertionMarkers) { - return this.draggedConnectionManager_.getInsertionMarkers(); - } - return []; -}; - registry.register(registry.Type.BLOCK_DRAGGER, registry.DEFAULT, BlockDragger); exports.BlockDragger = BlockDragger; diff --git a/core/block_svg.js b/core/block_svg.js index b3fcf2fc4..5b66d7ba9 100644 --- a/core/block_svg.js +++ b/core/block_svg.js @@ -25,13 +25,15 @@ const constants = goog.require('Blockly.constants'); const dom = goog.require('Blockly.utils.dom'); const eventUtils = goog.require('Blockly.Events.utils'); const internalConstants = goog.require('Blockly.internalConstants'); -const object = goog.require('Blockly.utils.object'); const svgMath = goog.require('Blockly.utils.svgMath'); const userAgent = goog.require('Blockly.utils.userAgent'); const {ASTNode} = goog.require('Blockly.ASTNode'); const {Block} = goog.require('Blockly.Block'); /* eslint-disable-next-line no-unused-vars */ +const {BlockMove} = goog.requireType('Blockly.Events.BlockMove'); +/* eslint-disable-next-line no-unused-vars */ const {Comment} = goog.requireType('Blockly.Comment'); +const {config} = goog.require('Blockly.config'); const {ConnectionType} = goog.require('Blockly.ConnectionType'); /* eslint-disable-next-line no-unused-vars */ const {Connection} = goog.requireType('Blockly.Connection'); @@ -81,107 +83,1797 @@ goog.require('Blockly.Touch'); /** * Class for a block's SVG representation. * Not normally called directly, workspace.newBlock() is preferred. - * @param {!WorkspaceSvg} workspace The block's workspace. - * @param {?string} prototypeName Name of the language object containing - * type-specific functions for this block. - * @param {string=} opt_id Optional ID. Use this ID if provided, otherwise - * create a new ID. * @extends {Block} * @implements {IASTNodeLocationSvg} * @implements {IBoundedElement} * @implements {ICopyable} * @implements {IDraggable} - * @constructor * @alias Blockly.BlockSvg */ -const BlockSvg = function(workspace, prototypeName, opt_id) { - // Create core elements for the block. +class BlockSvg extends Block { /** - * @type {!SVGGElement} - * @private + * @param {!WorkspaceSvg} workspace The block's workspace. + * @param {string} prototypeName Name of the language object containing + * type-specific functions for this block. + * @param {string=} opt_id Optional ID. Use this ID if provided, otherwise + * create a new ID. */ - this.svgGroup_ = dom.createSvgElement(Svg.G, {}, null); - this.svgGroup_.translate_ = ''; + constructor(workspace, prototypeName, opt_id) { + super(workspace, prototypeName, opt_id); + + /** + * An optional method called when a mutator dialog is first opened. + * This function must create and initialize a top-level block for the + * mutator dialog, and return it. This function should also populate this + * top-level block with any sub-blocks which are appropriate. This method + * must also be coupled with defining a `compose` method for the default + * mutation dialog button and UI to appear. + * @type {undefined|?function(WorkspaceSvg):!BlockSvg} + */ + this.decompose = this.decompose; + + /** + * An optional method called when a mutator dialog saves its content. + * This function is called to modify the original block according to new + * settings. This method must also be coupled with defining a `decompose` + * method for the default mutation dialog button and UI to appear. + * @type {undefined|?function(!BlockSvg)} + */ + this.compose = this.compose; + + /** + * An optional method called by the default mutator UI which gives the block + * a chance to save information about what child blocks are connected to + * what mutated connections. + * @type {undefined|?function(!BlockSvg)} + */ + this.saveConnections = this.saveConnections; + + /** + * An optional method for defining custom block context menu items. + * @type {undefined|?function(!Array)} + */ + this.customContextMenu = this.customContextMenu; + + /** + * An property used internally to reference the block's rendering debugger. + * @type {?BlockRenderingDebug} + * @package + */ + this.renderingDebugger = null; + + /** + * Height of this block, not including any statement blocks above or below. + * Height is in workspace units. + * @type {number} + */ + this.height = 0; + + /** + * Width of this block, including any connected value blocks. + * Width is in workspace units. + * @type {number} + */ + this.width = 0; + + /** + * Map from IDs for warnings text to PIDs of functions to apply them. + * Used to be able to maintain multiple warnings. + * @type {Object} + * @private + */ + this.warningTextDb_ = null; + + /** + * Block's mutator icon (if any). + * @type {?Mutator} + */ + this.mutator = null; + + /** + * Block's comment icon (if any). + * @type {?Comment} + * @deprecated August 2019. Use getCommentIcon instead. + */ + this.comment = null; + + /** + * Block's comment icon (if any). + * @type {?Comment} + * @private + */ + this.commentIcon_ = null; + + /** + * Block's warning icon (if any). + * @type {?Warning} + */ + this.warning = null; + + // Create core elements for the block. + /** + * @type {!SVGGElement} + * @private + */ + this.svgGroup_ = dom.createSvgElement(Svg.G, {}, null); + this.svgGroup_.translate_ = ''; + + /** + * A block style object. + * @type {!Theme.BlockStyle} + */ + this.style = workspace.getRenderer().getConstants().getBlockStyle(null); + + /** + * The renderer's path object. + * @type {IPathObject} + * @package + */ + this.pathObject = + workspace.getRenderer().makePathObject(this.svgGroup_, this.style); + + /** @type {boolean} */ + this.rendered = false; + /** + * Is this block currently rendering? Used to stop recursive render calls + * from actually triggering a re-render. + * @type {boolean} + * @private + */ + this.renderIsInProgress_ = false; + + /** + * Whether mousedown events have been bound yet. + * @type {boolean} + * @private + */ + this.eventsInit_ = false; + + /** @type {!WorkspaceSvg} */ + this.workspace; + /** @type {RenderedConnection} */ + this.outputConnection; + /** @type {RenderedConnection} */ + this.nextConnection; + /** @type {RenderedConnection} */ + this.previousConnection; + + /** + * Whether to move the block to the drag surface when it is dragged. + * True if it should move, false if it should be translated directly. + * @type {boolean} + * @private + */ + this.useDragSurface_ = + svgMath.is3dSupported() && !!workspace.getBlockDragSurface(); + + const svgPath = this.pathObject.svgPath; + svgPath.tooltip = this; + Tooltip.bindMouseEvents(svgPath); + + // Expose this block's ID on its top-level SVG group. + if (this.svgGroup_.dataset) { + this.svgGroup_.dataset['id'] = this.id; + } else if (userAgent.IE) { + // SVGElement.dataset is not available on IE11, but data-* properties + // can be set with setAttribute(). + this.svgGroup_.setAttribute('data-id', this.id); + } + + this.doInit_(); + } /** - * A block style object. - * @type {!Theme.BlockStyle} + * Create and initialize the SVG representation of the block. + * May be called more than once. */ - this.style = workspace.getRenderer().getConstants().getBlockStyle(null); + initSvg() { + if (!this.workspace.rendered) { + throw TypeError('Workspace is headless.'); + } + for (let i = 0, input; (input = this.inputList[i]); i++) { + input.init(); + } + const icons = this.getIcons(); + for (let i = 0; i < icons.length; i++) { + icons[i].createIcon(); + } + this.applyColour(); + this.pathObject.updateMovable(this.isMovable()); + const svg = this.getSvgRoot(); + if (!this.workspace.options.readOnly && !this.eventsInit_ && svg) { + browserEvents.conditionalBind(svg, 'mousedown', this, this.onMouseDown_); + } + this.eventsInit_ = true; + + if (!svg.parentNode) { + this.workspace.getCanvas().appendChild(svg); + } + } /** - * The renderer's path object. - * @type {IPathObject} + * Get the secondary colour of a block. + * @return {?string} #RRGGBB string. + */ + getColourSecondary() { + return this.style.colourSecondary; + } + + /** + * Get the tertiary colour of a block. + * @return {?string} #RRGGBB string. + */ + getColourTertiary() { + return this.style.colourTertiary; + } + + /** + * Selects this block. Highlights the block visually and fires a select event + * if the block is not already selected. + */ + select() { + if (this.isShadow() && this.getParent()) { + // Shadow blocks should not be selected. + this.getParent().select(); + return; + } + if (common.getSelected() === this) { + return; + } + let oldId = null; + if (common.getSelected()) { + oldId = common.getSelected().id; + // Unselect any previously selected block. + eventUtils.disable(); + try { + common.getSelected().unselect(); + } finally { + eventUtils.enable(); + } + } + const event = new (eventUtils.get(eventUtils.SELECTED))( + oldId, this.id, this.workspace.id); + eventUtils.fire(event); + common.setSelected(this); + this.addSelect(); + } + + /** + * Unselects this block. Unhighlights the block and fires a select (false) + * event if the block is currently selected. + */ + unselect() { + if (common.getSelected() !== this) { + return; + } + const event = new (eventUtils.get(eventUtils.SELECTED))( + this.id, null, this.workspace.id); + event.workspaceId = this.workspace.id; + eventUtils.fire(event); + common.setSelected(null); + this.removeSelect(); + } + + /** + * Returns a list of mutator, comment, and warning icons. + * @return {!Array} List of icons. + */ + getIcons() { + const icons = []; + if (this.mutator) { + icons.push(this.mutator); + } + if (this.commentIcon_) { + icons.push(this.commentIcon_); + } + if (this.warning) { + icons.push(this.warning); + } + return icons; + } + + /** + * Sets the parent of this block to be a new block or null. + * @param {?Block} newParent New parent block. + * @package + * @override + */ + setParent(newParent) { + const oldParent = this.parentBlock_; + if (newParent === oldParent) { + return; + } + + dom.startTextWidthCache(); + super.setParent(newParent); + dom.stopTextWidthCache(); + + const svgRoot = this.getSvgRoot(); + + // Bail early if workspace is clearing, or we aren't rendered. + // We won't need to reattach ourselves anywhere. + if (this.workspace.isClearing || !svgRoot) { + return; + } + + const oldXY = this.getRelativeToSurfaceXY(); + if (newParent) { + (/** @type {!BlockSvg} */ (newParent)).getSvgRoot().appendChild(svgRoot); + const newXY = this.getRelativeToSurfaceXY(); + // Move the connections to match the child's new position. + this.moveConnections(newXY.x - oldXY.x, newXY.y - oldXY.y); + } else if (oldParent) { + // If we are losing a parent, we want to move our DOM element to the + // root of the workspace. + this.workspace.getCanvas().appendChild(svgRoot); + this.translate(oldXY.x, oldXY.y); + } + + this.applyColour(); + } + + /** + * Return the coordinates of the top-left corner of this block relative to the + * drawing surface's origin (0,0), in workspace units. + * If the block is on the workspace, (0, 0) is the origin of the workspace + * coordinate system. + * This does not change with workspace scale. + * @return {!Coordinate} Object with .x and .y properties in + * workspace coordinates. + */ + getRelativeToSurfaceXY() { + let x = 0; + let y = 0; + + const dragSurfaceGroup = this.useDragSurface_ ? + this.workspace.getBlockDragSurface().getGroup() : + null; + + let element = this.getSvgRoot(); + if (element) { + do { + // Loop through this block and every parent. + const xy = svgMath.getRelativeXY(element); + x += xy.x; + y += xy.y; + // If this element is the current element on the drag surface, include + // the translation of the drag surface itself. + if (this.useDragSurface_ && + this.workspace.getBlockDragSurface().getCurrentBlock() === + element) { + const surfaceTranslation = + this.workspace.getBlockDragSurface().getSurfaceTranslation(); + x += surfaceTranslation.x; + y += surfaceTranslation.y; + } + element = /** @type {!SVGElement} */ (element.parentNode); + } while (element && element !== this.workspace.getCanvas() && + element !== dragSurfaceGroup); + } + return new Coordinate(x, y); + } + + /** + * Move a block by a relative offset. + * @param {number} dx Horizontal offset in workspace units. + * @param {number} dy Vertical offset in workspace units. + */ + moveBy(dx, dy) { + if (this.parentBlock_) { + throw Error('Block has parent.'); + } + const eventsEnabled = eventUtils.isEnabled(); + let event; + if (eventsEnabled) { + event = /** @type {!BlockMove} */ + (new (eventUtils.get(eventUtils.BLOCK_MOVE))(this)); + } + const xy = this.getRelativeToSurfaceXY(); + this.translate(xy.x + dx, xy.y + dy); + this.moveConnections(dx, dy); + if (eventsEnabled) { + event.recordNew(); + eventUtils.fire(event); + } + this.workspace.resizeContents(); + } + + /** + * Transforms a block by setting the translation on the transform attribute + * of the block's SVG. + * @param {number} x The x coordinate of the translation in workspace units. + * @param {number} y The y coordinate of the translation in workspace units. + */ + translate(x, y) { + this.getSvgRoot().setAttribute( + 'transform', 'translate(' + x + ',' + y + ')'); + } + + /** + * Move this block to its workspace's drag surface, accounting for + * positioning. Generally should be called at the same time as + * setDragging_(true). Does nothing if useDragSurface_ is false. * @package */ - this.pathObject = - workspace.getRenderer().makePathObject(this.svgGroup_, this.style); - - /** @type {boolean} */ - this.rendered = false; - /** - * Is this block currently rendering? Used to stop recursive render calls - * from actually triggering a re-render. - * @type {boolean} - * @private - */ - this.renderIsInProgress_ = false; - - - /** @type {!WorkspaceSvg} */ - this.workspace = workspace; - - /** @type {RenderedConnection} */ - this.outputConnection = null; - /** @type {RenderedConnection} */ - this.nextConnection = null; - /** @type {RenderedConnection} */ - this.previousConnection = null; - - /** - * Whether to move the block to the drag surface when it is dragged. - * True if it should move, false if it should be translated directly. - * @type {boolean} - * @private - */ - this.useDragSurface_ = - svgMath.is3dSupported() && !!workspace.getBlockDragSurface(); - - const svgPath = this.pathObject.svgPath; - svgPath.tooltip = this; - Tooltip.bindMouseEvents(svgPath); - BlockSvg.superClass_.constructor.call(this, workspace, prototypeName, opt_id); - - // Expose this block's ID on its top-level SVG group. - if (this.svgGroup_.dataset) { - this.svgGroup_.dataset['id'] = this.id; - } else if (userAgent.IE) { - // SVGElement.dataset is not available on IE11, but data-* properties - // can be set with setAttribute(). - this.svgGroup_.setAttribute('data-id', this.id); + moveToDragSurface() { + if (!this.useDragSurface_) { + return; + } + // The translation for drag surface blocks, + // is equal to the current relative-to-surface position, + // to keep the position in sync as it move on/off the surface. + // This is in workspace coordinates. + const xy = this.getRelativeToSurfaceXY(); + this.clearTransformAttributes_(); + this.workspace.getBlockDragSurface().translateSurface(xy.x, xy.y); + // Execute the move on the top-level SVG component + const svg = this.getSvgRoot(); + if (svg) { + this.workspace.getBlockDragSurface().setBlocksAndShow(svg); + } } -}; -object.inherits(BlockSvg, Block); -/** - * Height of this block, not including any statement blocks above or below. - * Height is in workspace units. - */ -BlockSvg.prototype.height = 0; + /** + * Move a block to a position. + * @param {Coordinate} xy The position to move to in workspace units. + */ + moveTo(xy) { + const curXY = this.getRelativeToSurfaceXY(); + this.moveBy(xy.x - curXY.x, xy.y - curXY.y); + } -/** - * Width of this block, including any connected value blocks. - * Width is in workspace units. - */ -BlockSvg.prototype.width = 0; + /** + * Move this block back to the workspace block canvas. + * Generally should be called at the same time as setDragging_(false). + * Does nothing if useDragSurface_ is false. + * @param {!Coordinate} newXY The position the block should take on + * on the workspace canvas, in workspace coordinates. + * @package + */ + moveOffDragSurface(newXY) { + if (!this.useDragSurface_) { + return; + } + // Translate to current position, turning off 3d. + this.translate(newXY.x, newXY.y); + this.workspace.getBlockDragSurface().clearAndHide( + this.workspace.getCanvas()); + } -/** - * Map from IDs for warnings text to PIDs of functions to apply them. - * Used to be able to maintain multiple warnings. - * @type {Object} - * @private - */ -BlockSvg.prototype.warningTextDb_ = null; + /** + * Move this block during a drag, taking into account whether we are using a + * drag surface to translate blocks. + * This block must be a top-level block. + * @param {!Coordinate} newLoc The location to translate to, in + * workspace coordinates. + * @package + */ + moveDuringDrag(newLoc) { + if (this.useDragSurface_) { + this.workspace.getBlockDragSurface().translateSurface(newLoc.x, newLoc.y); + } else { + this.svgGroup_.translate_ = + 'translate(' + newLoc.x + ',' + newLoc.y + ')'; + this.svgGroup_.setAttribute( + 'transform', this.svgGroup_.translate_ + this.svgGroup_.skew_); + } + } + + /** + * Clear the block of transform="..." attributes. + * Used when the block is switching from 3d to 2d transform or vice versa. + * @private + */ + clearTransformAttributes_() { + this.getSvgRoot().removeAttribute('transform'); + } + + /** + * Snap this block to the nearest grid point. + */ + snapToGrid() { + if (!this.workspace) { + return; // Deleted block. + } + if (this.workspace.isDragging()) { + return; // Don't bump blocks during a drag. + } + if (this.getParent()) { + return; // Only snap top-level blocks. + } + if (this.isInFlyout) { + return; // Don't move blocks around in a flyout. + } + const grid = this.workspace.getGrid(); + if (!grid || !grid.shouldSnap()) { + return; // Config says no snapping. + } + const spacing = grid.getSpacing(); + const half = spacing / 2; + const xy = this.getRelativeToSurfaceXY(); + const dx = + Math.round(Math.round((xy.x - half) / spacing) * spacing + half - xy.x); + const dy = + Math.round(Math.round((xy.y - half) / spacing) * spacing + half - xy.y); + if (dx || dy) { + this.moveBy(dx, dy); + } + } + + /** + * Returns the coordinates of a bounding box describing the dimensions of this + * block and any blocks stacked below it. + * Coordinate system: workspace coordinates. + * @return {!Rect} Object with coordinates of the bounding box. + */ + getBoundingRectangle() { + const blockXY = this.getRelativeToSurfaceXY(); + const blockBounds = this.getHeightWidth(); + let left; + let right; + if (this.RTL) { + left = blockXY.x - blockBounds.width; + right = blockXY.x; + } else { + left = blockXY.x; + right = blockXY.x + blockBounds.width; + } + return new Rect(blockXY.y, blockXY.y + blockBounds.height, left, right); + } + + /** + * Notify every input on this block to mark its fields as dirty. + * A dirty field is a field that needs to be re-rendered. + */ + markDirty() { + this.pathObject.constants = (/** @type {!WorkspaceSvg} */ (this.workspace)) + .getRenderer() + .getConstants(); + for (let i = 0, input; (input = this.inputList[i]); i++) { + input.markDirty(); + } + } + + /** + * Set whether the block is collapsed or not. + * @param {boolean} collapsed True if collapsed. + */ + setCollapsed(collapsed) { + if (this.collapsed_ === collapsed) { + return; + } + super.setCollapsed(collapsed); + if (!collapsed) { + this.updateCollapsed_(); + } else if (this.rendered) { + this.render(); + // Don't bump neighbours. Users like to store collapsed functions together + // and bumping makes them go out of alignment. + } + } + + /** + * Makes sure that when the block is collapsed, it is rendered correctly + * for that state. + * @private + */ + updateCollapsed_() { + const collapsed = this.isCollapsed(); + const collapsedInputName = constants.COLLAPSED_INPUT_NAME; + const collapsedFieldName = constants.COLLAPSED_FIELD_NAME; + + for (let i = 0, input; (input = this.inputList[i]); i++) { + if (input.name !== collapsedInputName) { + input.setVisible(!collapsed); + } + } + + if (!collapsed) { + this.updateDisabled(); + this.removeInput(collapsedInputName); + return; + } + + const icons = this.getIcons(); + for (let i = 0, icon; (icon = icons[i]); i++) { + icon.setVisible(false); + } + + const text = this.toString(internalConstants.COLLAPSE_CHARS); + const field = this.getField(collapsedFieldName); + if (field) { + field.setValue(text); + return; + } + const input = this.getInput(collapsedInputName) || + this.appendDummyInput(collapsedInputName); + input.appendField(new FieldLabel(text), collapsedFieldName); + } + + /** + * Open the next (or previous) FieldTextInput. + * @param {!Field} start Current field. + * @param {boolean} forward If true go forward, otherwise backward. + */ + tab(start, forward) { + const tabCursor = new TabNavigateCursor(); + tabCursor.setCurNode(ASTNode.createFieldNode(start)); + const currentNode = tabCursor.getCurNode(); + + if (forward) { + tabCursor.next(); + } else { + tabCursor.prev(); + } + + const nextNode = tabCursor.getCurNode(); + if (nextNode && nextNode !== currentNode) { + const nextField = /** @type {!Field} */ (nextNode.getLocation()); + nextField.showEditor(); + + // Also move the cursor if we're in keyboard nav mode. + if (this.workspace.keyboardAccessibilityMode) { + this.workspace.getCursor().setCurNode(nextNode); + } + } + } + + /** + * Handle a mouse-down on an SVG block. + * @param {!Event} e Mouse down event or touch start event. + * @private + */ + onMouseDown_(e) { + const gesture = this.workspace && this.workspace.getGesture(e); + if (gesture) { + gesture.handleBlockStart(e, this); + } + } + + /** + * Load the block's help page in a new window. + * @package + */ + showHelp() { + const url = + (typeof this.helpUrl === 'function') ? this.helpUrl() : this.helpUrl; + if (url) { + window.open(url); + } + } + + /** + * Generate the context menu for this block. + * @return {?Array} Context menu options or null if no menu. + * @protected + */ + generateContextMenu() { + if (this.workspace.options.readOnly || !this.contextMenu) { + return null; + } + const menuOptions = ContextMenuRegistry.registry.getContextMenuOptions( + ContextMenuRegistry.ScopeType.BLOCK, {block: this}); + + // Allow the block to add or modify menuOptions. + if (this.customContextMenu) { + this.customContextMenu(menuOptions); + } + + return menuOptions; + } + + /** + * Show the context menu for this block. + * @param {!Event} e Mouse event. + * @package + */ + showContextMenu(e) { + const menuOptions = this.generateContextMenu(); + + if (menuOptions && menuOptions.length) { + ContextMenu.show(e, menuOptions, this.RTL); + ContextMenu.setCurrentBlock(this); + } + } + + /** + * Move the connections for this block and all blocks attached under it. + * Also update any attached bubbles. + * @param {number} dx Horizontal offset from current location, in workspace + * units. + * @param {number} dy Vertical offset from current location, in workspace + * units. + * @package + */ + moveConnections(dx, dy) { + if (!this.rendered) { + // Rendering is required to lay out the blocks. + // This is probably an invisible block attached to a collapsed block. + return; + } + const myConnections = this.getConnections_(false); + for (let i = 0; i < myConnections.length; i++) { + myConnections[i].moveBy(dx, dy); + } + const icons = this.getIcons(); + for (let i = 0; i < icons.length; i++) { + icons[i].computeIconLocation(); + } + + // Recurse through all blocks attached under this one. + for (let i = 0; i < this.childBlocks_.length; i++) { + (/** @type {!BlockSvg} */ (this.childBlocks_[i])).moveConnections(dx, dy); + } + } + + /** + * Recursively adds or removes the dragging class to this node and its + * children. + * @param {boolean} adding True if adding, false if removing. + * @package + */ + setDragging(adding) { + if (adding) { + const group = this.getSvgRoot(); + group.translate_ = ''; + group.skew_ = ''; + common.draggingConnections.push(...this.getConnections_(true)); + dom.addClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklyDragging'); + } else { + common.draggingConnections.length = 0; + dom.removeClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklyDragging'); + } + // Recurse through all blocks attached under this one. + for (let i = 0; i < this.childBlocks_.length; i++) { + (/** @type {!BlockSvg} */ (this.childBlocks_[i])).setDragging(adding); + } + } + + /** + * Set whether this block is movable or not. + * @param {boolean} movable True if movable. + */ + setMovable(movable) { + super.setMovable(movable); + this.pathObject.updateMovable(movable); + } + + /** + * Set whether this block is editable or not. + * @param {boolean} editable True if editable. + */ + setEditable(editable) { + super.setEditable(editable); + const icons = this.getIcons(); + for (let i = 0; i < icons.length; i++) { + icons[i].updateEditable(); + } + } + + /** + * Sets whether this block is a shadow block or not. + * @param {boolean} shadow True if a shadow. + * @package + */ + setShadow(shadow) { + super.setShadow(shadow); + this.applyColour(); + } + + /** + * Set whether this block is an insertion marker block or not. + * Once set this cannot be unset. + * @param {boolean} insertionMarker True if an insertion marker. + * @package + */ + setInsertionMarker(insertionMarker) { + if (this.isInsertionMarker_ === insertionMarker) { + return; // No change. + } + this.isInsertionMarker_ = insertionMarker; + if (this.isInsertionMarker_) { + this.setColour( + this.workspace.getRenderer().getConstants().INSERTION_MARKER_COLOUR); + this.pathObject.updateInsertionMarker(true); + } + } + + /** + * Return the root node of the SVG or null if none exists. + * @return {!SVGGElement} The root SVG node (probably a group). + */ + getSvgRoot() { + return this.svgGroup_; + } + + /** + * Dispose of this block. + * @param {boolean=} healStack If true, then try to heal any gap by connecting + * the next statement with the previous statement. Otherwise, dispose of + * all children of this block. + * @param {boolean=} animate If true, show a disposal animation and sound. + * @suppress {checkTypes} + */ + dispose(healStack, animate) { + if (!this.workspace) { + // The block has already been deleted. + return; + } + Tooltip.dispose(); + Tooltip.unbindMouseEvents(this.pathObject.svgPath); + dom.startTextWidthCache(); + // Save the block's workspace temporarily so we can resize the + // contents once the block is disposed. + const blockWorkspace = this.workspace; + // If this block is being dragged, unlink the mouse events. + if (common.getSelected() === this) { + this.unselect(); + this.workspace.cancelCurrentGesture(); + } + // If this block has a context menu open, close it. + if (ContextMenu.getCurrentBlock() === this) { + ContextMenu.hide(); + } + + if (animate && this.rendered) { + this.unplug(healStack); + blockAnimations.disposeUiEffect(this); + } + // Stop rerendering. + this.rendered = false; + + // Clear pending warnings. + if (this.warningTextDb_) { + for (const n in this.warningTextDb_) { + clearTimeout(this.warningTextDb_[n]); + } + this.warningTextDb_ = null; + } + + const icons = this.getIcons(); + for (let i = 0; i < icons.length; i++) { + icons[i].dispose(); + } + super.dispose(!!healStack); + + dom.removeNode(this.svgGroup_); + blockWorkspace.resizeContents(); + // Sever JavaScript to DOM connections. + this.svgGroup_ = null; + dom.stopTextWidthCache(); + } + + /** + * Delete a block and hide chaff when doing so. The block will not be deleted + * if it's in a flyout. This is called from the context menu and keyboard + * shortcuts as the full delete action. If you are disposing of a block from + * the workspace and don't need to perform flyout checks, handle event + * grouping, or hide chaff, then use `block.dispose()` directly. + */ + checkAndDelete() { + if (this.workspace.isFlyout) { + return; + } + eventUtils.setGroup(true); + this.workspace.hideChaff(); + if (this.outputConnection) { + // Do not attempt to heal rows + // (https://github.com/google/blockly/issues/4832) + this.dispose(false, true); + } else { + this.dispose(/* heal */ true, true); + } + eventUtils.setGroup(false); + } + + /** + * Encode a block for copying. + * @return {?ICopyable.CopyData} Copy metadata, or null if the block is + * an insertion marker. + * @package + */ + toCopyData() { + if (this.isInsertionMarker_) { + return null; + } + return { + saveInfo: /** @type {!blocks.State} */ ( + blocks.save(this, {addCoordinates: true, addNextBlocks: false})), + source: this.workspace, + typeCounts: common.getBlockTypeCounts(this, true), + }; + } + + /** + * Updates the colour of the block to match the block's state. + * @package + */ + applyColour() { + this.pathObject.applyColour(this); + + const icons = this.getIcons(); + for (let i = 0; i < icons.length; i++) { + icons[i].applyColour(); + } + + for (let x = 0, input; (input = this.inputList[x]); x++) { + for (let y = 0, field; (field = input.fieldRow[y]); y++) { + field.applyColour(); + } + } + } + + /** + * Updates the color of the block (and children) to match the current disabled + * state. + * @package + */ + updateDisabled() { + const children = + /** @type {!Array} */ (this.getChildren(false)); + this.applyColour(); + if (this.isCollapsed()) { + return; + } + for (let i = 0, child; (child = children[i]); i++) { + if (child.rendered) { + child.updateDisabled(); + } + } + } + + /** + * Get the comment icon attached to this block, or null if the block has no + * comment. + * @return {?Comment} The comment icon attached to this block, or null. + */ + getCommentIcon() { + return this.commentIcon_; + } + + /** + * Set this block's comment text. + * @param {?string} text The text, or null to delete. + */ + setCommentText(text) { + const {Comment} = goog.module.get('Blockly.Comment'); + if (!Comment) { + throw Error('Missing require for Blockly.Comment'); + } + if (this.commentModel.text === text) { + return; + } + super.setCommentText(text); + + const shouldHaveComment = text !== null; + if (!!this.commentIcon_ === shouldHaveComment) { + // If the comment's state of existence is correct, but the text is new + // that means we're just updating a comment. + this.commentIcon_.updateText(); + return; + } + if (shouldHaveComment) { + this.commentIcon_ = new Comment(this); + this.comment = this.commentIcon_; // For backwards compatibility. + } else { + this.commentIcon_.dispose(); + this.commentIcon_ = null; + this.comment = null; // For backwards compatibility. + } + if (this.rendered) { + this.render(); + // Adding or removing a comment icon will cause the block to change shape. + this.bumpNeighbours(); + } + } + + /** + * Set this block's warning text. + * @param {?string} text The text, or null to delete. + * @param {string=} opt_id An optional ID for the warning text to be able to + * maintain multiple warnings. + */ + setWarningText(text, opt_id) { + const {Warning} = goog.module.get('Blockly.Warning'); + if (!Warning) { + throw Error('Missing require for Blockly.Warning'); + } + if (!this.warningTextDb_) { + // Create a database of warning PIDs. + // Only runs once per block (and only those with warnings). + this.warningTextDb_ = Object.create(null); + } + const id = opt_id || ''; + if (!id) { + // Kill all previous pending processes, this edit supersedes them all. + for (const n of Object.keys(this.warningTextDb_)) { + clearTimeout(this.warningTextDb_[n]); + delete this.warningTextDb_[n]; + } + } else if (this.warningTextDb_[id]) { + // Only queue up the latest change. Kill any earlier pending process. + clearTimeout(this.warningTextDb_[id]); + delete this.warningTextDb_[id]; + } + if (this.workspace.isDragging()) { + // Don't change the warning text during a drag. + // Wait until the drag finishes. + const thisBlock = this; + this.warningTextDb_[id] = setTimeout(function() { + if (thisBlock.workspace) { // Check block wasn't deleted. + delete thisBlock.warningTextDb_[id]; + thisBlock.setWarningText(text, id); + } + }, 100); + return; + } + if (this.isInFlyout) { + text = null; + } + + let changedState = false; + if (typeof text === 'string') { + // Bubble up to add a warning on top-most collapsed block. + let parent = this.getSurroundParent(); + let collapsedParent = null; + while (parent) { + if (parent.isCollapsed()) { + collapsedParent = parent; + } + parent = parent.getSurroundParent(); + } + if (collapsedParent) { + collapsedParent.setWarningText( + Msg['COLLAPSED_WARNINGS_WARNING'], BlockSvg.COLLAPSED_WARNING_ID); + } + + if (!this.warning) { + this.warning = new Warning(this); + changedState = true; + } + this.warning.setText(/** @type {string} */ (text), id); + } else { + // Dispose all warnings if no ID is given. + if (this.warning && !id) { + this.warning.dispose(); + changedState = true; + } else if (this.warning) { + const oldText = this.warning.getText(); + this.warning.setText('', id); + const newText = this.warning.getText(); + if (!newText) { + this.warning.dispose(); + } + changedState = oldText !== newText; + } + } + if (changedState && this.rendered) { + this.render(); + // Adding or removing a warning icon will cause the block to change shape. + this.bumpNeighbours(); + } + } + + /** + * Give this block a mutator dialog. + * @param {?Mutator} mutator A mutator dialog instance or null to remove. + */ + setMutator(mutator) { + if (this.mutator && this.mutator !== mutator) { + this.mutator.dispose(); + } + if (mutator) { + mutator.setBlock(this); + this.mutator = mutator; + mutator.createIcon(); + } + if (this.rendered) { + this.render(); + // Adding or removing a mutator icon will cause the block to change shape. + this.bumpNeighbours(); + } + } + + /** + * Set whether the block is enabled or not. + * @param {boolean} enabled True if enabled. + */ + setEnabled(enabled) { + if (this.isEnabled() !== enabled) { + super.setEnabled(enabled); + if (this.rendered && !this.getInheritedDisabled()) { + this.updateDisabled(); + } + } + } + + /** + * Set whether the block is highlighted or not. Block highlighting is + * often used to visually mark blocks currently being executed. + * @param {boolean} highlighted True if highlighted. + */ + setHighlighted(highlighted) { + if (!this.rendered) { + return; + } + this.pathObject.updateHighlighted(highlighted); + } + + /** + * Adds the visual "select" effect to the block, but does not actually select + * it or fire an event. + * @see BlockSvg#select + */ + addSelect() { + this.pathObject.updateSelected(true); + } + + /** + * Removes the visual "select" effect from the block, but does not actually + * unselect it or fire an event. + * @see BlockSvg#unselect + */ + removeSelect() { + this.pathObject.updateSelected(false); + } + + /** + * Update the cursor over this block by adding or removing a class. + * @param {boolean} enable True if the delete cursor should be shown, false + * otherwise. + * @package + */ + setDeleteStyle(enable) { + this.pathObject.updateDraggingDelete(enable); + } + + // Overrides of functions on Blockly.Block that take into account whether the + + // block has been rendered. + + /** + * Get the colour of a block. + * @return {string} #RRGGBB string. + */ + getColour() { + return this.style.colourPrimary; + } + + /** + * Change the colour of a block. + * @param {number|string} colour HSV hue value, or #RRGGBB string. + */ + setColour(colour) { + super.setColour(colour); + const styleObj = + this.workspace.getRenderer().getConstants().getBlockStyleForColour( + this.colour_); + + this.pathObject.setStyle(styleObj.style); + this.style = styleObj.style; + this.styleName_ = styleObj.name; + + this.applyColour(); + } + + /** + * Set the style and colour values of a block. + * @param {string} blockStyleName Name of the block style. + * @throws {Error} if the block style does not exist. + */ + setStyle(blockStyleName) { + const blockStyle = + this.workspace.getRenderer().getConstants().getBlockStyle( + blockStyleName); + this.styleName_ = blockStyleName; + + if (blockStyle) { + this.hat = blockStyle.hat; + this.pathObject.setStyle(blockStyle); + // Set colour to match Block. + this.colour_ = blockStyle.colourPrimary; + this.style = blockStyle; + + this.applyColour(); + } else { + throw Error('Invalid style name: ' + blockStyleName); + } + } + + /** + * Move this block to the front of the visible workspace. + * tags do not respect z-index so SVG renders them in the + * order that they are in the DOM. By placing this block first within the + * block group's , it will render on top of any other blocks. + * @package + */ + bringToFront() { + let block = this; + do { + const root = block.getSvgRoot(); + const parent = root.parentNode; + const childNodes = parent.childNodes; + // Avoid moving the block if it's already at the bottom. + if (childNodes[childNodes.length - 1] !== root) { + parent.appendChild(root); + } + block = block.getParent(); + } while (block); + } + + /** + * Set whether this block can chain onto the bottom of another block. + * @param {boolean} newBoolean True if there can be a previous statement. + * @param {(string|Array|null)=} opt_check Statement type or + * list of statement types. Null/undefined if any type could be + * connected. + */ + setPreviousStatement(newBoolean, opt_check) { + super.setPreviousStatement(newBoolean, opt_check); + + if (this.rendered) { + this.render(); + this.bumpNeighbours(); + } + } + + /** + * Set whether another block can chain onto the bottom of this block. + * @param {boolean} newBoolean True if there can be a next statement. + * @param {(string|Array|null)=} opt_check Statement type or + * list of statement types. Null/undefined if any type could be + * connected. + */ + setNextStatement(newBoolean, opt_check) { + super.setNextStatement(newBoolean, opt_check); + + if (this.rendered) { + this.render(); + this.bumpNeighbours(); + } + } + + /** + * Set whether this block returns a value. + * @param {boolean} newBoolean True if there is an output. + * @param {(string|Array|null)=} opt_check Returned type or list + * of returned types. Null or undefined if any type could be returned + * (e.g. variable get). + */ + setOutput(newBoolean, opt_check) { + super.setOutput(newBoolean, opt_check); + + if (this.rendered) { + this.render(); + this.bumpNeighbours(); + } + } + + /** + * Set whether value inputs are arranged horizontally or vertically. + * @param {boolean} newBoolean True if inputs are horizontal. + */ + setInputsInline(newBoolean) { + super.setInputsInline(newBoolean); + + if (this.rendered) { + this.render(); + this.bumpNeighbours(); + } + } + + /** + * Remove an input from this block. + * @param {string} name The name of the input. + * @param {boolean=} opt_quiet True to prevent error if input is not present. + * @return {boolean} True if operation succeeds, false if input is not present + * and opt_quiet is true + * @throws {Error} if the input is not present and opt_quiet is not true. + */ + removeInput(name, opt_quiet) { + const removed = super.removeInput(name, opt_quiet); + + if (this.rendered) { + this.render(); + // Removing an input will cause the block to change shape. + this.bumpNeighbours(); + } + + return removed; + } + + /** + * Move a numbered input to a different location on this block. + * @param {number} inputIndex Index of the input to move. + * @param {number} refIndex Index of input that should be after the moved + * input. + */ + moveNumberedInputBefore(inputIndex, refIndex) { + super.moveNumberedInputBefore(inputIndex, refIndex); + + if (this.rendered) { + this.render(); + // Moving an input will cause the block to change shape. + this.bumpNeighbours(); + } + } + + /** + * Add a value input, statement input or local variable to this block. + * @param {number} type One of Blockly.inputTypes. + * @param {string} name Language-neutral identifier which may used to find + * this input again. Should be unique to this block. + * @return {!Input} The input object created. + * @protected + * @override + */ + appendInput_(type, name) { + const input = super.appendInput_(type, name); + + if (this.rendered) { + this.render(); + // Adding an input will cause the block to change shape. + this.bumpNeighbours(); + } + return input; + } + + /** + * Sets whether this block's connections are tracked in the database or not. + * + * Used by the deserializer to be more efficient. Setting a connection's + * tracked_ value to false keeps it from adding itself to the db when it + * gets its first moveTo call, saving expensive ops for later. + * @param {boolean} track If true, start tracking. If false, stop tracking. + * @package + */ + setConnectionTracking(track) { + if (this.previousConnection) { + /** @type {!RenderedConnection} */ (this.previousConnection) + .setTracking(track); + } + if (this.outputConnection) { + /** @type {!RenderedConnection} */ (this.outputConnection) + .setTracking(track); + } + if (this.nextConnection) { + /** @type {!RenderedConnection} */ (this.nextConnection) + .setTracking(track); + const child = + /** @type {!RenderedConnection} */ (this.nextConnection) + .targetBlock(); + if (child) { + child.setConnectionTracking(track); + } + } + + if (this.collapsed_) { + // When track is true, we don't want to start tracking collapsed + // connections. When track is false, we're already not tracking + // collapsed connections, so no need to update. + return; + } + + for (let i = 0; i < this.inputList.length; i++) { + const conn = + /** @type {!RenderedConnection} */ (this.inputList[i].connection); + if (conn) { + conn.setTracking(track); + + // Pass tracking on down the chain. + const block = conn.targetBlock(); + if (block) { + block.setConnectionTracking(track); + } + } + } + } + + /** + * Returns connections originating from this block. + * @param {boolean} all If true, return all connections even hidden ones. + * Otherwise, for a non-rendered block return an empty list, and for a + * collapsed block don't return inputs connections. + * @return {!Array} Array of connections. + * @package + */ + getConnections_(all) { + const myConnections = []; + if (all || this.rendered) { + if (this.outputConnection) { + myConnections.push(this.outputConnection); + } + if (this.previousConnection) { + myConnections.push(this.previousConnection); + } + if (this.nextConnection) { + myConnections.push(this.nextConnection); + } + if (all || !this.collapsed_) { + for (let i = 0, input; (input = this.inputList[i]); i++) { + if (input.connection) { + myConnections.push(input.connection); + } + } + } + } + return myConnections; + } + + /** + * Walks down a stack of blocks and finds the last next connection on the + * stack. + * @param {boolean} ignoreShadows If true,the last connection on a non-shadow + * block will be returned. If false, this will follow shadows to find the + * last connection. + * @return {?RenderedConnection} The last next connection on the stack, + * or null. + * @package + * @override + */ + lastConnectionInStack(ignoreShadows) { + return /** @type {RenderedConnection} */ ( + super.lastConnectionInStack(ignoreShadows)); + } + + /** + * Find the connection on this block that corresponds to the given connection + * on the other block. + * Used to match connections between a block and its insertion marker. + * @param {!Block} otherBlock The other block to match against. + * @param {!Connection} conn The other connection to match. + * @return {?RenderedConnection} The matching connection on this block, + * or null. + * @package + * @override + */ + getMatchingConnection(otherBlock, conn) { + return /** @type {RenderedConnection} */ ( + super.getMatchingConnection(otherBlock, conn)); + } + + /** + * Create a connection of the specified type. + * @param {number} type The type of the connection to create. + * @return {!RenderedConnection} A new connection of the specified type. + * @protected + */ + makeConnection_(type) { + return new RenderedConnection(this, type); + } + + /** + * Bump unconnected blocks out of alignment. Two blocks which aren't actually + * connected should not coincidentally line up on screen. + */ + bumpNeighbours() { + if (!this.workspace) { + return; // Deleted block. + } + if (this.workspace.isDragging()) { + return; // Don't bump blocks during a drag. + } + const rootBlock = this.getRootBlock(); + if (rootBlock.isInFlyout) { + return; // Don't move blocks around in a flyout. + } + // Loop through every connection on this block. + const myConnections = this.getConnections_(false); + for (let i = 0, connection; (connection = myConnections[i]); i++) { + const renderedConn = /** @type {!RenderedConnection} */ (connection); + // Spider down from this block bumping all sub-blocks. + if (renderedConn.isConnected() && renderedConn.isSuperior()) { + renderedConn.targetBlock().bumpNeighbours(); + } + + const neighbours = connection.neighbours(config.snapRadius); + for (let j = 0, otherConnection; (otherConnection = neighbours[j]); j++) { + const renderedOther = + /** @type {!RenderedConnection} */ (otherConnection); + // If both connections are connected, that's probably fine. But if + // either one of them is unconnected, then there could be confusion. + if (!renderedConn.isConnected() || !renderedOther.isConnected()) { + // Only bump blocks if they are from different tree structures. + if (renderedOther.getSourceBlock().getRootBlock() !== rootBlock) { + // Always bump the inferior block. + if (renderedConn.isSuperior()) { + renderedOther.bumpAwayFrom(renderedConn); + } else { + renderedConn.bumpAwayFrom(renderedOther); + } + } + } + } + } + } + + /** + * Schedule snapping to grid and bumping neighbours to occur after a brief + * delay. + * @package + */ + scheduleSnapAndBump() { + const block = this; + // Ensure that any snap and bump are part of this move's event group. + const group = eventUtils.getGroup(); + + setTimeout(function() { + eventUtils.setGroup(group); + block.snapToGrid(); + eventUtils.setGroup(false); + }, config.bumpDelay / 2); + + setTimeout(function() { + eventUtils.setGroup(group); + block.bumpNeighbours(); + eventUtils.setGroup(false); + }, config.bumpDelay); + } + + /** + * Position a block so that it doesn't move the target block when connected. + * The block to position is usually either the first block in a dragged stack + * or an insertion marker. + * @param {!RenderedConnection} sourceConnection The connection on the + * moving block's stack. + * @param {!RenderedConnection} targetConnection The connection that + * should stay stationary as this block is positioned. + * @package + */ + positionNearConnection(sourceConnection, targetConnection) { + // We only need to position the new block if it's before the existing one, + // otherwise its position is set by the previous block. + if (sourceConnection.type === ConnectionType.NEXT_STATEMENT || + sourceConnection.type === ConnectionType.INPUT_VALUE) { + const dx = targetConnection.x - sourceConnection.x; + const dy = targetConnection.y - sourceConnection.y; + + this.moveBy(dx, dy); + } + } + + /** + * Return the parent block or null if this block is at the top level. + * @return {?BlockSvg} The block (if any) that holds the current block. + * @override + */ + getParent() { + return /** @type {?BlockSvg} */ (super.getParent()); + } + + /** + * @return {?BlockSvg} The block (if any) that surrounds the current block. + * @override + */ + getSurroundParent() { + return /** @type {?BlockSvg} */ (super.getSurroundParent()); + } + + /** + * @return {?BlockSvg} The next statement block or null. + * @override + */ + getNextBlock() { + return /** @type {?BlockSvg} */ (super.getNextBlock()); + } + + /** + * @return {?BlockSvg} The previou statement block or null. + * @override + */ + getPreviousBlock() { + return /** @type {?BlockSvg} */ (super.getPreviousBlock()); + } + + /** + * @return {?RenderedConnection} The first statement connection or null. + * @package + * @override + */ + getFirstStatementConnection() { + return /** @type {?RenderedConnection} */ ( + super.getFirstStatementConnection()); + } + + /** + * @return {!BlockSvg} The top block in a stack. + * @override + */ + getTopStackBlock() { + return /** @type {!BlockSvg} */ (super.getTopStackBlock()); + } + + /** + * @param {boolean} ordered Sort the list if true. + * @return {!Array} Children of this block. + * @override + */ + getChildren(ordered) { + return /** @type {!Array} */ (super.getChildren(ordered)); + } + + /** + * @param {boolean} ordered Sort the list if true. + * @return {!Array} Descendants of this block. + * @override + */ + getDescendants(ordered) { + return /** @type {!Array} */ (super.getDescendants(ordered)); + } + + /** + * @param {string} name The name of the input. + * @return {?BlockSvg} The attached value block, or null if the input is + * either disconnected or if the input does not exist. + * @override + */ + getInputTargetBlock(name) { + return /** @type {?BlockSvg} */ (super.getInputTargetBlock(name)); + } + + /** + * Return the top-most block in this block's tree. + * This will return itself if this block is at the top level. + * @return {!BlockSvg} The root block. + * @override + */ + getRootBlock() { + return /** @type {!BlockSvg} */ (super.getRootBlock()); + } + + /** + * Lays out and reflows a block based on its contents and settings. + * @param {boolean=} opt_bubble If false, just render this block. + * If true, also render block's parent, grandparent, etc. Defaults to true. + */ + render(opt_bubble) { + if (this.renderIsInProgress_) { + return; // Don't allow recursive renders. + } + this.renderIsInProgress_ = true; + try { + this.rendered = true; + dom.startTextWidthCache(); + + if (this.isCollapsed()) { + this.updateCollapsed_(); + } + this.workspace.getRenderer().render(this); + this.updateConnectionLocations_(); + + if (opt_bubble !== false) { + const parentBlock = this.getParent(); + if (parentBlock) { + parentBlock.render(true); + } else { + // Top-most block. Fire an event to allow scrollbars to resize. + this.workspace.resizeContents(); + } + } + + dom.stopTextWidthCache(); + this.updateMarkers_(); + } finally { + this.renderIsInProgress_ = false; + } + } + + /** + * Redraw any attached marker or cursor svgs if needed. + * @protected + */ + updateMarkers_() { + if (this.workspace.keyboardAccessibilityMode && this.pathObject.cursorSvg) { + this.workspace.getCursor().draw(); + } + if (this.workspace.keyboardAccessibilityMode && this.pathObject.markerSvg) { + // TODO(#4592): Update all markers on the block. + this.workspace.getMarker(MarkerManager.LOCAL_MARKER).draw(); + } + } + + /** + * Update all of the connections on this block with the new locations + * calculated during rendering. Also move all of the connected blocks based + * on the new connection locations. + * @private + */ + updateConnectionLocations_() { + const blockTL = this.getRelativeToSurfaceXY(); + // Don't tighten previous or output connections because they are inferior + // connections. + if (this.previousConnection) { + this.previousConnection.moveToOffset(blockTL); + } + if (this.outputConnection) { + this.outputConnection.moveToOffset(blockTL); + } + + for (let i = 0; i < this.inputList.length; i++) { + const conn = + /** @type {!RenderedConnection} */ (this.inputList[i].connection); + if (conn) { + conn.moveToOffset(blockTL); + if (conn.isConnected()) { + conn.tighten(); + } + } + } + + if (this.nextConnection) { + this.nextConnection.moveToOffset(blockTL); + if (this.nextConnection.isConnected()) { + this.nextConnection.tighten(); + } + } + } + + /** + * Add the cursor SVG to this block's SVG group. + * @param {SVGElement} cursorSvg The SVG root of the cursor to be added to the + * block SVG group. + * @package + */ + setCursorSvg(cursorSvg) { + this.pathObject.setCursorSvg(cursorSvg); + } + + /** + * Add the marker SVG to this block's SVG group. + * @param {SVGElement} markerSvg The SVG root of the marker to be added to the + * block SVG group. + * @package + */ + setMarkerSvg(markerSvg) { + this.pathObject.setMarkerSvg(markerSvg); + } + + /** + * Returns a bounding box describing the dimensions of this block + * and any blocks stacked below it. + * @return {!{height: number, width: number}} Object with height and width + * properties in workspace units. + * @package + */ + getHeightWidth() { + let height = this.height; + let width = this.width; + // Recursively add size of subsequent blocks. + const nextBlock = this.getNextBlock(); + if (nextBlock) { + const nextHeightWidth = nextBlock.getHeightWidth(); + const workspace = /** @type {!WorkspaceSvg} */ (this.workspace); + const tabHeight = workspace.getRenderer().getConstants().NOTCH_HEIGHT; + height += nextHeightWidth.height - tabHeight; + width = Math.max(width, nextHeightWidth.width); + } + return {height: height, width: width}; + } + + /** + * Visual effect to show that if the dragging block is dropped, this block + * will be replaced. If a shadow block, it will disappear. Otherwise it will + * bump. + * @param {boolean} add True if highlighting should be added. + * @package + */ + fadeForReplacement(add) { + this.pathObject.updateReplacementFade(add); + } + + /** + * Visual effect to show that if the dragging block is dropped it will connect + * to this input. + * @param {Connection} conn The connection on the input to highlight. + * @param {boolean} add True if highlighting should be added. + * @package + */ + highlightShapeForInput(conn, add) { + this.pathObject.updateShapeForInputHighlight(conn, add); + } +} /** * Constant for identifying rows that are to be rendered inline. @@ -199,1582 +1891,4 @@ BlockSvg.INLINE = -1; */ BlockSvg.COLLAPSED_WARNING_ID = 'TEMP_COLLAPSED_WARNING_'; -/** - * An optional method called when a mutator dialog is first opened. - * This function must create and initialize a top-level block for the mutator - * dialog, and return it. This function should also populate this top-level - * block with any sub-blocks which are appropriate. This method must also be - * coupled with defining a `compose` method for the default mutation dialog - * button and UI to appear. - * @type {?function(WorkspaceSvg):!BlockSvg} - */ -BlockSvg.prototype.decompose; - -/** - * An optional method called when a mutator dialog saves its content. - * This function is called to modify the original block according to new - * settings. This method must also be coupled with defining a `decompose` - * method for the default mutation dialog button and UI to appear. - * @type {?function(!BlockSvg)} - */ -BlockSvg.prototype.compose; - -/** - * An optional method for defining custom block context menu items. - * @type {?function(!Array)} - */ -BlockSvg.prototype.customContextMenu; - -/** - * An property used internally to reference the block's rendering debugger. - * @type {?BlockRenderingDebug} - * @package - */ -BlockSvg.prototype.renderingDebugger; - -/** - * Create and initialize the SVG representation of the block. - * May be called more than once. - */ -BlockSvg.prototype.initSvg = function() { - if (!this.workspace.rendered) { - throw TypeError('Workspace is headless.'); - } - for (let i = 0, input; (input = this.inputList[i]); i++) { - input.init(); - } - const icons = this.getIcons(); - for (let i = 0; i < icons.length; i++) { - icons[i].createIcon(); - } - this.applyColour(); - this.pathObject.updateMovable(this.isMovable()); - const svg = this.getSvgRoot(); - if (!this.workspace.options.readOnly && !this.eventsInit_ && svg) { - browserEvents.conditionalBind(svg, 'mousedown', this, this.onMouseDown_); - } - this.eventsInit_ = true; - - if (!svg.parentNode) { - this.workspace.getCanvas().appendChild(svg); - } -}; - -/** - * Get the secondary colour of a block. - * @return {?string} #RRGGBB string. - */ -BlockSvg.prototype.getColourSecondary = function() { - return this.style.colourSecondary; -}; - -/** - * Get the tertiary colour of a block. - * @return {?string} #RRGGBB string. - */ -BlockSvg.prototype.getColourTertiary = function() { - return this.style.colourTertiary; -}; - -/** - * Selects this block. Highlights the block visually and fires a select event - * if the block is not already selected. - */ -BlockSvg.prototype.select = function() { - if (this.isShadow() && this.getParent()) { - // Shadow blocks should not be selected. - this.getParent().select(); - return; - } - if (common.getSelected() === this) { - return; - } - let oldId = null; - if (common.getSelected()) { - oldId = common.getSelected().id; - // Unselect any previously selected block. - eventUtils.disable(); - try { - common.getSelected().unselect(); - } finally { - eventUtils.enable(); - } - } - const event = new (eventUtils.get(eventUtils.SELECTED))( - oldId, this.id, this.workspace.id); - eventUtils.fire(event); - common.setSelected(this); - this.addSelect(); -}; - -/** - * Unselects this block. Unhighlights the block and fires a select (false) event - * if the block is currently selected. - */ -BlockSvg.prototype.unselect = function() { - if (common.getSelected() !== this) { - return; - } - const event = new (eventUtils.get(eventUtils.SELECTED))( - this.id, null, this.workspace.id); - event.workspaceId = this.workspace.id; - eventUtils.fire(event); - common.setSelected(null); - this.removeSelect(); -}; - -/** - * Block's mutator icon (if any). - * @type {?Mutator} - */ -BlockSvg.prototype.mutator = null; - -/** - * Block's comment icon (if any). - * @type {?Comment} - * @deprecated August 2019. Use getCommentIcon instead. - */ -BlockSvg.prototype.comment = null; - -/** - * Block's comment icon (if any). - * @type {?Comment} - * @private - */ -BlockSvg.prototype.commentIcon_ = null; - -/** - * Block's warning icon (if any). - * @type {?Warning} - */ -BlockSvg.prototype.warning = null; - -/** - * Returns a list of mutator, comment, and warning icons. - * @return {!Array} List of icons. - */ -BlockSvg.prototype.getIcons = function() { - const icons = []; - if (this.mutator) { - icons.push(this.mutator); - } - if (this.commentIcon_) { - icons.push(this.commentIcon_); - } - if (this.warning) { - icons.push(this.warning); - } - return icons; -}; - -/** - * Sets the parent of this block to be a new block or null. - * @param {?Block} newParent New parent block. - * @package - * @override - */ -BlockSvg.prototype.setParent = function(newParent) { - const oldParent = this.parentBlock_; - if (newParent === oldParent) { - return; - } - - dom.startTextWidthCache(); - BlockSvg.superClass_.setParent.call(this, newParent); - dom.stopTextWidthCache(); - - const svgRoot = this.getSvgRoot(); - - // Bail early if workspace is clearing, or we aren't rendered. - // We won't need to reattach ourselves anywhere. - if (this.workspace.isClearing || !svgRoot) { - return; - } - - const oldXY = this.getRelativeToSurfaceXY(); - if (newParent) { - newParent.getSvgRoot().appendChild(svgRoot); - const newXY = this.getRelativeToSurfaceXY(); - // Move the connections to match the child's new position. - this.moveConnections(newXY.x - oldXY.x, newXY.y - oldXY.y); - } else if (oldParent) { - // If we are losing a parent, we want to move our DOM element to the - // root of the workspace. - this.workspace.getCanvas().appendChild(svgRoot); - this.translate(oldXY.x, oldXY.y); - } - - this.applyColour(); -}; - -/** - * Return the coordinates of the top-left corner of this block relative to the - * drawing surface's origin (0,0), in workspace units. - * If the block is on the workspace, (0, 0) is the origin of the workspace - * coordinate system. - * This does not change with workspace scale. - * @return {!Coordinate} Object with .x and .y properties in - * workspace coordinates. - */ -BlockSvg.prototype.getRelativeToSurfaceXY = function() { - let x = 0; - let y = 0; - - const dragSurfaceGroup = this.useDragSurface_ ? - this.workspace.getBlockDragSurface().getGroup() : - null; - - let element = this.getSvgRoot(); - if (element) { - do { - // Loop through this block and every parent. - const xy = svgMath.getRelativeXY(element); - x += xy.x; - y += xy.y; - // If this element is the current element on the drag surface, include - // the translation of the drag surface itself. - if (this.useDragSurface_ && - this.workspace.getBlockDragSurface().getCurrentBlock() === element) { - const surfaceTranslation = - this.workspace.getBlockDragSurface().getSurfaceTranslation(); - x += surfaceTranslation.x; - y += surfaceTranslation.y; - } - element = /** @type {!SVGElement} */ (element.parentNode); - } while (element && element !== this.workspace.getCanvas() && - element !== dragSurfaceGroup); - } - return new Coordinate(x, y); -}; - -/** - * Move a block by a relative offset. - * @param {number} dx Horizontal offset in workspace units. - * @param {number} dy Vertical offset in workspace units. - */ -BlockSvg.prototype.moveBy = function(dx, dy) { - if (this.parentBlock_) { - throw Error('Block has parent.'); - } - const eventsEnabled = eventUtils.isEnabled(); - let event; - if (eventsEnabled) { - event = new (eventUtils.get(eventUtils.BLOCK_MOVE))(this); - } - const xy = this.getRelativeToSurfaceXY(); - this.translate(xy.x + dx, xy.y + dy); - this.moveConnections(dx, dy); - if (eventsEnabled) { - event.recordNew(); - eventUtils.fire(event); - } - this.workspace.resizeContents(); -}; - -/** - * Transforms a block by setting the translation on the transform attribute - * of the block's SVG. - * @param {number} x The x coordinate of the translation in workspace units. - * @param {number} y The y coordinate of the translation in workspace units. - */ -BlockSvg.prototype.translate = function(x, y) { - this.getSvgRoot().setAttribute('transform', 'translate(' + x + ',' + y + ')'); -}; - -/** - * Move this block to its workspace's drag surface, accounting for positioning. - * Generally should be called at the same time as setDragging_(true). - * Does nothing if useDragSurface_ is false. - * @package - */ -BlockSvg.prototype.moveToDragSurface = function() { - if (!this.useDragSurface_) { - return; - } - // The translation for drag surface blocks, - // is equal to the current relative-to-surface position, - // to keep the position in sync as it move on/off the surface. - // This is in workspace coordinates. - const xy = this.getRelativeToSurfaceXY(); - this.clearTransformAttributes_(); - this.workspace.getBlockDragSurface().translateSurface(xy.x, xy.y); - // Execute the move on the top-level SVG component - const svg = this.getSvgRoot(); - if (svg) { - this.workspace.getBlockDragSurface().setBlocksAndShow(svg); - } -}; - -/** - * Move a block to a position. - * @param {Coordinate} xy The position to move to in workspace units. - */ -BlockSvg.prototype.moveTo = function(xy) { - const curXY = this.getRelativeToSurfaceXY(); - this.moveBy(xy.x - curXY.x, xy.y - curXY.y); -}; - -/** - * Move this block back to the workspace block canvas. - * Generally should be called at the same time as setDragging_(false). - * Does nothing if useDragSurface_ is false. - * @param {!Coordinate} newXY The position the block should take on - * on the workspace canvas, in workspace coordinates. - * @package - */ -BlockSvg.prototype.moveOffDragSurface = function(newXY) { - if (!this.useDragSurface_) { - return; - } - // Translate to current position, turning off 3d. - this.translate(newXY.x, newXY.y); - this.workspace.getBlockDragSurface().clearAndHide(this.workspace.getCanvas()); -}; - -/** - * Move this block during a drag, taking into account whether we are using a - * drag surface to translate blocks. - * This block must be a top-level block. - * @param {!Coordinate} newLoc The location to translate to, in - * workspace coordinates. - * @package - */ -BlockSvg.prototype.moveDuringDrag = function(newLoc) { - if (this.useDragSurface_) { - this.workspace.getBlockDragSurface().translateSurface(newLoc.x, newLoc.y); - } else { - this.svgGroup_.translate_ = 'translate(' + newLoc.x + ',' + newLoc.y + ')'; - this.svgGroup_.setAttribute( - 'transform', this.svgGroup_.translate_ + this.svgGroup_.skew_); - } -}; - -/** - * Clear the block of transform="..." attributes. - * Used when the block is switching from 3d to 2d transform or vice versa. - * @private - */ -BlockSvg.prototype.clearTransformAttributes_ = function() { - this.getSvgRoot().removeAttribute('transform'); -}; - -/** - * Snap this block to the nearest grid point. - */ -BlockSvg.prototype.snapToGrid = function() { - if (!this.workspace) { - return; // Deleted block. - } - if (this.workspace.isDragging()) { - return; // Don't bump blocks during a drag. - } - if (this.getParent()) { - return; // Only snap top-level blocks. - } - if (this.isInFlyout) { - return; // Don't move blocks around in a flyout. - } - const grid = this.workspace.getGrid(); - if (!grid || !grid.shouldSnap()) { - return; // Config says no snapping. - } - const spacing = grid.getSpacing(); - const half = spacing / 2; - const xy = this.getRelativeToSurfaceXY(); - const dx = - Math.round(Math.round((xy.x - half) / spacing) * spacing + half - xy.x); - const dy = - Math.round(Math.round((xy.y - half) / spacing) * spacing + half - xy.y); - if (dx || dy) { - this.moveBy(dx, dy); - } -}; - -/** - * Returns the coordinates of a bounding box describing the dimensions of this - * block and any blocks stacked below it. - * Coordinate system: workspace coordinates. - * @return {!Rect} Object with coordinates of the bounding box. - */ -BlockSvg.prototype.getBoundingRectangle = function() { - const blockXY = this.getRelativeToSurfaceXY(); - const blockBounds = this.getHeightWidth(); - let left; - let right; - if (this.RTL) { - left = blockXY.x - blockBounds.width; - right = blockXY.x; - } else { - left = blockXY.x; - right = blockXY.x + blockBounds.width; - } - return new Rect(blockXY.y, blockXY.y + blockBounds.height, left, right); -}; - -/** - * Notify every input on this block to mark its fields as dirty. - * A dirty field is a field that needs to be re-rendered. - */ -BlockSvg.prototype.markDirty = function() { - this.pathObject.constants = (/** @type {!WorkspaceSvg} */ (this.workspace)) - .getRenderer() - .getConstants(); - for (let i = 0, input; (input = this.inputList[i]); i++) { - input.markDirty(); - } -}; - -/** - * Set whether the block is collapsed or not. - * @param {boolean} collapsed True if collapsed. - */ -BlockSvg.prototype.setCollapsed = function(collapsed) { - if (this.collapsed_ === collapsed) { - return; - } - BlockSvg.superClass_.setCollapsed.call(this, collapsed); - if (!collapsed) { - this.updateCollapsed_(); - } else if (this.rendered) { - this.render(); - // Don't bump neighbours. Users like to store collapsed functions together - // and bumping makes them go out of alignment. - } -}; - -/** - * Makes sure that when the block is collapsed, it is rendered correctly - * for that state. - * @private - */ -BlockSvg.prototype.updateCollapsed_ = function() { - const collapsed = this.isCollapsed(); - const collapsedInputName = constants.COLLAPSED_INPUT_NAME; - const collapsedFieldName = constants.COLLAPSED_FIELD_NAME; - - for (let i = 0, input; (input = this.inputList[i]); i++) { - if (input.name !== collapsedInputName) { - input.setVisible(!collapsed); - } - } - - if (!collapsed) { - this.updateDisabled(); - this.removeInput(collapsedInputName); - return; - } - - const icons = this.getIcons(); - for (let i = 0, icon; (icon = icons[i]); i++) { - icon.setVisible(false); - } - - const text = this.toString(internalConstants.COLLAPSE_CHARS); - const field = this.getField(collapsedFieldName); - if (field) { - field.setValue(text); - return; - } - const input = this.getInput(collapsedInputName) || - this.appendDummyInput(collapsedInputName); - input.appendField(new FieldLabel(text), collapsedFieldName); -}; - -/** - * Open the next (or previous) FieldTextInput. - * @param {!Field} start Current field. - * @param {boolean} forward If true go forward, otherwise backward. - */ -BlockSvg.prototype.tab = function(start, forward) { - const tabCursor = new TabNavigateCursor(); - tabCursor.setCurNode(ASTNode.createFieldNode(start)); - const currentNode = tabCursor.getCurNode(); - - if (forward) { - tabCursor.next(); - } else { - tabCursor.prev(); - } - - const nextNode = tabCursor.getCurNode(); - if (nextNode && nextNode !== currentNode) { - const nextField = /** @type {!Field} */ (nextNode.getLocation()); - nextField.showEditor(); - - // Also move the cursor if we're in keyboard nav mode. - if (this.workspace.keyboardAccessibilityMode) { - this.workspace.getCursor().setCurNode(nextNode); - } - } -}; - -/** - * Handle a mouse-down on an SVG block. - * @param {!Event} e Mouse down event or touch start event. - * @private - */ -BlockSvg.prototype.onMouseDown_ = function(e) { - const gesture = this.workspace && this.workspace.getGesture(e); - if (gesture) { - gesture.handleBlockStart(e, this); - } -}; - -/** - * Load the block's help page in a new window. - * @package - */ -BlockSvg.prototype.showHelp = function() { - const url = - (typeof this.helpUrl === 'function') ? this.helpUrl() : this.helpUrl; - if (url) { - window.open(url); - } -}; - -/** - * Generate the context menu for this block. - * @return {?Array} Context menu options or null if no menu. - * @protected - */ -BlockSvg.prototype.generateContextMenu = function() { - if (this.workspace.options.readOnly || !this.contextMenu) { - return null; - } - const menuOptions = ContextMenuRegistry.registry.getContextMenuOptions( - ContextMenuRegistry.ScopeType.BLOCK, {block: this}); - - // Allow the block to add or modify menuOptions. - if (this.customContextMenu) { - this.customContextMenu(menuOptions); - } - - return menuOptions; -}; - -/** - * Show the context menu for this block. - * @param {!Event} e Mouse event. - * @package - */ -BlockSvg.prototype.showContextMenu = function(e) { - const menuOptions = this.generateContextMenu(); - - if (menuOptions && menuOptions.length) { - ContextMenu.show(e, menuOptions, this.RTL); - ContextMenu.setCurrentBlock(this); - } -}; - -/** - * Move the connections for this block and all blocks attached under it. - * Also update any attached bubbles. - * @param {number} dx Horizontal offset from current location, in workspace - * units. - * @param {number} dy Vertical offset from current location, in workspace - * units. - * @package - */ -BlockSvg.prototype.moveConnections = function(dx, dy) { - if (!this.rendered) { - // Rendering is required to lay out the blocks. - // This is probably an invisible block attached to a collapsed block. - return; - } - const myConnections = this.getConnections_(false); - for (let i = 0; i < myConnections.length; i++) { - myConnections[i].moveBy(dx, dy); - } - const icons = this.getIcons(); - for (let i = 0; i < icons.length; i++) { - icons[i].computeIconLocation(); - } - - // Recurse through all blocks attached under this one. - for (let i = 0; i < this.childBlocks_.length; i++) { - this.childBlocks_[i].moveConnections(dx, dy); - } -}; - -/** - * Recursively adds or removes the dragging class to this node and its children. - * @param {boolean} adding True if adding, false if removing. - * @package - */ -BlockSvg.prototype.setDragging = function(adding) { - if (adding) { - const group = this.getSvgRoot(); - group.translate_ = ''; - group.skew_ = ''; - common.draggingConnections.push(...this.getConnections_(true)); - dom.addClass( - /** @type {!Element} */ (this.svgGroup_), 'blocklyDragging'); - } else { - common.draggingConnections.length = 0; - dom.removeClass( - /** @type {!Element} */ (this.svgGroup_), 'blocklyDragging'); - } - // Recurse through all blocks attached under this one. - for (let i = 0; i < this.childBlocks_.length; i++) { - this.childBlocks_[i].setDragging(adding); - } -}; - -/** - * Set whether this block is movable or not. - * @param {boolean} movable True if movable. - */ -BlockSvg.prototype.setMovable = function(movable) { - BlockSvg.superClass_.setMovable.call(this, movable); - this.pathObject.updateMovable(movable); -}; - -/** - * Set whether this block is editable or not. - * @param {boolean} editable True if editable. - */ -BlockSvg.prototype.setEditable = function(editable) { - BlockSvg.superClass_.setEditable.call(this, editable); - const icons = this.getIcons(); - for (let i = 0; i < icons.length; i++) { - icons[i].updateEditable(); - } -}; - -/** - * Sets whether this block is a shadow block or not. - * @param {boolean} shadow True if a shadow. - * @package - */ -BlockSvg.prototype.setShadow = function(shadow) { - BlockSvg.superClass_.setShadow.call(this, shadow); - this.applyColour(); -}; - -/** - * Set whether this block is an insertion marker block or not. - * Once set this cannot be unset. - * @param {boolean} insertionMarker True if an insertion marker. - * @package - */ -BlockSvg.prototype.setInsertionMarker = function(insertionMarker) { - if (this.isInsertionMarker_ === insertionMarker) { - return; // No change. - } - this.isInsertionMarker_ = insertionMarker; - if (this.isInsertionMarker_) { - this.setColour( - this.workspace.getRenderer().getConstants().INSERTION_MARKER_COLOUR); - this.pathObject.updateInsertionMarker(true); - } -}; - -/** - * Return the root node of the SVG or null if none exists. - * @return {!SVGGElement} The root SVG node (probably a group). - */ -BlockSvg.prototype.getSvgRoot = function() { - return this.svgGroup_; -}; - -/** - * Dispose of this block. - * @param {boolean=} healStack If true, then try to heal any gap by connecting - * the next statement with the previous statement. Otherwise, dispose of - * all children of this block. - * @param {boolean=} animate If true, show a disposal animation and sound. - * @suppress {checkTypes} - */ -BlockSvg.prototype.dispose = function(healStack, animate) { - if (!this.workspace) { - // The block has already been deleted. - return; - } - Tooltip.dispose(); - Tooltip.unbindMouseEvents(this.pathObject.svgPath); - dom.startTextWidthCache(); - // Save the block's workspace temporarily so we can resize the - // contents once the block is disposed. - const blockWorkspace = this.workspace; - // If this block is being dragged, unlink the mouse events. - if (common.getSelected() === this) { - this.unselect(); - this.workspace.cancelCurrentGesture(); - } - // If this block has a context menu open, close it. - if (ContextMenu.getCurrentBlock() === this) { - ContextMenu.hide(); - } - - if (animate && this.rendered) { - this.unplug(healStack); - blockAnimations.disposeUiEffect(this); - } - // Stop rerendering. - this.rendered = false; - - // Clear pending warnings. - if (this.warningTextDb_) { - for (const n in this.warningTextDb_) { - clearTimeout(this.warningTextDb_[n]); - } - this.warningTextDb_ = null; - } - - const icons = this.getIcons(); - for (let i = 0; i < icons.length; i++) { - icons[i].dispose(); - } - BlockSvg.superClass_.dispose.call(this, !!healStack); - - dom.removeNode(this.svgGroup_); - blockWorkspace.resizeContents(); - // Sever JavaScript to DOM connections. - this.svgGroup_ = null; - dom.stopTextWidthCache(); -}; - -/** - * Delete a block and hide chaff when doing so. The block will not be deleted if - * it's in a flyout. This is called from the context menu and keyboard shortcuts - * as the full delete action. If you are disposing of a block from the workspace - * and don't need to perform flyout checks, handle event grouping, or hide - * chaff, then use `block.dispose()` directly. - */ -BlockSvg.prototype.checkAndDelete = function() { - if (this.workspace.isFlyout) { - return; - } - eventUtils.setGroup(true); - this.workspace.hideChaff(); - if (this.outputConnection) { - // Do not attempt to heal rows - // (https://github.com/google/blockly/issues/4832) - this.dispose(false, true); - } else { - this.dispose(/* heal */ true, true); - } - eventUtils.setGroup(false); -}; - -/** - * Encode a block for copying. - * @return {?ICopyable.CopyData} Copy metadata, or null if the block is - * an insertion marker. - * @package - */ -BlockSvg.prototype.toCopyData = function() { - if (this.isInsertionMarker_) { - return null; - } - return { - saveInfo: /** @type {!blocks.State} */ ( - blocks.save(this, {addCoordinates: true, addNextBlocks: false})), - source: this.workspace, - typeCounts: common.getBlockTypeCounts(this, true), - }; -}; - -/** - * Updates the colour of the block to match the block's state. - * @package - */ -BlockSvg.prototype.applyColour = function() { - this.pathObject.applyColour(this); - - const icons = this.getIcons(); - for (let i = 0; i < icons.length; i++) { - icons[i].applyColour(); - } - - for (let x = 0, input; (input = this.inputList[x]); x++) { - for (let y = 0, field; (field = input.fieldRow[y]); y++) { - field.applyColour(); - } - } -}; - -/** - * Updates the color of the block (and children) to match the current disabled - * state. - * @package - */ -BlockSvg.prototype.updateDisabled = function() { - const children = this.getChildren(false); - this.applyColour(); - if (this.isCollapsed()) { - return; - } - for (let i = 0, child; (child = children[i]); i++) { - if (child.rendered) { - child.updateDisabled(); - } - } -}; - -/** - * Get the comment icon attached to this block, or null if the block has no - * comment. - * @return {?Comment} The comment icon attached to this block, or null. - */ -BlockSvg.prototype.getCommentIcon = function() { - return this.commentIcon_; -}; - -/** - * Set this block's comment text. - * @param {?string} text The text, or null to delete. - */ -BlockSvg.prototype.setCommentText = function(text) { - const {Comment} = goog.module.get('Blockly.Comment'); - if (!Comment) { - throw Error('Missing require for Blockly.Comment'); - } - if (this.commentModel.text === text) { - return; - } - BlockSvg.superClass_.setCommentText.call(this, text); - - const shouldHaveComment = text !== null; - if (!!this.commentIcon_ === shouldHaveComment) { - // If the comment's state of existence is correct, but the text is new - // that means we're just updating a comment. - this.commentIcon_.updateText(); - return; - } - if (shouldHaveComment) { - this.commentIcon_ = new Comment(this); - this.comment = this.commentIcon_; // For backwards compatibility. - } else { - this.commentIcon_.dispose(); - this.commentIcon_ = null; - this.comment = null; // For backwards compatibility. - } - if (this.rendered) { - this.render(); - // Adding or removing a comment icon will cause the block to change shape. - this.bumpNeighbours(); - } -}; - -/** - * Set this block's warning text. - * @param {?string} text The text, or null to delete. - * @param {string=} opt_id An optional ID for the warning text to be able to - * maintain multiple warnings. - */ -BlockSvg.prototype.setWarningText = function(text, opt_id) { - const {Warning} = goog.module.get('Blockly.Warning'); - if (!Warning) { - throw Error('Missing require for Blockly.Warning'); - } - if (!this.warningTextDb_) { - // Create a database of warning PIDs. - // Only runs once per block (and only those with warnings). - this.warningTextDb_ = Object.create(null); - } - const id = opt_id || ''; - if (!id) { - // Kill all previous pending processes, this edit supersedes them all. - for (const n of Object.keys(this.warningTextDb_)) { - clearTimeout(this.warningTextDb_[n]); - delete this.warningTextDb_[n]; - } - } else if (this.warningTextDb_[id]) { - // Only queue up the latest change. Kill any earlier pending process. - clearTimeout(this.warningTextDb_[id]); - delete this.warningTextDb_[id]; - } - if (this.workspace.isDragging()) { - // Don't change the warning text during a drag. - // Wait until the drag finishes. - const thisBlock = this; - this.warningTextDb_[id] = setTimeout(function() { - if (thisBlock.workspace) { // Check block wasn't deleted. - delete thisBlock.warningTextDb_[id]; - thisBlock.setWarningText(text, id); - } - }, 100); - return; - } - if (this.isInFlyout) { - text = null; - } - - let changedState = false; - if (typeof text === 'string') { - // Bubble up to add a warning on top-most collapsed block. - let parent = this.getSurroundParent(); - let collapsedParent = null; - while (parent) { - if (parent.isCollapsed()) { - collapsedParent = parent; - } - parent = parent.getSurroundParent(); - } - if (collapsedParent) { - collapsedParent.setWarningText( - Msg['COLLAPSED_WARNINGS_WARNING'], BlockSvg.COLLAPSED_WARNING_ID); - } - - if (!this.warning) { - this.warning = new Warning(this); - changedState = true; - } - this.warning.setText(/** @type {string} */ (text), id); - } else { - // Dispose all warnings if no ID is given. - if (this.warning && !id) { - this.warning.dispose(); - changedState = true; - } else if (this.warning) { - const oldText = this.warning.getText(); - this.warning.setText('', id); - const newText = this.warning.getText(); - if (!newText) { - this.warning.dispose(); - } - changedState = oldText !== newText; - } - } - if (changedState && this.rendered) { - this.render(); - // Adding or removing a warning icon will cause the block to change shape. - this.bumpNeighbours(); - } -}; - -/** - * Give this block a mutator dialog. - * @param {?Mutator} mutator A mutator dialog instance or null to remove. - */ -BlockSvg.prototype.setMutator = function(mutator) { - if (this.mutator && this.mutator !== mutator) { - this.mutator.dispose(); - } - if (mutator) { - mutator.setBlock(this); - this.mutator = mutator; - mutator.createIcon(); - } - if (this.rendered) { - this.render(); - // Adding or removing a mutator icon will cause the block to change shape. - this.bumpNeighbours(); - } -}; - -/** - * Set whether the block is enabled or not. - * @param {boolean} enabled True if enabled. - */ -BlockSvg.prototype.setEnabled = function(enabled) { - if (this.isEnabled() !== enabled) { - BlockSvg.superClass_.setEnabled.call(this, enabled); - if (this.rendered && !this.getInheritedDisabled()) { - this.updateDisabled(); - } - } -}; - -/** - * Set whether the block is highlighted or not. Block highlighting is - * often used to visually mark blocks currently being executed. - * @param {boolean} highlighted True if highlighted. - */ -BlockSvg.prototype.setHighlighted = function(highlighted) { - if (!this.rendered) { - return; - } - this.pathObject.updateHighlighted(highlighted); -}; - -/** - * Adds the visual "select" effect to the block, but does not actually select - * it or fire an event. - * @see BlockSvg#select - */ -BlockSvg.prototype.addSelect = function() { - this.pathObject.updateSelected(true); -}; - -/** - * Removes the visual "select" effect from the block, but does not actually - * unselect it or fire an event. - * @see BlockSvg#unselect - */ -BlockSvg.prototype.removeSelect = function() { - this.pathObject.updateSelected(false); -}; - -/** - * Update the cursor over this block by adding or removing a class. - * @param {boolean} enable True if the delete cursor should be shown, false - * otherwise. - * @package - */ -BlockSvg.prototype.setDeleteStyle = function(enable) { - this.pathObject.updateDraggingDelete(enable); -}; - - -// Overrides of functions on Blockly.Block that take into account whether the -// block has been rendered. -/** - * Get the colour of a block. - * @return {string} #RRGGBB string. - */ -BlockSvg.prototype.getColour = function() { - return this.style.colourPrimary; -}; - -/** - * Change the colour of a block. - * @param {number|string} colour HSV hue value, or #RRGGBB string. - */ -BlockSvg.prototype.setColour = function(colour) { - BlockSvg.superClass_.setColour.call(this, colour); - const styleObj = - this.workspace.getRenderer().getConstants().getBlockStyleForColour( - this.colour_); - - this.pathObject.setStyle(styleObj.style); - this.style = styleObj.style; - this.styleName_ = styleObj.name; - - this.applyColour(); -}; - -/** - * Set the style and colour values of a block. - * @param {string} blockStyleName Name of the block style. - * @throws {Error} if the block style does not exist. - */ -BlockSvg.prototype.setStyle = function(blockStyleName) { - const blockStyle = - this.workspace.getRenderer().getConstants().getBlockStyle(blockStyleName); - this.styleName_ = blockStyleName; - - if (blockStyle) { - this.hat = blockStyle.hat; - this.pathObject.setStyle(blockStyle); - // Set colour to match Block. - this.colour_ = blockStyle.colourPrimary; - this.style = blockStyle; - - this.applyColour(); - } else { - throw Error('Invalid style name: ' + blockStyleName); - } -}; - -/** - * Move this block to the front of the visible workspace. - * tags do not respect z-index so SVG renders them in the - * order that they are in the DOM. By placing this block first within the - * block group's , it will render on top of any other blocks. - * @package - */ -BlockSvg.prototype.bringToFront = function() { - let block = this; - do { - const root = block.getSvgRoot(); - const parent = root.parentNode; - const childNodes = parent.childNodes; - // Avoid moving the block if it's already at the bottom. - if (childNodes[childNodes.length - 1] !== root) { - parent.appendChild(root); - } - block = block.getParent(); - } while (block); -}; - -/** - * Set whether this block can chain onto the bottom of another block. - * @param {boolean} newBoolean True if there can be a previous statement. - * @param {(string|Array|null)=} opt_check Statement type or - * list of statement types. Null/undefined if any type could be connected. - */ -BlockSvg.prototype.setPreviousStatement = function(newBoolean, opt_check) { - BlockSvg.superClass_.setPreviousStatement.call(this, newBoolean, opt_check); - - if (this.rendered) { - this.render(); - this.bumpNeighbours(); - } -}; - -/** - * Set whether another block can chain onto the bottom of this block. - * @param {boolean} newBoolean True if there can be a next statement. - * @param {(string|Array|null)=} opt_check Statement type or - * list of statement types. Null/undefined if any type could be connected. - */ -BlockSvg.prototype.setNextStatement = function(newBoolean, opt_check) { - BlockSvg.superClass_.setNextStatement.call(this, newBoolean, opt_check); - - if (this.rendered) { - this.render(); - this.bumpNeighbours(); - } -}; - -/** - * Set whether this block returns a value. - * @param {boolean} newBoolean True if there is an output. - * @param {(string|Array|null)=} opt_check Returned type or list - * of returned types. Null or undefined if any type could be returned - * (e.g. variable get). - */ -BlockSvg.prototype.setOutput = function(newBoolean, opt_check) { - BlockSvg.superClass_.setOutput.call(this, newBoolean, opt_check); - - if (this.rendered) { - this.render(); - this.bumpNeighbours(); - } -}; - -/** - * Set whether value inputs are arranged horizontally or vertically. - * @param {boolean} newBoolean True if inputs are horizontal. - */ -BlockSvg.prototype.setInputsInline = function(newBoolean) { - BlockSvg.superClass_.setInputsInline.call(this, newBoolean); - - if (this.rendered) { - this.render(); - this.bumpNeighbours(); - } -}; - -/** - * Remove an input from this block. - * @param {string} name The name of the input. - * @param {boolean=} opt_quiet True to prevent error if input is not present. - * @return {boolean} True if operation succeeds, false if input is not present - * and opt_quiet is true - * @throws {Error} if the input is not present and opt_quiet is not true. - */ -BlockSvg.prototype.removeInput = function(name, opt_quiet) { - const removed = BlockSvg.superClass_.removeInput.call(this, name, opt_quiet); - - if (this.rendered) { - this.render(); - // Removing an input will cause the block to change shape. - this.bumpNeighbours(); - } - - return removed; -}; - -/** - * Move a numbered input to a different location on this block. - * @param {number} inputIndex Index of the input to move. - * @param {number} refIndex Index of input that should be after the moved input. - */ -BlockSvg.prototype.moveNumberedInputBefore = function(inputIndex, refIndex) { - BlockSvg.superClass_.moveNumberedInputBefore.call(this, inputIndex, refIndex); - - if (this.rendered) { - this.render(); - // Moving an input will cause the block to change shape. - this.bumpNeighbours(); - } -}; - -/** - * Add a value input, statement input or local variable to this block. - * @param {number} type One of Blockly.inputTypes. - * @param {string} name Language-neutral identifier which may used to find this - * input again. Should be unique to this block. - * @return {!Input} The input object created. - * @protected - * @override - */ -BlockSvg.prototype.appendInput_ = function(type, name) { - const input = BlockSvg.superClass_.appendInput_.call(this, type, name); - - if (this.rendered) { - this.render(); - // Adding an input will cause the block to change shape. - this.bumpNeighbours(); - } - return input; -}; - -/** - * Sets whether this block's connections are tracked in the database or not. - * - * Used by the deserializer to be more efficient. Setting a connection's - * tracked_ value to false keeps it from adding itself to the db when it - * gets its first moveTo call, saving expensive ops for later. - * @param {boolean} track If true, start tracking. If false, stop tracking. - * @package - */ -BlockSvg.prototype.setConnectionTracking = function(track) { - if (this.previousConnection) { - this.previousConnection.setTracking(track); - } - if (this.outputConnection) { - this.outputConnection.setTracking(track); - } - if (this.nextConnection) { - this.nextConnection.setTracking(track); - const child = this.nextConnection.targetBlock(); - if (child) { - child.setConnectionTracking(track); - } - } - - if (this.collapsed_) { - // When track is true, we don't want to start tracking collapsed - // connections. When track is false, we're already not tracking - // collapsed connections, so no need to update. - return; - } - - for (let i = 0; i < this.inputList.length; i++) { - const conn = this.inputList[i].connection; - if (conn) { - conn.setTracking(track); - - // Pass tracking on down the chain. - const block = conn.targetBlock(); - if (block) { - block.setConnectionTracking(track); - } - } - } -}; - -/** - * Returns connections originating from this block. - * @param {boolean} all If true, return all connections even hidden ones. - * Otherwise, for a non-rendered block return an empty list, and for a - * collapsed block don't return inputs connections. - * @return {!Array} Array of connections. - * @package - */ -BlockSvg.prototype.getConnections_ = function(all) { - const myConnections = []; - if (all || this.rendered) { - if (this.outputConnection) { - myConnections.push(this.outputConnection); - } - if (this.previousConnection) { - myConnections.push(this.previousConnection); - } - if (this.nextConnection) { - myConnections.push(this.nextConnection); - } - if (all || !this.collapsed_) { - for (let i = 0, input; (input = this.inputList[i]); i++) { - if (input.connection) { - myConnections.push(input.connection); - } - } - } - } - return myConnections; -}; - -/** - * Walks down a stack of blocks and finds the last next connection on the stack. - * @param {boolean} ignoreShadows If true,the last connection on a non-shadow - * block will be returned. If false, this will follow shadows to find the - * last connection. - * @return {?RenderedConnection} The last next connection on the stack, - * or null. - * @package - * @override - */ -BlockSvg.prototype.lastConnectionInStack = function(ignoreShadows) { - return /** @type {RenderedConnection} */ ( - BlockSvg.superClass_.lastConnectionInStack.call(this, ignoreShadows)); -}; - -/** - * Find the connection on this block that corresponds to the given connection - * on the other block. - * Used to match connections between a block and its insertion marker. - * @param {!Block} otherBlock The other block to match against. - * @param {!Connection} conn The other connection to match. - * @return {?RenderedConnection} The matching connection on this block, - * or null. - * @package - * @override - */ -BlockSvg.prototype.getMatchingConnection = function(otherBlock, conn) { - return /** @type {RenderedConnection} */ ( - BlockSvg.superClass_.getMatchingConnection.call(this, otherBlock, conn)); -}; - -/** - * Create a connection of the specified type. - * @param {number} type The type of the connection to create. - * @return {!RenderedConnection} A new connection of the specified type. - * @protected - */ -BlockSvg.prototype.makeConnection_ = function(type) { - return new RenderedConnection(this, type); -}; - -/** - * Bump unconnected blocks out of alignment. Two blocks which aren't actually - * connected should not coincidentally line up on screen. - */ -BlockSvg.prototype.bumpNeighbours = function() { - if (!this.workspace) { - return; // Deleted block. - } - if (this.workspace.isDragging()) { - return; // Don't bump blocks during a drag. - } - const rootBlock = this.getRootBlock(); - if (rootBlock.isInFlyout) { - return; // Don't move blocks around in a flyout. - } - // Loop through every connection on this block. - const myConnections = this.getConnections_(false); - for (let i = 0, connection; (connection = myConnections[i]); i++) { - // Spider down from this block bumping all sub-blocks. - if (connection.isConnected() && connection.isSuperior()) { - connection.targetBlock().bumpNeighbours(); - } - - const neighbours = connection.neighbours(internalConstants.SNAP_RADIUS); - for (let j = 0, otherConnection; (otherConnection = neighbours[j]); j++) { - // If both connections are connected, that's probably fine. But if - // either one of them is unconnected, then there could be confusion. - if (!connection.isConnected() || !otherConnection.isConnected()) { - // Only bump blocks if they are from different tree structures. - if (otherConnection.getSourceBlock().getRootBlock() !== rootBlock) { - // Always bump the inferior block. - if (connection.isSuperior()) { - otherConnection.bumpAwayFrom(connection); - } else { - connection.bumpAwayFrom(otherConnection); - } - } - } - } - } -}; - -/** - * Schedule snapping to grid and bumping neighbours to occur after a brief - * delay. - * @package - */ -BlockSvg.prototype.scheduleSnapAndBump = function() { - const block = this; - // Ensure that any snap and bump are part of this move's event group. - const group = eventUtils.getGroup(); - - setTimeout(function() { - eventUtils.setGroup(group); - block.snapToGrid(); - eventUtils.setGroup(false); - }, internalConstants.BUMP_DELAY / 2); - - setTimeout(function() { - eventUtils.setGroup(group); - block.bumpNeighbours(); - eventUtils.setGroup(false); - }, internalConstants.BUMP_DELAY); -}; - -/** - * Position a block so that it doesn't move the target block when connected. - * The block to position is usually either the first block in a dragged stack or - * an insertion marker. - * @param {!RenderedConnection} sourceConnection The connection on the - * moving block's stack. - * @param {!RenderedConnection} targetConnection The connection that - * should stay stationary as this block is positioned. - * @package - */ -BlockSvg.prototype.positionNearConnection = function( - sourceConnection, targetConnection) { - // We only need to position the new block if it's before the existing one, - // otherwise its position is set by the previous block. - if (sourceConnection.type === ConnectionType.NEXT_STATEMENT || - sourceConnection.type === ConnectionType.INPUT_VALUE) { - const dx = targetConnection.x - sourceConnection.x; - const dy = targetConnection.y - sourceConnection.y; - - this.moveBy(dx, dy); - } -}; - -/** - * Return the parent block or null if this block is at the top level. - * @return {?BlockSvg} The block (if any) that holds the current block. - * @override - */ -BlockSvg.prototype.getParent = function() { - return /** @type {!BlockSvg} */ (BlockSvg.superClass_.getParent.call(this)); -}; - -/** - * Return the top-most block in this block's tree. - * This will return itself if this block is at the top level. - * @return {!BlockSvg} The root block. - * @override - */ -BlockSvg.prototype.getRootBlock = function() { - return /** @type {!BlockSvg} */ ( - BlockSvg.superClass_.getRootBlock.call(this)); -}; - -/** - * Lays out and reflows a block based on its contents and settings. - * @param {boolean=} opt_bubble If false, just render this block. - * If true, also render block's parent, grandparent, etc. Defaults to true. - */ -BlockSvg.prototype.render = function(opt_bubble) { - if (this.renderIsInProgress_) { - return; // Don't allow recursive renders. - } - this.renderIsInProgress_ = true; - try { - this.rendered = true; - dom.startTextWidthCache(); - - if (this.isCollapsed()) { - this.updateCollapsed_(); - } - this.workspace.getRenderer().render(this); - this.updateConnectionLocations_(); - - if (opt_bubble !== false) { - const parentBlock = this.getParent(); - if (parentBlock) { - parentBlock.render(true); - } else { - // Top-most block. Fire an event to allow scrollbars to resize. - this.workspace.resizeContents(); - } - } - - dom.stopTextWidthCache(); - this.updateMarkers_(); - } finally { - this.renderIsInProgress_ = false; - } -}; - -/** - * Redraw any attached marker or cursor svgs if needed. - * @protected - */ -BlockSvg.prototype.updateMarkers_ = function() { - if (this.workspace.keyboardAccessibilityMode && this.pathObject.cursorSvg) { - this.workspace.getCursor().draw(); - } - if (this.workspace.keyboardAccessibilityMode && this.pathObject.markerSvg) { - // TODO(#4592): Update all markers on the block. - this.workspace.getMarker(MarkerManager.LOCAL_MARKER).draw(); - } -}; - -/** - * Update all of the connections on this block with the new locations calculated - * during rendering. Also move all of the connected blocks based on the new - * connection locations. - * @private - */ -BlockSvg.prototype.updateConnectionLocations_ = function() { - const blockTL = this.getRelativeToSurfaceXY(); - // Don't tighten previous or output connections because they are inferior - // connections. - if (this.previousConnection) { - this.previousConnection.moveToOffset(blockTL); - } - if (this.outputConnection) { - this.outputConnection.moveToOffset(blockTL); - } - - for (let i = 0; i < this.inputList.length; i++) { - const conn = this.inputList[i].connection; - if (conn) { - conn.moveToOffset(blockTL); - if (conn.isConnected()) { - conn.tighten(); - } - } - } - - if (this.nextConnection) { - this.nextConnection.moveToOffset(blockTL); - if (this.nextConnection.isConnected()) { - this.nextConnection.tighten(); - } - } -}; - -/** - * Add the cursor SVG to this block's SVG group. - * @param {SVGElement} cursorSvg The SVG root of the cursor to be added to the - * block SVG group. - * @package - */ -BlockSvg.prototype.setCursorSvg = function(cursorSvg) { - this.pathObject.setCursorSvg(cursorSvg); -}; - -/** - * Add the marker SVG to this block's SVG group. - * @param {SVGElement} markerSvg The SVG root of the marker to be added to the - * block SVG group. - * @package - */ -BlockSvg.prototype.setMarkerSvg = function(markerSvg) { - this.pathObject.setMarkerSvg(markerSvg); -}; - -/** - * Returns a bounding box describing the dimensions of this block - * and any blocks stacked below it. - * @return {!{height: number, width: number}} Object with height and width - * properties in workspace units. - * @package - */ -BlockSvg.prototype.getHeightWidth = function() { - let height = this.height; - let width = this.width; - // Recursively add size of subsequent blocks. - const nextBlock = this.getNextBlock(); - if (nextBlock) { - const nextHeightWidth = nextBlock.getHeightWidth(); - const workspace = /** @type {!WorkspaceSvg} */ (this.workspace); - const tabHeight = workspace.getRenderer().getConstants().NOTCH_HEIGHT; - height += nextHeightWidth.height - tabHeight; - width = Math.max(width, nextHeightWidth.width); - } - return {height: height, width: width}; -}; - -/** - * Visual effect to show that if the dragging block is dropped, this block will - * be replaced. If a shadow block, it will disappear. Otherwise it will bump. - * @param {boolean} add True if highlighting should be added. - * @package - */ -BlockSvg.prototype.fadeForReplacement = function(add) { - this.pathObject.updateReplacementFade(add); -}; - -/** - * Visual effect to show that if the dragging block is dropped it will connect - * to this input. - * @param {Connection} conn The connection on the input to highlight. - * @param {boolean} add True if highlighting should be added. - * @package - */ -BlockSvg.prototype.highlightShapeForInput = function(conn, add) { - this.pathObject.updateShapeForInputHighlight(conn, add); -}; - exports.BlockSvg = BlockSvg; diff --git a/core/blockly.js b/core/blockly.js index b916abd6e..b2099f6d2 100644 --- a/core/blockly.js +++ b/core/blockly.js @@ -40,6 +40,7 @@ const common = goog.require('Blockly.common'); const constants = goog.require('Blockly.constants'); const deprecation = goog.require('Blockly.utils.deprecation'); const dialog = goog.require('Blockly.dialog'); +const dropDownDiv = goog.require('Blockly.dropDownDiv'); const fieldRegistry = goog.require('Blockly.fieldRegistry'); const geras = goog.require('Blockly.geras'); const internalConstants = goog.require('Blockly.internalConstants'); @@ -71,6 +72,7 @@ const {Bubble} = goog.require('Blockly.Bubble'); const {CollapsibleToolboxCategory} = goog.require('Blockly.CollapsibleToolboxCategory'); const {Comment} = goog.require('Blockly.Comment'); const {ComponentManager} = goog.require('Blockly.ComponentManager'); +const {config} = goog.require('Blockly.config'); const {ConnectionChecker} = goog.require('Blockly.ConnectionChecker'); const {ConnectionDB} = goog.require('Blockly.ConnectionDB'); const {ConnectionType} = goog.require('Blockly.ConnectionType'); @@ -79,7 +81,6 @@ const {ContextMenuRegistry} = goog.require('Blockly.ContextMenuRegistry'); const {Cursor} = goog.require('Blockly.Cursor'); const {DeleteArea} = goog.require('Blockly.DeleteArea'); const {DragTarget} = goog.require('Blockly.DragTarget'); -const {DropDownDiv} = goog.require('Blockly.DropDownDiv'); const {FieldAngle} = goog.require('Blockly.FieldAngle'); const {FieldCheckbox} = goog.require('Blockly.FieldCheckbox'); const {FieldColour} = goog.require('Blockly.FieldColour'); @@ -306,7 +307,8 @@ exports.svgResize = common.svgResize; * @alias Blockly.hideChaff */ const hideChaff = function(opt_onlyClosePopups) { - common.getMainWorkspace().hideChaff(opt_onlyClosePopups); + /** @type {!WorkspaceSvg} */ (common.getMainWorkspace()) + .hideChaff(opt_onlyClosePopups); }; exports.hideChaff = hideChaff; @@ -331,7 +333,7 @@ exports.defineBlocksWithJsonArray = common.defineBlocksWithJsonArray; /** * Set the parent container. This is the container element that the WidgetDiv, - * DropDownDiv, and Tooltip are rendered into the first time `Blockly.inject` + * dropDownDiv, and Tooltip are rendered into the first time `Blockly.inject` * is called. * This method is a NOP if called after the first ``Blockly.inject``. * @param {!Element} container The container element. @@ -528,7 +530,7 @@ const paste = function() { deprecation.warn( 'Blockly.paste', 'December 2021', 'December 2022', 'Blockly.clipboard.paste'); - return clipboard.paste(); + return !!clipboard.paste(); }; exports.paste = paste; @@ -655,25 +657,8 @@ const bindEventWithChecks_ = function( exports.bindEventWithChecks_ = bindEventWithChecks_; // Aliases to allow external code to access these values for legacy reasons. -exports.LINE_MODE_MULTIPLIER = internalConstants.LINE_MODE_MULTIPLIER; -exports.PAGE_MODE_MULTIPLIER = internalConstants.PAGE_MODE_MULTIPLIER; -exports.DRAG_RADIUS = internalConstants.DRAG_RADIUS; -exports.FLYOUT_DRAG_RADIUS = internalConstants.FLYOUT_DRAG_RADIUS; -exports.SNAP_RADIUS = internalConstants.SNAP_RADIUS; -exports.CONNECTING_SNAP_RADIUS = internalConstants.CONNECTING_SNAP_RADIUS; -exports.CURRENT_CONNECTION_PREFERENCE = - internalConstants.CURRENT_CONNECTION_PREFERENCE; -exports.BUMP_DELAY = internalConstants.BUMP_DELAY; -exports.BUMP_RANDOMNESS = internalConstants.BUMP_RANDOMNESS; exports.COLLAPSE_CHARS = internalConstants.COLLAPSE_CHARS; -exports.LONGPRESS = internalConstants.LONGPRESS; -exports.SOUND_LIMIT = internalConstants.SOUND_LIMIT; exports.DRAG_STACK = internalConstants.DRAG_STACK; -exports.SPRITE = internalConstants.SPRITE; -exports.DRAG_NONE = internalConstants.DRAG_NONE; -exports.DRAG_STICKY = internalConstants.DRAG_STICKY; -exports.DRAG_BEGIN = internalConstants.DRAG_BEGIN; -exports.DRAG_FREE = internalConstants.DRAG_FREE; exports.OPPOSITE_TYPE = internalConstants.OPPOSITE_TYPE; exports.RENAME_VARIABLE_ID = internalConstants.RENAME_VARIABLE_ID; exports.DELETE_VARIABLE_ID = internalConstants.DELETE_VARIABLE_ID; @@ -731,7 +716,7 @@ exports.Css = Css; exports.Cursor = Cursor; exports.DeleteArea = DeleteArea; exports.DragTarget = DragTarget; -exports.DropDownDiv = DropDownDiv; +exports.DropDownDiv = dropDownDiv; exports.Events = Events; exports.Extensions = Extensions; exports.Field = Field; @@ -833,6 +818,7 @@ exports.browserEvents = browserEvents; exports.bumpObjects = bumpObjects; exports.clipboard = clipboard; exports.common = common; +exports.config = config; /** @deprecated Use Blockly.ConnectionType instead. */ exports.connectionTypes = ConnectionType; exports.constants = constants; diff --git a/core/blocks.js b/core/blocks.js index 0af6f6135..c29eaa96f 100644 --- a/core/blocks.js +++ b/core/blocks.js @@ -16,11 +16,18 @@ goog.module('Blockly.blocks'); +/** + * A block definition. For now this very lose, but it can potentially + * be refined e.g. by replacing this typedef with a class definition. + * @typedef {!Object} + */ +let BlockDefinition; +exports.BlockDefinition = BlockDefinition; + /** * A mapping of block type names to block prototype objects. - * @type {!Object} + * @type {!Object} * @alias Blockly.blocks.Blocks */ const Blocks = Object.create(null); - exports.Blocks = Blocks; diff --git a/core/browser_events.js b/core/browser_events.js index c4f16a4df..3a33cbbcc 100644 --- a/core/browser_events.js +++ b/core/browser_events.js @@ -16,7 +16,6 @@ goog.module('Blockly.browserEvents'); const Touch = goog.require('Blockly.Touch'); -const internalConstants = goog.require('Blockly.internalConstants'); const userAgent = goog.require('Blockly.utils.userAgent'); const {globalThis} = goog.require('Blockly.utils.global'); @@ -30,6 +29,24 @@ const {globalThis} = goog.require('Blockly.utils.global'); let Data; exports.Data = Data; +/** + * The multiplier for scroll wheel deltas using the line delta mode. + * See https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/deltaMode + * for more information on deltaMode. + * @type {number} + * @const + */ +const LINE_MODE_MULTIPLIER = 40; + +/** + * The multiplier for scroll wheel deltas using the page delta mode. + * See https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/deltaMode + * for more information on deltaMode. + * @type {number} + * @const + */ +const PAGE_MODE_MULTIPLIER = 125; + /** * Bind an event handler that can be ignored if it is not part of the active * touch stream. @@ -254,13 +271,13 @@ const getScrollDeltaPixels = function(e) { return {x: e.deltaX, y: e.deltaY}; case 0x01: // Line mode. return { - x: e.deltaX * internalConstants.LINE_MODE_MULTIPLIER, - y: e.deltaY * internalConstants.LINE_MODE_MULTIPLIER, + x: e.deltaX * LINE_MODE_MULTIPLIER, + y: e.deltaY * LINE_MODE_MULTIPLIER, }; case 0x02: // Page mode. return { - x: e.deltaX * internalConstants.PAGE_MODE_MULTIPLIER, - y: e.deltaY * internalConstants.PAGE_MODE_MULTIPLIER, + x: e.deltaX * PAGE_MODE_MULTIPLIER, + y: e.deltaY * PAGE_MODE_MULTIPLIER, }; } }; diff --git a/core/bubble.js b/core/bubble.js index d9e6df9f0..0ab9bc10b 100644 --- a/core/bubble.js +++ b/core/bubble.js @@ -40,81 +40,906 @@ goog.require('Blockly.Workspace'); /** * Class for UI bubble. - * @param {!WorkspaceSvg} workspace The workspace on which to draw the - * bubble. - * @param {!Element} content SVG content for the bubble. - * @param {!Element} shape SVG element to avoid eclipsing. - * @param {!Coordinate} anchorXY Absolute position of bubble's - * anchor point. - * @param {?number} bubbleWidth Width of bubble, or null if not resizable. - * @param {?number} bubbleHeight Height of bubble, or null if not resizable. * @implements {IBubble} - * @constructor * @alias Blockly.Bubble */ -const Bubble = function( - workspace, content, shape, anchorXY, bubbleWidth, bubbleHeight) { - this.workspace_ = workspace; - this.content_ = content; - this.shape_ = shape; +const Bubble = class { + /** + * @param {!WorkspaceSvg} workspace The workspace on which to draw the + * bubble. + * @param {!Element} content SVG content for the bubble. + * @param {!Element} shape SVG element to avoid eclipsing. + * @param {!Coordinate} anchorXY Absolute position of bubble's + * anchor point. + * @param {?number} bubbleWidth Width of bubble, or null if not resizable. + * @param {?number} bubbleHeight Height of bubble, or null if not resizable. + * @struct + */ + constructor(workspace, content, shape, anchorXY, bubbleWidth, bubbleHeight) { + this.workspace_ = workspace; + this.content_ = content; + this.shape_ = shape; + + /** + * Flag to stop incremental rendering during construction. + * @type {boolean} + * @private + */ + this.rendered_ = false; + + /** + * The SVG group containing all parts of the bubble. + * @type {SVGGElement} + * @private + */ + this.bubbleGroup_ = null; + + /** + * The SVG path for the arrow from the bubble to the icon on the block. + * @type {SVGPathElement} + * @private + */ + this.bubbleArrow_ = null; + + /** + * The SVG rect for the main body of the bubble. + * @type {SVGRectElement} + * @private + */ + this.bubbleBack_ = null; + + /** + * The SVG group for the resize hash marks on some bubbles. + * @type {SVGGElement} + * @private + */ + this.resizeGroup_ = null; + + /** + * Absolute coordinate of anchor point, in workspace coordinates. + * @type {Coordinate} + * @private + */ + this.anchorXY_ = null; + + /** + * Relative X coordinate of bubble with respect to the anchor's centre, + * in workspace units. + * In RTL mode the initial value is negated. + * @type {number} + * @private + */ + this.relativeLeft_ = 0; + + /** + * Relative Y coordinate of bubble with respect to the anchor's centre, in + * workspace units. + * @type {number} + * @private + */ + this.relativeTop_ = 0; + + /** + * Width of bubble, in workspace units. + * @type {number} + * @private + */ + this.width_ = 0; + + /** + * Height of bubble, in workspace units. + * @type {number} + * @private + */ + this.height_ = 0; + + /** + * Automatically position and reposition the bubble. + * @type {boolean} + * @private + */ + this.autoLayout_ = true; + + /** + * Method to call on resize of bubble. + * @type {?function()} + * @private + */ + this.resizeCallback_ = null; + + /** + * Method to call on move of bubble. + * @type {?function()} + * @private + */ + this.moveCallback_ = null; + + /** + * Mouse down on bubbleBack_ event data. + * @type {?browserEvents.Data} + * @private + */ + this.onMouseDownBubbleWrapper_ = null; + + /** + * Mouse down on resizeGroup_ event data. + * @type {?browserEvents.Data} + * @private + */ + this.onMouseDownResizeWrapper_ = null; + + /** + * Describes whether this bubble has been disposed of (nodes and event + * listeners removed from the page) or not. + * @type {boolean} + * @package + */ + this.disposed = false; + + let angle = Bubble.ARROW_ANGLE; + if (this.workspace_.RTL) { + angle = -angle; + } + this.arrow_radians_ = math.toRadians(angle); + + const canvas = workspace.getBubbleCanvas(); + canvas.appendChild( + this.createDom_(content, !!(bubbleWidth && bubbleHeight))); + + this.setAnchorLocation(anchorXY); + if (!bubbleWidth || !bubbleHeight) { + const bBox = /** @type {SVGLocatable} */ (this.content_).getBBox(); + bubbleWidth = bBox.width + 2 * Bubble.BORDER_WIDTH; + bubbleHeight = bBox.height + 2 * Bubble.BORDER_WIDTH; + } + this.setBubbleSize(bubbleWidth, bubbleHeight); + + // Render the bubble. + this.positionBubble_(); + this.renderArrow_(); + this.rendered_ = true; + } /** - * Method to call on resize of bubble. - * @type {?function()} + * Create the bubble's DOM. + * @param {!Element} content SVG content for the bubble. + * @param {boolean} hasResize Add diagonal resize gripper if true. + * @return {!SVGElement} The bubble's SVG group. * @private */ - this.resizeCallback_ = null; + createDom_(content, hasResize) { + /* Create the bubble. Here's the markup that will be generated: + + + + + + + + + + + [...content goes here...] + + */ + this.bubbleGroup_ = dom.createSvgElement(Svg.G, {}, null); + let filter = { + 'filter': 'url(#' + + this.workspace_.getRenderer().getConstants().embossFilterId + ')', + }; + if (userAgent.JAVA_FX) { + // Multiple reports that JavaFX can't handle filters. + // https://github.com/google/blockly/issues/99 + filter = {}; + } + const bubbleEmboss = dom.createSvgElement(Svg.G, filter, this.bubbleGroup_); + this.bubbleArrow_ = dom.createSvgElement(Svg.PATH, {}, bubbleEmboss); + this.bubbleBack_ = dom.createSvgElement( + Svg.RECT, { + 'class': 'blocklyDraggable', + 'x': 0, + 'y': 0, + 'rx': Bubble.BORDER_WIDTH, + 'ry': Bubble.BORDER_WIDTH, + }, + bubbleEmboss); + if (hasResize) { + this.resizeGroup_ = dom.createSvgElement( + Svg.G, { + 'class': this.workspace_.RTL ? 'blocklyResizeSW' : + 'blocklyResizeSE', + }, + this.bubbleGroup_); + const resizeSize = 2 * Bubble.BORDER_WIDTH; + dom.createSvgElement( + Svg.POLYGON, + {'points': '0,x x,x x,0'.replace(/x/g, resizeSize.toString())}, + this.resizeGroup_); + dom.createSvgElement( + Svg.LINE, { + 'class': 'blocklyResizeLine', + 'x1': resizeSize / 3, + 'y1': resizeSize - 1, + 'x2': resizeSize - 1, + 'y2': resizeSize / 3, + }, + this.resizeGroup_); + dom.createSvgElement( + Svg.LINE, { + 'class': 'blocklyResizeLine', + 'x1': resizeSize * 2 / 3, + 'y1': resizeSize - 1, + 'x2': resizeSize - 1, + 'y2': resizeSize * 2 / 3, + }, + this.resizeGroup_); + } else { + this.resizeGroup_ = null; + } + + if (!this.workspace_.options.readOnly) { + this.onMouseDownBubbleWrapper_ = browserEvents.conditionalBind( + this.bubbleBack_, 'mousedown', this, this.bubbleMouseDown_); + if (this.resizeGroup_) { + this.onMouseDownResizeWrapper_ = browserEvents.conditionalBind( + this.resizeGroup_, 'mousedown', this, this.resizeMouseDown_); + } + } + this.bubbleGroup_.appendChild(content); + return this.bubbleGroup_; + } /** - * Method to call on move of bubble. - * @type {?function()} + * Return the root node of the bubble's SVG group. + * @return {!SVGElement} The root SVG node of the bubble's group. + */ + getSvgRoot() { + return /** @type {!SVGElement} */ (this.bubbleGroup_); + } + + /** + * Expose the block's ID on the bubble's top-level SVG group. + * @param {string} id ID of block. + */ + setSvgId(id) { + if (this.bubbleGroup_.dataset) { + this.bubbleGroup_.dataset['blockId'] = id; + } + } + + /** + * Handle a mouse-down on bubble's border. + * @param {!Event} e Mouse down event. * @private */ - this.moveCallback_ = null; + bubbleMouseDown_(e) { + const gesture = this.workspace_.getGesture(e); + if (gesture) { + gesture.handleBubbleStart(e, this); + } + } /** - * Mouse down on bubbleBack_ event data. - * @type {?browserEvents.Data} - * @private - */ - this.onMouseDownBubbleWrapper_ = null; - - /** - * Mouse down on resizeGroup_ event data. - * @type {?browserEvents.Data} - * @private - */ - this.onMouseDownResizeWrapper_ = null; - - /** - * Describes whether this bubble has been disposed of (nodes and event - * listeners removed from the page) or not. - * @type {boolean} + * Show the context menu for this bubble. + * @param {!Event} _e Mouse event. * @package */ - this.disposed = false; - - let angle = Bubble.ARROW_ANGLE; - if (this.workspace_.RTL) { - angle = -angle; + showContextMenu(_e) { + // NOP on bubbles, but used by the bubble dragger to pass events to + // workspace comments. } - this.arrow_radians_ = math.toRadians(angle); - const canvas = workspace.getBubbleCanvas(); - canvas.appendChild(this.createDom_(content, !!(bubbleWidth && bubbleHeight))); - - this.setAnchorLocation(anchorXY); - if (!bubbleWidth || !bubbleHeight) { - const bBox = /** @type {SVGLocatable} */ (this.content_).getBBox(); - bubbleWidth = bBox.width + 2 * Bubble.BORDER_WIDTH; - bubbleHeight = bBox.height + 2 * Bubble.BORDER_WIDTH; + /** + * Get whether this bubble is deletable or not. + * @return {boolean} True if deletable. + * @package + */ + isDeletable() { + return false; } - this.setBubbleSize(bubbleWidth, bubbleHeight); - // Render the bubble. - this.positionBubble_(); - this.renderArrow_(); - this.rendered_ = true; + /** + * Update the style of this bubble when it is dragged over a delete area. + * @param {boolean} _enable True if the bubble is about to be deleted, false + * otherwise. + */ + setDeleteStyle(_enable) { + // NOP if bubble is not deletable. + } + + /** + * Handle a mouse-down on bubble's resize corner. + * @param {!Event} e Mouse down event. + * @private + */ + resizeMouseDown_(e) { + this.promote(); + Bubble.unbindDragEvents_(); + if (browserEvents.isRightButton(e)) { + // No right-click. + e.stopPropagation(); + return; + } + // Left-click (or middle click) + this.workspace_.startDrag( + e, + new Coordinate( + this.workspace_.RTL ? -this.width_ : this.width_, this.height_)); + + Bubble.onMouseUpWrapper_ = browserEvents.conditionalBind( + document, 'mouseup', this, Bubble.bubbleMouseUp_); + Bubble.onMouseMoveWrapper_ = browserEvents.conditionalBind( + document, 'mousemove', this, this.resizeMouseMove_); + this.workspace_.hideChaff(); + // This event has been handled. No need to bubble up to the document. + e.stopPropagation(); + } + + /** + * Resize this bubble to follow the mouse. + * @param {!Event} e Mouse move event. + * @private + */ + resizeMouseMove_(e) { + this.autoLayout_ = false; + const newXY = this.workspace_.moveDrag(e); + this.setBubbleSize(this.workspace_.RTL ? -newXY.x : newXY.x, newXY.y); + if (this.workspace_.RTL) { + // RTL requires the bubble to move its left edge. + this.positionBubble_(); + } + } + + /** + * Register a function as a callback event for when the bubble is resized. + * @param {!Function} callback The function to call on resize. + */ + registerResizeEvent(callback) { + this.resizeCallback_ = callback; + } + + /** + * Register a function as a callback event for when the bubble is moved. + * @param {!Function} callback The function to call on move. + */ + registerMoveEvent(callback) { + this.moveCallback_ = callback; + } + + /** + * Move this bubble to the top of the stack. + * @return {boolean} Whether or not the bubble has been moved. + * @package + */ + promote() { + const svgGroup = this.bubbleGroup_.parentNode; + if (svgGroup.lastChild !== this.bubbleGroup_) { + svgGroup.appendChild(this.bubbleGroup_); + return true; + } + return false; + } + + /** + * Notification that the anchor has moved. + * Update the arrow and bubble accordingly. + * @param {!Coordinate} xy Absolute location. + */ + setAnchorLocation(xy) { + this.anchorXY_ = xy; + if (this.rendered_) { + this.positionBubble_(); + } + } + + /** + * Position the bubble so that it does not fall off-screen. + * @private + */ + layoutBubble_() { + // Get the metrics in workspace units. + const viewMetrics = + this.workspace_.getMetricsManager().getViewMetrics(true); + + const optimalLeft = this.getOptimalRelativeLeft_(viewMetrics); + const optimalTop = this.getOptimalRelativeTop_(viewMetrics); + const bbox = this.shape_.getBBox(); + + const topPosition = { + x: optimalLeft, + y: -this.height_ - + this.workspace_.getRenderer().getConstants().MIN_BLOCK_HEIGHT, + }; + const startPosition = {x: -this.width_ - 30, y: optimalTop}; + const endPosition = {x: bbox.width, y: optimalTop}; + const bottomPosition = {x: optimalLeft, y: bbox.height}; + + const closerPosition = + bbox.width < bbox.height ? endPosition : bottomPosition; + const fartherPosition = + bbox.width < bbox.height ? bottomPosition : endPosition; + + const topPositionOverlap = this.getOverlap_(topPosition, viewMetrics); + const startPositionOverlap = this.getOverlap_(startPosition, viewMetrics); + const closerPositionOverlap = this.getOverlap_(closerPosition, viewMetrics); + const fartherPositionOverlap = + this.getOverlap_(fartherPosition, viewMetrics); + + // Set the position to whichever position shows the most of the bubble, + // with tiebreaks going in the order: top > start > close > far. + const mostOverlap = Math.max( + topPositionOverlap, startPositionOverlap, closerPositionOverlap, + fartherPositionOverlap); + if (topPositionOverlap === mostOverlap) { + this.relativeLeft_ = topPosition.x; + this.relativeTop_ = topPosition.y; + return; + } + if (startPositionOverlap === mostOverlap) { + this.relativeLeft_ = startPosition.x; + this.relativeTop_ = startPosition.y; + return; + } + if (closerPositionOverlap === mostOverlap) { + this.relativeLeft_ = closerPosition.x; + this.relativeTop_ = closerPosition.y; + return; + } + // TODO: I believe relativeLeft_ should actually be called relativeStart_ + // and then the math should be fixed to reflect this. (hopefully it'll + // make it look simpler) + this.relativeLeft_ = fartherPosition.x; + this.relativeTop_ = fartherPosition.y; + } + + /** + * Calculate the what percentage of the bubble overlaps with the visible + * workspace (what percentage of the bubble is visible). + * @param {!{x: number, y: number}} relativeMin The position of the top-left + * corner of the bubble relative to the anchor point. + * @param {!MetricsManager.ContainerRegion} viewMetrics The view metrics + * of the workspace the bubble will appear in. + * @return {number} The percentage of the bubble that is visible. + * @private + */ + getOverlap_(relativeMin, viewMetrics) { + // The position of the top-left corner of the bubble in workspace units. + const bubbleMin = { + x: this.workspace_.RTL ? + (this.anchorXY_.x - relativeMin.x - this.width_) : + (relativeMin.x + this.anchorXY_.x), + y: relativeMin.y + this.anchorXY_.y, + }; + // The position of the bottom-right corner of the bubble in workspace units. + const bubbleMax = { + x: bubbleMin.x + this.width_, + y: bubbleMin.y + this.height_, + }; + + // We could adjust these values to account for the scrollbars, but the + // bubbles should have been adjusted to not collide with them anyway, so + // giving the workspace a slightly larger "bounding box" shouldn't affect + // the calculation. + + // The position of the top-left corner of the workspace. + const workspaceMin = {x: viewMetrics.left, y: viewMetrics.top}; + // The position of the bottom-right corner of the workspace. + const workspaceMax = { + x: viewMetrics.left + viewMetrics.width, + y: viewMetrics.top + viewMetrics.height, + }; + + const overlapWidth = Math.min(bubbleMax.x, workspaceMax.x) - + Math.max(bubbleMin.x, workspaceMin.x); + const overlapHeight = Math.min(bubbleMax.y, workspaceMax.y) - + Math.max(bubbleMin.y, workspaceMin.y); + return Math.max( + 0, + Math.min( + 1, (overlapWidth * overlapHeight) / (this.width_ * this.height_))); + } + + /** + * Calculate what the optimal horizontal position of the top-left corner of + * the bubble is (relative to the anchor point) so that the most area of the + * bubble is shown. + * @param {!MetricsManager.ContainerRegion} viewMetrics The view metrics + * of the workspace the bubble will appear in. + * @return {number} The optimal horizontal position of the top-left corner + * of the bubble. + * @private + */ + getOptimalRelativeLeft_(viewMetrics) { + let relativeLeft = -this.width_ / 4; + + // No amount of sliding left or right will give us a better overlap. + if (this.width_ > viewMetrics.width) { + return relativeLeft; + } + + if (this.workspace_.RTL) { + // Bubble coordinates are flipped in RTL. + const bubbleRight = this.anchorXY_.x - relativeLeft; + const bubbleLeft = bubbleRight - this.width_; + + const workspaceRight = viewMetrics.left + viewMetrics.width; + const workspaceLeft = viewMetrics.left + + // Thickness in workspace units. + (Scrollbar.scrollbarThickness / this.workspace_.scale); + + if (bubbleLeft < workspaceLeft) { + // Slide the bubble right until it is onscreen. + relativeLeft = -(workspaceLeft - this.anchorXY_.x + this.width_); + } else if (bubbleRight > workspaceRight) { + // Slide the bubble left until it is onscreen. + relativeLeft = -(workspaceRight - this.anchorXY_.x); + } + } else { + const bubbleLeft = relativeLeft + this.anchorXY_.x; + const bubbleRight = bubbleLeft + this.width_; + + const workspaceLeft = viewMetrics.left; + const workspaceRight = viewMetrics.left + viewMetrics.width - + // Thickness in workspace units. + (Scrollbar.scrollbarThickness / this.workspace_.scale); + + if (bubbleLeft < workspaceLeft) { + // Slide the bubble right until it is onscreen. + relativeLeft = workspaceLeft - this.anchorXY_.x; + } else if (bubbleRight > workspaceRight) { + // Slide the bubble left until it is onscreen. + relativeLeft = workspaceRight - this.anchorXY_.x - this.width_; + } + } + + return relativeLeft; + } + + /** + * Calculate what the optimal vertical position of the top-left corner of + * the bubble is (relative to the anchor point) so that the most area of the + * bubble is shown. + * @param {!MetricsManager.ContainerRegion} viewMetrics The view metrics + * of the workspace the bubble will appear in. + * @return {number} The optimal vertical position of the top-left corner + * of the bubble. + * @private + */ + getOptimalRelativeTop_(viewMetrics) { + let relativeTop = -this.height_ / 4; + + // No amount of sliding up or down will give us a better overlap. + if (this.height_ > viewMetrics.height) { + return relativeTop; + } + + const bubbleTop = this.anchorXY_.y + relativeTop; + const bubbleBottom = bubbleTop + this.height_; + const workspaceTop = viewMetrics.top; + const workspaceBottom = viewMetrics.top + viewMetrics.height - + // Thickness in workspace units. + (Scrollbar.scrollbarThickness / this.workspace_.scale); + + const anchorY = this.anchorXY_.y; + if (bubbleTop < workspaceTop) { + // Slide the bubble down until it is onscreen. + relativeTop = workspaceTop - anchorY; + } else if (bubbleBottom > workspaceBottom) { + // Slide the bubble up until it is onscreen. + relativeTop = workspaceBottom - anchorY - this.height_; + } + + return relativeTop; + } + + /** + * Move the bubble to a location relative to the anchor's centre. + * @private + */ + positionBubble_() { + let left = this.anchorXY_.x; + if (this.workspace_.RTL) { + left -= this.relativeLeft_ + this.width_; + } else { + left += this.relativeLeft_; + } + const top = this.relativeTop_ + this.anchorXY_.y; + 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 + */ + moveTo(x, y) { + this.bubbleGroup_.setAttribute( + 'transform', 'translate(' + x + ',' + y + ')'); + } + + /** + * Triggers a move callback if one exists at the end of a drag. + * @param {boolean} adding True if adding, false if removing. + * @package + */ + setDragging(adding) { + if (!adding && this.moveCallback_) { + this.moveCallback_(); + } + } + + /** + * Get the dimensions of this bubble. + * @return {!Size} The height and width of the bubble. + */ + getBubbleSize() { + return new Size(this.width_, this.height_); + } + + /** + * Size this bubble. + * @param {number} width Width of the bubble. + * @param {number} height Height of the bubble. + */ + setBubbleSize(width, height) { + const doubleBorderWidth = 2 * Bubble.BORDER_WIDTH; + // Minimum size of a bubble. + width = Math.max(width, doubleBorderWidth + 45); + height = Math.max(height, doubleBorderWidth + 20); + this.width_ = width; + this.height_ = height; + this.bubbleBack_.setAttribute('width', width); + this.bubbleBack_.setAttribute('height', height); + if (this.resizeGroup_) { + if (this.workspace_.RTL) { + // Mirror the resize group. + const resizeSize = 2 * Bubble.BORDER_WIDTH; + this.resizeGroup_.setAttribute( + 'transform', + 'translate(' + resizeSize + ',' + (height - doubleBorderWidth) + + ') scale(-1 1)'); + } else { + this.resizeGroup_.setAttribute( + 'transform', + 'translate(' + (width - doubleBorderWidth) + ',' + + (height - doubleBorderWidth) + ')'); + } + } + if (this.autoLayout_) { + this.layoutBubble_(); + } + this.positionBubble_(); + this.renderArrow_(); + + // Allow the contents to resize. + if (this.resizeCallback_) { + this.resizeCallback_(); + } + } + + /** + * Draw the arrow between the bubble and the origin. + * @private + */ + renderArrow_() { + const steps = []; + // Find the relative coordinates of the center of the bubble. + const relBubbleX = this.width_ / 2; + const relBubbleY = this.height_ / 2; + // Find the relative coordinates of the center of the anchor. + let relAnchorX = -this.relativeLeft_; + let relAnchorY = -this.relativeTop_; + if (relBubbleX === relAnchorX && relBubbleY === relAnchorY) { + // Null case. Bubble is directly on top of the anchor. + // Short circuit this rather than wade through divide by zeros. + steps.push('M ' + relBubbleX + ',' + relBubbleY); + } else { + // Compute the angle of the arrow's line. + const rise = relAnchorY - relBubbleY; + let run = relAnchorX - relBubbleX; + if (this.workspace_.RTL) { + run *= -1; + } + const hypotenuse = Math.sqrt(rise * rise + run * run); + let angle = Math.acos(run / hypotenuse); + if (rise < 0) { + angle = 2 * Math.PI - angle; + } + // Compute a line perpendicular to the arrow. + let rightAngle = angle + Math.PI / 2; + if (rightAngle > Math.PI * 2) { + rightAngle -= Math.PI * 2; + } + const rightRise = Math.sin(rightAngle); + const rightRun = Math.cos(rightAngle); + + // Calculate the thickness of the base of the arrow. + const bubbleSize = this.getBubbleSize(); + let thickness = + (bubbleSize.width + bubbleSize.height) / Bubble.ARROW_THICKNESS; + thickness = Math.min(thickness, bubbleSize.width, bubbleSize.height) / 4; + + // Back the tip of the arrow off of the anchor. + const backoffRatio = 1 - Bubble.ANCHOR_RADIUS / hypotenuse; + relAnchorX = relBubbleX + backoffRatio * run; + relAnchorY = relBubbleY + backoffRatio * rise; + + // Coordinates for the base of the arrow. + const baseX1 = relBubbleX + thickness * rightRun; + const baseY1 = relBubbleY + thickness * rightRise; + const baseX2 = relBubbleX - thickness * rightRun; + const baseY2 = relBubbleY - thickness * rightRise; + + // Distortion to curve the arrow. + let swirlAngle = angle + this.arrow_radians_; + if (swirlAngle > Math.PI * 2) { + swirlAngle -= Math.PI * 2; + } + const swirlRise = Math.sin(swirlAngle) * hypotenuse / Bubble.ARROW_BEND; + const swirlRun = Math.cos(swirlAngle) * hypotenuse / Bubble.ARROW_BEND; + + steps.push('M' + baseX1 + ',' + baseY1); + steps.push( + 'C' + (baseX1 + swirlRun) + ',' + (baseY1 + swirlRise) + ' ' + + relAnchorX + ',' + relAnchorY + ' ' + relAnchorX + ',' + relAnchorY); + steps.push( + 'C' + relAnchorX + ',' + relAnchorY + ' ' + (baseX2 + swirlRun) + + ',' + (baseY2 + swirlRise) + ' ' + baseX2 + ',' + baseY2); + } + steps.push('z'); + this.bubbleArrow_.setAttribute('d', steps.join(' ')); + } + + /** + * Change the colour of a bubble. + * @param {string} hexColour Hex code of colour. + */ + setColour(hexColour) { + this.bubbleBack_.setAttribute('fill', hexColour); + this.bubbleArrow_.setAttribute('fill', hexColour); + } + + /** + * Dispose of this bubble. + */ + dispose() { + if (this.onMouseDownBubbleWrapper_) { + browserEvents.unbind(this.onMouseDownBubbleWrapper_); + } + if (this.onMouseDownResizeWrapper_) { + browserEvents.unbind(this.onMouseDownResizeWrapper_); + } + Bubble.unbindDragEvents_(); + dom.removeNode(this.bubbleGroup_); + this.disposed = true; + } + + /** + * Move this bubble during a drag, taking into account whether or not there is + * a drag surface. + * @param {BlockDragSurfaceSvg} dragSurface The surface that carries + * rendered items during a drag, or null if no drag surface is in use. + * @param {!Coordinate} newLoc The location to translate to, in + * workspace coordinates. + * @package + */ + moveDuringDrag(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 {!Coordinate} Object with .x and .y properties. + */ + getRelativeToSurfaceXY() { + return new Coordinate( + this.workspace_.RTL ? + -this.relativeLeft_ + this.anchorXY_.x - this.width_ : + 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 + */ + setAutoLayout(enable) { + this.autoLayout_ = enable; + } + + /** + * Stop binding to the global mouseup and mousemove events. + * @private + */ + static unbindDragEvents_() { + if (Bubble.onMouseUpWrapper_) { + browserEvents.unbind(Bubble.onMouseUpWrapper_); + Bubble.onMouseUpWrapper_ = null; + } + if (Bubble.onMouseMoveWrapper_) { + browserEvents.unbind(Bubble.onMouseMoveWrapper_); + Bubble.onMouseMoveWrapper_ = null; + } + } + + /** + * Handle a mouse-up event while dragging a bubble's border or resize handle. + * @param {!Event} _e Mouse up event. + * @private + */ + static bubbleMouseUp_(_e) { + Touch.clearTouchIdentifier(); + Bubble.unbindDragEvents_(); + } + + /** + * Create the text for a non editable bubble. + * @param {string} text The text to display. + * @return {!SVGTextElement} The top-level node of the text. + * @package + */ + static textToDom(text) { + const paragraph = dom.createSvgElement( + Svg.TEXT, { + 'class': 'blocklyText blocklyBubbleText blocklyNoPointerEvents', + 'y': Bubble.BORDER_WIDTH, + }, + null); + const lines = text.split('\n'); + for (let i = 0; i < lines.length; i++) { + const tspanElement = dom.createSvgElement( + Svg.TSPAN, {'dy': '1em', 'x': Bubble.BORDER_WIDTH}, paragraph); + const textNode = document.createTextNode(lines[i]); + tspanElement.appendChild(textNode); + } + return paragraph; + } + + /** + * Creates a bubble that can not be edited. + * @param {!SVGTextElement} paragraphElement The text element for the non + * editable bubble. + * @param {!BlockSvg} block The block that the bubble is attached to. + * @param {!Coordinate} iconXY The coordinate of the icon. + * @return {!Bubble} The non editable bubble. + * @package + */ + static createNonEditableBubble(paragraphElement, block, iconXY) { + const bubble = new Bubble( + /** @type {!WorkspaceSvg} */ (block.workspace), paragraphElement, + block.pathObject.svgPath, + /** @type {!Coordinate} */ (iconXY), null, null); + // Expose this bubble's block's ID on its top-level SVG group. + bubble.setSvgId(block.id); + if (block.RTL) { + // Right-align the paragraph. + // This cannot be done until the bubble is rendered on screen. + const maxWidth = paragraphElement.getBBox().width; + for (let i = 0, textElement; + (textElement = paragraphElement.childNodes[i]); i++) { + textElement.setAttribute('text-anchor', 'end'); + textElement.setAttribute('x', maxWidth + Bubble.BORDER_WIDTH); + } + } + return bubble; + } }; /** @@ -157,785 +982,4 @@ Bubble.onMouseUpWrapper_ = null; */ Bubble.onMouseMoveWrapper_ = null; -/** - * Stop binding to the global mouseup and mousemove events. - * @private - */ -Bubble.unbindDragEvents_ = function() { - if (Bubble.onMouseUpWrapper_) { - browserEvents.unbind(Bubble.onMouseUpWrapper_); - Bubble.onMouseUpWrapper_ = null; - } - if (Bubble.onMouseMoveWrapper_) { - browserEvents.unbind(Bubble.onMouseMoveWrapper_); - Bubble.onMouseMoveWrapper_ = null; - } -}; - -/** - * Handle a mouse-up event while dragging a bubble's border or resize handle. - * @param {!Event} _e Mouse up event. - * @private - */ -Bubble.bubbleMouseUp_ = function(_e) { - Touch.clearTouchIdentifier(); - Bubble.unbindDragEvents_(); -}; - -/** - * Flag to stop incremental rendering during construction. - * @private - */ -Bubble.prototype.rendered_ = false; - -/** - * Absolute coordinate of anchor point, in workspace coordinates. - * @type {Coordinate} - * @private - */ -Bubble.prototype.anchorXY_ = null; - -/** - * Relative X coordinate of bubble with respect to the anchor's centre, - * in workspace units. - * In RTL mode the initial value is negated. - * @private - */ -Bubble.prototype.relativeLeft_ = 0; - -/** - * Relative Y coordinate of bubble with respect to the anchor's centre, in - * workspace units. - * @private - */ -Bubble.prototype.relativeTop_ = 0; - -/** - * Width of bubble, in workspace units. - * @private - */ -Bubble.prototype.width_ = 0; - -/** - * Height of bubble, in workspace units. - * @private - */ -Bubble.prototype.height_ = 0; - -/** - * Automatically position and reposition the bubble. - * @private - */ -Bubble.prototype.autoLayout_ = true; - -/** - * Create the bubble's DOM. - * @param {!Element} content SVG content for the bubble. - * @param {boolean} hasResize Add diagonal resize gripper if true. - * @return {!SVGElement} The bubble's SVG group. - * @private - */ -Bubble.prototype.createDom_ = function(content, hasResize) { - /* Create the bubble. Here's the markup that will be generated: - - - - - - - - - - - [...content goes here...] - - */ - this.bubbleGroup_ = dom.createSvgElement(Svg.G, {}, null); - let filter = { - 'filter': 'url(#' + - this.workspace_.getRenderer().getConstants().embossFilterId + ')', - }; - if (userAgent.JAVA_FX) { - // Multiple reports that JavaFX can't handle filters. - // https://github.com/google/blockly/issues/99 - filter = {}; - } - const bubbleEmboss = dom.createSvgElement(Svg.G, filter, this.bubbleGroup_); - this.bubbleArrow_ = dom.createSvgElement(Svg.PATH, {}, bubbleEmboss); - this.bubbleBack_ = dom.createSvgElement( - Svg.RECT, { - 'class': 'blocklyDraggable', - 'x': 0, - 'y': 0, - 'rx': Bubble.BORDER_WIDTH, - 'ry': Bubble.BORDER_WIDTH, - }, - bubbleEmboss); - if (hasResize) { - this.resizeGroup_ = dom.createSvgElement( - Svg.G, - {'class': this.workspace_.RTL ? 'blocklyResizeSW' : 'blocklyResizeSE'}, - this.bubbleGroup_); - const resizeSize = 2 * Bubble.BORDER_WIDTH; - dom.createSvgElement( - Svg.POLYGON, - {'points': '0,x x,x x,0'.replace(/x/g, resizeSize.toString())}, - this.resizeGroup_); - dom.createSvgElement( - Svg.LINE, { - 'class': 'blocklyResizeLine', - 'x1': resizeSize / 3, - 'y1': resizeSize - 1, - 'x2': resizeSize - 1, - 'y2': resizeSize / 3, - }, - this.resizeGroup_); - dom.createSvgElement( - Svg.LINE, { - 'class': 'blocklyResizeLine', - 'x1': resizeSize * 2 / 3, - 'y1': resizeSize - 1, - 'x2': resizeSize - 1, - 'y2': resizeSize * 2 / 3, - }, - this.resizeGroup_); - } else { - this.resizeGroup_ = null; - } - - if (!this.workspace_.options.readOnly) { - this.onMouseDownBubbleWrapper_ = browserEvents.conditionalBind( - this.bubbleBack_, 'mousedown', this, this.bubbleMouseDown_); - if (this.resizeGroup_) { - this.onMouseDownResizeWrapper_ = browserEvents.conditionalBind( - this.resizeGroup_, 'mousedown', this, this.resizeMouseDown_); - } - } - this.bubbleGroup_.appendChild(content); - return this.bubbleGroup_; -}; - -/** - * Return the root node of the bubble's SVG group. - * @return {!SVGElement} The root SVG node of the bubble's group. - */ -Bubble.prototype.getSvgRoot = function() { - return this.bubbleGroup_; -}; - -/** - * Expose the block's ID on the bubble's top-level SVG group. - * @param {string} id ID of block. - */ -Bubble.prototype.setSvgId = function(id) { - if (this.bubbleGroup_.dataset) { - this.bubbleGroup_.dataset['blockId'] = id; - } -}; - -/** - * Handle a mouse-down on bubble's border. - * @param {!Event} e Mouse down event. - * @private - */ -Bubble.prototype.bubbleMouseDown_ = function(e) { - const gesture = this.workspace_.getGesture(e); - if (gesture) { - gesture.handleBubbleStart(e, this); - } -}; - -/** - * Show the context menu for this bubble. - * @param {!Event} _e Mouse event. - * @package - */ -Bubble.prototype.showContextMenu = function(_e) { - // NOP on bubbles, but used by the bubble dragger to pass events to - // workspace comments. -}; - -/** - * Get whether this bubble is deletable or not. - * @return {boolean} True if deletable. - * @package - */ -Bubble.prototype.isDeletable = function() { - return false; -}; - -/** - * Update the style of this bubble when it is dragged over a delete area. - * @param {boolean} _enable True if the bubble is about to be deleted, false - * otherwise. - */ -Bubble.prototype.setDeleteStyle = function(_enable) { - // NOP if bubble is not deletable. -}; - -/** - * Handle a mouse-down on bubble's resize corner. - * @param {!Event} e Mouse down event. - * @private - */ -Bubble.prototype.resizeMouseDown_ = function(e) { - this.promote(); - Bubble.unbindDragEvents_(); - if (browserEvents.isRightButton(e)) { - // No right-click. - e.stopPropagation(); - return; - } - // Left-click (or middle click) - this.workspace_.startDrag( - e, - new Coordinate( - this.workspace_.RTL ? -this.width_ : this.width_, this.height_)); - - Bubble.onMouseUpWrapper_ = browserEvents.conditionalBind( - document, 'mouseup', this, Bubble.bubbleMouseUp_); - Bubble.onMouseMoveWrapper_ = browserEvents.conditionalBind( - document, 'mousemove', this, this.resizeMouseMove_); - this.workspace_.hideChaff(); - // This event has been handled. No need to bubble up to the document. - e.stopPropagation(); -}; - -/** - * Resize this bubble to follow the mouse. - * @param {!Event} e Mouse move event. - * @private - */ -Bubble.prototype.resizeMouseMove_ = function(e) { - this.autoLayout_ = false; - const newXY = this.workspace_.moveDrag(e); - this.setBubbleSize(this.workspace_.RTL ? -newXY.x : newXY.x, newXY.y); - if (this.workspace_.RTL) { - // RTL requires the bubble to move its left edge. - this.positionBubble_(); - } -}; - -/** - * Register a function as a callback event for when the bubble is resized. - * @param {!Function} callback The function to call on resize. - */ -Bubble.prototype.registerResizeEvent = function(callback) { - this.resizeCallback_ = callback; -}; - -/** - * Register a function as a callback event for when the bubble is moved. - * @param {!Function} callback The function to call on move. - */ -Bubble.prototype.registerMoveEvent = function(callback) { - this.moveCallback_ = callback; -}; - -/** - * Move this bubble to the top of the stack. - * @return {boolean} Whether or not the bubble has been moved. - * @package - */ -Bubble.prototype.promote = function() { - const svgGroup = this.bubbleGroup_.parentNode; - if (svgGroup.lastChild !== this.bubbleGroup_) { - svgGroup.appendChild(this.bubbleGroup_); - return true; - } - return false; -}; - -/** - * Notification that the anchor has moved. - * Update the arrow and bubble accordingly. - * @param {!Coordinate} xy Absolute location. - */ -Bubble.prototype.setAnchorLocation = function(xy) { - this.anchorXY_ = xy; - if (this.rendered_) { - this.positionBubble_(); - } -}; - -/** - * Position the bubble so that it does not fall off-screen. - * @private - */ -Bubble.prototype.layoutBubble_ = function() { - // Get the metrics in workspace units. - const viewMetrics = this.workspace_.getMetricsManager().getViewMetrics(true); - - const optimalLeft = this.getOptimalRelativeLeft_(viewMetrics); - const optimalTop = this.getOptimalRelativeTop_(viewMetrics); - const bbox = this.shape_.getBBox(); - - const topPosition = { - x: optimalLeft, - y: -this.height_ - - this.workspace_.getRenderer().getConstants().MIN_BLOCK_HEIGHT, - }; - const startPosition = {x: -this.width_ - 30, y: optimalTop}; - const endPosition = {x: bbox.width, y: optimalTop}; - const bottomPosition = {x: optimalLeft, y: bbox.height}; - - const closerPosition = - bbox.width < bbox.height ? endPosition : bottomPosition; - const fartherPosition = - bbox.width < bbox.height ? bottomPosition : endPosition; - - const topPositionOverlap = this.getOverlap_(topPosition, viewMetrics); - const startPositionOverlap = this.getOverlap_(startPosition, viewMetrics); - const closerPositionOverlap = this.getOverlap_(closerPosition, viewMetrics); - const fartherPositionOverlap = this.getOverlap_(fartherPosition, viewMetrics); - - // Set the position to whichever position shows the most of the bubble, - // with tiebreaks going in the order: top > start > close > far. - const mostOverlap = Math.max( - topPositionOverlap, startPositionOverlap, closerPositionOverlap, - fartherPositionOverlap); - if (topPositionOverlap === mostOverlap) { - this.relativeLeft_ = topPosition.x; - this.relativeTop_ = topPosition.y; - return; - } - if (startPositionOverlap === mostOverlap) { - this.relativeLeft_ = startPosition.x; - this.relativeTop_ = startPosition.y; - return; - } - if (closerPositionOverlap === mostOverlap) { - this.relativeLeft_ = closerPosition.x; - this.relativeTop_ = closerPosition.y; - return; - } - // TODO: I believe relativeLeft_ should actually be called relativeStart_ - // and then the math should be fixed to reflect this. (hopefully it'll - // make it look simpler) - this.relativeLeft_ = fartherPosition.x; - this.relativeTop_ = fartherPosition.y; -}; - -/** - * Calculate the what percentage of the bubble overlaps with the visible - * workspace (what percentage of the bubble is visible). - * @param {!{x: number, y: number}} relativeMin The position of the top-left - * corner of the bubble relative to the anchor point. - * @param {!MetricsManager.ContainerRegion} viewMetrics The view metrics - * of the workspace the bubble will appear in. - * @return {number} The percentage of the bubble that is visible. - * @private - */ -Bubble.prototype.getOverlap_ = function(relativeMin, viewMetrics) { - // The position of the top-left corner of the bubble in workspace units. - const bubbleMin = { - x: this.workspace_.RTL ? (this.anchorXY_.x - relativeMin.x - this.width_) : - (relativeMin.x + this.anchorXY_.x), - y: relativeMin.y + this.anchorXY_.y, - }; - // The position of the bottom-right corner of the bubble in workspace units. - const bubbleMax = { - x: bubbleMin.x + this.width_, - y: bubbleMin.y + this.height_, - }; - - // We could adjust these values to account for the scrollbars, but the - // bubbles should have been adjusted to not collide with them anyway, so - // giving the workspace a slightly larger "bounding box" shouldn't affect the - // calculation. - - // The position of the top-left corner of the workspace. - const workspaceMin = {x: viewMetrics.left, y: viewMetrics.top}; - // The position of the bottom-right corner of the workspace. - const workspaceMax = { - x: viewMetrics.left + viewMetrics.width, - y: viewMetrics.top + viewMetrics.height, - }; - - const overlapWidth = Math.min(bubbleMax.x, workspaceMax.x) - - Math.max(bubbleMin.x, workspaceMin.x); - const overlapHeight = Math.min(bubbleMax.y, workspaceMax.y) - - Math.max(bubbleMin.y, workspaceMin.y); - return Math.max( - 0, - Math.min( - 1, (overlapWidth * overlapHeight) / (this.width_ * this.height_))); -}; - -/** - * Calculate what the optimal horizontal position of the top-left corner of the - * bubble is (relative to the anchor point) so that the most area of the - * bubble is shown. - * @param {!MetricsManager.ContainerRegion} viewMetrics The view metrics - * of the workspace the bubble will appear in. - * @return {number} The optimal horizontal position of the top-left corner - * of the bubble. - * @private - */ -Bubble.prototype.getOptimalRelativeLeft_ = function(viewMetrics) { - let relativeLeft = -this.width_ / 4; - - // No amount of sliding left or right will give us a better overlap. - if (this.width_ > viewMetrics.width) { - return relativeLeft; - } - - if (this.workspace_.RTL) { - // Bubble coordinates are flipped in RTL. - const bubbleRight = this.anchorXY_.x - relativeLeft; - const bubbleLeft = bubbleRight - this.width_; - - const workspaceRight = viewMetrics.left + viewMetrics.width; - const workspaceLeft = viewMetrics.left + - // Thickness in workspace units. - (Scrollbar.scrollbarThickness / this.workspace_.scale); - - if (bubbleLeft < workspaceLeft) { - // Slide the bubble right until it is onscreen. - relativeLeft = -(workspaceLeft - this.anchorXY_.x + this.width_); - } else if (bubbleRight > workspaceRight) { - // Slide the bubble left until it is onscreen. - relativeLeft = -(workspaceRight - this.anchorXY_.x); - } - } else { - const bubbleLeft = relativeLeft + this.anchorXY_.x; - const bubbleRight = bubbleLeft + this.width_; - - const workspaceLeft = viewMetrics.left; - const workspaceRight = viewMetrics.left + viewMetrics.width - - // Thickness in workspace units. - (Scrollbar.scrollbarThickness / this.workspace_.scale); - - if (bubbleLeft < workspaceLeft) { - // Slide the bubble right until it is onscreen. - relativeLeft = workspaceLeft - this.anchorXY_.x; - } else if (bubbleRight > workspaceRight) { - // Slide the bubble left until it is onscreen. - relativeLeft = workspaceRight - this.anchorXY_.x - this.width_; - } - } - - return relativeLeft; -}; - -/** - * Calculate what the optimal vertical position of the top-left corner of - * the bubble is (relative to the anchor point) so that the most area of the - * bubble is shown. - * @param {!MetricsManager.ContainerRegion} viewMetrics The view metrics - * of the workspace the bubble will appear in. - * @return {number} The optimal vertical position of the top-left corner - * of the bubble. - * @private - */ -Bubble.prototype.getOptimalRelativeTop_ = function(viewMetrics) { - let relativeTop = -this.height_ / 4; - - // No amount of sliding up or down will give us a better overlap. - if (this.height_ > viewMetrics.height) { - return relativeTop; - } - - const bubbleTop = this.anchorXY_.y + relativeTop; - const bubbleBottom = bubbleTop + this.height_; - const workspaceTop = viewMetrics.top; - const workspaceBottom = viewMetrics.top + viewMetrics.height - - // Thickness in workspace units. - (Scrollbar.scrollbarThickness / this.workspace_.scale); - - const anchorY = this.anchorXY_.y; - if (bubbleTop < workspaceTop) { - // Slide the bubble down until it is onscreen. - relativeTop = workspaceTop - anchorY; - } else if (bubbleBottom > workspaceBottom) { - // Slide the bubble up until it is onscreen. - relativeTop = workspaceBottom - anchorY - this.height_; - } - - return relativeTop; -}; - -/** - * Move the bubble to a location relative to the anchor's centre. - * @private - */ -Bubble.prototype.positionBubble_ = function() { - let left = this.anchorXY_.x; - if (this.workspace_.RTL) { - left -= this.relativeLeft_ + this.width_; - } else { - left += this.relativeLeft_; - } - const top = this.relativeTop_ + this.anchorXY_.y; - 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 - */ -Bubble.prototype.moveTo = function(x, y) { - this.bubbleGroup_.setAttribute('transform', 'translate(' + x + ',' + y + ')'); -}; - -/** - * Triggers a move callback if one exists at the end of a drag. - * @param {boolean} adding True if adding, false if removing. - * @package - */ -Bubble.prototype.setDragging = function(adding) { - if (!adding && this.moveCallback_) { - this.moveCallback_(); - } -}; - -/** - * Get the dimensions of this bubble. - * @return {!Size} The height and width of the bubble. - */ -Bubble.prototype.getBubbleSize = function() { - return new Size(this.width_, this.height_); -}; - -/** - * Size this bubble. - * @param {number} width Width of the bubble. - * @param {number} height Height of the bubble. - */ -Bubble.prototype.setBubbleSize = function(width, height) { - const doubleBorderWidth = 2 * Bubble.BORDER_WIDTH; - // Minimum size of a bubble. - width = Math.max(width, doubleBorderWidth + 45); - height = Math.max(height, doubleBorderWidth + 20); - this.width_ = width; - this.height_ = height; - this.bubbleBack_.setAttribute('width', width); - this.bubbleBack_.setAttribute('height', height); - if (this.resizeGroup_) { - if (this.workspace_.RTL) { - // Mirror the resize group. - const resizeSize = 2 * Bubble.BORDER_WIDTH; - this.resizeGroup_.setAttribute( - 'transform', - 'translate(' + resizeSize + ',' + (height - doubleBorderWidth) + - ') scale(-1 1)'); - } else { - this.resizeGroup_.setAttribute( - 'transform', - 'translate(' + (width - doubleBorderWidth) + ',' + - (height - doubleBorderWidth) + ')'); - } - } - if (this.autoLayout_) { - this.layoutBubble_(); - } - this.positionBubble_(); - this.renderArrow_(); - - // Allow the contents to resize. - if (this.resizeCallback_) { - this.resizeCallback_(); - } -}; - -/** - * Draw the arrow between the bubble and the origin. - * @private - */ -Bubble.prototype.renderArrow_ = function() { - const steps = []; - // Find the relative coordinates of the center of the bubble. - const relBubbleX = this.width_ / 2; - const relBubbleY = this.height_ / 2; - // Find the relative coordinates of the center of the anchor. - let relAnchorX = -this.relativeLeft_; - let relAnchorY = -this.relativeTop_; - if (relBubbleX === relAnchorX && relBubbleY === relAnchorY) { - // Null case. Bubble is directly on top of the anchor. - // Short circuit this rather than wade through divide by zeros. - steps.push('M ' + relBubbleX + ',' + relBubbleY); - } else { - // Compute the angle of the arrow's line. - const rise = relAnchorY - relBubbleY; - let run = relAnchorX - relBubbleX; - if (this.workspace_.RTL) { - run *= -1; - } - const hypotenuse = Math.sqrt(rise * rise + run * run); - let angle = Math.acos(run / hypotenuse); - if (rise < 0) { - angle = 2 * Math.PI - angle; - } - // Compute a line perpendicular to the arrow. - let rightAngle = angle + Math.PI / 2; - if (rightAngle > Math.PI * 2) { - rightAngle -= Math.PI * 2; - } - const rightRise = Math.sin(rightAngle); - const rightRun = Math.cos(rightAngle); - - // Calculate the thickness of the base of the arrow. - const bubbleSize = this.getBubbleSize(); - let thickness = - (bubbleSize.width + bubbleSize.height) / Bubble.ARROW_THICKNESS; - thickness = Math.min(thickness, bubbleSize.width, bubbleSize.height) / 4; - - // Back the tip of the arrow off of the anchor. - const backoffRatio = 1 - Bubble.ANCHOR_RADIUS / hypotenuse; - relAnchorX = relBubbleX + backoffRatio * run; - relAnchorY = relBubbleY + backoffRatio * rise; - - // Coordinates for the base of the arrow. - const baseX1 = relBubbleX + thickness * rightRun; - const baseY1 = relBubbleY + thickness * rightRise; - const baseX2 = relBubbleX - thickness * rightRun; - const baseY2 = relBubbleY - thickness * rightRise; - - // Distortion to curve the arrow. - let swirlAngle = angle + this.arrow_radians_; - if (swirlAngle > Math.PI * 2) { - swirlAngle -= Math.PI * 2; - } - const swirlRise = Math.sin(swirlAngle) * hypotenuse / Bubble.ARROW_BEND; - const swirlRun = Math.cos(swirlAngle) * hypotenuse / Bubble.ARROW_BEND; - - steps.push('M' + baseX1 + ',' + baseY1); - steps.push( - 'C' + (baseX1 + swirlRun) + ',' + (baseY1 + swirlRise) + ' ' + - relAnchorX + ',' + relAnchorY + ' ' + relAnchorX + ',' + relAnchorY); - steps.push( - 'C' + relAnchorX + ',' + relAnchorY + ' ' + (baseX2 + swirlRun) + ',' + - (baseY2 + swirlRise) + ' ' + baseX2 + ',' + baseY2); - } - steps.push('z'); - this.bubbleArrow_.setAttribute('d', steps.join(' ')); -}; - -/** - * Change the colour of a bubble. - * @param {string} hexColour Hex code of colour. - */ -Bubble.prototype.setColour = function(hexColour) { - this.bubbleBack_.setAttribute('fill', hexColour); - this.bubbleArrow_.setAttribute('fill', hexColour); -}; - -/** - * Dispose of this bubble. - */ -Bubble.prototype.dispose = function() { - if (this.onMouseDownBubbleWrapper_) { - browserEvents.unbind(this.onMouseDownBubbleWrapper_); - } - if (this.onMouseDownResizeWrapper_) { - browserEvents.unbind(this.onMouseDownResizeWrapper_); - } - Bubble.unbindDragEvents_(); - dom.removeNode(this.bubbleGroup_); - this.disposed = true; -}; - -/** - * Move this bubble during a drag, taking into account whether or not there is - * a drag surface. - * @param {BlockDragSurfaceSvg} dragSurface The surface that carries - * rendered items during a drag, or null if no drag surface is in use. - * @param {!Coordinate} newLoc The location to translate to, in - * workspace coordinates. - * @package - */ -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 {!Coordinate} Object with .x and .y properties. - */ -Bubble.prototype.getRelativeToSurfaceXY = function() { - return new Coordinate( - this.workspace_.RTL ? - -this.relativeLeft_ + this.anchorXY_.x - this.width_ : - 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 - */ -Bubble.prototype.setAutoLayout = function(enable) { - this.autoLayout_ = enable; -}; - -/** - * Create the text for a non editable bubble. - * @param {string} text The text to display. - * @return {!SVGTextElement} The top-level node of the text. - * @package - */ -Bubble.textToDom = function(text) { - const paragraph = dom.createSvgElement( - Svg.TEXT, { - 'class': 'blocklyText blocklyBubbleText blocklyNoPointerEvents', - 'y': Bubble.BORDER_WIDTH, - }, - null); - const lines = text.split('\n'); - for (let i = 0; i < lines.length; i++) { - const tspanElement = dom.createSvgElement( - Svg.TSPAN, {'dy': '1em', 'x': Bubble.BORDER_WIDTH}, paragraph); - const textNode = document.createTextNode(lines[i]); - tspanElement.appendChild(textNode); - } - return paragraph; -}; - -/** - * Creates a bubble that can not be edited. - * @param {!SVGTextElement} paragraphElement The text element for the non - * editable bubble. - * @param {!BlockSvg} block The block that the bubble is attached to. - * @param {!Coordinate} iconXY The coordinate of the icon. - * @return {!Bubble} The non editable bubble. - * @package - */ -Bubble.createNonEditableBubble = function(paragraphElement, block, iconXY) { - const bubble = new Bubble( - /** @type {!WorkspaceSvg} */ (block.workspace), paragraphElement, - block.pathObject.svgPath, - /** @type {!Coordinate} */ (iconXY), null, null); - // Expose this bubble's block's ID on its top-level SVG group. - bubble.setSvgId(block.id); - if (block.RTL) { - // Right-align the paragraph. - // This cannot be done until the bubble is rendered on screen. - const maxWidth = paragraphElement.getBBox().width; - for (let i = 0, textElement; (textElement = paragraphElement.childNodes[i]); - i++) { - textElement.setAttribute('text-anchor', 'end'); - textElement.setAttribute('x', maxWidth + Bubble.BORDER_WIDTH); - } - } - return bubble; -}; - exports.Bubble = Bubble; diff --git a/core/bubble_dragger.js b/core/bubble_dragger.js index e312ecdc2..f806841ef 100644 --- a/core/bubble_dragger.js +++ b/core/bubble_dragger.js @@ -19,6 +19,8 @@ const eventUtils = goog.require('Blockly.Events.utils'); const svgMath = goog.require('Blockly.utils.svgMath'); /* eslint-disable-next-line no-unused-vars */ const {BlockDragSurfaceSvg} = goog.requireType('Blockly.BlockDragSurfaceSvg'); +/* eslint-disable-next-line no-unused-vars */ +const {CommentMove} = goog.requireType('Blockly.Events.CommentMove'); const {ComponentManager} = goog.require('Blockly.ComponentManager'); const {Coordinate} = goog.require('Blockly.utils.Coordinate'); /* eslint-disable-next-line no-unused-vars */ @@ -43,249 +45,254 @@ goog.require('Blockly.constants'); * Class for a bubble dragger. It moves things on the bubble canvas around the * workspace when they are being dragged by a mouse or touch. These can be * block comments, mutators, warnings, or workspace comments. - * @param {!IBubble} bubble The item on the bubble canvas to drag. - * @param {!WorkspaceSvg} workspace The workspace to drag on. - * @constructor * @alias Blockly.BubbleDragger */ -const BubbleDragger = function(bubble, workspace) { +const BubbleDragger = class { /** - * The item on the bubble canvas that is being dragged. - * @type {!IBubble} - * @private + * @param {!IBubble} bubble The item on the bubble canvas to drag. + * @param {!WorkspaceSvg} workspace The workspace to drag on. */ - this.draggingBubble_ = bubble; + constructor(bubble, workspace) { + /** + * The item on the bubble canvas that is being dragged. + * @type {!IBubble} + * @private + */ + this.draggingBubble_ = bubble; - /** - * The workspace on which the bubble is being dragged. - * @type {!WorkspaceSvg} - * @private - */ - this.workspace_ = workspace; + /** + * The workspace on which the bubble is being dragged. + * @type {!WorkspaceSvg} + * @private + */ + this.workspace_ = workspace; - /** - * Which drag target the mouse pointer is over, if any. - * @type {?IDragTarget} - * @private - */ - this.dragTarget_ = null; + /** + * Which drag target the mouse pointer is over, if any. + * @type {?IDragTarget} + * @private + */ + this.dragTarget_ = null; - /** - * Whether the bubble would be deleted if dropped immediately. - * @type {boolean} - * @private - */ - this.wouldDeleteBubble_ = false; + /** + * Whether the bubble would be deleted if dropped immediately. + * @type {boolean} + * @private + */ + this.wouldDeleteBubble_ = false; - /** - * The location of the top left corner of the dragging bubble's body at the - * beginning of the drag, in workspace coordinates. - * @type {!Coordinate} - * @private - */ - this.startXY_ = this.draggingBubble_.getRelativeToSurfaceXY(); + /** + * The location of the top left corner of the dragging bubble's body at the + * beginning of the drag, in workspace coordinates. + * @type {!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 {BlockDragSurfaceSvg} - * @private - */ - this.dragSurface_ = - svgMath.is3dSupported() && !!workspace.getBlockDragSurface() ? - workspace.getBlockDragSurface() : - null; -}; - -/** - * Sever all links from this object. - * @package - * @suppress {checkTypes} - */ -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 - */ -BubbleDragger.prototype.startBubbleDrag = function() { - if (!eventUtils.getGroup()) { - eventUtils.setGroup(true); + /** + * 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 {BlockDragSurfaceSvg} + * @private + */ + this.dragSurface_ = + svgMath.is3dSupported() && !!workspace.getBlockDragSurface() ? + workspace.getBlockDragSurface() : + null; } - this.workspace_.setResizesEnabled(false); - this.draggingBubble_.setAutoLayout(false); - if (this.dragSurface_) { - this.moveToDragSurface_(); + /** + * Sever all links from this object. + * @package + * @suppress {checkTypes} + */ + dispose() { + this.draggingBubble_ = null; + this.workspace_ = null; + this.dragSurface_ = null; } - this.draggingBubble_.setDragging && this.draggingBubble_.setDragging(true); -}; - -/** - * Execute a step of bubble dragging, based on the given event. Update the - * display accordingly. - * @param {!Event} e The most recent move event. - * @param {!Coordinate} currentDragDeltaXY How far the pointer has - * moved from the position at the start of the drag, in pixel units. - * @package - */ -BubbleDragger.prototype.dragBubble = function(e, currentDragDeltaXY) { - const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY); - const newLoc = Coordinate.sum(this.startXY_, delta); - this.draggingBubble_.moveDuringDrag(this.dragSurface_, newLoc); - - const oldDragTarget = this.dragTarget_; - this.dragTarget_ = this.workspace_.getDragTarget(e); - - const oldWouldDeleteBubble = this.wouldDeleteBubble_; - this.wouldDeleteBubble_ = this.shouldDelete_(this.dragTarget_); - if (oldWouldDeleteBubble !== this.wouldDeleteBubble_) { - // Prevent unnecessary add/remove class calls. - this.updateCursorDuringBubbleDrag_(); - } - - // Call drag enter/exit/over after wouldDeleteBlock is called in shouldDelete_ - if (this.dragTarget_ !== oldDragTarget) { - oldDragTarget && oldDragTarget.onDragExit(this.draggingBubble_); - this.dragTarget_ && this.dragTarget_.onDragEnter(this.draggingBubble_); - } - this.dragTarget_ && this.dragTarget_.onDragOver(this.draggingBubble_); -}; - -/** - * Whether ending the drag would delete the bubble. - * @param {?IDragTarget} dragTarget The drag target that the bubblee is - * currently over. - * @return {boolean} Whether dropping the bubble immediately would delete the - * block. - * @private - */ -BubbleDragger.prototype.shouldDelete_ = function(dragTarget) { - if (dragTarget) { - const componentManager = this.workspace_.getComponentManager(); - const isDeleteArea = componentManager.hasCapability( - dragTarget.id, ComponentManager.Capability.DELETE_AREA); - if (isDeleteArea) { - return (/** @type {!IDeleteArea} */ (dragTarget)) - .wouldDelete(this.draggingBubble_, false); + /** + * Start dragging a bubble. This includes moving it to the drag surface. + * @package + */ + startBubbleDrag() { + if (!eventUtils.getGroup()) { + eventUtils.setGroup(true); } - } - return false; -}; -/** - * Update the cursor (and possibly the trash can lid) to reflect whether the - * dragging bubble would be deleted if released immediately. - * @private - */ -BubbleDragger.prototype.updateCursorDuringBubbleDrag_ = function() { - this.draggingBubble_.setDeleteStyle(this.wouldDeleteBubble_); -}; - -/** - * Finish a bubble drag and put the bubble back on the workspace. - * @param {!Event} e The mouseup/touchend event. - * @param {!Coordinate} currentDragDeltaXY How far the pointer has - * moved from the position at the start of the drag, in pixel units. - * @package - */ -BubbleDragger.prototype.endBubbleDrag = function(e, currentDragDeltaXY) { - // Make sure internal state is fresh. - this.dragBubble(e, currentDragDeltaXY); - - const preventMove = this.dragTarget_ && - this.dragTarget_.shouldPreventMove(this.draggingBubble_); - let newLoc; - if (preventMove) { - newLoc = this.startXY_; - } else { - const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY); - newLoc = Coordinate.sum(this.startXY_, delta); - } - // Move the bubble to its final location. - this.draggingBubble_.moveTo(newLoc.x, newLoc.y); - - if (this.dragTarget_) { - this.dragTarget_.onDrop(this.draggingBubble_); - } - - if (this.wouldDeleteBubble_) { - // Fire a move event, so we know where to go back to for an undo. - this.fireMoveEvent_(); - this.draggingBubble_.dispose(false, true); - } else { - // Put everything back onto the bubble canvas. + this.workspace_.setResizesEnabled(false); + this.draggingBubble_.setAutoLayout(false); if (this.dragSurface_) { - this.dragSurface_.clearAndHide(this.workspace_.getBubbleCanvas()); + this.moveToDragSurface_(); } - if (this.draggingBubble_.setDragging) { - this.draggingBubble_.setDragging(false); + + this.draggingBubble_.setDragging && this.draggingBubble_.setDragging(true); + } + + /** + * Execute a step of bubble dragging, based on the given event. Update the + * display accordingly. + * @param {!Event} e The most recent move event. + * @param {!Coordinate} currentDragDeltaXY How far the pointer has + * moved from the position at the start of the drag, in pixel units. + * @package + */ + dragBubble(e, currentDragDeltaXY) { + const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY); + const newLoc = Coordinate.sum(this.startXY_, delta); + this.draggingBubble_.moveDuringDrag(this.dragSurface_, newLoc); + + const oldDragTarget = this.dragTarget_; + this.dragTarget_ = this.workspace_.getDragTarget(e); + + const oldWouldDeleteBubble = this.wouldDeleteBubble_; + this.wouldDeleteBubble_ = this.shouldDelete_(this.dragTarget_); + if (oldWouldDeleteBubble !== this.wouldDeleteBubble_) { + // Prevent unnecessary add/remove class calls. + this.updateCursorDuringBubbleDrag_(); } - this.fireMoveEvent_(); + + // Call drag enter/exit/over after wouldDeleteBlock is called in + // shouldDelete_ + if (this.dragTarget_ !== oldDragTarget) { + oldDragTarget && oldDragTarget.onDragExit(this.draggingBubble_); + this.dragTarget_ && this.dragTarget_.onDragEnter(this.draggingBubble_); + } + this.dragTarget_ && this.dragTarget_.onDragOver(this.draggingBubble_); } - this.workspace_.setResizesEnabled(true); - eventUtils.setGroup(false); -}; - -/** - * Fire a move event at the end of a bubble drag. - * @private - */ -BubbleDragger.prototype.fireMoveEvent_ = function() { - if (this.draggingBubble_.isComment) { - // TODO (adodson): Resolve build errors when requiring WorkspaceCommentSvg. - const event = new (eventUtils.get(eventUtils.COMMENT_MOVE))( - /** @type {!WorkspaceCommentSvg} */ (this.draggingBubble_)); - event.setOldCoordinate(this.startXY_); - event.recordNew(); - eventUtils.fire(event); + /** + * Whether ending the drag would delete the bubble. + * @param {?IDragTarget} dragTarget The drag target that the bubblee is + * currently over. + * @return {boolean} Whether dropping the bubble immediately would delete the + * block. + * @private + */ + shouldDelete_(dragTarget) { + if (dragTarget) { + const componentManager = this.workspace_.getComponentManager(); + const isDeleteArea = componentManager.hasCapability( + dragTarget.id, ComponentManager.Capability.DELETE_AREA); + if (isDeleteArea) { + return (/** @type {!IDeleteArea} */ (dragTarget)) + .wouldDelete(this.draggingBubble_, false); + } + } + return false; } - // 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 {!Coordinate} pixelCoord A coordinate with x and y - * values in CSS pixel units. - * @return {!Coordinate} The input coordinate divided by the - * workspace scale. - * @private - */ -BubbleDragger.prototype.pixelsToWorkspaceUnits_ = function(pixelCoord) { - const result = new 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. - const mainScale = this.workspace_.options.parentWorkspace.scale; - result.scale(1 / mainScale); + /** + * Update the cursor (and possibly the trash can lid) to reflect whether the + * dragging bubble would be deleted if released immediately. + * @private + */ + updateCursorDuringBubbleDrag_() { + this.draggingBubble_.setDeleteStyle(this.wouldDeleteBubble_); } - 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 - */ -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()); + /** + * Finish a bubble drag and put the bubble back on the workspace. + * @param {!Event} e The mouseup/touchend event. + * @param {!Coordinate} currentDragDeltaXY How far the pointer has + * moved from the position at the start of the drag, in pixel units. + * @package + */ + endBubbleDrag(e, currentDragDeltaXY) { + // Make sure internal state is fresh. + this.dragBubble(e, currentDragDeltaXY); + + const preventMove = this.dragTarget_ && + this.dragTarget_.shouldPreventMove(this.draggingBubble_); + let newLoc; + if (preventMove) { + newLoc = this.startXY_; + } else { + const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY); + newLoc = Coordinate.sum(this.startXY_, delta); + } + // Move the bubble to its final location. + this.draggingBubble_.moveTo(newLoc.x, newLoc.y); + + if (this.dragTarget_) { + this.dragTarget_.onDrop(this.draggingBubble_); + } + + if (this.wouldDeleteBubble_) { + // Fire a move event, so we know where to go back to for an undo. + this.fireMoveEvent_(); + this.draggingBubble_.dispose(false, true); + } else { + // Put everything back onto the bubble canvas. + if (this.dragSurface_) { + this.dragSurface_.clearAndHide(this.workspace_.getBubbleCanvas()); + } + if (this.draggingBubble_.setDragging) { + this.draggingBubble_.setDragging(false); + } + this.fireMoveEvent_(); + } + this.workspace_.setResizesEnabled(true); + + eventUtils.setGroup(false); + } + + /** + * Fire a move event at the end of a bubble drag. + * @private + */ + fireMoveEvent_() { + if (this.draggingBubble_.isComment) { + // TODO (adodson): Resolve build errors when requiring + // WorkspaceCommentSvg. + const event = /** @type {!CommentMove} */ + (new (eventUtils.get(eventUtils.COMMENT_MOVE))( + /** @type {!WorkspaceCommentSvg} */ (this.draggingBubble_))); + event.setOldCoordinate(this.startXY_); + event.recordNew(); + eventUtils.fire(event); + } + // 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 {!Coordinate} pixelCoord A coordinate with x and y + * values in CSS pixel units. + * @return {!Coordinate} The input coordinate divided by the + * workspace scale. + * @private + */ + pixelsToWorkspaceUnits_(pixelCoord) { + const result = new 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. + const mainScale = this.workspace_.options.parentWorkspace.scale; + 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 + */ + moveToDragSurface_() { + 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()); + } }; exports.BubbleDragger = BubbleDragger; diff --git a/core/bump_objects.js b/core/bump_objects.js index 2b7dc82a7..3cff5ed51 100644 --- a/core/bump_objects.js +++ b/core/bump_objects.js @@ -15,11 +15,11 @@ */ goog.module('Blockly.bumpObjects'); -/* eslint-disable-next-line no-unused-vars */ -const Abstract = goog.requireType('Blockly.Events.Abstract'); const eventUtils = goog.require('Blockly.Events.utils'); const mathUtils = goog.require('Blockly.utils.math'); /* eslint-disable-next-line no-unused-vars */ +const {Abstract} = goog.requireType('Blockly.Events.Abstract'); +/* eslint-disable-next-line no-unused-vars */ const {BlockSvg} = goog.requireType('Blockly.BlockSvg'); /* eslint-disable-next-line no-unused-vars */ const {IBoundedElement} = goog.requireType('Blockly.IBoundedElement'); diff --git a/core/clipboard.js b/core/clipboard.js index ee7ef2a54..5d6d9526a 100644 --- a/core/clipboard.js +++ b/core/clipboard.js @@ -15,7 +15,6 @@ */ goog.module('Blockly.clipboard'); -const eventUtils = goog.require('Blockly.Events.utils'); /* eslint-disable-next-line no-unused-vars */ const {ICopyable} = goog.requireType('Blockly.ICopyable'); @@ -39,13 +38,14 @@ exports.copy = copy; /** * Paste a block or workspace comment on to the main workspace. - * @return {boolean} True if the paste was successful, false otherwise. + * @return {!ICopyable|null} The pasted thing if the paste + * was successful, null otherwise. * @alias Blockly.clipboard.paste * @package */ const paste = function() { if (!copyData) { - return false; + return null; } // Pasting always pastes to the main workspace, even if the copy // started in a flyout workspace. @@ -55,12 +55,9 @@ const paste = function() { } if (copyData.typeCounts && workspace.isCapacityAvailable(copyData.typeCounts)) { - eventUtils.setGroup(true); - workspace.paste(copyData.saveInfo); - eventUtils.setGroup(false); - return true; + return workspace.paste(copyData.saveInfo); } - return false; + return null; }; exports.paste = paste; @@ -68,13 +65,16 @@ exports.paste = paste; * Duplicate this block and its children, or a workspace comment. * @param {!ICopyable} toDuplicate Block or Workspace Comment to be * duplicated. + * @return {!ICopyable|null} The block or workspace comment that was duplicated, + * or null if the duplication failed. * @alias Blockly.clipboard.duplicate * @package */ const duplicate = function(toDuplicate) { const oldCopyData = copyData; copy(toDuplicate); - toDuplicate.workspace.paste(copyData.saveInfo); + const pastedThing = toDuplicate.workspace.paste(copyData.saveInfo); copyData = oldCopyData; + return pastedThing; }; exports.duplicate = duplicate; diff --git a/core/comment.js b/core/comment.js index 8dc56bdb8..251385ded 100644 --- a/core/comment.js +++ b/core/comment.js @@ -19,7 +19,6 @@ const Css = goog.require('Blockly.Css'); const browserEvents = goog.require('Blockly.browserEvents'); const dom = goog.require('Blockly.utils.dom'); const eventUtils = goog.require('Blockly.Events.utils'); -const object = goog.require('Blockly.utils.object'); const userAgent = goog.require('Blockly.utils.userAgent'); /* eslint-disable-next-line no-unused-vars */ const {BlockSvg} = goog.requireType('Blockly.BlockSvg'); @@ -44,384 +43,406 @@ goog.require('Blockly.Warning'); /** * Class for a comment. - * @param {!Block} block The block associated with this comment. * @extends {Icon} - * @constructor * @alias Blockly.Comment */ -const Comment = function(block) { - Comment.superClass_.constructor.call(this, block); - +class Comment extends Icon { /** - * The model for this comment. - * @type {!Block.CommentModel} - * @private + * @param {!BlockSvg} block The block associated with this comment. */ - this.model_ = block.commentModel; - // If someone creates the comment directly instead of calling - // block.setCommentText we want to make sure the text is non-null; - this.model_.text = this.model_.text || ''; + constructor(block) { + super(block); - /** - * The model's text value at the start of an edit. - * Used to tell if an event should be fired at the end of an edit. - * @type {?string} - * @private - */ - this.cachedText_ = ''; + /** + * The model for this comment. + * @type {!Block.CommentModel} + * @private + */ + this.model_ = block.commentModel; + // If someone creates the comment directly instead of calling + // block.setCommentText we want to make sure the text is non-null; + this.model_.text = this.model_.text || ''; - /** - * Mouse up event data. - * @type {?browserEvents.Data} - * @private - */ - this.onMouseUpWrapper_ = null; + /** + * The model's text value at the start of an edit. + * Used to tell if an event should be fired at the end of an edit. + * @type {?string} + * @private + */ + this.cachedText_ = ''; - /** - * Wheel event data. - * @type {?browserEvents.Data} - * @private - */ - this.onWheelWrapper_ = null; - - /** - * Change event data. - * @type {?browserEvents.Data} - * @private - */ - this.onChangeWrapper_ = null; - - /** - * Input event data. - * @type {?browserEvents.Data} - * @private - */ - this.onInputWrapper_ = null; - - this.createIcon(); -}; -object.inherits(Comment, Icon); - -/** - * Draw the comment icon. - * @param {!Element} group The icon group. - * @protected - */ -Comment.prototype.drawIcon_ = function(group) { - // Circle. - dom.createSvgElement( - Svg.CIRCLE, {'class': 'blocklyIconShape', 'r': '8', 'cx': '8', 'cy': '8'}, - group); - // Can't use a real '?' text character since different browsers and operating - // systems render it differently. - // Body of question mark. - dom.createSvgElement( - Svg.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', - }, - group); - // Dot of question mark. - dom.createSvgElement( - Svg.RECT, { - 'class': 'blocklyIconSymbol', - 'x': '6.8', - 'y': '10.78', - 'height': '2', - 'width': '2', - }, - group); -}; - -/** - * Create the editor for the comment's bubble. - * @return {!SVGElement} The top-level node of the editor. - * @private - */ -Comment.prototype.createEditor_ = function() { - /* Create the editor. Here's the markup that will be generated in - * editable mode: - - - - - - * For non-editable mode see Warning.textToDom_. - */ - - this.foreignObject_ = dom.createSvgElement( - Svg.FOREIGNOBJECT, {'x': Bubble.BORDER_WIDTH, 'y': Bubble.BORDER_WIDTH}, - null); - - const body = document.createElementNS(dom.HTML_NS, 'body'); - body.setAttribute('xmlns', dom.HTML_NS); - body.className = 'blocklyMinimalBody'; - - this.textarea_ = document.createElementNS(dom.HTML_NS, 'textarea'); - const textarea = this.textarea_; - textarea.className = 'blocklyCommentTextarea'; - textarea.setAttribute('dir', this.block_.RTL ? 'RTL' : 'LTR'); - textarea.value = this.model_.text; - this.resizeTextarea_(); - - body.appendChild(textarea); - this.foreignObject_.appendChild(body); - - // 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. - this.onMouseUpWrapper_ = browserEvents.conditionalBind( - textarea, 'mouseup', this, this.startEdit_, true, true); - // Don't zoom with mousewheel. - this.onWheelWrapper_ = - browserEvents.conditionalBind(textarea, 'wheel', this, function(e) { - e.stopPropagation(); - }); - this.onChangeWrapper_ = browserEvents.conditionalBind( - textarea, 'change', this, - /** - * @this {Comment} - * @param {Event} _e Unused event parameter. - */ - function(_e) { - if (this.cachedText_ !== this.model_.text) { - eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))( - this.block_, 'comment', null, this.cachedText_, - this.model_.text)); - } - }); - this.onInputWrapper_ = browserEvents.conditionalBind( - textarea, 'input', this, - /** - * @this {Comment} - * @param {Event} _e Unused event parameter. - */ - function(_e) { - this.model_.text = textarea.value; - }); - - setTimeout(textarea.focus.bind(textarea), 0); - - return this.foreignObject_; -}; - -/** - * Add or remove editability of the comment. - * @override - */ -Comment.prototype.updateEditable = function() { - Comment.superClass_.updateEditable.call(this); - if (this.isVisible()) { - // Recreate the bubble with the correct UI. - this.disposeBubble_(); - this.createBubble_(); - } -}; - -/** - * Callback function triggered when the bubble has resized. - * Resize the text area accordingly. - * @private - */ -Comment.prototype.onBubbleResize_ = function() { - if (!this.isVisible()) { - return; - } - this.model_.size = this.bubble_.getBubbleSize(); - this.resizeTextarea_(); -}; - -/** - * Resizes the text area to match the size defined on the model (which is - * the size of the bubble). - * @private - */ -Comment.prototype.resizeTextarea_ = function() { - const size = this.model_.size; - const doubleBorderWidth = 2 * Bubble.BORDER_WIDTH; - const widthMinusBorder = size.width - doubleBorderWidth; - const heightMinusBorder = size.height - doubleBorderWidth; - this.foreignObject_.setAttribute('width', widthMinusBorder); - this.foreignObject_.setAttribute('height', heightMinusBorder); - this.textarea_.style.width = (widthMinusBorder - 4) + 'px'; - this.textarea_.style.height = (heightMinusBorder - 4) + 'px'; -}; - -/** - * Show or hide the comment bubble. - * @param {boolean} visible True if the bubble should be visible. - */ -Comment.prototype.setVisible = function(visible) { - if (visible === this.isVisible()) { - return; - } - eventUtils.fire(new (eventUtils.get(eventUtils.BUBBLE_OPEN))( - this.block_, visible, 'comment')); - this.model_.pinned = visible; - if (visible) { - this.createBubble_(); - } else { - this.disposeBubble_(); - } -}; - -/** - * Show the bubble. Handles deciding if it should be editable or not. - * @private - */ -Comment.prototype.createBubble_ = function() { - if (!this.block_.isEditable() || userAgent.IE) { - // MSIE does not support foreignobject; textareas are impossible. - // https://docs.microsoft.com/en-us/openspecs/ie_standards/ms-svg/56e6e04c-7c8c-44dd-8100-bd745ee42034 - // Always treat comments in IE as uneditable. - this.createNonEditableBubble_(); - } else { - this.createEditableBubble_(); - } -}; - -/** - * Show an editable bubble. - * @private - */ -Comment.prototype.createEditableBubble_ = function() { - this.bubble_ = new Bubble( - /** @type {!WorkspaceSvg} */ (this.block_.workspace), - this.createEditor_(), this.block_.pathObject.svgPath, - /** @type {!Coordinate} */ (this.iconXY_), this.model_.size.width, - this.model_.size.height); - // Expose this comment's block's ID on its top-level SVG group. - this.bubble_.setSvgId(this.block_.id); - this.bubble_.registerResizeEvent(this.onBubbleResize_.bind(this)); - this.applyColour(); -}; - -/** - * Show a non-editable bubble. - * @private - * @suppress {checkTypes} Suppress `this` type mismatch. - */ -Comment.prototype.createNonEditableBubble_ = function() { - // TODO (#2917): It would be great if the comment could support line breaks. - this.paragraphElement_ = Bubble.textToDom(this.block_.getCommentText()); - this.bubble_ = Bubble.createNonEditableBubble( - this.paragraphElement_, /** @type {!BlockSvg} */ (this.block_), - /** @type {!Coordinate} */ (this.iconXY_)); - this.applyColour(); -}; - -/** - * Dispose of the bubble. - * @private - * @suppress {checkTypes} Suppress `this` type mismatch. - */ -Comment.prototype.disposeBubble_ = function() { - if (this.onMouseUpWrapper_) { - browserEvents.unbind(this.onMouseUpWrapper_); + /** + * Mouse up event data. + * @type {?browserEvents.Data} + * @private + */ this.onMouseUpWrapper_ = null; - } - if (this.onWheelWrapper_) { - browserEvents.unbind(this.onWheelWrapper_); + + /** + * Wheel event data. + * @type {?browserEvents.Data} + * @private + */ this.onWheelWrapper_ = null; - } - if (this.onChangeWrapper_) { - browserEvents.unbind(this.onChangeWrapper_); + + /** + * Change event data. + * @type {?browserEvents.Data} + * @private + */ this.onChangeWrapper_ = null; - } - if (this.onInputWrapper_) { - browserEvents.unbind(this.onInputWrapper_); + + /** + * Input event data. + * @type {?browserEvents.Data} + * @private + */ this.onInputWrapper_ = null; - } - this.bubble_.dispose(); - this.bubble_ = null; - this.textarea_ = null; - this.foreignObject_ = null; - this.paragraphElement_ = null; -}; -/** - * Callback fired when an edit starts. - * - * Bring the comment to the top of the stack when clicked on. Also cache the - * current text so it can be used to fire a change event. - * @param {!Event} _e Mouse up event. - * @private - */ -Comment.prototype.startEdit_ = function(_e) { - if (this.bubble_.promote()) { - // Since the act of moving this node within the DOM causes a loss of focus, - // we need to reapply the focus. - this.textarea_.focus(); + /** + * The SVG element that contains the text edit area, or null if not created. + * @type {?SVGForeignObjectElement} + * @private + */ + this.foreignObject_ = null; + + /** + * The editable text area, or null if not created. + * @type {?Element} + * @private + */ + this.textarea_ = null; + + /** + * The top-level node of the comment text, or null if not created. + * @type {?SVGTextElement} + * @private + */ + this.paragraphElement_ = null; + + this.createIcon(); } - this.cachedText_ = this.model_.text; -}; - -/** - * Get the dimensions of this comment's bubble. - * @return {Size} Object with width and height properties. - */ -Comment.prototype.getBubbleSize = function() { - return this.model_.size; -}; - -/** - * Size this comment's bubble. - * @param {number} width Width of the bubble. - * @param {number} height Height of the bubble. - */ -Comment.prototype.setBubbleSize = function(width, height) { - if (this.bubble_) { - this.bubble_.setBubbleSize(width, height); - } else { - this.model_.size.width = width; - this.model_.size.height = height; + /** + * Draw the comment icon. + * @param {!Element} group The icon group. + * @protected + */ + drawIcon_(group) { + // Circle. + dom.createSvgElement( + Svg.CIRCLE, + {'class': 'blocklyIconShape', 'r': '8', 'cx': '8', 'cy': '8'}, group); + // Can't use a real '?' text character since different browsers and + // operating systems render it differently. Body of question mark. + dom.createSvgElement( + Svg.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', + }, + group); + // Dot of question mark. + dom.createSvgElement( + Svg.RECT, { + 'class': 'blocklyIconSymbol', + 'x': '6.8', + 'y': '10.78', + 'height': '2', + 'width': '2', + }, + group); } -}; -/** - * Update the comment's view to match the model. - * @package - */ -Comment.prototype.updateText = function() { - if (this.textarea_) { - this.textarea_.value = this.model_.text; - } else if (this.paragraphElement_) { - // Non-Editable mode. - // TODO (#2917): If 2917 gets added this will probably need to be updated. - this.paragraphElement_.firstChild.textContent = this.model_.text; + /** + * Create the editor for the comment's bubble. + * @return {!SVGElement} The top-level node of the editor. + * @private + */ + createEditor_() { + /* Create the editor. Here's the markup that will be generated in + * editable mode: + + + + + + * For non-editable mode see Warning.textToDom_. + */ + + this.foreignObject_ = dom.createSvgElement( + Svg.FOREIGNOBJECT, {'x': Bubble.BORDER_WIDTH, 'y': Bubble.BORDER_WIDTH}, + null); + + const body = document.createElementNS(dom.HTML_NS, 'body'); + body.setAttribute('xmlns', dom.HTML_NS); + body.className = 'blocklyMinimalBody'; + + this.textarea_ = document.createElementNS(dom.HTML_NS, 'textarea'); + const textarea = this.textarea_; + textarea.className = 'blocklyCommentTextarea'; + textarea.setAttribute('dir', this.block_.RTL ? 'RTL' : 'LTR'); + textarea.value = this.model_.text; + this.resizeTextarea_(); + + body.appendChild(textarea); + this.foreignObject_.appendChild(body); + + // 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. + this.onMouseUpWrapper_ = browserEvents.conditionalBind( + textarea, 'mouseup', this, this.startEdit_, true, true); + // Don't zoom with mousewheel. + this.onWheelWrapper_ = + browserEvents.conditionalBind(textarea, 'wheel', this, function(e) { + e.stopPropagation(); + }); + this.onChangeWrapper_ = browserEvents.conditionalBind( + textarea, 'change', this, + /** + * @this {Comment} + * @param {Event} _e Unused event parameter. + */ + function(_e) { + if (this.cachedText_ !== this.model_.text) { + eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))( + this.block_, 'comment', null, this.cachedText_, + this.model_.text)); + } + }); + this.onInputWrapper_ = browserEvents.conditionalBind( + textarea, 'input', this, + /** + * @this {Comment} + * @param {Event} _e Unused event parameter. + */ + function(_e) { + this.model_.text = textarea.value; + }); + + setTimeout(textarea.focus.bind(textarea), 0); + + return this.foreignObject_; } -}; -/** - * Dispose of this comment. - * - * If you want to receive a comment "delete" event (newValue: null), then this - * should not be called directly. Instead call block.setCommentText(null); - */ -Comment.prototype.dispose = function() { - this.block_.comment = null; - Icon.prototype.dispose.call(this); -}; + /** + * Add or remove editability of the comment. + * @override + */ + updateEditable() { + super.updateEditable(); + if (this.isVisible()) { + // Recreate the bubble with the correct UI. + this.disposeBubble_(); + this.createBubble_(); + } + } + + /** + * Callback function triggered when the bubble has resized. + * Resize the text area accordingly. + * @private + */ + onBubbleResize_() { + if (!this.isVisible()) { + return; + } + this.model_.size = this.bubble_.getBubbleSize(); + this.resizeTextarea_(); + } + + /** + * Resizes the text area to match the size defined on the model (which is + * the size of the bubble). + * @private + */ + resizeTextarea_() { + const size = this.model_.size; + const doubleBorderWidth = 2 * Bubble.BORDER_WIDTH; + const widthMinusBorder = size.width - doubleBorderWidth; + const heightMinusBorder = size.height - doubleBorderWidth; + this.foreignObject_.setAttribute('width', widthMinusBorder); + this.foreignObject_.setAttribute('height', heightMinusBorder); + this.textarea_.style.width = (widthMinusBorder - 4) + 'px'; + this.textarea_.style.height = (heightMinusBorder - 4) + 'px'; + } + + /** + * Show or hide the comment bubble. + * @param {boolean} visible True if the bubble should be visible. + */ + setVisible(visible) { + if (visible === this.isVisible()) { + return; + } + eventUtils.fire(new (eventUtils.get(eventUtils.BUBBLE_OPEN))( + this.block_, visible, 'comment')); + this.model_.pinned = visible; + if (visible) { + this.createBubble_(); + } else { + this.disposeBubble_(); + } + } + + /** + * Show the bubble. Handles deciding if it should be editable or not. + * @private + */ + createBubble_() { + if (!this.block_.isEditable() || userAgent.IE) { + // MSIE does not support foreignobject; textareas are impossible. + // https://docs.microsoft.com/en-us/openspecs/ie_standards/ms-svg/56e6e04c-7c8c-44dd-8100-bd745ee42034 + // Always treat comments in IE as uneditable. + this.createNonEditableBubble_(); + } else { + this.createEditableBubble_(); + } + } + + /** + * Show an editable bubble. + * @private + */ + createEditableBubble_() { + this.bubble_ = new Bubble( + /** @type {!WorkspaceSvg} */ (this.block_.workspace), + this.createEditor_(), this.block_.pathObject.svgPath, + /** @type {!Coordinate} */ (this.iconXY_), this.model_.size.width, + this.model_.size.height); + // Expose this comment's block's ID on its top-level SVG group. + this.bubble_.setSvgId(this.block_.id); + this.bubble_.registerResizeEvent(this.onBubbleResize_.bind(this)); + this.applyColour(); + } + + /** + * Show a non-editable bubble. + * @private + * @suppress {checkTypes} Suppress `this` type mismatch. + */ + createNonEditableBubble_() { + // TODO (#2917): It would be great if the comment could support line breaks. + this.paragraphElement_ = Bubble.textToDom(this.block_.getCommentText()); + this.bubble_ = Bubble.createNonEditableBubble( + this.paragraphElement_, /** @type {!BlockSvg} */ (this.block_), + /** @type {!Coordinate} */ (this.iconXY_)); + this.applyColour(); + } + + /** + * Dispose of the bubble. + * @private + * @suppress {checkTypes} Suppress `this` type mismatch. + */ + disposeBubble_() { + if (this.onMouseUpWrapper_) { + browserEvents.unbind(this.onMouseUpWrapper_); + this.onMouseUpWrapper_ = null; + } + if (this.onWheelWrapper_) { + browserEvents.unbind(this.onWheelWrapper_); + this.onWheelWrapper_ = null; + } + if (this.onChangeWrapper_) { + browserEvents.unbind(this.onChangeWrapper_); + this.onChangeWrapper_ = null; + } + if (this.onInputWrapper_) { + browserEvents.unbind(this.onInputWrapper_); + this.onInputWrapper_ = null; + } + this.bubble_.dispose(); + this.bubble_ = null; + this.textarea_ = null; + this.foreignObject_ = null; + this.paragraphElement_ = null; + } + + /** + * Callback fired when an edit starts. + * + * Bring the comment to the top of the stack when clicked on. Also cache the + * current text so it can be used to fire a change event. + * @param {!Event} _e Mouse up event. + * @private + */ + startEdit_(_e) { + if (this.bubble_.promote()) { + // Since the act of moving this node within the DOM causes a loss of + // focus, we need to reapply the focus. + this.textarea_.focus(); + } + + this.cachedText_ = this.model_.text; + } + + /** + * Get the dimensions of this comment's bubble. + * @return {Size} Object with width and height properties. + */ + getBubbleSize() { + return this.model_.size; + } + + /** + * Size this comment's bubble. + * @param {number} width Width of the bubble. + * @param {number} height Height of the bubble. + */ + setBubbleSize(width, height) { + if (this.bubble_) { + this.bubble_.setBubbleSize(width, height); + } else { + this.model_.size.width = width; + this.model_.size.height = height; + } + } + + /** + * Update the comment's view to match the model. + * @package + */ + updateText() { + if (this.textarea_) { + this.textarea_.value = this.model_.text; + } else if (this.paragraphElement_) { + // Non-Editable mode. + // TODO (#2917): If 2917 gets added this will probably need to be updated. + this.paragraphElement_.firstChild.textContent = this.model_.text; + } + } + + /** + * Dispose of this comment. + * + * If you want to receive a comment "delete" event (newValue: null), then this + * should not be called directly. Instead call block.setCommentText(null); + */ + dispose() { + this.block_.comment = null; + Icon.prototype.dispose.call(this); + } +} /** * CSS for block comment. See css.js for use. */ Css.register(` - .blocklyCommentTextarea { - background-color: #fef49c; - border: 0; - display: block; - margin: 0; - outline: 0; - padding: 3px; - resize: none; - text-overflow: hidden; - } +.blocklyCommentTextarea { + background-color: #fef49c; + border: 0; + display: block; + margin: 0; + outline: 0; + padding: 3px; + resize: none; + text-overflow: hidden; +} `); exports.Comment = Comment; diff --git a/core/common.js b/core/common.js index 59052b55d..29a4af214 100644 --- a/core/common.js +++ b/core/common.js @@ -17,7 +17,8 @@ */ goog.module('Blockly.common'); -const {Blocks} = goog.require('Blockly.blocks'); +/* eslint-disable-next-line no-unused-vars */ +const {BlockDefinition, Blocks} = goog.require('Blockly.blocks'); /* eslint-disable-next-line no-unused-vars */ const {Connection} = goog.requireType('Blockly.Connection'); /* eslint-disable-next-line no-unused-vars */ @@ -210,27 +211,55 @@ const jsonInitFactory = function(jsonDef) { * @alias Blockly.common.defineBlocksWithJsonArray */ const defineBlocksWithJsonArray = function(jsonArray) { + defineBlocks(createBlockDefinitionsFromJsonArray(jsonArray)); +}; +exports.defineBlocksWithJsonArray = defineBlocksWithJsonArray; + +/** + * Define blocks from an array of JSON block definitions, as might be generated + * by the Blockly Developer Tools. + * @param {!Array} jsonArray An array of JSON block definitions. + * @return {!Object} A map of the block + * definitions created. + * @alias Blockly.common.defineBlocksWithJsonArray + */ +const createBlockDefinitionsFromJsonArray = function(jsonArray) { + const /** @type {!Object} */ blocks = {}; for (let i = 0; i < jsonArray.length; i++) { const elem = jsonArray[i]; if (!elem) { - console.warn( - 'Block definition #' + i + ' in JSON array is ' + elem + '. ' + - 'Skipping.'); - } else { - const typename = elem.type; - if (!typename) { - console.warn( - 'Block definition #' + i + - ' in JSON array is missing a type attribute. Skipping.'); - } else { - if (Blocks[typename]) { - console.warn( - 'Block definition #' + i + ' in JSON array' + - ' overwrites prior definition of "' + typename + '".'); - } - Blocks[typename] = {init: jsonInitFactory(elem)}; - } + console.warn(`Block definition #${i} in JSON array is ${elem}. Skipping`); + continue; } + const type = elem.type; + if (!type) { + console.warn( + `Block definition #${i} in JSON array is missing a type attribute. ` + + 'Skipping.'); + continue; + } + blocks[type] = {init: jsonInitFactory(elem)}; + } + return blocks; +}; +exports.createBlockDefinitionsFromJsonArray = + createBlockDefinitionsFromJsonArray; + +/** + * Add the specified block definitions to the block definitions + * dictionary (Blockly.Blocks). + * @param {!Object} blocks A map of block + * type names to block definitions. + * @alias Blockly.common.defineBlocks + */ +const defineBlocks = function(blocks) { + // Iterate over own enumerable properties. + for (const type of Object.keys(blocks)) { + const definition = blocks[type]; + if (type in Blocks) { + console.warn(`Block definiton "${type}" overwrites previous definition.`); + } + Blocks[type] = definition; } }; -exports.defineBlocksWithJsonArray = defineBlocksWithJsonArray; +exports.defineBlocks = defineBlocks; diff --git a/core/component_manager.js b/core/component_manager.js index 5010f538e..1e1cf04d7 100644 --- a/core/component_manager.js +++ b/core/component_manager.js @@ -31,24 +31,179 @@ const {IPositionable} = goog.requireType('Blockly.IPositionable'); /** * Manager for all items registered with the workspace. - * @constructor * @alias Blockly.ComponentManager */ -const ComponentManager = function() { +class ComponentManager { /** - * A map of the components registered with the workspace, mapped to id. - * @type {!Object} - * @private + * Creates a new ComponentManager instance. */ - this.componentData_ = Object.create(null); + constructor() { + /** + * A map of the components registered with the workspace, mapped to id. + * @type {!Object} + * @private + */ + this.componentData_ = Object.create(null); + + /** + * A map of capabilities to component IDs. + * @type {!Object>} + * @private + */ + this.capabilityToComponentIds_ = Object.create(null); + } /** - * A map of capabilities to component IDs. - * @type {!Object>} - * @private + * Adds a component. + * @param {!ComponentManager.ComponentDatum} componentInfo The data for + * the component to register. + * @param {boolean=} opt_allowOverrides True to prevent an error when + * overriding an already registered item. */ - this.capabilityToComponentIds_ = Object.create(null); -}; + addComponent(componentInfo, opt_allowOverrides) { + // Don't throw an error if opt_allowOverrides is true. + const id = componentInfo.component.id; + if (!opt_allowOverrides && this.componentData_[id]) { + throw Error( + 'Plugin "' + id + '" with capabilities "' + + this.componentData_[id].capabilities + '" already added.'); + } + this.componentData_[id] = componentInfo; + const stringCapabilities = []; + for (let i = 0; i < componentInfo.capabilities.length; i++) { + const capability = String(componentInfo.capabilities[i]).toLowerCase(); + stringCapabilities.push(capability); + if (this.capabilityToComponentIds_[capability] === undefined) { + this.capabilityToComponentIds_[capability] = [id]; + } else { + this.capabilityToComponentIds_[capability].push(id); + } + } + this.componentData_[id].capabilities = stringCapabilities; + } + + /** + * Removes a component. + * @param {string} id The ID of the component to remove. + */ + removeComponent(id) { + const componentInfo = this.componentData_[id]; + if (!componentInfo) { + return; + } + for (let i = 0; i < componentInfo.capabilities.length; i++) { + const capability = String(componentInfo.capabilities[i]).toLowerCase(); + arrayUtils.removeElem(this.capabilityToComponentIds_[capability], id); + } + delete this.componentData_[id]; + } + + /** + * Adds a capability to a existing registered component. + * @param {string} id The ID of the component to add the capability to. + * @param {string|!ComponentManager.Capability} capability The + * capability to add. + * @template T + */ + addCapability(id, capability) { + if (!this.getComponent(id)) { + throw Error( + 'Cannot add capability, "' + capability + '". Plugin "' + id + + '" has not been added to the ComponentManager'); + } + if (this.hasCapability(id, capability)) { + console.warn( + 'Plugin "' + id + 'already has capability "' + capability + '"'); + return; + } + capability = String(capability).toLowerCase(); + this.componentData_[id].capabilities.push(capability); + this.capabilityToComponentIds_[capability].push(id); + } + + /** + * Removes a capability from an existing registered component. + * @param {string} id The ID of the component to remove the capability from. + * @param {string|!ComponentManager.Capability} capability The + * capability to remove. + * @template T + */ + removeCapability(id, capability) { + if (!this.getComponent(id)) { + throw Error( + 'Cannot remove capability, "' + capability + '". Plugin "' + id + + '" has not been added to the ComponentManager'); + } + if (!this.hasCapability(id, capability)) { + console.warn( + 'Plugin "' + id + 'doesn\'t have capability "' + capability + + '" to remove'); + return; + } + capability = String(capability).toLowerCase(); + arrayUtils.removeElem(this.componentData_[id].capabilities, capability); + arrayUtils.removeElem(this.capabilityToComponentIds_[capability], id); + } + + /** + * Returns whether the component with this id has the specified capability. + * @param {string} id The ID of the component to check. + * @param {string|!ComponentManager.Capability} capability The + * capability to check for. + * @return {boolean} Whether the component has the capability. + * @template T + */ + hasCapability(id, capability) { + capability = String(capability).toLowerCase(); + return this.componentData_[id].capabilities.indexOf(capability) !== -1; + } + + /** + * Gets the component with the given ID. + * @param {string} id The ID of the component to get. + * @return {!IComponent|undefined} The component with the given name + * or undefined if not found. + */ + getComponent(id) { + return this.componentData_[id] && this.componentData_[id].component; + } + + /** + * Gets all the components with the specified capability. + * @param {string|!ComponentManager.Capability + * } capability The capability of the component. + * @param {boolean} sorted Whether to return list ordered by weights. + * @return {!Array} The components that match the specified capability. + * @template T + */ + getComponents(capability, sorted) { + capability = String(capability).toLowerCase(); + const componentIds = this.capabilityToComponentIds_[capability]; + if (!componentIds) { + return []; + } + const components = []; + if (sorted) { + const componentDataList = []; + const componentData = this.componentData_; + componentIds.forEach(function(id) { + componentDataList.push(componentData[id]); + }); + componentDataList.sort(function(a, b) { + return a.weight - b.weight; + }); + componentDataList.forEach(function(ComponentDatum) { + components.push(ComponentDatum.component); + }); + } else { + const componentData = this.componentData_; + componentIds.forEach(function(id) { + components.push(componentData[id].component); + }); + } + return components; + } +} /** * An object storing component information. @@ -62,179 +217,31 @@ const ComponentManager = function() { */ ComponentManager.ComponentDatum; -/** - * Adds a component. - * @param {!ComponentManager.ComponentDatum} componentInfo The data for - * the component to register. - * @param {boolean=} opt_allowOverrides True to prevent an error when overriding - * an already registered item. - */ -ComponentManager.prototype.addComponent = function( - componentInfo, opt_allowOverrides) { - // Don't throw an error if opt_allowOverrides is true. - const id = componentInfo.component.id; - if (!opt_allowOverrides && this.componentData_[id]) { - throw Error( - 'Plugin "' + id + '" with capabilities "' + - this.componentData_[id].capabilities + '" already added.'); - } - this.componentData_[id] = componentInfo; - const stringCapabilities = []; - for (let i = 0; i < componentInfo.capabilities.length; i++) { - const capability = String(componentInfo.capabilities[i]).toLowerCase(); - stringCapabilities.push(capability); - if (this.capabilityToComponentIds_[capability] === undefined) { - this.capabilityToComponentIds_[capability] = [id]; - } else { - this.capabilityToComponentIds_[capability].push(id); - } - } - this.componentData_[id].capabilities = stringCapabilities; -}; - -/** - * Removes a component. - * @param {string} id The ID of the component to remove. - */ -ComponentManager.prototype.removeComponent = function(id) { - const componentInfo = this.componentData_[id]; - if (!componentInfo) { - return; - } - for (let i = 0; i < componentInfo.capabilities.length; i++) { - const capability = String(componentInfo.capabilities[i]).toLowerCase(); - arrayUtils.removeElem(this.capabilityToComponentIds_[capability], id); - } - delete this.componentData_[id]; -}; - -/** - * Adds a capability to a existing registered component. - * @param {string} id The ID of the component to add the capability to. - * @param {string|!ComponentManager.Capability} capability The - * capability to add. - * @template T - */ -ComponentManager.prototype.addCapability = function(id, capability) { - if (!this.getComponent(id)) { - throw Error( - 'Cannot add capability, "' + capability + '". Plugin "' + id + - '" has not been added to the ComponentManager'); - } - if (this.hasCapability(id, capability)) { - console.warn( - 'Plugin "' + id + 'already has capability "' + capability + '"'); - return; - } - capability = String(capability).toLowerCase(); - this.componentData_[id].capabilities.push(capability); - this.capabilityToComponentIds_[capability].push(id); -}; - -/** - * Removes a capability from an existing registered component. - * @param {string} id The ID of the component to remove the capability from. - * @param {string|!ComponentManager.Capability} capability The - * capability to remove. - * @template T - */ -ComponentManager.prototype.removeCapability = function(id, capability) { - if (!this.getComponent(id)) { - throw Error( - 'Cannot remove capability, "' + capability + '". Plugin "' + id + - '" has not been added to the ComponentManager'); - } - if (!this.hasCapability(id, capability)) { - console.warn( - 'Plugin "' + id + 'doesn\'t have capability "' + capability + - '" to remove'); - return; - } - capability = String(capability).toLowerCase(); - arrayUtils.removeElem(this.componentData_[id].capabilities, capability); - arrayUtils.removeElem(this.capabilityToComponentIds_[capability], id); -}; - -/** - * Returns whether the component with this id has the specified capability. - * @param {string} id The ID of the component to check. - * @param {string|!ComponentManager.Capability} capability The - * capability to check for. - * @return {boolean} Whether the component has the capability. - * @template T - */ -ComponentManager.prototype.hasCapability = function(id, capability) { - capability = String(capability).toLowerCase(); - return this.componentData_[id].capabilities.indexOf(capability) !== -1; -}; - -/** - * Gets the component with the given ID. - * @param {string} id The ID of the component to get. - * @return {!IComponent|undefined} The component with the given name - * or undefined if not found. - */ -ComponentManager.prototype.getComponent = function(id) { - return this.componentData_[id] && this.componentData_[id].component; -}; - -/** - * Gets all the components with the specified capability. - * @param {string|!ComponentManager.Capability - * } capability The capability of the component. - * @param {boolean} sorted Whether to return list ordered by weights. - * @return {!Array} The components that match the specified capability. - * @template T - */ -ComponentManager.prototype.getComponents = function(capability, sorted) { - capability = String(capability).toLowerCase(); - const componentIds = this.capabilityToComponentIds_[capability]; - if (!componentIds) { - return []; - } - const components = []; - if (sorted) { - const componentDataList = []; - const componentData = this.componentData_; - componentIds.forEach(function(id) { - componentDataList.push(componentData[id]); - }); - componentDataList.sort(function(a, b) { - return a.weight - b.weight; - }); - componentDataList.forEach(function(ComponentDatum) { - components.push(ComponentDatum.component); - }); - } else { - const componentData = this.componentData_; - componentIds.forEach(function(id) { - components.push(componentData[id].component); - }); - } - return components; -}; - /** * A name with the capability of the element stored in the generic. - * @param {string} name The name of the component capability. - * @constructor * @template T + * @alias Blockly.ComponentManager.Capability */ -ComponentManager.Capability = function(name) { +ComponentManager.Capability = class { /** - * @type {string} - * @private + * @param {string} name The name of the component capability. */ - this.name_ = name; -}; + constructor(name) { + /** + * @type {string} + * @private + */ + this.name_ = name; + } -/** - * Returns the name of the capability. - * @return {string} The name. - * @override - */ -ComponentManager.Capability.prototype.toString = function() { - return this.name_; + /** + * Returns the name of the capability. + * @return {string} The name. + * @override + */ + toString() { + return this.name_; + } }; /** @type {!ComponentManager.Capability} */ diff --git a/core/config.js b/core/config.js new file mode 100644 index 000000000..b211ebe5f --- /dev/null +++ b/core/config.js @@ -0,0 +1,87 @@ +/** + * @license + * Copyright 2022 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview All the values that we expect developers to be able to change + * before injecting Blockly. Changing these values during run time is not + * generally recommended. + */ +'use strict'; + +/** + * All the values that we expect developers to be able to change + * before injecting Blockly. Changing these values during run time is not + * generally recommended. + * @namespace Blockly.config + */ +goog.module('Blockly.config'); + + +/** + * All the values that we expect developers to be able to change + * before injecting Blockly. + * @typedef {{ + * dragRadius: number, + * flyoutDragRadius: number, + * snapRadius: number, + * currentConnectionPreference: number, + * bumpDelay: number, + * connectingSnapRadius: number + * }} + */ +let Config; // eslint-disable-line no-unused-vars + +/** + * Default snap radius. + * @type {number} + */ +const DEFAULT_SNAP_RADIUS = 28; + +/** + * Object holding all the values on Blockly that we expect developers to be + * able to change. + * @type {Config} + */ +const config = { + /** + * Number of pixels the mouse must move before a drag starts. + * @alias Blockly.config.dragRadius + */ + dragRadius: 5, + /** + * Number of pixels the mouse must move before a drag/scroll starts from the + * flyout. Because the drag-intention is determined when this is reached, it + * is larger than dragRadius so that the drag-direction is clearer. + * @alias Blockly.config.flyoutDragRadius + */ + flyoutDragRadius: 10, + /** + * Maximum misalignment between connections for them to snap together. + * @alias Blockly.config.snapRadius + */ + snapRadius: DEFAULT_SNAP_RADIUS, + /** + * Maximum misalignment between connections for them to snap together. + * This should be the same as the snap radius. + * @alias Blockly.config.connectingSnapRadius + */ + connectingSnapRadius: DEFAULT_SNAP_RADIUS, + /** + * How much to prefer staying connected to the current connection over moving + * to a new connection. The current previewed connection is considered to be + * this much closer to the matching connection on the block than it actually + * is. + * @alias Blockly.config.currentConnectionPreference + */ + currentConnectionPreference: 8, + /** + * Delay in ms between trigger and bumping unconnected block out of alignment. + * @alias Blockly.config.bumpDelay + */ + bumpDelay: 250, +}; + +exports.config = config; diff --git a/core/connection.js b/core/connection.js index 25f31bbe5..e12070e11 100644 --- a/core/connection.js +++ b/core/connection.js @@ -20,6 +20,8 @@ const blocks = goog.require('Blockly.serialization.blocks'); const eventUtils = goog.require('Blockly.Events.utils'); /* eslint-disable-next-line no-unused-vars */ const {Block} = goog.requireType('Blockly.Block'); +/* eslint-disable-next-line no-unused-vars */ +const {BlockMove} = goog.requireType('Blockly.Events.BlockMove'); const {ConnectionType} = goog.require('Blockly.ConnectionType'); /* eslint-disable-next-line no-unused-vars */ const {IASTNodeLocationWithBlock} = goog.require('Blockly.IASTNodeLocationWithBlock'); @@ -35,21 +37,629 @@ goog.require('Blockly.constants'); /** * Class for a connection between blocks. - * @param {!Block} source The block establishing this connection. - * @param {number} type The type of the connection. - * @constructor * @implements {IASTNodeLocationWithBlock} * @alias Blockly.Connection */ -const Connection = function(source, type) { +class Connection { /** - * @type {!Block} + * @param {!Block} source The block establishing this connection. + * @param {number} type The type of the connection. + */ + constructor(source, type) { + /** + * @type {!Block} + * @protected + */ + this.sourceBlock_ = source; + /** @type {number} */ + this.type = type; + + /** + * Connection this connection connects to. Null if not connected. + * @type {Connection} + */ + this.targetConnection = null; + + /** + * Has this connection been disposed of? + * @type {boolean} + * @package + */ + this.disposed = false; + + /** + * List of compatible value types. Null if all types are compatible. + * @type {Array} + * @private + */ + this.check_ = null; + + /** + * DOM representation of a shadow block, or null if none. + * @type {Element} + * @private + */ + this.shadowDom_ = null; + + /** + * Horizontal location of this connection. + * @type {number} + * @package + */ + this.x = 0; + + /** + * Vertical location of this connection. + * @type {number} + * @package + */ + this.y = 0; + + /** + * @type {?blocks.State} + * @private + */ + this.shadowState_ = null; + } + + /** + * Connect two connections together. This is the connection on the superior + * block. + * @param {!Connection} childConnection Connection on inferior block. * @protected */ - this.sourceBlock_ = source; - /** @type {number} */ - this.type = type; -}; + connect_(childConnection) { + const INPUT = ConnectionType.INPUT_VALUE; + const parentConnection = this; + const parentBlock = parentConnection.getSourceBlock(); + const childBlock = childConnection.getSourceBlock(); + + // Make sure the childConnection is available. + if (childConnection.isConnected()) { + childConnection.disconnect(); + } + + // Make sure the parentConnection is available. + let orphan; + if (parentConnection.isConnected()) { + const shadowState = parentConnection.stashShadowState_(); + const target = parentConnection.targetBlock(); + if (target.isShadow()) { + target.dispose(false); + } else { + parentConnection.disconnect(); + orphan = target; + } + parentConnection.applyShadowState_(shadowState); + } + + // Connect the new connection to the parent. + let event; + if (eventUtils.isEnabled()) { + event = /** @type {!BlockMove} */ + (new (eventUtils.get(eventUtils.BLOCK_MOVE))(childBlock)); + } + connectReciprocally(parentConnection, childConnection); + childBlock.setParent(parentBlock); + if (event) { + event.recordNew(); + eventUtils.fire(event); + } + + // Deal with the orphan if it exists. + if (orphan) { + const orphanConnection = parentConnection.type === INPUT ? + orphan.outputConnection : + orphan.previousConnection; + const connection = Connection.getConnectionForOrphanedConnection( + childBlock, /** @type {!Connection} */ (orphanConnection)); + if (connection) { + orphanConnection.connect(connection); + } else { + orphanConnection.onFailedConnect(parentConnection); + } + } + } + + /** + * Dispose of this connection and deal with connected blocks. + * @package + */ + dispose() { + // isConnected returns true for shadows and non-shadows. + if (this.isConnected()) { + // Destroy the attached shadow block & its children (if it exists). + this.setShadowStateInternal_(); + + const targetBlock = this.targetBlock(); + if (targetBlock) { + // Disconnect the attached normal block. + targetBlock.unplug(); + } + } + + this.disposed = true; + } + + /** + * Get the source block for this connection. + * @return {!Block} The source block. + */ + getSourceBlock() { + return this.sourceBlock_; + } + + /** + * Does the connection belong to a superior block (higher in the source + * stack)? + * @return {boolean} True if connection faces down or right. + */ + isSuperior() { + return this.type === ConnectionType.INPUT_VALUE || + this.type === ConnectionType.NEXT_STATEMENT; + } + + /** + * Is the connection connected? + * @return {boolean} True if connection is connected to another connection. + */ + isConnected() { + return !!this.targetConnection; + } + + /** + * Get the workspace's connection type checker object. + * @return {!IConnectionChecker} The connection type checker for the + * source block's workspace. + * @package + */ + getConnectionChecker() { + return this.sourceBlock_.workspace.connectionChecker; + } + + /** + * Called when an attempted connection fails. NOP by default (i.e. for + * headless workspaces). + * @param {!Connection} _otherConnection Connection that this connection + * failed to connect to. + * @package + */ + onFailedConnect(_otherConnection) { + // NOP + } + + /** + * Connect this connection to another connection. + * @param {!Connection} otherConnection Connection to connect to. + * @return {boolean} Whether the the blocks are now connected or not. + */ + connect(otherConnection) { + if (this.targetConnection === otherConnection) { + // Already connected together. NOP. + return true; + } + + const checker = this.getConnectionChecker(); + if (checker.canConnect(this, otherConnection, false)) { + const eventGroup = eventUtils.getGroup(); + if (!eventGroup) { + eventUtils.setGroup(true); + } + // Determine which block is superior (higher in the source stack). + if (this.isSuperior()) { + // Superior block. + this.connect_(otherConnection); + } else { + // Inferior block. + otherConnection.connect_(this); + } + if (!eventGroup) { + eventUtils.setGroup(false); + } + } + + return this.isConnected(); + } + + /** + * Disconnect this connection. + */ + disconnect() { + const otherConnection = this.targetConnection; + if (!otherConnection) { + throw Error('Source connection not connected.'); + } + if (otherConnection.targetConnection !== this) { + throw Error('Target connection not connected to source connection.'); + } + let parentBlock; + let childBlock; + let parentConnection; + if (this.isSuperior()) { + // Superior block. + parentBlock = this.sourceBlock_; + childBlock = otherConnection.getSourceBlock(); + parentConnection = this; + } else { + // Inferior block. + parentBlock = otherConnection.getSourceBlock(); + childBlock = this.sourceBlock_; + parentConnection = otherConnection; + } + + const eventGroup = eventUtils.getGroup(); + if (!eventGroup) { + eventUtils.setGroup(true); + } + this.disconnectInternal_(parentBlock, childBlock); + if (!childBlock.isShadow()) { + // If we were disconnecting a shadow, no need to spawn a new one. + parentConnection.respawnShadow_(); + } + if (!eventGroup) { + eventUtils.setGroup(false); + } + } + + /** + * Disconnect two blocks that are connected by this connection. + * @param {!Block} parentBlock The superior block. + * @param {!Block} childBlock The inferior block. + * @protected + */ + disconnectInternal_(parentBlock, childBlock) { + let event; + if (eventUtils.isEnabled()) { + event = /** @type {!BlockMove} */ + (new (eventUtils.get(eventUtils.BLOCK_MOVE))(childBlock)); + } + const otherConnection = this.targetConnection; + otherConnection.targetConnection = null; + this.targetConnection = null; + childBlock.setParent(null); + if (event) { + event.recordNew(); + eventUtils.fire(event); + } + } + + /** + * Respawn the shadow block if there was one connected to the this connection. + * @protected + */ + respawnShadow_() { + // Have to keep respawnShadow_ for backwards compatibility. + this.createShadowBlock_(true); + } + + /** + * Returns the block that this connection connects to. + * @return {?Block} The connected block or null if none is connected. + */ + targetBlock() { + if (this.isConnected()) { + return this.targetConnection.getSourceBlock(); + } + return null; + } + + /** + * Function to be called when this connection's compatible types have changed. + * @protected + */ + onCheckChanged_() { + // The new value type may not be compatible with the existing connection. + if (this.isConnected() && + (!this.targetConnection || + !this.getConnectionChecker().canConnect( + this, this.targetConnection, false))) { + const child = this.isSuperior() ? this.targetBlock() : this.sourceBlock_; + child.unplug(); + } + } + + /** + * Change a connection's compatibility. + * @param {?(string|!Array)} check Compatible value type or list of + * value types. Null if all types are compatible. + * @return {!Connection} The connection being modified + * (to allow chaining). + */ + setCheck(check) { + if (check) { + // Ensure that check is in an array. + if (!Array.isArray(check)) { + check = [check]; + } + this.check_ = check; + this.onCheckChanged_(); + } else { + this.check_ = null; + } + return this; + } + + /** + * Get a connection's compatibility. + * @return {?Array} List of compatible value types. + * Null if all types are compatible. + * @public + */ + getCheck() { + return this.check_; + } + + /** + * Changes the connection's shadow block. + * @param {?Element} shadowDom DOM representation of a block or null. + */ + setShadowDom(shadowDom) { + this.setShadowStateInternal_({shadowDom: shadowDom}); + } + + /** + * Returns the xml representation of the connection's shadow block. + * @param {boolean=} returnCurrent If true, and the shadow block is currently + * attached to this connection, this serializes the state of that block + * and returns it (so that field values are correct). Otherwise the saved + * shadowDom is just returned. + * @return {?Element} Shadow DOM representation of a block or null. + */ + getShadowDom(returnCurrent) { + return (returnCurrent && this.targetBlock().isShadow()) ? + /** @type {!Element} */ (Xml.blockToDom( + /** @type {!Block} */ (this.targetBlock()))) : + this.shadowDom_; + } + + /** + * Changes the connection's shadow block. + * @param {?blocks.State} shadowState An state represetation of the block or + * null. + */ + setShadowState(shadowState) { + this.setShadowStateInternal_({shadowState: shadowState}); + } + + /** + * Returns the serialized object representation of the connection's shadow + * block. + * @param {boolean=} returnCurrent If true, and the shadow block is currently + * attached to this connection, this serializes the state of that block + * and returns it (so that field values are correct). Otherwise the saved + * state is just returned. + * @return {?blocks.State} Serialized object representation of the block, or + * null. + */ + getShadowState(returnCurrent) { + if (returnCurrent && this.targetBlock() && this.targetBlock().isShadow()) { + return blocks.save(/** @type {!Block} */ (this.targetBlock())); + } + return this.shadowState_; + } + + /** + * Find all nearby compatible connections to this connection. + * Type checking does not apply, since this function is used for bumping. + * + * Headless configurations (the default) do not have neighboring connection, + * and always return an empty list (the default). + * {@link Blockly.RenderedConnection} overrides this behavior with a list + * computed from the rendered positioning. + * @param {number} _maxLimit The maximum radius to another connection. + * @return {!Array} List of connections. + * @package + */ + neighbours(_maxLimit) { + return []; + } + + /** + * Get the parent input of a connection. + * @return {?Input} The input that the connection belongs to or null if + * no parent exists. + * @package + */ + getParentInput() { + let parentInput = null; + const inputs = this.sourceBlock_.inputList; + for (let i = 0; i < inputs.length; i++) { + if (inputs[i].connection === this) { + parentInput = inputs[i]; + break; + } + } + return parentInput; + } + + /** + * This method returns a string describing this Connection in developer terms + * (English only). Intended to on be used in console logs and errors. + * @return {string} The description. + */ + toString() { + const block = this.sourceBlock_; + if (!block) { + return 'Orphan Connection'; + } + let msg; + if (block.outputConnection === this) { + msg = 'Output Connection of '; + } else if (block.previousConnection === this) { + msg = 'Previous Connection of '; + } else if (block.nextConnection === this) { + msg = 'Next Connection of '; + } else { + let parentInput = null; + for (let i = 0, input; (input = block.inputList[i]); i++) { + if (input.connection === this) { + parentInput = input; + break; + } + } + if (parentInput) { + msg = 'Input "' + parentInput.name + '" connection on '; + } else { + console.warn('Connection not actually connected to sourceBlock_'); + return 'Orphan Connection'; + } + } + return msg + block.toDevString(); + } + + /** + * Returns the state of the shadowDom_ and shadowState_ properties, then + * temporarily sets those properties to null so no shadow respawns. + * @return {{shadowDom: ?Element, shadowState: ?blocks.State}} The state of + * both the shadowDom_ and shadowState_ properties. + * @private + */ + stashShadowState_() { + const shadowDom = this.getShadowDom(true); + const shadowState = this.getShadowState(true); + // Set to null so it doesn't respawn. + this.shadowDom_ = null; + this.shadowState_ = null; + return {shadowDom, shadowState}; + } + + /** + * Reapplies the stashed state of the shadowDom_ and shadowState_ properties. + * @param {{shadowDom: ?Element, shadowState: ?blocks.State}} param0 The state + * to reapply to the shadowDom_ and shadowState_ properties. + * @private + */ + applyShadowState_({shadowDom, shadowState}) { + this.shadowDom_ = shadowDom; + this.shadowState_ = shadowState; + } + + /** + * Sets the state of the shadow of this connection. + * @param {{shadowDom: (?Element|undefined), shadowState: + * (?blocks.State|undefined)}=} param0 The state to set the shadow of this + * connection to. + * @private + */ + setShadowStateInternal_({shadowDom = null, shadowState = null} = {}) { + // One or both of these should always be null. + // If neither is null, the shadowState will get priority. + this.shadowDom_ = shadowDom; + this.shadowState_ = shadowState; + + const target = this.targetBlock(); + if (!target) { + this.respawnShadow_(); + if (this.targetBlock() && this.targetBlock().isShadow()) { + this.serializeShadow_(this.targetBlock()); + } + } else if (target.isShadow()) { + target.dispose(false); + this.respawnShadow_(); + if (this.targetBlock() && this.targetBlock().isShadow()) { + this.serializeShadow_(this.targetBlock()); + } + } else { + const shadow = this.createShadowBlock_(false); + this.serializeShadow_(shadow); + if (shadow) { + shadow.dispose(false); + } + } + } + + /** + * Creates a shadow block based on the current shadowState_ or shadowDom_. + * shadowState_ gets priority. + * @param {boolean} attemptToConnect Whether to try to connect the shadow + * block to this connection or not. + * @return {?Block} The shadow block that was created, or null if both the + * shadowState_ and shadowDom_ are null. + * @private + */ + createShadowBlock_(attemptToConnect) { + const parentBlock = this.getSourceBlock(); + const shadowState = this.getShadowState(); + const shadowDom = this.getShadowDom(); + if (!parentBlock.workspace || (!shadowState && !shadowDom)) { + return null; + } + + let blockShadow; + if (shadowState) { + blockShadow = blocks.appendInternal(shadowState, parentBlock.workspace, { + parentConnection: attemptToConnect ? this : undefined, + isShadow: true, + recordUndo: false, + }); + return blockShadow; + } + + if (shadowDom) { + blockShadow = Xml.domToBlock(shadowDom, parentBlock.workspace); + if (attemptToConnect) { + if (this.type === ConnectionType.INPUT_VALUE) { + if (!blockShadow.outputConnection) { + throw new Error('Shadow block is missing an output connection'); + } + if (!this.connect(blockShadow.outputConnection)) { + throw new Error('Could not connect shadow block to connection'); + } + } else if (this.type === ConnectionType.NEXT_STATEMENT) { + if (!blockShadow.previousConnection) { + throw new Error('Shadow block is missing previous connection'); + } + if (!this.connect(blockShadow.previousConnection)) { + throw new Error('Could not connect shadow block to connection'); + } + } else { + throw new Error( + 'Cannot connect a shadow block to a previous/output connection'); + } + } + return blockShadow; + } + return null; + } + + /** + * Saves the given shadow block to both the shadowDom_ and shadowState_ + * properties, in their respective serialized forms. + * @param {?Block} shadow The shadow to serialize, or null. + * @private + */ + serializeShadow_(shadow) { + if (!shadow) { + return; + } + this.shadowDom_ = /** @type {!Element} */ (Xml.blockToDom(shadow)); + this.shadowState_ = blocks.save(shadow); + } + + /** + * Returns the connection (starting at the startBlock) which will accept + * the given connection. This includes compatible connection types and + * connection checks. + * @param {!Block} startBlock The block on which to start the search. + * @param {!Connection} orphanConnection The connection that is looking + * for a home. + * @return {?Connection} The suitable connection point on the chain of + * blocks, or null. + */ + static getConnectionForOrphanedConnection(startBlock, orphanConnection) { + if (orphanConnection.type === ConnectionType.OUTPUT_VALUE) { + return getConnectionForOrphanedOutput( + startBlock, orphanConnection.getSourceBlock()); + } + // Otherwise we're dealing with a stack. + const connection = startBlock.lastConnectionInStack(true); + const checker = orphanConnection.getConnectionChecker(); + if (connection && checker.canConnect(orphanConnection, connection, false)) { + return connection; + } + return null; + } +} /** * Constants for checking whether two connections are compatible. @@ -64,205 +674,6 @@ Connection.REASON_SHADOW_PARENT = 6; Connection.REASON_DRAG_CHECKS_FAILED = 7; Connection.REASON_PREVIOUS_AND_OUTPUT = 8; -/** - * Connection this connection connects to. Null if not connected. - * @type {Connection} - */ -Connection.prototype.targetConnection = null; - -/** - * Has this connection been disposed of? - * @type {boolean} - * @package - */ -Connection.prototype.disposed = false; - -/** - * List of compatible value types. Null if all types are compatible. - * @type {Array} - * @private - */ -Connection.prototype.check_ = null; - -/** - * DOM representation of a shadow block, or null if none. - * @type {Element} - * @private - */ -Connection.prototype.shadowDom_ = null; - -/** - * Horizontal location of this connection. - * @type {number} - * @package - */ -Connection.prototype.x = 0; - -/** - * Vertical location of this connection. - * @type {number} - * @package - */ -Connection.prototype.y = 0; - -/** - * Connect two connections together. This is the connection on the superior - * block. - * @param {!Connection} childConnection Connection on inferior block. - * @protected - */ -Connection.prototype.connect_ = function(childConnection) { - const INPUT = ConnectionType.INPUT_VALUE; - const parentConnection = this; - const parentBlock = parentConnection.getSourceBlock(); - const childBlock = childConnection.getSourceBlock(); - - // Make sure the childConnection is available. - if (childConnection.isConnected()) { - childConnection.disconnect(); - } - - // Make sure the parentConnection is available. - let orphan; - if (parentConnection.isConnected()) { - const shadowState = parentConnection.stashShadowState_(); - const target = parentConnection.targetBlock(); - if (target.isShadow()) { - target.dispose(false); - } else { - parentConnection.disconnect(); - orphan = target; - } - parentConnection.applyShadowState_(shadowState); - } - - // Connect the new connection to the parent. - let event; - if (eventUtils.isEnabled()) { - event = new (eventUtils.get(eventUtils.BLOCK_MOVE))(childBlock); - } - connectReciprocally(parentConnection, childConnection); - childBlock.setParent(parentBlock); - if (event) { - event.recordNew(); - eventUtils.fire(event); - } - - // Deal with the orphan if it exists. - if (orphan) { - const orphanConnection = parentConnection.type === INPUT ? - orphan.outputConnection : - orphan.previousConnection; - const connection = Connection.getConnectionForOrphanedConnection( - childBlock, /** @type {!Connection} */ (orphanConnection)); - if (connection) { - orphanConnection.connect(connection); - } else { - orphanConnection.onFailedConnect(parentConnection); - } - } -}; - - -/** - * Dispose of this connection and deal with connected blocks. - * @package - */ -Connection.prototype.dispose = function() { - // isConnected returns true for shadows and non-shadows. - if (this.isConnected()) { - // Destroy the attached shadow block & its children (if it exists). - this.setShadowStateInternal_(); - - const targetBlock = this.targetBlock(); - if (targetBlock) { - // Disconnect the attached normal block. - targetBlock.unplug(); - } - } - - this.disposed = true; -}; - -/** - * Get the source block for this connection. - * @return {!Block} The source block. - */ -Connection.prototype.getSourceBlock = function() { - return this.sourceBlock_; -}; - -/** - * Does the connection belong to a superior block (higher in the source stack)? - * @return {boolean} True if connection faces down or right. - */ -Connection.prototype.isSuperior = function() { - return this.type === ConnectionType.INPUT_VALUE || - this.type === ConnectionType.NEXT_STATEMENT; -}; - -/** - * Is the connection connected? - * @return {boolean} True if connection is connected to another connection. - */ -Connection.prototype.isConnected = function() { - return !!this.targetConnection; -}; - -/** - * Get the workspace's connection type checker object. - * @return {!IConnectionChecker} The connection type checker for the - * source block's workspace. - * @package - */ -Connection.prototype.getConnectionChecker = function() { - return this.sourceBlock_.workspace.connectionChecker; -}; - -/** - * Called when an attempted connection fails. NOP by default (i.e. for headless - * workspaces). - * @param {!Connection} _otherConnection Connection that this connection - * failed to connect to. - * @package - */ -Connection.prototype.onFailedConnect = function(_otherConnection) { - // NOP -}; - -/** - * Connect this connection to another connection. - * @param {!Connection} otherConnection Connection to connect to. - * @return {boolean} Whether the the blocks are now connected or not. - */ -Connection.prototype.connect = function(otherConnection) { - if (this.targetConnection === otherConnection) { - // Already connected together. NOP. - return true; - } - - const checker = this.getConnectionChecker(); - if (checker.canConnect(this, otherConnection, false)) { - const eventGroup = eventUtils.getGroup(); - if (!eventGroup) { - eventUtils.setGroup(true); - } - // Determine which block is superior (higher in the source stack). - if (this.isSuperior()) { - // Superior block. - this.connect_(otherConnection); - } else { - // Inferior block. - otherConnection.connect_(this); - } - if (!eventGroup) { - eventUtils.setGroup(false); - } - } - - return this.isConnected(); -}; - /** * Update two connections to target each other. * @param {Connection} first The first connection to update. @@ -328,404 +739,4 @@ const getConnectionForOrphanedOutput = function(startBlock, orphanBlock) { return null; }; -/** - * Returns the connection (starting at the startBlock) which will accept - * the given connection. This includes compatible connection types and - * connection checks. - * @param {!Block} startBlock The block on which to start the search. - * @param {!Connection} orphanConnection The connection that is looking - * for a home. - * @return {?Connection} The suitable connection point on the chain of - * blocks, or null. - */ -Connection.getConnectionForOrphanedConnection = function( - startBlock, orphanConnection) { - if (orphanConnection.type === ConnectionType.OUTPUT_VALUE) { - return getConnectionForOrphanedOutput( - startBlock, orphanConnection.getSourceBlock()); - } - // Otherwise we're dealing with a stack. - const connection = startBlock.lastConnectionInStack(true); - const checker = orphanConnection.getConnectionChecker(); - if (connection && checker.canConnect(orphanConnection, connection, false)) { - return connection; - } - return null; -}; - -/** - * Disconnect this connection. - */ -Connection.prototype.disconnect = function() { - const otherConnection = this.targetConnection; - if (!otherConnection) { - throw Error('Source connection not connected.'); - } - if (otherConnection.targetConnection !== this) { - throw Error('Target connection not connected to source connection.'); - } - let parentBlock; - let childBlock; - let parentConnection; - if (this.isSuperior()) { - // Superior block. - parentBlock = this.sourceBlock_; - childBlock = otherConnection.getSourceBlock(); - parentConnection = this; - } else { - // Inferior block. - parentBlock = otherConnection.getSourceBlock(); - childBlock = this.sourceBlock_; - parentConnection = otherConnection; - } - - const eventGroup = eventUtils.getGroup(); - if (!eventGroup) { - eventUtils.setGroup(true); - } - this.disconnectInternal_(parentBlock, childBlock); - if (!childBlock.isShadow()) { - // If we were disconnecting a shadow, no need to spawn a new one. - parentConnection.respawnShadow_(); - } - if (!eventGroup) { - eventUtils.setGroup(false); - } -}; - -/** - * Disconnect two blocks that are connected by this connection. - * @param {!Block} parentBlock The superior block. - * @param {!Block} childBlock The inferior block. - * @protected - */ -Connection.prototype.disconnectInternal_ = function(parentBlock, childBlock) { - let event; - if (eventUtils.isEnabled()) { - event = new (eventUtils.get(eventUtils.BLOCK_MOVE))(childBlock); - } - const otherConnection = this.targetConnection; - otherConnection.targetConnection = null; - this.targetConnection = null; - childBlock.setParent(null); - if (event) { - event.recordNew(); - eventUtils.fire(event); - } -}; - -/** - * Respawn the shadow block if there was one connected to the this connection. - * @protected - */ -Connection.prototype.respawnShadow_ = function() { - // Have to keep respawnShadow_ for backwards compatibility. - this.createShadowBlock_(true); -}; - -/** - * Returns the block that this connection connects to. - * @return {?Block} The connected block or null if none is connected. - */ -Connection.prototype.targetBlock = function() { - if (this.isConnected()) { - return this.targetConnection.getSourceBlock(); - } - return null; -}; - -/** - * Function to be called when this connection's compatible types have changed. - * @protected - */ -Connection.prototype.onCheckChanged_ = function() { - // The new value type may not be compatible with the existing connection. - if (this.isConnected() && - (!this.targetConnection || - !this.getConnectionChecker().canConnect( - this, this.targetConnection, false))) { - const child = this.isSuperior() ? this.targetBlock() : this.sourceBlock_; - child.unplug(); - } -}; - -/** - * Change a connection's compatibility. - * @param {?(string|!Array)} check Compatible value type or list of - * value types. Null if all types are compatible. - * @return {!Connection} The connection being modified - * (to allow chaining). - */ -Connection.prototype.setCheck = function(check) { - if (check) { - // Ensure that check is in an array. - if (!Array.isArray(check)) { - check = [check]; - } - this.check_ = check; - this.onCheckChanged_(); - } else { - this.check_ = null; - } - return this; -}; - -/** - * Get a connection's compatibility. - * @return {?Array} List of compatible value types. - * Null if all types are compatible. - * @public - */ -Connection.prototype.getCheck = function() { - return this.check_; -}; - -/** - * Changes the connection's shadow block. - * @param {?Element} shadowDom DOM representation of a block or null. - */ -Connection.prototype.setShadowDom = function(shadowDom) { - this.setShadowStateInternal_({shadowDom: shadowDom}); -}; - -/** - * Returns the xml representation of the connection's shadow block. - * @param {boolean=} returnCurrent If true, and the shadow block is currently - * attached to this connection, this serializes the state of that block - * and returns it (so that field values are correct). Otherwise the saved - * shadowDom is just returned. - * @return {?Element} Shadow DOM representation of a block or null. - */ -Connection.prototype.getShadowDom = function(returnCurrent) { - return (returnCurrent && this.targetBlock().isShadow()) ? - /** @type {!Element} */ (Xml.blockToDom( - /** @type {!Block} */ (this.targetBlock()))) : - this.shadowDom_; -}; - -/** - * Changes the connection's shadow block. - * @param {?blocks.State} shadowState An state represetation of the block or - * null. - */ -Connection.prototype.setShadowState = function(shadowState) { - this.setShadowStateInternal_({shadowState: shadowState}); -}; - -/** - * Returns the serialized object representation of the connection's shadow - * block. - * @param {boolean=} returnCurrent If true, and the shadow block is currently - * attached to this connection, this serializes the state of that block - * and returns it (so that field values are correct). Otherwise the saved - * state is just returned. - * @return {?blocks.State} Serialized object representation of the block, or - * null. - */ -Connection.prototype.getShadowState = function(returnCurrent) { - if (returnCurrent && this.targetBlock() && this.targetBlock().isShadow()) { - return blocks.save(/** @type {!Block} */ (this.targetBlock())); - } - return this.shadowState_; -}; - -/** - * Find all nearby compatible connections to this connection. - * Type checking does not apply, since this function is used for bumping. - * - * Headless configurations (the default) do not have neighboring connection, - * and always return an empty list (the default). - * {@link Blockly.RenderedConnection} overrides this behavior with a list - * computed from the rendered positioning. - * @param {number} _maxLimit The maximum radius to another connection. - * @return {!Array} List of connections. - * @package - */ -Connection.prototype.neighbours = function(_maxLimit) { - return []; -}; - -/** - * Get the parent input of a connection. - * @return {?Input} The input that the connection belongs to or null if - * no parent exists. - * @package - */ -Connection.prototype.getParentInput = function() { - let parentInput = null; - const inputs = this.sourceBlock_.inputList; - for (let i = 0; i < inputs.length; i++) { - if (inputs[i].connection === this) { - parentInput = inputs[i]; - break; - } - } - return parentInput; -}; - -/** - * This method returns a string describing this Connection in developer terms - * (English only). Intended to on be used in console logs and errors. - * @return {string} The description. - */ -Connection.prototype.toString = function() { - const block = this.sourceBlock_; - if (!block) { - return 'Orphan Connection'; - } - let msg; - if (block.outputConnection === this) { - msg = 'Output Connection of '; - } else if (block.previousConnection === this) { - msg = 'Previous Connection of '; - } else if (block.nextConnection === this) { - msg = 'Next Connection of '; - } else { - let parentInput = null; - for (let i = 0, input; (input = block.inputList[i]); i++) { - if (input.connection === this) { - parentInput = input; - break; - } - } - if (parentInput) { - msg = 'Input "' + parentInput.name + '" connection on '; - } else { - console.warn('Connection not actually connected to sourceBlock_'); - return 'Orphan Connection'; - } - } - return msg + block.toDevString(); -}; - -/** - * Returns the state of the shadowDom_ and shadowState_ properties, then - * temporarily sets those properties to null so no shadow respawns. - * @return {{shadowDom: ?Element, shadowState: ?blocks.State}} The state of both - * the shadowDom_ and shadowState_ properties. - * @private - */ -Connection.prototype.stashShadowState_ = function() { - const shadowDom = this.getShadowDom(true); - const shadowState = this.getShadowState(true); - // Set to null so it doesn't respawn. - this.shadowDom_ = null; - this.shadowState_ = null; - return {shadowDom, shadowState}; -}; - -/** - * Reapplies the stashed state of the shadowDom_ and shadowState_ properties. - * @param {{shadowDom: ?Element, shadowState: ?blocks.State}} param0 The state - * to reapply to the shadowDom_ and shadowState_ properties. - * @private - */ -Connection.prototype.applyShadowState_ = function({shadowDom, shadowState}) { - this.shadowDom_ = shadowDom; - this.shadowState_ = shadowState; -}; - -/** - * Sets the state of the shadow of this connection. - * @param {{shadowDom: (?Element|undefined), shadowState: - * (?blocks.State|undefined)}=} param0 The state to set the shadow of this - * connection to. - * @private - */ -Connection.prototype.setShadowStateInternal_ = function( - {shadowDom = null, shadowState = null} = {}) { - // One or both of these should always be null. - // If neither is null, the shadowState will get priority. - this.shadowDom_ = shadowDom; - this.shadowState_ = shadowState; - - const target = this.targetBlock(); - if (!target) { - this.respawnShadow_(); - if (this.targetBlock() && this.targetBlock().isShadow()) { - this.serializeShadow_(this.targetBlock()); - } - } else if (target.isShadow()) { - target.dispose(false); - this.respawnShadow_(); - if (this.targetBlock() && this.targetBlock().isShadow()) { - this.serializeShadow_(this.targetBlock()); - } - } else { - const shadow = this.createShadowBlock_(false); - this.serializeShadow_(shadow); - if (shadow) { - shadow.dispose(false); - } - } -}; - -/** - * Creates a shadow block based on the current shadowState_ or shadowDom_. - * shadowState_ gets priority. - * @param {boolean} attemptToConnect Whether to try to connect the shadow block - * to this connection or not. - * @return {?Block} The shadow block that was created, or null if both the - * shadowState_ and shadowDom_ are null. - * @private - */ -Connection.prototype.createShadowBlock_ = function(attemptToConnect) { - const parentBlock = this.getSourceBlock(); - const shadowState = this.getShadowState(); - const shadowDom = this.getShadowDom(); - if (!parentBlock.workspace || (!shadowState && !shadowDom)) { - return null; - } - - let blockShadow; - if (shadowState) { - blockShadow = blocks.appendInternal(shadowState, parentBlock.workspace, { - parentConnection: attemptToConnect ? this : undefined, - isShadow: true, - recordUndo: false, - }); - return blockShadow; - } - - if (shadowDom) { - blockShadow = Xml.domToBlock(shadowDom, parentBlock.workspace); - if (attemptToConnect) { - if (this.type === ConnectionType.INPUT_VALUE) { - if (!blockShadow.outputConnection) { - throw new Error('Shadow block is missing an output connection'); - } - if (!this.connect(blockShadow.outputConnection)) { - throw new Error('Could not connect shadow block to connection'); - } - } else if (this.type === ConnectionType.NEXT_STATEMENT) { - if (!blockShadow.previousConnection) { - throw new Error('Shadow block is missing previous connection'); - } - if (!this.connect(blockShadow.previousConnection)) { - throw new Error('Could not connect shadow block to connection'); - } - } else { - throw new Error( - 'Cannot connect a shadow block to a previous/output connection'); - } - } - return blockShadow; - } - return null; -}; - -/** - * Saves the given shadow block to both the shadowDom_ and shadowState_ - * properties, in their respective serialized forms. - * @param {?Block} shadow The shadow to serialize, or null. - * @private - */ -Connection.prototype.serializeShadow_ = function(shadow) { - if (!shadow) { - return; - } - this.shadowDom_ = /** @type {!Element} */ (Xml.blockToDom(shadow)); - this.shadowState_ = blocks.save(shadow); -}; - exports.Connection = Connection; diff --git a/core/connection_checker.js b/core/connection_checker.js index d512488ef..33bf256a2 100644 --- a/core/connection_checker.js +++ b/core/connection_checker.js @@ -31,282 +31,280 @@ const {RenderedConnection} = goog.requireType('Blockly.RenderedConnection'); /** * Class for connection type checking logic. * @implements {IConnectionChecker} - * @constructor * @alias Blockly.ConnectionChecker */ -const ConnectionChecker = function() {}; - -/** - * Check whether the current connection can connect with the target - * connection. - * @param {Connection} a Connection to check compatibility with. - * @param {Connection} b Connection to check compatibility with. - * @param {boolean} isDragging True if the connection is being made by dragging - * a block. - * @param {number=} opt_distance The max allowable distance between the - * connections for drag checks. - * @return {boolean} Whether the connection is legal. - * @public - */ -ConnectionChecker.prototype.canConnect = function( - a, b, isDragging, opt_distance) { - return this.canConnectWithReason(a, b, isDragging, opt_distance) === - Connection.CAN_CONNECT; -}; - -/** - * Checks whether the current connection can connect with the target - * connection, and return an error code if there are problems. - * @param {Connection} a Connection to check compatibility with. - * @param {Connection} b Connection to check compatibility with. - * @param {boolean} isDragging True if the connection is being made by dragging - * a block. - * @param {number=} opt_distance The max allowable distance between the - * connections for drag checks. - * @return {number} Connection.CAN_CONNECT if the connection is legal, - * an error code otherwise. - * @public - */ -ConnectionChecker.prototype.canConnectWithReason = function( - a, b, isDragging, opt_distance) { - const safety = this.doSafetyChecks(a, b); - if (safety !== Connection.CAN_CONNECT) { - return safety; +class ConnectionChecker { + /** + * Check whether the current connection can connect with the target + * connection. + * @param {Connection} a Connection to check compatibility with. + * @param {Connection} b Connection to check compatibility with. + * @param {boolean} isDragging True if the connection is being made by + * dragging a block. + * @param {number=} opt_distance The max allowable distance between the + * connections for drag checks. + * @return {boolean} Whether the connection is legal. + * @public + */ + canConnect(a, b, isDragging, opt_distance) { + return this.canConnectWithReason(a, b, isDragging, opt_distance) === + Connection.CAN_CONNECT; } - // If the safety checks passed, both connections are non-null. - const connOne = /** @type {!Connection} **/ (a); - const connTwo = /** @type {!Connection} **/ (b); - if (!this.doTypeChecks(connOne, connTwo)) { - return Connection.REASON_CHECKS_FAILED; - } - - if (isDragging && - !this.doDragChecks( - /** @type {!RenderedConnection} **/ (a), - /** @type {!RenderedConnection} **/ (b), opt_distance || 0)) { - return Connection.REASON_DRAG_CHECKS_FAILED; - } - - return Connection.CAN_CONNECT; -}; - -/** - * Helper method that translates a connection error code into a string. - * @param {number} errorCode The error code. - * @param {Connection} a One of the two connections being checked. - * @param {Connection} b The second of the two connections being - * checked. - * @return {string} A developer-readable error string. - * @public - */ -ConnectionChecker.prototype.getErrorMessage = function(errorCode, a, b) { - switch (errorCode) { - case Connection.REASON_SELF_CONNECTION: - return 'Attempted to connect a block to itself.'; - case Connection.REASON_DIFFERENT_WORKSPACES: - // Usually this means one block has been deleted. - return 'Blocks not on same workspace.'; - case Connection.REASON_WRONG_TYPE: - return 'Attempt to connect incompatible types.'; - case Connection.REASON_TARGET_NULL: - return 'Target connection is null.'; - case Connection.REASON_CHECKS_FAILED: { - const connOne = /** @type {!Connection} **/ (a); - const connTwo = /** @type {!Connection} **/ (b); - let msg = 'Connection checks failed. '; - msg += connOne + ' expected ' + connOne.getCheck() + ', found ' + - connTwo.getCheck(); - return msg; + /** + * Checks whether the current connection can connect with the target + * connection, and return an error code if there are problems. + * @param {Connection} a Connection to check compatibility with. + * @param {Connection} b Connection to check compatibility with. + * @param {boolean} isDragging True if the connection is being made by + * dragging a block. + * @param {number=} opt_distance The max allowable distance between the + * connections for drag checks. + * @return {number} Connection.CAN_CONNECT if the connection is legal, + * an error code otherwise. + * @public + */ + canConnectWithReason(a, b, isDragging, opt_distance) { + const safety = this.doSafetyChecks(a, b); + if (safety !== Connection.CAN_CONNECT) { + return safety; } - case Connection.REASON_SHADOW_PARENT: - return 'Connecting non-shadow to shadow block.'; - case Connection.REASON_DRAG_CHECKS_FAILED: - return 'Drag checks failed.'; - case Connection.REASON_PREVIOUS_AND_OUTPUT: - return 'Block would have an output and a previous connection.'; - default: - return 'Unknown connection failure: this should never happen!'; - } -}; -/** - * Check that connecting the given connections is safe, meaning that it would - * not break any of Blockly's basic assumptions (e.g. no self connections). - * @param {Connection} a The first of the connections to check. - * @param {Connection} b The second of the connections to check. - * @return {number} An enum with the reason this connection is safe or unsafe. - * @public - */ -ConnectionChecker.prototype.doSafetyChecks = function(a, b) { - if (!a || !b) { - return Connection.REASON_TARGET_NULL; - } - let superiorBlock; - let inferiorBlock; - let superiorConnection; - let inferiorConnection; - if (a.isSuperior()) { - superiorBlock = a.getSourceBlock(); - inferiorBlock = b.getSourceBlock(); - superiorConnection = a; - inferiorConnection = b; - } else { - inferiorBlock = a.getSourceBlock(); - superiorBlock = b.getSourceBlock(); - inferiorConnection = a; - superiorConnection = b; - } - if (superiorBlock === inferiorBlock) { - return Connection.REASON_SELF_CONNECTION; - } else if ( - inferiorConnection.type !== - internalConstants.OPPOSITE_TYPE[superiorConnection.type]) { - return Connection.REASON_WRONG_TYPE; - } else if (superiorBlock.workspace !== inferiorBlock.workspace) { - return Connection.REASON_DIFFERENT_WORKSPACES; - } else if (superiorBlock.isShadow() && !inferiorBlock.isShadow()) { - return Connection.REASON_SHADOW_PARENT; - } else if ( - inferiorConnection.type === ConnectionType.OUTPUT_VALUE && - inferiorBlock.previousConnection && - inferiorBlock.previousConnection.isConnected()) { - return Connection.REASON_PREVIOUS_AND_OUTPUT; - } else if ( - inferiorConnection.type === ConnectionType.PREVIOUS_STATEMENT && - inferiorBlock.outputConnection && - inferiorBlock.outputConnection.isConnected()) { - return Connection.REASON_PREVIOUS_AND_OUTPUT; - } - return Connection.CAN_CONNECT; -}; + // If the safety checks passed, both connections are non-null. + const connOne = /** @type {!Connection} **/ (a); + const connTwo = /** @type {!Connection} **/ (b); + if (!this.doTypeChecks(connOne, connTwo)) { + return Connection.REASON_CHECKS_FAILED; + } -/** - * Check whether this connection is compatible with another connection with - * respect to the value type system. E.g. square_root("Hello") is not - * compatible. - * @param {!Connection} a Connection to compare. - * @param {!Connection} b Connection to compare against. - * @return {boolean} True if the connections share a type. - * @public - */ -ConnectionChecker.prototype.doTypeChecks = function(a, b) { - const checkArrayOne = a.getCheck(); - const checkArrayTwo = b.getCheck(); + if (isDragging && + !this.doDragChecks( + /** @type {!RenderedConnection} **/ (a), + /** @type {!RenderedConnection} **/ (b), opt_distance || 0)) { + return Connection.REASON_DRAG_CHECKS_FAILED; + } - if (!checkArrayOne || !checkArrayTwo) { - // One or both sides are promiscuous enough that anything will fit. - return true; + return Connection.CAN_CONNECT; } - // Find any intersection in the check lists. - for (let i = 0; i < checkArrayOne.length; i++) { - if (checkArrayTwo.indexOf(checkArrayOne[i]) !== -1) { + + /** + * Helper method that translates a connection error code into a string. + * @param {number} errorCode The error code. + * @param {Connection} a One of the two connections being checked. + * @param {Connection} b The second of the two connections being + * checked. + * @return {string} A developer-readable error string. + * @public + */ + getErrorMessage(errorCode, a, b) { + switch (errorCode) { + case Connection.REASON_SELF_CONNECTION: + return 'Attempted to connect a block to itself.'; + case Connection.REASON_DIFFERENT_WORKSPACES: + // Usually this means one block has been deleted. + return 'Blocks not on same workspace.'; + case Connection.REASON_WRONG_TYPE: + return 'Attempt to connect incompatible types.'; + case Connection.REASON_TARGET_NULL: + return 'Target connection is null.'; + case Connection.REASON_CHECKS_FAILED: { + const connOne = /** @type {!Connection} **/ (a); + const connTwo = /** @type {!Connection} **/ (b); + let msg = 'Connection checks failed. '; + msg += connOne + ' expected ' + connOne.getCheck() + ', found ' + + connTwo.getCheck(); + return msg; + } + case Connection.REASON_SHADOW_PARENT: + return 'Connecting non-shadow to shadow block.'; + case Connection.REASON_DRAG_CHECKS_FAILED: + return 'Drag checks failed.'; + case Connection.REASON_PREVIOUS_AND_OUTPUT: + return 'Block would have an output and a previous connection.'; + default: + return 'Unknown connection failure: this should never happen!'; + } + } + + /** + * Check that connecting the given connections is safe, meaning that it would + * not break any of Blockly's basic assumptions (e.g. no self connections). + * @param {Connection} a The first of the connections to check. + * @param {Connection} b The second of the connections to check. + * @return {number} An enum with the reason this connection is safe or unsafe. + * @public + */ + doSafetyChecks(a, b) { + if (!a || !b) { + return Connection.REASON_TARGET_NULL; + } + let superiorBlock; + let inferiorBlock; + let superiorConnection; + let inferiorConnection; + if (a.isSuperior()) { + superiorBlock = a.getSourceBlock(); + inferiorBlock = b.getSourceBlock(); + superiorConnection = a; + inferiorConnection = b; + } else { + inferiorBlock = a.getSourceBlock(); + superiorBlock = b.getSourceBlock(); + inferiorConnection = a; + superiorConnection = b; + } + if (superiorBlock === inferiorBlock) { + return Connection.REASON_SELF_CONNECTION; + } else if ( + inferiorConnection.type !== + internalConstants.OPPOSITE_TYPE[superiorConnection.type]) { + return Connection.REASON_WRONG_TYPE; + } else if (superiorBlock.workspace !== inferiorBlock.workspace) { + return Connection.REASON_DIFFERENT_WORKSPACES; + } else if (superiorBlock.isShadow() && !inferiorBlock.isShadow()) { + return Connection.REASON_SHADOW_PARENT; + } else if ( + inferiorConnection.type === ConnectionType.OUTPUT_VALUE && + inferiorBlock.previousConnection && + inferiorBlock.previousConnection.isConnected()) { + return Connection.REASON_PREVIOUS_AND_OUTPUT; + } else if ( + inferiorConnection.type === ConnectionType.PREVIOUS_STATEMENT && + inferiorBlock.outputConnection && + inferiorBlock.outputConnection.isConnected()) { + return Connection.REASON_PREVIOUS_AND_OUTPUT; + } + return Connection.CAN_CONNECT; + } + + /** + * Check whether this connection is compatible with another connection with + * respect to the value type system. E.g. square_root("Hello") is not + * compatible. + * @param {!Connection} a Connection to compare. + * @param {!Connection} b Connection to compare against. + * @return {boolean} True if the connections share a type. + * @public + */ + doTypeChecks(a, b) { + const checkArrayOne = a.getCheck(); + const checkArrayTwo = b.getCheck(); + + if (!checkArrayOne || !checkArrayTwo) { + // One or both sides are promiscuous enough that anything will fit. return true; } - } - // No intersection. - return false; -}; - -/** - * Check whether this connection can be made by dragging. - * @param {!RenderedConnection} a Connection to compare. - * @param {!RenderedConnection} b Connection to compare against. - * @param {number} distance The maximum allowable distance between connections. - * @return {boolean} True if the connection is allowed during a drag. - * @public - */ -ConnectionChecker.prototype.doDragChecks = function(a, b, distance) { - if (a.distanceFrom(b) > distance) { + // Find any intersection in the check lists. + for (let i = 0; i < checkArrayOne.length; i++) { + if (checkArrayTwo.indexOf(checkArrayOne[i]) !== -1) { + return true; + } + } + // No intersection. return false; } - // Don't consider insertion markers. - if (b.getSourceBlock().isInsertionMarker()) { - return false; - } - - switch (b.type) { - case ConnectionType.PREVIOUS_STATEMENT: - return this.canConnectToPrevious_(a, b); - case ConnectionType.OUTPUT_VALUE: { - // Don't offer to connect an already connected left (male) value plug to - // an available right (female) value plug. - if ((b.isConnected() && !b.targetBlock().isInsertionMarker()) || - a.isConnected()) { - return false; - } - break; - } - case ConnectionType.INPUT_VALUE: { - // Offering to connect the left (male) of a value block to an already - // connected value pair is ok, we'll splice it in. - // However, don't offer to splice into an immovable block. - if (b.isConnected() && !b.targetBlock().isMovable() && - !b.targetBlock().isShadow()) { - return false; - } - break; - } - case ConnectionType.NEXT_STATEMENT: { - // Don't let a block with no next connection bump other blocks out of the - // stack. But covering up a shadow block or stack of shadow blocks is - // fine. Similarly, replacing a terminal statement with another terminal - // statement is allowed. - if (b.isConnected() && !a.getSourceBlock().nextConnection && - !b.targetBlock().isShadow() && b.targetBlock().nextConnection) { - return false; - } - break; - } - default: - // Unexpected connection type. + /** + * Check whether this connection can be made by dragging. + * @param {!RenderedConnection} a Connection to compare. + * @param {!RenderedConnection} b Connection to compare against. + * @param {number} distance The maximum allowable distance between + * connections. + * @return {boolean} True if the connection is allowed during a drag. + * @public + */ + doDragChecks(a, b, distance) { + if (a.distanceFrom(b) > distance) { return false; - } + } - // Don't let blocks try to connect to themselves or ones they nest. - if (common.draggingConnections.indexOf(b) !== -1) { - return false; - } + // Don't consider insertion markers. + if (b.getSourceBlock().isInsertionMarker()) { + return false; + } - return true; -}; + switch (b.type) { + case ConnectionType.PREVIOUS_STATEMENT: + return this.canConnectToPrevious_(a, b); + case ConnectionType.OUTPUT_VALUE: { + // Don't offer to connect an already connected left (male) value plug to + // an available right (female) value plug. + if ((b.isConnected() && !b.targetBlock().isInsertionMarker()) || + a.isConnected()) { + return false; + } + break; + } + case ConnectionType.INPUT_VALUE: { + // Offering to connect the left (male) of a value block to an already + // connected value pair is ok, we'll splice it in. + // However, don't offer to splice into an immovable block. + if (b.isConnected() && !b.targetBlock().isMovable() && + !b.targetBlock().isShadow()) { + return false; + } + break; + } + case ConnectionType.NEXT_STATEMENT: { + // Don't let a block with no next connection bump other blocks out of + // the stack. But covering up a shadow block or stack of shadow blocks + // is fine. Similarly, replacing a terminal statement with another + // terminal statement is allowed. + if (b.isConnected() && !a.getSourceBlock().nextConnection && + !b.targetBlock().isShadow() && b.targetBlock().nextConnection) { + return false; + } + break; + } + default: + // Unexpected connection type. + return false; + } -/** - * Helper function for drag checking. - * @param {!Connection} a The connection to check, which must be a - * statement input or next connection. - * @param {!Connection} b A nearby connection to check, which - * must be a previous connection. - * @return {boolean} True if the connection is allowed, false otherwise. - * @protected - */ -ConnectionChecker.prototype.canConnectToPrevious_ = function(a, b) { - if (a.targetConnection) { - // This connection is already occupied. - // A next connection will never disconnect itself mid-drag. - return false; - } + // Don't let blocks try to connect to themselves or ones they nest. + if (common.draggingConnections.indexOf(b) !== -1) { + return false; + } - // Don't let blocks try to connect to themselves or ones they nest. - if (common.draggingConnections.indexOf(b) !== -1) { - return false; - } - - if (!b.targetConnection) { return true; } - const targetBlock = b.targetBlock(); - // If it is connected to a real block, game over. - if (!targetBlock.isInsertionMarker()) { - return false; + /** + * Helper function for drag checking. + * @param {!Connection} a The connection to check, which must be a + * statement input or next connection. + * @param {!Connection} b A nearby connection to check, which + * must be a previous connection. + * @return {boolean} True if the connection is allowed, false otherwise. + * @protected + */ + canConnectToPrevious_(a, b) { + if (a.targetConnection) { + // This connection is already occupied. + // A next connection will never disconnect itself mid-drag. + return false; + } + + // Don't let blocks try to connect to themselves or ones they nest. + if (common.draggingConnections.indexOf(b) !== -1) { + return false; + } + + if (!b.targetConnection) { + return true; + } + + const targetBlock = b.targetBlock(); + // If it is connected to a real block, game over. + if (!targetBlock.isInsertionMarker()) { + return false; + } + // If it's connected to an insertion marker but that insertion marker + // is the first block in a stack, it's still fine. If that insertion + // marker is in the middle of a stack, it won't work. + return !targetBlock.getPreviousBlock(); } - // If it's connected to an insertion marker but that insertion marker - // is the first block in a stack, it's still fine. If that insertion - // marker is in the middle of a stack, it won't work. - return !targetBlock.getPreviousBlock(); -}; +} registry.register( registry.Type.CONNECTION_CHECKER, registry.DEFAULT, ConnectionChecker); diff --git a/core/connection_db.js b/core/connection_db.js index 35c1363a1..83931f370 100644 --- a/core/connection_db.js +++ b/core/connection_db.js @@ -34,276 +34,281 @@ goog.require('Blockly.constants'); * Database of connections. * Connections are stored in order of their vertical component. This way * connections in an area may be looked up quickly using a binary search. - * @param {!IConnectionChecker} checker The workspace's - * connection type checker, used to decide if connections are valid during a - * drag. - * @constructor * @alias Blockly.ConnectionDB */ -const ConnectionDB = function(checker) { +class ConnectionDB { /** - * Array of connections sorted by y position in workspace units. - * @type {!Array} + * @param {!IConnectionChecker} checker The workspace's + * connection type checker, used to decide if connections are valid during + * a drag. + */ + constructor(checker) { + /** + * Array of connections sorted by y position in workspace units. + * @type {!Array} + * @private + */ + this.connections_ = []; + /** + * The workspace's connection type checker, used to decide if connections + * are valid during a drag. + * @type {!IConnectionChecker} + * @private + */ + this.connectionChecker_ = checker; + } + + /** + * Add a connection to the database. Should not already exist in the database. + * @param {!RenderedConnection} connection The connection to be added. + * @param {number} yPos The y position used to decide where to insert the + * connection. + * @package + */ + addConnection(connection, yPos) { + const index = this.calculateIndexForYPos_(yPos); + this.connections_.splice(index, 0, connection); + } + + /** + * Finds the index of the given connection. + * + * Starts by doing a binary search to find the approximate location, then + * linearly searches nearby for the exact connection. + * @param {!RenderedConnection} conn The connection to find. + * @param {number} yPos The y position used to find the index of the + * connection. + * @return {number} The index of the connection, or -1 if the connection was + * not found. * @private */ - this.connections_ = []; - /** - * The workspace's connection type checker, used to decide if connections are - * valid during a drag. - * @type {!IConnectionChecker} - * @private - */ - this.connectionChecker_ = checker; -}; + findIndexOfConnection_(conn, yPos) { + if (!this.connections_.length) { + return -1; + } -/** - * Add a connection to the database. Should not already exist in the database. - * @param {!RenderedConnection} connection The connection to be added. - * @param {number} yPos The y position used to decide where to insert the - * connection. - * @package - */ -ConnectionDB.prototype.addConnection = function(connection, yPos) { - const index = this.calculateIndexForYPos_(yPos); - this.connections_.splice(index, 0, connection); -}; + const bestGuess = this.calculateIndexForYPos_(yPos); + if (bestGuess >= this.connections_.length) { + // Not in list + return -1; + } -/** - * Finds the index of the given connection. - * - * Starts by doing a binary search to find the approximate location, then - * linearly searches nearby for the exact connection. - * @param {!RenderedConnection} conn The connection to find. - * @param {number} yPos The y position used to find the index of the connection. - * @return {number} The index of the connection, or -1 if the connection was - * not found. - * @private - */ -ConnectionDB.prototype.findIndexOfConnection_ = function(conn, yPos) { - if (!this.connections_.length) { + yPos = conn.y; + // Walk forward and back on the y axis looking for the connection. + let pointer = bestGuess; + while (pointer >= 0 && this.connections_[pointer].y === yPos) { + if (this.connections_[pointer] === conn) { + return pointer; + } + pointer--; + } + + pointer = bestGuess; + while (pointer < this.connections_.length && + this.connections_[pointer].y === yPos) { + if (this.connections_[pointer] === conn) { + return pointer; + } + pointer++; + } return -1; } - const bestGuess = this.calculateIndexForYPos_(yPos); - if (bestGuess >= this.connections_.length) { - // Not in list - return -1; - } - - yPos = conn.y; - // Walk forward and back on the y axis looking for the connection. - let pointer = bestGuess; - while (pointer >= 0 && this.connections_[pointer].y === yPos) { - if (this.connections_[pointer] === conn) { - return pointer; - } - pointer--; - } - - pointer = bestGuess; - while (pointer < this.connections_.length && - this.connections_[pointer].y === yPos) { - if (this.connections_[pointer] === conn) { - return pointer; - } - pointer++; - } - return -1; -}; - -/** - * Finds the correct index for the given y position. - * @param {number} yPos The y position used to decide where to - * insert the connection. - * @return {number} The candidate index. - * @private - */ -ConnectionDB.prototype.calculateIndexForYPos_ = function(yPos) { - if (!this.connections_.length) { - return 0; - } - let pointerMin = 0; - let pointerMax = this.connections_.length; - while (pointerMin < pointerMax) { - const pointerMid = Math.floor((pointerMin + pointerMax) / 2); - if (this.connections_[pointerMid].y < yPos) { - pointerMin = pointerMid + 1; - } else if (this.connections_[pointerMid].y > yPos) { - pointerMax = pointerMid; - } else { - pointerMin = pointerMid; - break; - } - } - return pointerMin; -}; - -/** - * Remove a connection from the database. Must already exist in DB. - * @param {!RenderedConnection} connection The connection to be removed. - * @param {number} yPos The y position used to find the index of the connection. - * @throws {Error} If the connection cannot be found in the database. - */ -ConnectionDB.prototype.removeConnection = function(connection, yPos) { - const index = this.findIndexOfConnection_(connection, yPos); - if (index === -1) { - throw Error('Unable to find connection in connectionDB.'); - } - this.connections_.splice(index, 1); -}; - -/** - * Find all nearby connections to the given connection. - * Type checking does not apply, since this function is used for bumping. - * @param {!RenderedConnection} connection The connection whose - * neighbours should be returned. - * @param {number} maxRadius The maximum radius to another connection. - * @return {!Array} List of connections. - */ -ConnectionDB.prototype.getNeighbours = function(connection, maxRadius) { - const db = this.connections_; - const currentX = connection.x; - const currentY = connection.y; - - // Binary search to find the closest y location. - let pointerMin = 0; - let pointerMax = db.length - 2; - let pointerMid = pointerMax; - while (pointerMin < pointerMid) { - if (db[pointerMid].y < currentY) { - pointerMin = pointerMid; - } else { - pointerMax = pointerMid; - } - pointerMid = Math.floor((pointerMin + pointerMax) / 2); - } - - const neighbours = []; /** - * Computes if the current connection is within the allowed radius of another - * connection. - * This function is a closure and has access to outside variables. - * @param {number} yIndex The other connection's index in the database. - * @return {boolean} True if the current connection's vertical distance from - * the other connection is less than the allowed radius. + * Finds the correct index for the given y position. + * @param {number} yPos The y position used to decide where to + * insert the connection. + * @return {number} The candidate index. + * @private */ - function checkConnection_(yIndex) { - const dx = currentX - db[yIndex].x; - const dy = currentY - db[yIndex].y; - const r = Math.sqrt(dx * dx + dy * dy); - if (r <= maxRadius) { - neighbours.push(db[yIndex]); + calculateIndexForYPos_(yPos) { + if (!this.connections_.length) { + return 0; } - return dy < maxRadius; + let pointerMin = 0; + let pointerMax = this.connections_.length; + while (pointerMin < pointerMax) { + const pointerMid = Math.floor((pointerMin + pointerMax) / 2); + if (this.connections_[pointerMid].y < yPos) { + pointerMin = pointerMid + 1; + } else if (this.connections_[pointerMid].y > yPos) { + pointerMax = pointerMid; + } else { + pointerMin = pointerMid; + break; + } + } + return pointerMin; } - // Walk forward and back on the y axis looking for the closest x,y point. - pointerMin = pointerMid; - pointerMax = pointerMid; - if (db.length) { - while (pointerMin >= 0 && checkConnection_(pointerMin)) { + /** + * Remove a connection from the database. Must already exist in DB. + * @param {!RenderedConnection} connection The connection to be removed. + * @param {number} yPos The y position used to find the index of the + * connection. + * @throws {Error} If the connection cannot be found in the database. + */ + removeConnection(connection, yPos) { + const index = this.findIndexOfConnection_(connection, yPos); + if (index === -1) { + throw Error('Unable to find connection in connectionDB.'); + } + this.connections_.splice(index, 1); + } + + /** + * Find all nearby connections to the given connection. + * Type checking does not apply, since this function is used for bumping. + * @param {!RenderedConnection} connection The connection whose + * neighbours should be returned. + * @param {number} maxRadius The maximum radius to another connection. + * @return {!Array} List of connections. + */ + getNeighbours(connection, maxRadius) { + const db = this.connections_; + const currentX = connection.x; + const currentY = connection.y; + + // Binary search to find the closest y location. + let pointerMin = 0; + let pointerMax = db.length - 2; + let pointerMid = pointerMax; + while (pointerMin < pointerMid) { + if (db[pointerMid].y < currentY) { + pointerMin = pointerMid; + } else { + pointerMax = pointerMid; + } + pointerMid = Math.floor((pointerMin + pointerMax) / 2); + } + + const neighbours = []; + /** + * Computes if the current connection is within the allowed radius of + * another connection. This function is a closure and has access to outside + * variables. + * @param {number} yIndex The other connection's index in the database. + * @return {boolean} True if the current connection's vertical distance from + * the other connection is less than the allowed radius. + */ + function checkConnection_(yIndex) { + const dx = currentX - db[yIndex].x; + const dy = currentY - db[yIndex].y; + const r = Math.sqrt(dx * dx + dy * dy); + if (r <= maxRadius) { + neighbours.push(db[yIndex]); + } + return dy < maxRadius; + } + + // Walk forward and back on the y axis looking for the closest x,y point. + pointerMin = pointerMid; + pointerMax = pointerMid; + if (db.length) { + while (pointerMin >= 0 && checkConnection_(pointerMin)) { + pointerMin--; + } + do { + pointerMax++; + } while (pointerMax < db.length && checkConnection_(pointerMax)); + } + + return neighbours; + } + + /** + * Is the candidate connection close to the reference connection. + * Extremely fast; only looks at Y distance. + * @param {number} index Index in database of candidate connection. + * @param {number} baseY Reference connection's Y value. + * @param {number} maxRadius The maximum radius to another connection. + * @return {boolean} True if connection is in range. + * @private + */ + isInYRange_(index, baseY, maxRadius) { + return (Math.abs(this.connections_[index].y - baseY) <= maxRadius); + } + + /** + * Find the closest compatible connection to this connection. + * @param {!RenderedConnection} conn The connection searching for a compatible + * mate. + * @param {number} maxRadius The maximum radius to another connection. + * @param {!Coordinate} dxy Offset between this connection's + * location in the database and the current location (as a result of + * dragging). + * @return {!{connection: RenderedConnection, radius: number}} + * Contains two properties: 'connection' which is either another + * connection or null, and 'radius' which is the distance. + */ + searchForClosest(conn, maxRadius, dxy) { + if (!this.connections_.length) { + // Don't bother. + return {connection: null, radius: maxRadius}; + } + + // Stash the values of x and y from before the drag. + const baseY = conn.y; + const baseX = conn.x; + + conn.x = baseX + dxy.x; + conn.y = baseY + dxy.y; + + // calculateIndexForYPos_ finds an index for insertion, which is always + // after any block with the same y index. We want to search both forward + // and back, so search on both sides of the index. + const closestIndex = this.calculateIndexForYPos_(conn.y); + + let bestConnection = null; + let bestRadius = maxRadius; + let temp; + + // Walk forward and back on the y axis looking for the closest x,y point. + let pointerMin = closestIndex - 1; + while (pointerMin >= 0 && this.isInYRange_(pointerMin, conn.y, maxRadius)) { + temp = this.connections_[pointerMin]; + if (this.connectionChecker_.canConnect(conn, temp, true, bestRadius)) { + bestConnection = temp; + bestRadius = temp.distanceFrom(conn); + } pointerMin--; } - do { + + let pointerMax = closestIndex; + while (pointerMax < this.connections_.length && + this.isInYRange_(pointerMax, conn.y, maxRadius)) { + temp = this.connections_[pointerMax]; + if (this.connectionChecker_.canConnect(conn, temp, true, bestRadius)) { + bestConnection = temp; + bestRadius = temp.distanceFrom(conn); + } pointerMax++; - } while (pointerMax < db.length && checkConnection_(pointerMax)); - } - - return neighbours; -}; - -/** - * Is the candidate connection close to the reference connection. - * Extremely fast; only looks at Y distance. - * @param {number} index Index in database of candidate connection. - * @param {number} baseY Reference connection's Y value. - * @param {number} maxRadius The maximum radius to another connection. - * @return {boolean} True if connection is in range. - * @private - */ -ConnectionDB.prototype.isInYRange_ = function(index, baseY, maxRadius) { - return (Math.abs(this.connections_[index].y - baseY) <= maxRadius); -}; - -/** - * Find the closest compatible connection to this connection. - * @param {!RenderedConnection} conn The connection searching for a compatible - * mate. - * @param {number} maxRadius The maximum radius to another connection. - * @param {!Coordinate} dxy Offset between this connection's - * location in the database and the current location (as a result of - * dragging). - * @return {!{connection: RenderedConnection, radius: number}} - * Contains two properties: 'connection' which is either another - * connection or null, and 'radius' which is the distance. - */ -ConnectionDB.prototype.searchForClosest = function(conn, maxRadius, dxy) { - if (!this.connections_.length) { - // Don't bother. - return {connection: null, radius: maxRadius}; - } - - // Stash the values of x and y from before the drag. - const baseY = conn.y; - const baseX = conn.x; - - conn.x = baseX + dxy.x; - conn.y = baseY + dxy.y; - - // calculateIndexForYPos_ finds an index for insertion, which is always - // after any block with the same y index. We want to search both forward - // and back, so search on both sides of the index. - const closestIndex = this.calculateIndexForYPos_(conn.y); - - let bestConnection = null; - let bestRadius = maxRadius; - let temp; - - // Walk forward and back on the y axis looking for the closest x,y point. - let pointerMin = closestIndex - 1; - while (pointerMin >= 0 && this.isInYRange_(pointerMin, conn.y, maxRadius)) { - temp = this.connections_[pointerMin]; - if (this.connectionChecker_.canConnect(conn, temp, true, bestRadius)) { - bestConnection = temp; - bestRadius = temp.distanceFrom(conn); } - pointerMin--; + + // Reset the values of x and y. + conn.x = baseX; + conn.y = baseY; + + // If there were no valid connections, bestConnection will be null. + return {connection: bestConnection, radius: bestRadius}; } - let pointerMax = closestIndex; - while (pointerMax < this.connections_.length && - this.isInYRange_(pointerMax, conn.y, maxRadius)) { - temp = this.connections_[pointerMax]; - if (this.connectionChecker_.canConnect(conn, temp, true, bestRadius)) { - bestConnection = temp; - bestRadius = temp.distanceFrom(conn); - } - pointerMax++; + /** + * Initialize a set of connection DBs for a workspace. + * @param {!IConnectionChecker} checker The workspace's + * connection checker, used to decide if connections are valid during a + * drag. + * @return {!Array} Array of databases. + */ + static init(checker) { + // Create four databases, one for each connection type. + const dbList = []; + dbList[ConnectionType.INPUT_VALUE] = new ConnectionDB(checker); + dbList[ConnectionType.OUTPUT_VALUE] = new ConnectionDB(checker); + dbList[ConnectionType.NEXT_STATEMENT] = new ConnectionDB(checker); + dbList[ConnectionType.PREVIOUS_STATEMENT] = new ConnectionDB(checker); + return dbList; } - - // Reset the values of x and y. - conn.x = baseX; - conn.y = baseY; - - // If there were no valid connections, bestConnection will be null. - return {connection: bestConnection, radius: bestRadius}; -}; - -/** - * Initialize a set of connection DBs for a workspace. - * @param {!IConnectionChecker} checker The workspace's - * connection checker, used to decide if connections are valid during a - * drag. - * @return {!Array} Array of databases. - */ -ConnectionDB.init = function(checker) { - // Create four databases, one for each connection type. - const dbList = []; - dbList[ConnectionType.INPUT_VALUE] = new ConnectionDB(checker); - dbList[ConnectionType.OUTPUT_VALUE] = new ConnectionDB(checker); - dbList[ConnectionType.NEXT_STATEMENT] = new ConnectionDB(checker); - dbList[ConnectionType.PREVIOUS_STATEMENT] = new ConnectionDB(checker); - return dbList; -}; +} exports.ConnectionDB = ConnectionDB; diff --git a/core/contextmenu.js b/core/contextmenu.js index 6f665eb49..b2fa7c265 100644 --- a/core/contextmenu.js +++ b/core/contextmenu.js @@ -23,11 +23,13 @@ const clipboard = goog.require('Blockly.clipboard'); const deprecation = goog.require('Blockly.utils.deprecation'); const dom = goog.require('Blockly.utils.dom'); const eventUtils = goog.require('Blockly.Events.utils'); -const internalConstants = goog.require('Blockly.internalConstants'); const userAgent = goog.require('Blockly.utils.userAgent'); const svgMath = goog.require('Blockly.utils.svgMath'); /* eslint-disable-next-line no-unused-vars */ const {Block} = goog.requireType('Blockly.Block'); +/* eslint-disable-next-line no-unused-vars */ +const {BlockSvg} = goog.requireType('Blockly.BlockSvg'); +const {config} = goog.require('Blockly.config'); const {Coordinate} = goog.require('Blockly.utils.Coordinate'); const {MenuItem} = goog.require('Blockly.MenuItem'); const {Menu} = goog.require('Blockly.Menu'); @@ -262,15 +264,16 @@ const callbackFactory = function(block, xml) { eventUtils.disable(); let newBlock; try { - newBlock = Xml.domToBlock(xml, block.workspace); + newBlock = + /** @type {!BlockSvg} */ (Xml.domToBlock(xml, block.workspace)); // Move the new block next to the old block. const xy = block.getRelativeToSurfaceXY(); if (block.RTL) { - xy.x -= internalConstants.SNAP_RADIUS; + xy.x -= config.snapRadius; } else { - xy.x += internalConstants.SNAP_RADIUS; + xy.x += config.snapRadius; } - xy.y += internalConstants.SNAP_RADIUS * 2; + xy.y += config.snapRadius * 2; newBlock.moveBy(xy.x, xy.y); } finally { eventUtils.enable(); @@ -339,7 +342,7 @@ exports.commentDuplicateOption = commentDuplicateOption; * @alias Blockly.ContextMenu.workspaceCommentOption */ const workspaceCommentOption = function(ws, e) { - const WorkspaceCommentSvg = goog.module.get('Blockly.WorkspaceCommentSvg'); + const {WorkspaceCommentSvg} = goog.module.get('Blockly.WorkspaceCommentSvg'); if (!WorkspaceCommentSvg) { throw Error('Missing require for Blockly.WorkspaceCommentSvg'); } diff --git a/core/contextmenu_items.js b/core/contextmenu_items.js index aa0abbf5d..0d0696f42 100644 --- a/core/contextmenu_items.js +++ b/core/contextmenu_items.js @@ -239,8 +239,7 @@ const addDeletableBlocks_ = function(block, deleteList) { if (block.isDeletable()) { Array.prototype.push.apply(deleteList, block.getDescendants(false)); } else { - const children = /* eslint-disable-next-line indent */ - /** @type {!Array} */ (block.getChildren(false)); + const children = block.getChildren(false); for (let i = 0; i < children.length; i++) { addDeletableBlocks_(children[i], deleteList); } diff --git a/core/contextmenu_registry.js b/core/contextmenu_registry.js index 91a015c3b..42130dbfc 100644 --- a/core/contextmenu_registry.js +++ b/core/contextmenu_registry.js @@ -25,21 +25,102 @@ const {WorkspaceSvg} = goog.requireType('Blockly.WorkspaceSvg'); * Class for the registry of context menu items. This is intended to be a * singleton. You should not create a new instance, and only access this class * from ContextMenuRegistry.registry. - * @constructor - * @private * @alias Blockly.ContextMenuRegistry */ -const ContextMenuRegistry = function() { - // Singleton instance should be registered once. - ContextMenuRegistry.registry = this; +class ContextMenuRegistry { + /** + * Resets the existing singleton instance of ContextMenuRegistry. + */ + constructor() { + this.reset(); + } /** - * Registry of all registered RegistryItems, keyed by ID. - * @type {!Object} - * @private + * Clear and recreate the registry. */ - this.registry_ = Object.create(null); -}; + reset() { + /** + * Registry of all registered RegistryItems, keyed by ID. + * @type {!Object} + * @private + */ + this.registry_ = Object.create(null); + } + + /** + * Registers a RegistryItem. + * @param {!ContextMenuRegistry.RegistryItem} item Context menu item to + * register. + * @throws {Error} if an item with the given ID already exists. + */ + register(item) { + if (this.registry_[item.id]) { + throw Error('Menu item with ID "' + item.id + '" is already registered.'); + } + this.registry_[item.id] = item; + } + + /** + * Unregisters a RegistryItem with the given ID. + * @param {string} id The ID of the RegistryItem to remove. + * @throws {Error} if an item with the given ID does not exist. + */ + unregister(id) { + if (!this.registry_[id]) { + throw new Error('Menu item with ID "' + id + '" not found.'); + } + delete this.registry_[id]; + } + + /** + * @param {string} id The ID of the RegistryItem to get. + * @return {?ContextMenuRegistry.RegistryItem} RegistryItem or null if not + * found + */ + getItem(id) { + return this.registry_[id] || null; + } + + /** + * Gets the valid context menu options for the given scope type (e.g. block or + * workspace) and scope. Blocks are only shown if the preconditionFn shows + * they should not be hidden. + * @param {!ContextMenuRegistry.ScopeType} scopeType Type of scope where menu + * should be shown (e.g. on a block or on a workspace) + * @param {!ContextMenuRegistry.Scope} scope Current scope of context menu + * (i.e., the exact workspace or block being clicked on) + * @return {!Array} the list of + * ContextMenuOptions + */ + getContextMenuOptions(scopeType, scope) { + const menuOptions = []; + const registry = this.registry_; + Object.keys(registry).forEach(function(id) { + const item = registry[id]; + if (scopeType === item.scopeType) { + const precondition = item.preconditionFn(scope); + if (precondition !== 'hidden') { + const displayText = typeof item.displayText === 'function' ? + item.displayText(scope) : + item.displayText; + /** @type {!ContextMenuRegistry.ContextMenuOption} */ + const menuOption = { + text: displayText, + enabled: (precondition === 'enabled'), + callback: item.callback, + scope: scope, + weight: item.weight, + }; + menuOptions.push(menuOption); + } + } + }); + menuOptions.sort(function(a, b) { + return a.weight - b.weight; + }); + return menuOptions; + } +} /** * Where this menu item should be rendered. If the menu item should be rendered @@ -90,85 +171,8 @@ ContextMenuRegistry.ContextMenuOption; /** * Singleton instance of this class. All interactions with this class should be * done on this object. - * @type {?ContextMenuRegistry} + * @type {!ContextMenuRegistry} */ -ContextMenuRegistry.registry = null; - -/** - * Registers a RegistryItem. - * @param {!ContextMenuRegistry.RegistryItem} item Context menu item to - * register. - * @throws {Error} if an item with the given ID already exists. - */ -ContextMenuRegistry.prototype.register = function(item) { - if (this.registry_[item.id]) { - throw Error('Menu item with ID "' + item.id + '" is already registered.'); - } - this.registry_[item.id] = item; -}; - -/** - * Unregisters a RegistryItem with the given ID. - * @param {string} id The ID of the RegistryItem to remove. - * @throws {Error} if an item with the given ID does not exist. - */ -ContextMenuRegistry.prototype.unregister = function(id) { - if (!this.registry_[id]) { - throw new Error('Menu item with ID "' + id + '" not found.'); - } - delete this.registry_[id]; -}; - -/** - * @param {string} id The ID of the RegistryItem to get. - * @return {?ContextMenuRegistry.RegistryItem} RegistryItem or null if not found - */ -ContextMenuRegistry.prototype.getItem = function(id) { - return this.registry_[id] || null; -}; - -/** - * Gets the valid context menu options for the given scope type (e.g. block or - * workspace) and scope. Blocks are only shown if the preconditionFn shows they - * should not be hidden. - * @param {!ContextMenuRegistry.ScopeType} scopeType Type of scope where menu - * should be shown (e.g. on a block or on a workspace) - * @param {!ContextMenuRegistry.Scope} scope Current scope of context menu - * (i.e., the exact workspace or block being clicked on) - * @return {!Array} the list of - * ContextMenuOptions - */ -ContextMenuRegistry.prototype.getContextMenuOptions = function( - scopeType, scope) { - const menuOptions = []; - const registry = this.registry_; - Object.keys(registry).forEach(function(id) { - const item = registry[id]; - if (scopeType === item.scopeType) { - const precondition = item.preconditionFn(scope); - if (precondition !== 'hidden') { - const displayText = typeof item.displayText === 'function' ? - item.displayText(scope) : - item.displayText; - /** @type {!ContextMenuRegistry.ContextMenuOption} */ - const menuOption = { - text: displayText, - enabled: (precondition === 'enabled'), - callback: item.callback, - scope: scope, - weight: item.weight, - }; - menuOptions.push(menuOption); - } - } - }); - menuOptions.sort(function(a, b) { - return a.weight - b.weight; - }); - return menuOptions; -}; - -// Creates and assigns the singleton instance. -new ContextMenuRegistry(); +ContextMenuRegistry.registry = new ContextMenuRegistry(); exports.ContextMenuRegistry = ContextMenuRegistry; diff --git a/core/css.js b/core/css.js index 7cea9284f..e7ed4ec8c 100644 --- a/core/css.js +++ b/core/css.js @@ -89,480 +89,480 @@ exports.inject = inject; * @alias Blockly.Css.content */ let content = (` - .blocklySvg { - background-color: #fff; - outline: none; - overflow: hidden; /* IE overflows by default. */ - position: absolute; - display: block; - } +.blocklySvg { + background-color: #fff; + outline: none; + overflow: hidden; /* IE overflows by default. */ + position: absolute; + display: block; +} - .blocklyWidgetDiv { - display: none; - position: absolute; - z-index: 99999; /* big value for bootstrap3 compatibility */ - } +.blocklyWidgetDiv { + display: none; + position: absolute; + z-index: 99999; /* big value for bootstrap3 compatibility */ +} - .injectionDiv { - height: 100%; - position: relative; - overflow: hidden; /* So blocks in drag surface disappear at edges */ - touch-action: none; - } +.injectionDiv { + height: 100%; + position: relative; + overflow: hidden; /* So blocks in drag surface disappear at edges */ + touch-action: none; +} - .blocklyNonSelectable { - user-select: none; - -ms-user-select: none; - -webkit-user-select: none; - } +.blocklyNonSelectable { + user-select: none; + -ms-user-select: none; + -webkit-user-select: none; +} - .blocklyWsDragSurface { - display: none; - position: absolute; - top: 0; - left: 0; - } +.blocklyWsDragSurface { + display: none; + position: absolute; + top: 0; + left: 0; +} - /* Added as a separate rule with multiple classes to make it more specific - than a bootstrap rule that selects svg:root. See issue #1275 for context. +/* Added as a separate rule with multiple classes to make it more specific + than a bootstrap rule that selects svg:root. See issue #1275 for context. +*/ +.blocklyWsDragSurface.blocklyOverflowVisible { + overflow: visible; +} + +.blocklyBlockDragSurface { + display: none; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + overflow: visible !important; + z-index: 50; /* Display below toolbox, but above everything else. */ +} + +.blocklyBlockCanvas.blocklyCanvasTransitioning, +.blocklyBubbleCanvas.blocklyCanvasTransitioning { + transition: transform .5s; +} + +.blocklyTooltipDiv { + background-color: #ffffc7; + border: 1px solid #ddc; + box-shadow: 4px 4px 20px 1px rgba(0,0,0,.15); + color: #000; + display: none; + font: 9pt sans-serif; + opacity: .9; + padding: 2px; + position: absolute; + z-index: 100000; /* big value for bootstrap3 compatibility */ +} + +.blocklyDropDownDiv { + position: absolute; + left: 0; + top: 0; + z-index: 1000; + display: none; + border: 1px solid; + border-color: #dadce0; + background-color: #fff; + border-radius: 2px; + padding: 4px; + box-shadow: 0 0 3px 1px rgba(0,0,0,.3); +} + +.blocklyDropDownDiv.blocklyFocused { + box-shadow: 0 0 6px 1px rgba(0,0,0,.3); +} + +.blocklyDropDownContent { + max-height: 300px; // @todo: spec for maximum height. + overflow: auto; + overflow-x: hidden; + position: relative; +} + +.blocklyDropDownArrow { + position: absolute; + left: 0; + top: 0; + width: 16px; + height: 16px; + z-index: -1; + background-color: inherit; + border-color: inherit; +} + +.blocklyDropDownButton { + display: inline-block; + float: left; + padding: 0; + margin: 4px; + border-radius: 4px; + outline: none; + border: 1px solid; + transition: box-shadow .1s; + cursor: pointer; +} + +.blocklyArrowTop { + border-top: 1px solid; + border-left: 1px solid; + border-top-left-radius: 4px; + border-color: inherit; +} + +.blocklyArrowBottom { + border-bottom: 1px solid; + border-right: 1px solid; + border-bottom-right-radius: 4px; + border-color: inherit; +} + +.blocklyResizeSE { + cursor: se-resize; + fill: #aaa; +} + +.blocklyResizeSW { + cursor: sw-resize; + fill: #aaa; +} + +.blocklyResizeLine { + stroke: #515A5A; + stroke-width: 1; +} + +.blocklyHighlightedConnectionPath { + fill: none; + stroke: #fc3; + stroke-width: 4px; +} + +.blocklyPathLight { + fill: none; + stroke-linecap: round; + stroke-width: 1; +} + +.blocklySelected>.blocklyPathLight { + display: none; +} + +.blocklyDraggable { + /* backup for browsers (e.g. IE11) that don't support grab */ + cursor: url("<<>>/handopen.cur"), auto; + cursor: grab; + cursor: -webkit-grab; +} + + /* backup for browsers (e.g. IE11) that don't support grabbing */ +.blocklyDragging { + /* backup for browsers (e.g. IE11) that don't support grabbing */ + cursor: url("<<>>/handclosed.cur"), auto; + cursor: grabbing; + cursor: -webkit-grabbing; +} + + /* Changes cursor on mouse down. Not effective in Firefox because of + https://bugzilla.mozilla.org/show_bug.cgi?id=771241 */ +.blocklyDraggable:active { + /* backup for browsers (e.g. IE11) that don't support grabbing */ + cursor: url("<<>>/handclosed.cur"), auto; + cursor: grabbing; + cursor: -webkit-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. */ - .blocklyWsDragSurface.blocklyOverflowVisible { - overflow: visible; - } - - .blocklyBlockDragSurface { - display: none; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - overflow: visible !important; - z-index: 50; /* Display below toolbox, but above everything else. */ - } - - .blocklyBlockCanvas.blocklyCanvasTransitioning, - .blocklyBubbleCanvas.blocklyCanvasTransitioning { - transition: transform .5s; - } - - .blocklyTooltipDiv { - background-color: #ffffc7; - border: 1px solid #ddc; - box-shadow: 4px 4px 20px 1px rgba(0,0,0,.15); - color: #000; - display: none; - font: 9pt sans-serif; - opacity: .9; - padding: 2px; - position: absolute; - z-index: 100000; /* big value for bootstrap3 compatibility */ - } - - .blocklyDropDownDiv { - position: absolute; - left: 0; - top: 0; - z-index: 1000; - display: none; - border: 1px solid; - border-color: #dadce0; - background-color: #fff; - border-radius: 2px; - padding: 4px; - box-shadow: 0 0 3px 1px rgba(0,0,0,.3); - } - - .blocklyDropDownDiv.blocklyFocused { - box-shadow: 0 0 6px 1px rgba(0,0,0,.3); - } - - .blocklyDropDownContent { - max-height: 300px; // @todo: spec for maximum height. - overflow: auto; - overflow-x: hidden; - position: relative; - } - - .blocklyDropDownArrow { - position: absolute; - left: 0; - top: 0; - width: 16px; - height: 16px; - z-index: -1; - background-color: inherit; - border-color: inherit; - } - - .blocklyDropDownButton { - display: inline-block; - float: left; - padding: 0; - margin: 4px; - border-radius: 4px; - outline: none; - border: 1px solid; - transition: box-shadow .1s; - cursor: pointer; - } - - .blocklyArrowTop { - border-top: 1px solid; - border-left: 1px solid; - border-top-left-radius: 4px; - border-color: inherit; - } - - .blocklyArrowBottom { - border-bottom: 1px solid; - border-right: 1px solid; - border-bottom-right-radius: 4px; - border-color: inherit; - } - - .blocklyResizeSE { - cursor: se-resize; - fill: #aaa; - } - - .blocklyResizeSW { - cursor: sw-resize; - fill: #aaa; - } - - .blocklyResizeLine { - stroke: #515A5A; - stroke-width: 1; - } - - .blocklyHighlightedConnectionPath { - fill: none; - stroke: #fc3; - stroke-width: 4px; - } - - .blocklyPathLight { - fill: none; - stroke-linecap: round; - stroke-width: 1; - } - - .blocklySelected>.blocklyPathLight { - display: none; - } - - .blocklyDraggable { - /* backup for browsers (e.g. IE11) that don't support grab */ - cursor: url("<<>>/handopen.cur"), auto; - cursor: grab; - cursor: -webkit-grab; - } - - /* backup for browsers (e.g. IE11) that don't support grabbing */ - .blocklyDragging { - /* backup for browsers (e.g. IE11) that don't support grabbing */ - cursor: url("<<>>/handclosed.cur"), auto; - cursor: grabbing; - cursor: -webkit-grabbing; - } - - /* Changes cursor on mouse down. Not effective in Firefox because of - https://bugzilla.mozilla.org/show_bug.cgi?id=771241 */ - .blocklyDraggable:active { - /* backup for browsers (e.g. IE11) that don't support grabbing */ - cursor: url("<<>>/handclosed.cur"), auto; - cursor: grabbing; - cursor: -webkit-grabbing; - } - - /* Change the cursor on the whole drag surface in case the mouse gets - ahead of block during a drag. This way the cursor is still a closed hand. - */ - .blocklyBlockDragSurface .blocklyDraggable { - /* backup for browsers (e.g. IE11) that don't support grabbing */ - cursor: url("<<>>/handclosed.cur"), auto; - cursor: grabbing; - cursor: -webkit-grabbing; - } - - .blocklyDragging.blocklyDraggingDelete { - cursor: url("<<>>/handdelete.cur"), auto; - } - - .blocklyDragging>.blocklyPath, - .blocklyDragging>.blocklyPathLight { - fill-opacity: .8; - stroke-opacity: .8; - } - - .blocklyDragging>.blocklyPathDark { - display: none; - } - - .blocklyDisabled>.blocklyPath { - fill-opacity: .5; - stroke-opacity: .5; - } - - .blocklyDisabled>.blocklyPathLight, - .blocklyDisabled>.blocklyPathDark { - display: none; - } - - .blocklyInsertionMarker>.blocklyPath, - .blocklyInsertionMarker>.blocklyPathLight, - .blocklyInsertionMarker>.blocklyPathDark { - fill-opacity: .2; - stroke: none; - } - - .blocklyMultilineText { - font-family: monospace; - } - - .blocklyNonEditableText>text { - pointer-events: none; - } - - .blocklyFlyout { - position: absolute; - z-index: 20; - } - - .blocklyText text { - cursor: default; - } - - /* - Don't allow users to select text. It gets annoying when trying to - drag a block and selected text moves instead. - */ - .blocklySvg text, - .blocklyBlockDragSurface text { - user-select: none; - -ms-user-select: none; - -webkit-user-select: none; - cursor: inherit; - } - - .blocklyHidden { - display: none; - } - - .blocklyFieldDropdown:not(.blocklyHidden) { - display: block; - } - - .blocklyIconGroup { - cursor: default; - } - - .blocklyIconGroup:not(:hover), - .blocklyIconGroupReadonly { - opacity: .6; - } - - .blocklyIconShape { - fill: #00f; - stroke: #fff; - stroke-width: 1px; - } - - .blocklyIconSymbol { - fill: #fff; - } - - .blocklyMinimalBody { - margin: 0; - padding: 0; - } - - .blocklyHtmlInput { - border: none; - border-radius: 4px; - height: 100%; - margin: 0; - outline: none; - padding: 0; - width: 100%; - text-align: center; - display: block; - box-sizing: border-box; - } - - /* Edge and IE introduce a close icon when the input value is longer than a - certain length. This affects our sizing calculations of the text input. - Hiding the close icon to avoid that. */ - .blocklyHtmlInput::-ms-clear { - display: none; - } - - .blocklyMainBackground { - stroke-width: 1; - stroke: #c6c6c6; /* Equates to #ddd due to border being off-pixel. */ - } - - .blocklyMutatorBackground { - fill: #fff; - stroke: #ddd; - stroke-width: 1; - } - - .blocklyFlyoutBackground { - fill: #ddd; - fill-opacity: .8; - } - - .blocklyMainWorkspaceScrollbar { - z-index: 20; - } - - .blocklyFlyoutScrollbar { - z-index: 30; - } - - .blocklyScrollbarHorizontal, - .blocklyScrollbarVertical { - position: absolute; - outline: none; - } - - .blocklyScrollbarBackground { - opacity: 0; - } - - .blocklyScrollbarHandle { - fill: #ccc; - } - - .blocklyScrollbarBackground:hover+.blocklyScrollbarHandle, - .blocklyScrollbarHandle:hover { - fill: #bbb; - } - - /* Darken flyout scrollbars due to being on a grey background. */ - /* By contrast, workspace scrollbars are on a white background. */ - .blocklyFlyout .blocklyScrollbarHandle { - fill: #bbb; - } - - .blocklyFlyout .blocklyScrollbarBackground:hover+.blocklyScrollbarHandle, - .blocklyFlyout .blocklyScrollbarHandle:hover { - fill: #aaa; - } - - .blocklyInvalidInput { - background: #faa; - } - - .blocklyVerticalMarker { - stroke-width: 3px; - fill: rgba(255,255,255,.5); - pointer-events: none; - } - - .blocklyComputeCanvas { - position: absolute; - width: 0; - height: 0; - } - - .blocklyNoPointerEvents { - pointer-events: none; - } - - .blocklyContextMenu { - border-radius: 4px; - max-height: 100%; - } - - .blocklyDropdownMenu { - border-radius: 2px; - padding: 0 !important; - } - - .blocklyDropdownMenu .blocklyMenuItem { - /* 28px on the left for icon or checkbox. */ - padding-left: 28px; - } - - /* BiDi override for the resting state. */ - .blocklyDropdownMenu .blocklyMenuItemRtl { - /* Flip left/right padding for BiDi. */ - padding-left: 5px; - padding-right: 28px; - } - - .blocklyWidgetDiv .blocklyMenu { - background: #fff; - border: 1px solid transparent; - box-shadow: 0 0 3px 1px rgba(0,0,0,.3); - font: normal 13px Arial, sans-serif; - margin: 0; - outline: none; - padding: 4px 0; - position: absolute; - overflow-y: auto; - overflow-x: hidden; - max-height: 100%; - z-index: 20000; /* Arbitrary, but some apps depend on it... */ - } - - .blocklyWidgetDiv .blocklyMenu.blocklyFocused { - box-shadow: 0 0 6px 1px rgba(0,0,0,.3); - } - - .blocklyDropDownDiv .blocklyMenu { - background: inherit; /* Compatibility with gapi, reset from goog-menu */ - border: inherit; /* Compatibility with gapi, reset from goog-menu */ - font: normal 13px "Helvetica Neue", Helvetica, sans-serif; - outline: none; - position: relative; /* Compatibility with gapi, reset from goog-menu */ - z-index: 20000; /* Arbitrary, but some apps depend on it... */ - } - - /* State: resting. */ - .blocklyMenuItem { - border: none; - color: #000; - cursor: pointer; - list-style: none; - margin: 0; - /* 7em on the right for shortcut. */ - min-width: 7em; - padding: 6px 15px; - white-space: nowrap; - } - - /* State: disabled. */ - .blocklyMenuItemDisabled { - color: #ccc; - cursor: inherit; - } - - /* State: hover. */ - .blocklyMenuItemHighlight { - background-color: rgba(0,0,0,.1); - } - - /* State: selected/checked. */ - .blocklyMenuItemCheckbox { - height: 16px; - position: absolute; - width: 16px; - } - - .blocklyMenuItemSelected .blocklyMenuItemCheckbox { - background: url(<<>>/sprites.png) no-repeat -48px -16px; - float: left; - margin-left: -24px; - position: static; /* Scroll with the menu. */ - } - - .blocklyMenuItemRtl .blocklyMenuItemCheckbox { - float: right; - margin-right: -24px; - } +.blocklyBlockDragSurface .blocklyDraggable { + /* backup for browsers (e.g. IE11) that don't support grabbing */ + cursor: url("<<>>/handclosed.cur"), auto; + cursor: grabbing; + cursor: -webkit-grabbing; +} + +.blocklyDragging.blocklyDraggingDelete { + cursor: url("<<>>/handdelete.cur"), auto; +} + +.blocklyDragging>.blocklyPath, +.blocklyDragging>.blocklyPathLight { + fill-opacity: .8; + stroke-opacity: .8; +} + +.blocklyDragging>.blocklyPathDark { + display: none; +} + +.blocklyDisabled>.blocklyPath { + fill-opacity: .5; + stroke-opacity: .5; +} + +.blocklyDisabled>.blocklyPathLight, +.blocklyDisabled>.blocklyPathDark { + display: none; +} + +.blocklyInsertionMarker>.blocklyPath, +.blocklyInsertionMarker>.blocklyPathLight, +.blocklyInsertionMarker>.blocklyPathDark { + fill-opacity: .2; + stroke: none; +} + +.blocklyMultilineText { + font-family: monospace; +} + +.blocklyNonEditableText>text { + pointer-events: none; +} + +.blocklyFlyout { + position: absolute; + z-index: 20; +} + +.blocklyText text { + cursor: default; +} + +/* + Don't allow users to select text. It gets annoying when trying to + drag a block and selected text moves instead. +*/ +.blocklySvg text, +.blocklyBlockDragSurface text { + user-select: none; + -ms-user-select: none; + -webkit-user-select: none; + cursor: inherit; +} + +.blocklyHidden { + display: none; +} + +.blocklyFieldDropdown:not(.blocklyHidden) { + display: block; +} + +.blocklyIconGroup { + cursor: default; +} + +.blocklyIconGroup:not(:hover), +.blocklyIconGroupReadonly { + opacity: .6; +} + +.blocklyIconShape { + fill: #00f; + stroke: #fff; + stroke-width: 1px; +} + +.blocklyIconSymbol { + fill: #fff; +} + +.blocklyMinimalBody { + margin: 0; + padding: 0; +} + +.blocklyHtmlInput { + border: none; + border-radius: 4px; + height: 100%; + margin: 0; + outline: none; + padding: 0; + width: 100%; + text-align: center; + display: block; + box-sizing: border-box; +} + +/* Edge and IE introduce a close icon when the input value is longer than a + certain length. This affects our sizing calculations of the text input. + Hiding the close icon to avoid that. */ +.blocklyHtmlInput::-ms-clear { + display: none; +} + +.blocklyMainBackground { + stroke-width: 1; + stroke: #c6c6c6; /* Equates to #ddd due to border being off-pixel. */ +} + +.blocklyMutatorBackground { + fill: #fff; + stroke: #ddd; + stroke-width: 1; +} + +.blocklyFlyoutBackground { + fill: #ddd; + fill-opacity: .8; +} + +.blocklyMainWorkspaceScrollbar { + z-index: 20; +} + +.blocklyFlyoutScrollbar { + z-index: 30; +} + +.blocklyScrollbarHorizontal, +.blocklyScrollbarVertical { + position: absolute; + outline: none; +} + +.blocklyScrollbarBackground { + opacity: 0; +} + +.blocklyScrollbarHandle { + fill: #ccc; +} + +.blocklyScrollbarBackground:hover+.blocklyScrollbarHandle, +.blocklyScrollbarHandle:hover { + fill: #bbb; +} + +/* Darken flyout scrollbars due to being on a grey background. */ +/* By contrast, workspace scrollbars are on a white background. */ +.blocklyFlyout .blocklyScrollbarHandle { + fill: #bbb; +} + +.blocklyFlyout .blocklyScrollbarBackground:hover+.blocklyScrollbarHandle, +.blocklyFlyout .blocklyScrollbarHandle:hover { + fill: #aaa; +} + +.blocklyInvalidInput { + background: #faa; +} + +.blocklyVerticalMarker { + stroke-width: 3px; + fill: rgba(255,255,255,.5); + pointer-events: none; +} + +.blocklyComputeCanvas { + position: absolute; + width: 0; + height: 0; +} + +.blocklyNoPointerEvents { + pointer-events: none; +} + +.blocklyContextMenu { + border-radius: 4px; + max-height: 100%; +} + +.blocklyDropdownMenu { + border-radius: 2px; + padding: 0 !important; +} + +.blocklyDropdownMenu .blocklyMenuItem { + /* 28px on the left for icon or checkbox. */ + padding-left: 28px; +} + +/* BiDi override for the resting state. */ +.blocklyDropdownMenu .blocklyMenuItemRtl { + /* Flip left/right padding for BiDi. */ + padding-left: 5px; + padding-right: 28px; +} + +.blocklyWidgetDiv .blocklyMenu { + background: #fff; + border: 1px solid transparent; + box-shadow: 0 0 3px 1px rgba(0,0,0,.3); + font: normal 13px Arial, sans-serif; + margin: 0; + outline: none; + padding: 4px 0; + position: absolute; + overflow-y: auto; + overflow-x: hidden; + max-height: 100%; + z-index: 20000; /* Arbitrary, but some apps depend on it... */ +} + +.blocklyWidgetDiv .blocklyMenu.blocklyFocused { + box-shadow: 0 0 6px 1px rgba(0,0,0,.3); +} + +.blocklyDropDownDiv .blocklyMenu { + background: inherit; /* Compatibility with gapi, reset from goog-menu */ + border: inherit; /* Compatibility with gapi, reset from goog-menu */ + font: normal 13px "Helvetica Neue", Helvetica, sans-serif; + outline: none; + position: relative; /* Compatibility with gapi, reset from goog-menu */ + z-index: 20000; /* Arbitrary, but some apps depend on it... */ +} + +/* State: resting. */ +.blocklyMenuItem { + border: none; + color: #000; + cursor: pointer; + list-style: none; + margin: 0; + /* 7em on the right for shortcut. */ + min-width: 7em; + padding: 6px 15px; + white-space: nowrap; +} + +/* State: disabled. */ +.blocklyMenuItemDisabled { + color: #ccc; + cursor: inherit; +} + +/* State: hover. */ +.blocklyMenuItemHighlight { + background-color: rgba(0,0,0,.1); +} + +/* State: selected/checked. */ +.blocklyMenuItemCheckbox { + height: 16px; + position: absolute; + width: 16px; +} + +.blocklyMenuItemSelected .blocklyMenuItemCheckbox { + background: url(<<>>/sprites.png) no-repeat -48px -16px; + float: left; + margin-left: -24px; + position: static; /* Scroll with the menu. */ +} + +.blocklyMenuItemRtl .blocklyMenuItemCheckbox { + float: right; + margin-right: -24px; +} `); exports.content = content; diff --git a/core/delete_area.js b/core/delete_area.js index b856d8a97..3985fca90 100644 --- a/core/delete_area.js +++ b/core/delete_area.js @@ -18,7 +18,6 @@ */ goog.module('Blockly.DeleteArea'); -const object = goog.require('Blockly.utils.object'); const {BlockSvg} = goog.require('Blockly.BlockSvg'); const {DragTarget} = goog.require('Blockly.DragTarget'); /* eslint-disable-next-line no-unused-vars */ @@ -32,53 +31,57 @@ const {IDraggable} = goog.requireType('Blockly.IDraggable'); * dropped on top of it. * @extends {DragTarget} * @implements {IDeleteArea} - * @constructor * @alias Blockly.DeleteArea */ -const DeleteArea = function() { - DeleteArea.superClass_.constructor.call(this); +class DeleteArea extends DragTarget { + /** + * Constructor for DeleteArea. Should not be called directly, only by a + * subclass. + */ + constructor() { + super(); + + /** + * Whether the last block or bubble dragged over this delete area would be + * deleted if dropped on this component. + * This property is not updated after the block or bubble is deleted. + * @type {boolean} + * @protected + */ + this.wouldDelete_ = false; + } /** - * Whether the last block or bubble dragged over this delete area would be - * deleted if dropped on this component. - * This property is not updated after the block or bubble is deleted. - * @type {boolean} + * Returns whether the provided block or bubble would be deleted if dropped on + * this area. + * This method should check if the element is deletable and is always called + * before onDragEnter/onDragOver/onDragExit. + * @param {!IDraggable} element The block or bubble currently being + * dragged. + * @param {boolean} couldConnect Whether the element could could connect to + * another. + * @return {boolean} Whether the element provided would be deleted if dropped + * on this area. + */ + wouldDelete(element, couldConnect) { + if (element instanceof BlockSvg) { + const block = /** @type {BlockSvg} */ (element); + const couldDeleteBlock = !block.getParent() && block.isDeletable(); + this.updateWouldDelete_(couldDeleteBlock && !couldConnect); + } else { + this.updateWouldDelete_(element.isDeletable()); + } + return this.wouldDelete_; + } + + /** + * Updates the internal wouldDelete_ state. + * @param {boolean} wouldDelete The new value for the wouldDelete state. * @protected */ - this.wouldDelete_ = false; -}; -object.inherits(DeleteArea, DragTarget); - -/** - * Returns whether the provided block or bubble would be deleted if dropped on - * this area. - * This method should check if the element is deletable and is always called - * before onDragEnter/onDragOver/onDragExit. - * @param {!IDraggable} element The block or bubble currently being - * dragged. - * @param {boolean} couldConnect Whether the element could could connect to - * another. - * @return {boolean} Whether the element provided would be deleted if dropped on - * this area. - */ -DeleteArea.prototype.wouldDelete = function(element, couldConnect) { - if (element instanceof BlockSvg) { - const block = /** @type {BlockSvg} */ (element); - const couldDeleteBlock = !block.getParent() && block.isDeletable(); - this.updateWouldDelete_(couldDeleteBlock && !couldConnect); - } else { - this.updateWouldDelete_(element.isDeletable()); + updateWouldDelete_(wouldDelete) { + this.wouldDelete_ = wouldDelete; } - return this.wouldDelete_; -}; - -/** - * Updates the internal wouldDelete_ state. - * @param {boolean} wouldDelete The new value for the wouldDelete state. - * @protected - */ -DeleteArea.prototype.updateWouldDelete_ = function(wouldDelete) { - this.wouldDelete_ = wouldDelete; -}; +} exports.DeleteArea = DeleteArea; diff --git a/core/drag_target.js b/core/drag_target.js index f552586bd..64a3e3ce3 100644 --- a/core/drag_target.js +++ b/core/drag_target.js @@ -30,68 +30,69 @@ const {Rect} = goog.requireType('Blockly.utils.Rect'); * Abstract class for a component with custom behaviour when a block or bubble * is dragged over or dropped on top of it. * @implements {IDragTarget} - * @constructor * @alias Blockly.DragTarget */ -const DragTarget = function() {}; +class DragTarget { + /** + * Handles when a cursor with a block or bubble enters this drag target. + * @param {!IDraggable} _dragElement The block or bubble currently being + * dragged. + */ + onDragEnter(_dragElement) { + // no-op + } -/** - * Returns the bounding rectangle of the drag target area in pixel units - * relative to the Blockly injection div. - * @return {?Rect} The component's bounding box. Null if drag - * target area should be ignored. - */ -DragTarget.prototype.getClientRect; + /** + * Handles when a cursor with a block or bubble is dragged over this drag + * target. + * @param {!IDraggable} _dragElement The block or bubble currently being + * dragged. + */ + onDragOver(_dragElement) { + // no-op + } -/** - * Handles when a cursor with a block or bubble enters this drag target. - * @param {!IDraggable} _dragElement The block or bubble currently being - * dragged. - */ -DragTarget.prototype.onDragEnter = function(_dragElement) { - // no-op -}; + /** + * Handles when a cursor with a block or bubble exits this drag target. + * @param {!IDraggable} _dragElement The block or bubble currently being + * dragged. + */ + onDragExit(_dragElement) { + // no-op + } -/** - * Handles when a cursor with a block or bubble is dragged over this drag - * target. - * @param {!IDraggable} _dragElement The block or bubble currently being - * dragged. - */ -DragTarget.prototype.onDragOver = function(_dragElement) { - // no-op -}; + /** + * Handles when a block or bubble is dropped on this component. + * Should not handle delete here. + * @param {!IDraggable} _dragElement The block or bubble currently being + * dragged. + */ + onDrop(_dragElement) { + // no-op + } -/** - * Handles when a cursor with a block or bubble exits this drag target. - * @param {!IDraggable} _dragElement The block or bubble currently being - * dragged. - */ -DragTarget.prototype.onDragExit = function(_dragElement) { - // no-op -}; + /** + * Returns the bounding rectangle of the drag target area in pixel units + * relative to the Blockly injection div. + * @return {?Rect} The component's bounding box. Null if drag + * target area should be ignored. + */ + getClientRect() { + return null; + } -/** - * Handles when a block or bubble is dropped on this component. - * Should not handle delete here. - * @param {!IDraggable} _dragElement The block or bubble currently being - * dragged. - */ -DragTarget.prototype.onDrop = function(_dragElement) { - // no-op -}; - -/** - * Returns whether the provided block or bubble should not be moved after being - * dropped on this component. If true, the element will return to where it was - * when the drag started. - * @param {!IDraggable} _dragElement The block or bubble currently being - * dragged. - * @return {boolean} Whether the block or bubble provided should be returned to - * drag start. - */ -DragTarget.prototype.shouldPreventMove = function(_dragElement) { - return false; -}; + /** + * Returns whether the provided block or bubble should not be moved after + * being dropped on this component. If true, the element will return to where + * it was when the drag started. + * @param {!IDraggable} _dragElement The block or bubble currently being + * dragged. + * @return {boolean} Whether the block or bubble provided should be returned + * to drag start. + */ + shouldPreventMove(_dragElement) { + return false; + } +} exports.DragTarget = DragTarget; diff --git a/core/dropdowndiv.js b/core/dropdowndiv.js index a22baa944..bd6e8afea 100644 --- a/core/dropdowndiv.js +++ b/core/dropdowndiv.js @@ -16,7 +16,7 @@ * A div that floats on top of the workspace, for drop-down menus. * @class */ -goog.module('Blockly.DropDownDiv'); +goog.module('Blockly.dropDownDiv'); const common = goog.require('Blockly.common'); const dom = goog.require('Blockly.utils.dom'); @@ -33,21 +33,14 @@ const {Size} = goog.requireType('Blockly.utils.Size'); const {WorkspaceSvg} = goog.requireType('Blockly.WorkspaceSvg'); -/** - * Class for drop-down div. - * @constructor - * @package - * @alias Blockly.DropDownDiv - */ -const DropDownDiv = function() {}; - /** * Arrow size in px. Should match the value in CSS * (need to position pre-render). * @type {number} * @const */ -DropDownDiv.ARROW_SIZE = 16; +const ARROW_SIZE = 16; +exports.ARROW_SIZE = ARROW_SIZE; /** * Drop-down border size in px. Should match the value in CSS (need to position @@ -55,7 +48,8 @@ DropDownDiv.ARROW_SIZE = 16; * @type {number} * @const */ -DropDownDiv.BORDER_SIZE = 1; +const BORDER_SIZE = 1; +exports.BORDER_SIZE = BORDER_SIZE; /** * Amount the arrow must be kept away from the edges of the main drop-down div, @@ -63,93 +57,86 @@ DropDownDiv.BORDER_SIZE = 1; * @type {number} * @const */ -DropDownDiv.ARROW_HORIZONTAL_PADDING = 12; +const ARROW_HORIZONTAL_PADDING = 12; +exports.ARROW_HORIZONTAL_PADDING = ARROW_HORIZONTAL_PADDING; /** * Amount drop-downs should be padded away from the source, in px. * @type {number} * @const */ -DropDownDiv.PADDING_Y = 16; +const PADDING_Y = 16; +exports.PADDING_Y = PADDING_Y; /** * Length of animations in seconds. * @type {number} * @const */ -DropDownDiv.ANIMATION_TIME = 0.25; +const ANIMATION_TIME = 0.25; +exports.ANIMATION_TIME = ANIMATION_TIME; /** * Timer for animation out, to be cleared if we need to immediately hide * without disrupting new shows. * @type {?number} - * @private */ -DropDownDiv.animateOutTimer_ = null; +let animateOutTimer = null; /** * Callback for when the drop-down is hidden. * @type {?Function} - * @private */ -DropDownDiv.onHide_ = null; +let onHide = null; /** * A class name representing the current owner's workspace renderer. * @type {string} - * @private */ -DropDownDiv.rendererClassName_ = ''; +let renderedClassName = ''; /** * A class name representing the current owner's workspace theme. * @type {string} - * @private */ -DropDownDiv.themeClassName_ = ''; +let themeClassName = ''; /** * The content element. - * @type {!Element} - * @private + * @type {!HTMLDivElement} */ -DropDownDiv.DIV_; +let div; /** * The content element. - * @type {!Element} - * @private + * @type {!HTMLDivElement} */ -DropDownDiv.content_; +let content; /** * The arrow element. - * @type {!Element} - * @private + * @type {!HTMLDivElement} */ -DropDownDiv.arrow_; +let arrow; /** * Drop-downs will appear within the bounds of this element if possible. - * Set in DropDownDiv.setBoundsElement. + * Set in setBoundsElement. * @type {?Element} - * @private */ -DropDownDiv.boundsElement_ = null; +let boundsElement = null; /** * The object currently using the drop-down. * @type {?Object} - * @private */ -DropDownDiv.owner_ = null; +let owner = null; /** * Whether the dropdown was positioned to a field or the source block. * @type {?boolean} - * @private */ -DropDownDiv.positionToField_ = null; +let positionToField = null; /** * Dropdown bounds info object used to encapsulate sizing information about a @@ -163,7 +150,8 @@ DropDownDiv.positionToField_ = null; * height:number * }} */ -DropDownDiv.BoundsInfo; +let BoundsInfo; +exports.BoundsInfo = BoundsInfo; /** * Dropdown position metrics. @@ -178,84 +166,85 @@ DropDownDiv.BoundsInfo; * arrowVisible:boolean * }} */ -DropDownDiv.PositionMetrics; +let PositionMetrics; +exports.PositionMetrics = PositionMetrics; /** * Create and insert the DOM element for this div. * @package */ -DropDownDiv.createDom = function() { - if (DropDownDiv.DIV_) { +const createDom = function() { + if (div) { return; // Already created. } - const containerDiv = document.createElement('div'); - containerDiv.className = 'blocklyDropDownDiv'; + div = /** @type {!HTMLDivElement} */ (document.createElement('div')); + div.className = 'blocklyDropDownDiv'; const parentDiv = common.getParentContainer() || document.body; - parentDiv.appendChild(containerDiv); + parentDiv.appendChild(div); - DropDownDiv.DIV_ = containerDiv; - - const content = document.createElement('div'); + content = /** @type {!HTMLDivElement} */ (document.createElement('div')); content.className = 'blocklyDropDownContent'; - DropDownDiv.DIV_.appendChild(content); - DropDownDiv.content_ = content; + div.appendChild(content); - const arrow = document.createElement('div'); + arrow = /** @type {!HTMLDivElement} */ (document.createElement('div')); arrow.className = 'blocklyDropDownArrow'; - DropDownDiv.DIV_.appendChild(arrow); - DropDownDiv.arrow_ = arrow; + div.appendChild(arrow); - DropDownDiv.DIV_.style.opacity = 0; + div.style.opacity = 0; // Transition animation for transform: translate() and opacity. - DropDownDiv.DIV_.style.transition = 'transform ' + - DropDownDiv.ANIMATION_TIME + 's, ' + - 'opacity ' + DropDownDiv.ANIMATION_TIME + 's'; + div.style.transition = 'transform ' + ANIMATION_TIME + 's, ' + + 'opacity ' + ANIMATION_TIME + 's'; // Handle focusin/out events to add a visual indicator when // a child is focused or blurred. - DropDownDiv.DIV_.addEventListener('focusin', function() { - dom.addClass(DropDownDiv.DIV_, 'blocklyFocused'); + div.addEventListener('focusin', function() { + dom.addClass(div, 'blocklyFocused'); }); - DropDownDiv.DIV_.addEventListener('focusout', function() { - dom.removeClass(DropDownDiv.DIV_, 'blocklyFocused'); + div.addEventListener('focusout', function() { + dom.removeClass(div, 'blocklyFocused'); }); }; +exports.createDom = createDom; /** * Set an element to maintain bounds within. Drop-downs will appear * within the box of this element if possible. - * @param {?Element} boundsElement Element to bind drop-down to. + * @param {?Element} boundsElem Element to bind drop-down to. */ -DropDownDiv.setBoundsElement = function(boundsElement) { - DropDownDiv.boundsElement_ = boundsElement; +const setBoundsElement = function(boundsElem) { + boundsElement = boundsElem; }; +exports.setBoundsElement = setBoundsElement; /** * Provide the div for inserting content into the drop-down. * @return {!Element} Div to populate with content. */ -DropDownDiv.getContentDiv = function() { - return DropDownDiv.content_; +const getContentDiv = function() { + return content; }; +exports.getContentDiv = getContentDiv; /** * Clear the content of the drop-down. */ -DropDownDiv.clearContent = function() { - DropDownDiv.content_.textContent = ''; - DropDownDiv.content_.style.width = ''; +const clearContent = function() { + content.textContent = ''; + content.style.width = ''; }; +exports.clearContent = clearContent; /** * Set the colour for the drop-down. * @param {string} backgroundColour Any CSS colour for the background. * @param {string} borderColour Any CSS colour for the border. */ -DropDownDiv.setColour = function(backgroundColour, borderColour) { - DropDownDiv.DIV_.style.backgroundColor = backgroundColour; - DropDownDiv.DIV_.style.borderColor = borderColour; +const setColour = function(backgroundColour, borderColour) { + div.style.backgroundColor = backgroundColour; + div.style.borderColor = borderColour; }; +exports.setColour = setColour; /** * Shortcut to show and place the drop-down with positioning determined @@ -270,11 +259,12 @@ DropDownDiv.setColour = function(backgroundColour, borderColour) { * positioning. * @return {boolean} True if the menu rendered below block; false if above. */ -DropDownDiv.showPositionedByBlock = function( +const showPositionedByBlock = function( field, block, opt_onHide, opt_secondaryYOffset) { return showPositionedByRect( getScaledBboxOfBlock(block), field, opt_onHide, opt_secondaryYOffset); }; +exports.showPositionedByBlock = showPositionedByBlock; /** * Shortcut to show and place the drop-down with positioning determined @@ -288,14 +278,13 @@ DropDownDiv.showPositionedByBlock = function( * positioning. * @return {boolean} True if the menu rendered below block; false if above. */ -DropDownDiv.showPositionedByField = function( +const showPositionedByField = function( field, opt_onHide, opt_secondaryYOffset) { - DropDownDiv.positionToField_ = true; + positionToField = true; return showPositionedByRect( getScaledBboxOfField(field), field, opt_onHide, opt_secondaryYOffset); }; - -const internal = {}; +exports.showPositionedByField = showPositionedByField; /** * Get the scaled bounding box of a block. @@ -353,9 +342,9 @@ const showPositionedByRect = function( workspace = /** @type {!WorkspaceSvg} */ (workspace.options.parentWorkspace); } - DropDownDiv.setBoundsElement( + setBoundsElement( /** @type {?Element} */ (workspace.getParentSvg().parentNode)); - return DropDownDiv.show( + return show( field, sourceBlock.RTL, primaryX, primaryY, secondaryX, secondaryY, opt_onHide); }; @@ -368,7 +357,7 @@ const showPositionedByRect = function( * will point there, and the container will be positioned below it. * If we can't maintain the container bounds at the primary point, fall-back to * the secondary point and position above. - * @param {?Object} owner The object showing the drop-down + * @param {?Object} newOwner The object showing the drop-down * @param {boolean} rtl Right-to-left (true) or left-to-right (false). * @param {number} primaryX Desired origin point x, in absolute px. * @param {number} primaryY Desired origin point y, in absolute px. @@ -381,20 +370,19 @@ const showPositionedByRect = function( * @return {boolean} True if the menu rendered at the primary origin point. * @package */ -DropDownDiv.show = function( - owner, rtl, primaryX, primaryY, secondaryX, secondaryY, opt_onHide) { - DropDownDiv.owner_ = owner; - DropDownDiv.onHide_ = opt_onHide || null; +const show = function( + newOwner, rtl, primaryX, primaryY, secondaryX, secondaryY, opt_onHide) { + owner = newOwner; + onHide = opt_onHide || null; // Set direction. - const div = DropDownDiv.DIV_; div.style.direction = rtl ? 'rtl' : 'ltr'; const mainWorkspace = /** @type {!WorkspaceSvg} */ (common.getMainWorkspace()); - DropDownDiv.rendererClassName_ = mainWorkspace.getRenderer().getClassName(); - DropDownDiv.themeClassName_ = mainWorkspace.getTheme().getClassName(); - dom.addClass(div, DropDownDiv.rendererClassName_); - dom.addClass(div, DropDownDiv.themeClassName_); + renderedClassName = mainWorkspace.getRenderer().getClassName(); + themeClassName = mainWorkspace.getTheme().getClassName(); + dom.addClass(div, renderedClassName); + dom.addClass(div, themeClassName); // When we change `translate` multiple times in close succession, // Chrome may choose to wait and apply them all at once. @@ -407,17 +395,20 @@ DropDownDiv.show = function( return positionInternal(primaryX, primaryY, secondaryX, secondaryY); }; +exports.show = show; + +const internal = {}; /** * Get sizing info about the bounding element. - * @return {!DropDownDiv.BoundsInfo} An object containing size + * @return {!BoundsInfo} An object containing size * information about the bounding element (bounding box and width/height). */ internal.getBoundsInfo = function() { const boundPosition = style.getPageOffset( - /** @type {!Element} */ (DropDownDiv.boundsElement_)); + /** @type {!Element} */ (boundsElement)); const boundSize = style.getSize( - /** @type {!Element} */ (DropDownDiv.boundsElement_)); + /** @type {!Element} */ (boundsElement)); return { left: boundPosition.x, @@ -431,21 +422,21 @@ internal.getBoundsInfo = function() { /** * Helper to position the drop-down and the arrow, maintaining bounds. - * See explanation of origin points in DropDownDiv.show. + * See explanation of origin points in show. * @param {number} primaryX Desired origin point x, in absolute px. * @param {number} primaryY Desired origin point y, in absolute px. * @param {number} secondaryX Secondary/alternative origin point x, * in absolute px. * @param {number} secondaryY Secondary/alternative origin point y, * in absolute px. - * @return {!DropDownDiv.PositionMetrics} Various final metrics, + * @return {!PositionMetrics} Various final metrics, * including rendered positions for drop-down and arrow. */ internal.getPositionMetrics = function( primaryX, primaryY, secondaryX, secondaryY) { const boundsInfo = internal.getBoundsInfo(); const divSize = style.getSize( - /** @type {!Element} */ (DropDownDiv.DIV_)); + /** @type {!Element} */ (div)); // Can we fit in-bounds below the target? if (primaryY + divSize.height < boundsInfo.bottom) { @@ -472,20 +463,20 @@ internal.getPositionMetrics = function( * Get the metrics for positioning the div below the source. * @param {number} primaryX Desired origin point x, in absolute px. * @param {number} primaryY Desired origin point y, in absolute px. - * @param {!DropDownDiv.BoundsInfo} boundsInfo An object containing size + * @param {!BoundsInfo} boundsInfo An object containing size * information about the bounding element (bounding box and width/height). * @param {!Size} divSize An object containing information about * the size of the DropDownDiv (width & height). - * @return {!DropDownDiv.PositionMetrics} Various final metrics, + * @return {!PositionMetrics} Various final metrics, * including rendered positions for drop-down and arrow. */ const getPositionBelowMetrics = function( primaryX, primaryY, boundsInfo, divSize) { - const xCoords = DropDownDiv.getPositionX( - primaryX, boundsInfo.left, boundsInfo.right, divSize.width); + const xCoords = + getPositionX(primaryX, boundsInfo.left, boundsInfo.right, divSize.width); - const arrowY = -(DropDownDiv.ARROW_SIZE / 2 + DropDownDiv.BORDER_SIZE); - const finalY = primaryY + DropDownDiv.PADDING_Y; + const arrowY = -(ARROW_SIZE / 2 + BORDER_SIZE); + const finalY = primaryY + PADDING_Y; return { initialX: xCoords.divX, @@ -505,21 +496,20 @@ const getPositionBelowMetrics = function( * in absolute px. * @param {number} secondaryY Secondary/alternative origin point y, * in absolute px. - * @param {!DropDownDiv.BoundsInfo} boundsInfo An object containing size + * @param {!BoundsInfo} boundsInfo An object containing size * information about the bounding element (bounding box and width/height). * @param {!Size} divSize An object containing information about * the size of the DropDownDiv (width & height). - * @return {!DropDownDiv.PositionMetrics} Various final metrics, + * @return {!PositionMetrics} Various final metrics, * including rendered positions for drop-down and arrow. */ const getPositionAboveMetrics = function( secondaryX, secondaryY, boundsInfo, divSize) { - const xCoords = DropDownDiv.getPositionX( + const xCoords = getPositionX( secondaryX, boundsInfo.left, boundsInfo.right, divSize.width); - const arrowY = divSize.height - (DropDownDiv.BORDER_SIZE * 2) - - (DropDownDiv.ARROW_SIZE / 2); - const finalY = secondaryY - divSize.height - DropDownDiv.PADDING_Y; + const arrowY = divSize.height - (BORDER_SIZE * 2) - (ARROW_SIZE / 2); + const finalY = secondaryY - divSize.height - PADDING_Y; const initialY = secondaryY - divSize.height; // No padding on Y. return { @@ -537,16 +527,16 @@ const getPositionAboveMetrics = function( /** * Get the metrics for positioning the div at the top of the page. * @param {number} sourceX Desired origin point x, in absolute px. - * @param {!DropDownDiv.BoundsInfo} boundsInfo An object containing size + * @param {!BoundsInfo} boundsInfo An object containing size * information about the bounding element (bounding box and width/height). * @param {!Size} divSize An object containing information about * the size of the DropDownDiv (width & height). - * @return {!DropDownDiv.PositionMetrics} Various final metrics, + * @return {!PositionMetrics} Various final metrics, * including rendered positions for drop-down and arrow. */ const getPositionTopOfPageMetrics = function(sourceX, boundsInfo, divSize) { - const xCoords = DropDownDiv.getPositionX( - sourceX, boundsInfo.left, boundsInfo.right, divSize.width); + const xCoords = + getPositionX(sourceX, boundsInfo.left, boundsInfo.right, divSize.width); // No need to provide arrow-specific information because it won't be visible. return { @@ -574,8 +564,7 @@ const getPositionTopOfPageMetrics = function(sourceX, boundsInfo, divSize) { * the x positions of the left side of the DropDownDiv and the arrow. * @package */ -DropDownDiv.getPositionX = function( - sourceX, boundsLeft, boundsRight, divWidth) { +const getPositionX = function(sourceX, boundsLeft, boundsRight, divWidth) { let divX = sourceX; // Offset the topLeft coord so that the dropdowndiv is centered. divX -= divWidth / 2; @@ -584,77 +573,79 @@ DropDownDiv.getPositionX = function( let arrowX = sourceX; // Offset the arrow coord so that the arrow is centered. - arrowX -= DropDownDiv.ARROW_SIZE / 2; + arrowX -= ARROW_SIZE / 2; // Convert the arrow position to be relative to the top left of the div. let relativeArrowX = arrowX - divX; - const horizPadding = DropDownDiv.ARROW_HORIZONTAL_PADDING; + const horizPadding = ARROW_HORIZONTAL_PADDING; // Clamp the arrow position so that it stays attached to the dropdowndiv. relativeArrowX = math.clamp( - horizPadding, relativeArrowX, - divWidth - horizPadding - DropDownDiv.ARROW_SIZE); + horizPadding, relativeArrowX, divWidth - horizPadding - ARROW_SIZE); return {arrowX: relativeArrowX, divX: divX}; }; +exports.getPositionX = getPositionX; /** * Is the container visible? * @return {boolean} True if visible. */ -DropDownDiv.isVisible = function() { - return !!DropDownDiv.owner_; +const isVisible = function() { + return !!owner; }; +exports.isVisible = isVisible; /** * Hide the menu only if it is owned by the provided object. - * @param {?Object} owner Object which must be owning the drop-down to hide. + * @param {?Object} divOwner Object which must be owning the drop-down to hide. * @param {boolean=} opt_withoutAnimation True if we should hide the dropdown * without animating. * @return {boolean} True if hidden. */ -DropDownDiv.hideIfOwner = function(owner, opt_withoutAnimation) { - if (DropDownDiv.owner_ === owner) { +const hideIfOwner = function(divOwner, opt_withoutAnimation) { + if (owner === divOwner) { if (opt_withoutAnimation) { - DropDownDiv.hideWithoutAnimation(); + hideWithoutAnimation(); } else { - DropDownDiv.hide(); + hide(); } return true; } return false; }; +exports.hideIfOwner = hideIfOwner; /** * Hide the menu, triggering animation. */ -DropDownDiv.hide = function() { +const hide = function() { // Start the animation by setting the translation and fading out. // Reset to (initialX, initialY) - i.e., no translation. - DropDownDiv.DIV_.style.transform = 'translate(0, 0)'; - DropDownDiv.DIV_.style.opacity = 0; + div.style.transform = 'translate(0, 0)'; + div.style.opacity = 0; // Finish animation - reset all values to default. - DropDownDiv.animateOutTimer_ = setTimeout(function() { - DropDownDiv.hideWithoutAnimation(); - }, DropDownDiv.ANIMATION_TIME * 1000); - if (DropDownDiv.onHide_) { - DropDownDiv.onHide_(); - DropDownDiv.onHide_ = null; + animateOutTimer = setTimeout(function() { + hideWithoutAnimation(); + }, ANIMATION_TIME * 1000); + if (onHide) { + onHide(); + onHide = null; } }; +exports.hide = hide; /** * Hide the menu, without animation. */ -DropDownDiv.hideWithoutAnimation = function() { - if (!DropDownDiv.isVisible()) { +const hideWithoutAnimation = function() { + if (!isVisible()) { return; } - if (DropDownDiv.animateOutTimer_) { - clearTimeout(DropDownDiv.animateOutTimer_); + if (animateOutTimer) { + clearTimeout(animateOutTimer); } // Reset style properties in case this gets called directly // instead of hide() - see discussion on #2551. - const div = DropDownDiv.DIV_; div.style.transform = ''; div.style.left = ''; div.style.top = ''; @@ -663,23 +654,24 @@ DropDownDiv.hideWithoutAnimation = function() { div.style.backgroundColor = ''; div.style.borderColor = ''; - if (DropDownDiv.onHide_) { - DropDownDiv.onHide_(); - DropDownDiv.onHide_ = null; + if (onHide) { + onHide(); + onHide = null; } - DropDownDiv.clearContent(); - DropDownDiv.owner_ = null; + clearContent(); + owner = null; - if (DropDownDiv.rendererClassName_) { - dom.removeClass(div, DropDownDiv.rendererClassName_); - DropDownDiv.rendererClassName_ = ''; + if (renderedClassName) { + dom.removeClass(div, renderedClassName); + renderedClassName = ''; } - if (DropDownDiv.themeClassName_) { - dom.removeClass(div, DropDownDiv.themeClassName_); - DropDownDiv.themeClassName_ = ''; + if (themeClassName) { + dom.removeClass(div, themeClassName); + themeClassName = ''; } (/** @type {!WorkspaceSvg} */ (common.getMainWorkspace())).markFocused(); }; +exports.hideWithoutAnimation = hideWithoutAnimation; /** * Set the dropdown div's position. @@ -697,15 +689,15 @@ const positionInternal = function(primaryX, primaryY, secondaryX, secondaryY) { // Update arrow CSS. if (metrics.arrowVisible) { - DropDownDiv.arrow_.style.display = ''; - DropDownDiv.arrow_.style.transform = 'translate(' + metrics.arrowX + 'px,' + + arrow.style.display = ''; + arrow.style.transform = 'translate(' + metrics.arrowX + 'px,' + metrics.arrowY + 'px) rotate(45deg)'; - DropDownDiv.arrow_.setAttribute( + arrow.setAttribute( 'class', metrics.arrowAtTop ? 'blocklyDropDownArrow blocklyArrowTop' : 'blocklyDropDownArrow blocklyArrowBottom'); } else { - DropDownDiv.arrow_.style.display = 'none'; + arrow.style.display = 'none'; } const initialX = Math.floor(metrics.initialX); @@ -713,7 +705,6 @@ const positionInternal = function(primaryX, primaryY, secondaryX, secondaryY) { const finalX = Math.floor(metrics.finalX); const finalY = Math.floor(metrics.finalY); - const div = DropDownDiv.DIV_; // First apply initial translation. div.style.left = initialX + 'px'; div.style.top = initialY + 'px'; @@ -736,17 +727,17 @@ const positionInternal = function(primaryX, primaryY, secondaryX, secondaryY) { * calculate the new position, it will just hide it instead. * @package */ -DropDownDiv.repositionForWindowResize = function() { +const repositionForWindowResize = function() { // This condition mainly catches the dropdown div when it is being used as a // dropdown. It is important not to close it in this case because on Android, // when a field is focused, the soft keyboard opens triggering a window resize // event and we want the dropdown div to stick around so users can type into // it. - if (DropDownDiv.owner_) { - const field = /** @type {!Field} */ (DropDownDiv.owner_); + if (owner) { + const field = /** @type {!Field} */ (owner); const block = /** @type {!BlockSvg} */ (field.getSourceBlock()); - const bBox = DropDownDiv.positionToField_ ? getScaledBboxOfField(field) : - getScaledBboxOfBlock(block); + const bBox = positionToField ? getScaledBboxOfField(field) : + getScaledBboxOfBlock(block); // If we can fit it, render below the block. const primaryX = bBox.left + (bBox.right - bBox.left) / 2; const primaryY = bBox.bottom; @@ -755,10 +746,9 @@ DropDownDiv.repositionForWindowResize = function() { const secondaryY = bBox.top; positionInternal(primaryX, primaryY, secondaryX, secondaryY); } else { - DropDownDiv.hide(); + hide(); } }; +exports.repositionForWindowResize = repositionForWindowResize; -DropDownDiv.TEST_ONLY = internal; - -exports.DropDownDiv = DropDownDiv; +exports.TEST_ONLY = internal; diff --git a/core/events/events.js b/core/events/events.js index d4cb5f7d4..cd393c309 100644 --- a/core/events/events.js +++ b/core/events/events.js @@ -15,9 +15,9 @@ */ goog.module('Blockly.Events'); -const Abstract = goog.require('Blockly.Events.Abstract'); const deprecation = goog.require('Blockly.utils.deprecation'); const eventUtils = goog.require('Blockly.Events.utils'); +const {Abstract: AbstractEvent} = goog.require('Blockly.Events.Abstract'); const {BlockBase} = goog.require('Blockly.Events.BlockBase'); const {BlockChange} = goog.require('Blockly.Events.BlockChange'); const {BlockCreate} = goog.require('Blockly.Events.BlockCreate'); @@ -47,7 +47,7 @@ const {ViewportChange} = goog.require('Blockly.Events.ViewportChange'); // Events. -exports.Abstract = Abstract; +exports.Abstract = AbstractEvent; exports.BubbleOpen = BubbleOpen; exports.BlockBase = BlockBase; exports.BlockChange = BlockChange; diff --git a/core/events/events_abstract.js b/core/events/events_abstract.js index d88d8efab..b2e2fc140 100644 --- a/core/events/events_abstract.js +++ b/core/events/events_abstract.js @@ -24,98 +24,110 @@ const {Workspace} = goog.requireType('Blockly.Workspace'); /** * Abstract class for an event. - * @constructor + * @abstract * @alias Blockly.Events.Abstract */ -const Abstract = function() { +class Abstract { /** - * Whether or not the event is blank (to be populated by fromJson). - * @type {?boolean} + * @alias Blockly.Events.Abstract */ - this.isBlank = null; + constructor() { + /** + * Whether or not the event is blank (to be populated by fromJson). + * @type {?boolean} + */ + this.isBlank = null; - /** - * The workspace identifier for this event. - * @type {string|undefined} - */ - this.workspaceId = undefined; + /** + * The workspace identifier for this event. + * @type {string|undefined} + */ + this.workspaceId = undefined; - /** - * The event group id for the group this event belongs to. Groups define - * events that should be treated as an single action from the user's - * perspective, and should be undone together. - * @type {string} - */ - this.group = eventUtils.getGroup(); + /** + * The event group id for the group this event belongs to. Groups define + * events that should be treated as an single action from the user's + * perspective, and should be undone together. + * @type {string} + */ + this.group = eventUtils.getGroup(); - /** - * Sets whether the event should be added to the undo stack. - * @type {boolean} - */ - this.recordUndo = eventUtils.getRecordUndo(); -}; + /** + * Sets whether the event should be added to the undo stack. + * @type {boolean} + */ + this.recordUndo = eventUtils.getRecordUndo(); -/** - * Whether or not the event is a UI event. - * @type {boolean} - */ -Abstract.prototype.isUiEvent = false; + /** + * Whether or not the event is a UI event. + * @type {boolean} + */ + this.isUiEvent = false; -/** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ -Abstract.prototype.toJson = function() { - const json = {'type': this.type}; - if (this.group) { - json['group'] = this.group; + /** + * Type of this event. + * @type {string|undefined} + */ + this.type = undefined; } - return json; -}; -/** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ -Abstract.prototype.fromJson = function(json) { - this.isBlank = false; - this.group = json['group']; -}; - -/** - * Does this event record any change of state? - * @return {boolean} True if null, false if something changed. - */ -Abstract.prototype.isNull = function() { - return false; -}; - -/** - * Run an event. - * @param {boolean} _forward True if run forward, false if run backward (undo). - */ -Abstract.prototype.run = function(_forward) { - // Defined by subclasses. -}; - -/** - * Get workspace the event belongs to. - * @return {!Workspace} The workspace the event belongs to. - * @throws {Error} if workspace is null. - * @protected - */ -Abstract.prototype.getEventWorkspace_ = function() { - let workspace; - if (this.workspaceId) { - const {Workspace} = goog.module.get('Blockly.Workspace'); - workspace = Workspace.getById(this.workspaceId); + /** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ + toJson() { + const json = {'type': this.type}; + if (this.group) { + json['group'] = this.group; + } + return json; } - if (!workspace) { - throw Error( - 'Workspace is null. Event must have been generated from real' + - ' Blockly events.'); - } - return workspace; -}; -exports = Abstract; + /** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ + fromJson(json) { + this.isBlank = false; + this.group = json['group']; + } + + /** + * Does this event record any change of state? + * @return {boolean} True if null, false if something changed. + */ + isNull() { + return false; + } + + /** + * Run an event. + * @param {boolean} _forward True if run forward, false if run backward + * (undo). + */ + run(_forward) { + // Defined by subclasses. + } + + /** + * Get workspace the event belongs to. + * @return {!Workspace} The workspace the event belongs to. + * @throws {Error} if workspace is null. + * @protected + */ + getEventWorkspace_() { + let workspace; + if (this.workspaceId) { + const {Workspace} = goog.module.get('Blockly.Workspace'); + workspace = Workspace.getById(this.workspaceId); + } + if (!workspace) { + throw Error( + 'Workspace is null. Event must have been generated from real' + + ' Blockly events.'); + } + return workspace; + } +} + +exports.Abstract = Abstract; diff --git a/core/events/events_block_base.js b/core/events/events_block_base.js index c243575fa..11d857847 100644 --- a/core/events/events_block_base.js +++ b/core/events/events_block_base.js @@ -15,55 +15,56 @@ */ goog.module('Blockly.Events.BlockBase'); -const Abstract = goog.require('Blockly.Events.Abstract'); -const object = goog.require('Blockly.utils.object'); +const {Abstract: AbstractEvent} = goog.require('Blockly.Events.Abstract'); /* eslint-disable-next-line no-unused-vars */ const {Block} = goog.requireType('Blockly.Block'); /** * Abstract class for a block event. - * @param {!Block=} opt_block The block this event corresponds to. - * Undefined for a blank event. - * @extends {Abstract} - * @constructor + * @extends {AbstractEvent} * @alias Blockly.Events.BlockBase */ -const BlockBase = function(opt_block) { - BlockBase.superClass_.constructor.call(this); - this.isBlank = typeof opt_block === 'undefined'; +class BlockBase extends AbstractEvent { + /** + * @param {!Block=} opt_block The block this event corresponds to. + * Undefined for a blank event. + */ + constructor(opt_block) { + super(); + this.isBlank = typeof opt_block === 'undefined'; + + /** + * The block ID for the block this event pertains to + * @type {string} + */ + this.blockId = this.isBlank ? '' : opt_block.id; + + /** + * The workspace identifier for this event. + * @type {string} + */ + this.workspaceId = this.isBlank ? '' : opt_block.workspace.id; + } /** - * The block ID for the block this event pertains to - * @type {string} + * Encode the event as JSON. + * @return {!Object} JSON representation. */ - this.blockId = this.isBlank ? '' : opt_block.id; + toJson() { + const json = super.toJson(); + json['blockId'] = this.blockId; + return json; + } /** - * The workspace identifier for this event. - * @type {string} + * Decode the JSON event. + * @param {!Object} json JSON representation. */ - this.workspaceId = this.isBlank ? '' : opt_block.workspace.id; -}; -object.inherits(BlockBase, Abstract); - -/** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ -BlockBase.prototype.toJson = function() { - const json = BlockBase.superClass_.toJson.call(this); - json['blockId'] = this.blockId; - return json; -}; - -/** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ -BlockBase.prototype.fromJson = function(json) { - BlockBase.superClass_.fromJson.call(this, json); - this.blockId = json['blockId']; -}; + fromJson(json) { + super.fromJson(json); + this.blockId = json['blockId']; + } +} exports.BlockBase = BlockBase; diff --git a/core/events/events_block_change.js b/core/events/events_block_change.js index 4a9720ad8..e197b0da3 100644 --- a/core/events/events_block_change.js +++ b/core/events/events_block_change.js @@ -17,7 +17,6 @@ goog.module('Blockly.Events.BlockChange'); const Xml = goog.require('Blockly.Xml'); const eventUtils = goog.require('Blockly.Events.utils'); -const object = goog.require('Blockly.utils.object'); const registry = goog.require('Blockly.registry'); const {BlockBase} = goog.require('Blockly.Events.BlockBase'); /* eslint-disable-next-line no-unused-vars */ @@ -28,145 +27,152 @@ const {Block} = goog.requireType('Blockly.Block'); /** * Class for a block change event. - * @param {!Block=} opt_block The changed block. Undefined for a blank - * event. - * @param {string=} opt_element One of 'field', 'comment', 'disabled', etc. - * @param {?string=} opt_name Name of input or field affected, or null. - * @param {*=} opt_oldValue Previous value of element. - * @param {*=} opt_newValue New value of element. * @extends {BlockBase} - * @constructor * @alias Blockly.Events.BlockChange */ -const BlockChange = function( - opt_block, opt_element, opt_name, opt_oldValue, opt_newValue) { - BlockChange.superClass_.constructor.call(this, opt_block); - if (!opt_block) { - return; // Blank event to be populated by fromJson. - } - this.element = typeof opt_element === 'undefined' ? '' : opt_element; - this.name = typeof opt_name === 'undefined' ? '' : opt_name; - this.oldValue = typeof opt_oldValue === 'undefined' ? '' : opt_oldValue; - this.newValue = typeof opt_newValue === 'undefined' ? '' : opt_newValue; -}; -object.inherits(BlockChange, BlockBase); +class BlockChange extends BlockBase { + /** + * @param {!Block=} opt_block The changed block. Undefined for a blank + * event. + * @param {string=} opt_element One of 'field', 'comment', 'disabled', etc. + * @param {?string=} opt_name Name of input or field affected, or null. + * @param {*=} opt_oldValue Previous value of element. + * @param {*=} opt_newValue New value of element. + */ + constructor(opt_block, opt_element, opt_name, opt_oldValue, opt_newValue) { + super(opt_block); -/** - * Type of this event. - * @type {string} - */ -BlockChange.prototype.type = eventUtils.BLOCK_CHANGE; + /** + * Type of this event. + * @type {string} + */ + this.type = eventUtils.BLOCK_CHANGE; -/** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ -BlockChange.prototype.toJson = function() { - const json = BlockChange.superClass_.toJson.call(this); - json['element'] = this.element; - if (this.name) { - json['name'] = this.name; - } - json['oldValue'] = this.oldValue; - json['newValue'] = this.newValue; - return json; -}; - -/** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ -BlockChange.prototype.fromJson = function(json) { - BlockChange.superClass_.fromJson.call(this, json); - this.element = json['element']; - this.name = json['name']; - this.oldValue = json['oldValue']; - this.newValue = json['newValue']; -}; - -/** - * Does this event record any change of state? - * @return {boolean} False if something changed. - */ -BlockChange.prototype.isNull = function() { - return this.oldValue === this.newValue; -}; - -/** - * Run a change event. - * @param {boolean} forward True if run forward, false if run backward (undo). - */ -BlockChange.prototype.run = function(forward) { - const workspace = this.getEventWorkspace_(); - const block = workspace.getBlockById(this.blockId); - if (!block) { - console.warn('Can\'t change non-existent block: ' + this.blockId); - return; - } - if (block.mutator) { - // Close the mutator (if open) since we don't want to update it. - block.mutator.setVisible(false); - } - const value = forward ? this.newValue : this.oldValue; - switch (this.element) { - case 'field': { - const field = block.getField(this.name); - if (field) { - field.setValue(value); - } else { - console.warn('Can\'t set non-existent field: ' + this.name); - } - break; + if (!opt_block) { + return; // Blank event to be populated by fromJson. } - case 'comment': - block.setCommentText(/** @type {string} */ (value) || null); - break; - case 'collapsed': - block.setCollapsed(!!value); - break; - case 'disabled': - block.setEnabled(!value); - break; - case 'inline': - block.setInputsInline(!!value); - break; - case 'mutation': { - const oldState = BlockChange.getExtraBlockState_( - /** @type {!BlockSvg} */ (block)); - if (block.loadExtraState) { - block.loadExtraState(JSON.parse(/** @type {string} */ (value) || '{}')); - } else if (block.domToMutation) { - block.domToMutation( - Xml.textToDom(/** @type {string} */ (value) || '')); - } - eventUtils.fire( - new BlockChange(block, 'mutation', null, oldState, value)); - break; - } - default: - console.warn('Unknown change type: ' + this.element); + this.element = typeof opt_element === 'undefined' ? '' : opt_element; + this.name = typeof opt_name === 'undefined' ? '' : opt_name; + this.oldValue = typeof opt_oldValue === 'undefined' ? '' : opt_oldValue; + this.newValue = typeof opt_newValue === 'undefined' ? '' : opt_newValue; } -}; -// TODO (#5397): Encapsulate this in the BlocklyMutationChange event when -// refactoring change events. -/** - * Returns the extra state of the given block (either as XML or a JSO, depending - * on the block's definition). - * @param {!BlockSvg} block The block to get the extra state of. - * @return {string} A stringified version of the extra state of the given block. - * @package - */ -BlockChange.getExtraBlockState_ = function(block) { - if (block.saveExtraState) { - const state = block.saveExtraState(); - return state ? JSON.stringify(state) : ''; - } else if (block.mutationToDom) { - const state = block.mutationToDom(); - return state ? Xml.domToText(state) : ''; + /** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ + toJson() { + const json = super.toJson(); + json['element'] = this.element; + if (this.name) { + json['name'] = this.name; + } + json['oldValue'] = this.oldValue; + json['newValue'] = this.newValue; + return json; } - return ''; -}; + + /** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ + fromJson(json) { + super.fromJson(json); + this.element = json['element']; + this.name = json['name']; + this.oldValue = json['oldValue']; + this.newValue = json['newValue']; + } + + /** + * Does this event record any change of state? + * @return {boolean} False if something changed. + */ + isNull() { + return this.oldValue === this.newValue; + } + + /** + * Run a change event. + * @param {boolean} forward True if run forward, false if run backward (undo). + */ + run(forward) { + const workspace = this.getEventWorkspace_(); + const block = workspace.getBlockById(this.blockId); + if (!block) { + console.warn('Can\'t change non-existent block: ' + this.blockId); + return; + } + + // Assume the block is rendered so that then we can check. + const blockSvg = /** @type {!BlockSvg} */ (block); + if (blockSvg.mutator) { + // Close the mutator (if open) since we don't want to update it. + blockSvg.mutator.setVisible(false); + } + const value = forward ? this.newValue : this.oldValue; + switch (this.element) { + case 'field': { + const field = block.getField(this.name); + if (field) { + field.setValue(value); + } else { + console.warn('Can\'t set non-existent field: ' + this.name); + } + break; + } + case 'comment': + block.setCommentText(/** @type {string} */ (value) || null); + break; + case 'collapsed': + block.setCollapsed(!!value); + break; + case 'disabled': + block.setEnabled(!value); + break; + case 'inline': + block.setInputsInline(!!value); + break; + case 'mutation': { + const oldState = BlockChange.getExtraBlockState_( + /** @type {!BlockSvg} */ (block)); + if (block.loadExtraState) { + block.loadExtraState( + JSON.parse(/** @type {string} */ (value) || '{}')); + } else if (block.domToMutation) { + block.domToMutation( + Xml.textToDom(/** @type {string} */ (value) || '')); + } + eventUtils.fire( + new BlockChange(block, 'mutation', null, oldState, value)); + break; + } + default: + console.warn('Unknown change type: ' + this.element); + } + } + + // TODO (#5397): Encapsulate this in the BlocklyMutationChange event when + // refactoring change events. + /** + * Returns the extra state of the given block (either as XML or a JSO, + * depending on the block's definition). + * @param {!BlockSvg} block The block to get the extra state of. + * @return {string} A stringified version of the extra state of the given + * block. + * @package + */ + static getExtraBlockState_(block) { + if (block.saveExtraState) { + const state = block.saveExtraState(); + return state ? JSON.stringify(state) : ''; + } else if (block.mutationToDom) { + const state = block.mutationToDom(); + return state ? Xml.domToText(state) : ''; + } + return ''; + } +} registry.register(registry.Type.EVENT, eventUtils.CHANGE, BlockChange); diff --git a/core/events/events_block_create.js b/core/events/events_block_create.js index 9a3254358..6d3a03368 100644 --- a/core/events/events_block_create.js +++ b/core/events/events_block_create.js @@ -18,7 +18,6 @@ goog.module('Blockly.Events.BlockCreate'); const Xml = goog.require('Blockly.Xml'); const blocks = goog.require('Blockly.serialization.blocks'); const eventUtils = goog.require('Blockly.Events.utils'); -const object = goog.require('Blockly.utils.object'); const registry = goog.require('Blockly.registry'); const {BlockBase} = goog.require('Blockly.Events.BlockBase'); /* eslint-disable-next-line no-unused-vars */ @@ -27,90 +26,93 @@ const {Block} = goog.requireType('Blockly.Block'); /** * Class for a block creation event. - * @param {!Block=} opt_block The created block. Undefined for a blank - * event. * @extends {BlockBase} - * @constructor * @alias Blockly.Events.BlockCreate */ -const BlockCreate = function(opt_block) { - BlockCreate.superClass_.constructor.call(this, opt_block); - if (!opt_block) { - return; // Blank event to be populated by fromJson. - } - if (opt_block.isShadow()) { - // Moving shadow blocks is handled via disconnection. - this.recordUndo = false; - } +class BlockCreate extends BlockBase { + /** + * @param {!Block=} opt_block The created block. Undefined for a blank + * event. + */ + constructor(opt_block) { + super(opt_block); - this.xml = Xml.blockToDomWithXY(opt_block); - this.ids = eventUtils.getDescendantIds(opt_block); + /** + * Type of this event. + * @type {string} + */ + this.type = eventUtils.BLOCK_CREATE; + + if (!opt_block) { + return; // Blank event to be populated by fromJson. + } + if (opt_block.isShadow()) { + // Moving shadow blocks is handled via disconnection. + this.recordUndo = false; + } + + this.xml = Xml.blockToDomWithXY(opt_block); + this.ids = eventUtils.getDescendantIds(opt_block); + + /** + * JSON representation of the block that was just created. + * @type {!blocks.State} + */ + this.json = /** @type {!blocks.State} */ ( + blocks.save(opt_block, {addCoordinates: true})); + } /** - * JSON representation of the block that was just created. - * @type {!blocks.State} + * Encode the event as JSON. + * @return {!Object} JSON representation. */ - this.json = /** @type {!blocks.State} */ ( - blocks.save(opt_block, {addCoordinates: true})); -}; -object.inherits(BlockCreate, BlockBase); - -/** - * Type of this event. - * @type {string} - */ -BlockCreate.prototype.type = eventUtils.BLOCK_CREATE; - -/** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ -BlockCreate.prototype.toJson = function() { - const json = BlockCreate.superClass_.toJson.call(this); - json['xml'] = Xml.domToText(this.xml); - json['ids'] = this.ids; - json['json'] = this.json; - if (!this.recordUndo) { - json['recordUndo'] = this.recordUndo; + toJson() { + const json = super.toJson(); + json['xml'] = Xml.domToText(this.xml); + json['ids'] = this.ids; + json['json'] = this.json; + if (!this.recordUndo) { + json['recordUndo'] = this.recordUndo; + } + return json; } - return json; -}; -/** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ -BlockCreate.prototype.fromJson = function(json) { - BlockCreate.superClass_.fromJson.call(this, json); - this.xml = Xml.textToDom(json['xml']); - this.ids = json['ids']; - this.json = /** @type {!blocks.State} */ (json['json']); - if (json['recordUndo'] !== undefined) { - this.recordUndo = json['recordUndo']; + /** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ + fromJson(json) { + super.fromJson(json); + this.xml = Xml.textToDom(json['xml']); + this.ids = json['ids']; + this.json = /** @type {!blocks.State} */ (json['json']); + if (json['recordUndo'] !== undefined) { + this.recordUndo = json['recordUndo']; + } } -}; -/** - * Run a creation event. - * @param {boolean} forward True if run forward, false if run backward (undo). - */ -BlockCreate.prototype.run = function(forward) { - const workspace = this.getEventWorkspace_(); - if (forward) { - blocks.append(this.json, workspace); - } else { - for (let i = 0; i < this.ids.length; i++) { - const id = this.ids[i]; - const block = workspace.getBlockById(id); - if (block) { - block.dispose(false); - } else if (id === this.blockId) { - // Only complain about root-level block. - console.warn('Can\'t uncreate non-existent block: ' + id); + /** + * Run a creation event. + * @param {boolean} forward True if run forward, false if run backward (undo). + */ + run(forward) { + const workspace = this.getEventWorkspace_(); + if (forward) { + blocks.append(this.json, workspace); + } else { + for (let i = 0; i < this.ids.length; i++) { + const id = this.ids[i]; + const block = workspace.getBlockById(id); + if (block) { + block.dispose(false); + } else if (id === this.blockId) { + // Only complain about root-level block. + console.warn('Can\'t uncreate non-existent block: ' + id); + } } } } -}; +} registry.register(registry.Type.EVENT, eventUtils.CREATE, BlockCreate); diff --git a/core/events/events_block_delete.js b/core/events/events_block_delete.js index 80ef65d5e..10b96dd69 100644 --- a/core/events/events_block_delete.js +++ b/core/events/events_block_delete.js @@ -18,7 +18,6 @@ goog.module('Blockly.Events.BlockDelete'); const Xml = goog.require('Blockly.Xml'); const blocks = goog.require('Blockly.serialization.blocks'); const eventUtils = goog.require('Blockly.Events.utils'); -const object = goog.require('Blockly.utils.object'); const registry = goog.require('Blockly.registry'); const {BlockBase} = goog.require('Blockly.Events.BlockBase'); /* eslint-disable-next-line no-unused-vars */ @@ -27,102 +26,105 @@ const {Block} = goog.requireType('Blockly.Block'); /** * Class for a block deletion event. - * @param {!Block=} opt_block The deleted block. Undefined for a blank - * event. * @extends {BlockBase} - * @constructor * @alias Blockly.Events.BlockDelete */ -const BlockDelete = function(opt_block) { - BlockDelete.superClass_.constructor.call(this, opt_block); - if (!opt_block) { - return; // Blank event to be populated by fromJson. - } - if (opt_block.getParent()) { - throw Error('Connected blocks cannot be deleted.'); - } - if (opt_block.isShadow()) { - // Respawning shadow blocks is handled via disconnection. - this.recordUndo = false; - } - - this.oldXml = Xml.blockToDomWithXY(opt_block); - this.ids = eventUtils.getDescendantIds(opt_block); - +class BlockDelete extends BlockBase { /** - * Was the block that was just deleted a shadow? - * @type {boolean} + * @param {!Block=} opt_block The deleted block. Undefined for a blank + * event. */ - this.wasShadow = opt_block.isShadow(); + constructor(opt_block) { + super(opt_block); - /** - * JSON representation of the block that was just deleted. - * @type {!blocks.State} - */ - this.oldJson = /** @type {!blocks.State} */ ( - blocks.save(opt_block, {addCoordinates: true})); -}; -object.inherits(BlockDelete, BlockBase); + /** + * Type of this event. + * @type {string} + */ + this.type = eventUtils.BLOCK_DELETE; -/** - * Type of this event. - * @type {string} - */ -BlockDelete.prototype.type = eventUtils.BLOCK_DELETE; - -/** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ -BlockDelete.prototype.toJson = function() { - const json = BlockDelete.superClass_.toJson.call(this); - json['oldXml'] = Xml.domToText(this.oldXml); - json['ids'] = this.ids; - json['wasShadow'] = this.wasShadow; - json['oldJson'] = this.oldJson; - if (!this.recordUndo) { - json['recordUndo'] = this.recordUndo; - } - return json; -}; - -/** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ -BlockDelete.prototype.fromJson = function(json) { - BlockDelete.superClass_.fromJson.call(this, json); - this.oldXml = Xml.textToDom(json['oldXml']); - this.ids = json['ids']; - this.wasShadow = - json['wasShadow'] || this.oldXml.tagName.toLowerCase() === 'shadow'; - this.oldJson = /** @type {!blocks.State} */ (json['oldJson']); - if (json['recordUndo'] !== undefined) { - this.recordUndo = json['recordUndo']; - } -}; - -/** - * Run a deletion event. - * @param {boolean} forward True if run forward, false if run backward (undo). - */ -BlockDelete.prototype.run = function(forward) { - const workspace = this.getEventWorkspace_(); - if (forward) { - for (let i = 0; i < this.ids.length; i++) { - const id = this.ids[i]; - const block = workspace.getBlockById(id); - if (block) { - block.dispose(false); - } else if (id === this.blockId) { - // Only complain about root-level block. - console.warn('Can\'t delete non-existent block: ' + id); - } + if (!opt_block) { + return; // Blank event to be populated by fromJson. } - } else { - blocks.append(this.oldJson, workspace); + if (opt_block.getParent()) { + throw Error('Connected blocks cannot be deleted.'); + } + if (opt_block.isShadow()) { + // Respawning shadow blocks is handled via disconnection. + this.recordUndo = false; + } + + this.oldXml = Xml.blockToDomWithXY(opt_block); + this.ids = eventUtils.getDescendantIds(opt_block); + + /** + * Was the block that was just deleted a shadow? + * @type {boolean} + */ + this.wasShadow = opt_block.isShadow(); + + /** + * JSON representation of the block that was just deleted. + * @type {!blocks.State} + */ + this.oldJson = /** @type {!blocks.State} */ ( + blocks.save(opt_block, {addCoordinates: true})); } -}; + + /** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ + toJson() { + const json = super.toJson(); + json['oldXml'] = Xml.domToText(this.oldXml); + json['ids'] = this.ids; + json['wasShadow'] = this.wasShadow; + json['oldJson'] = this.oldJson; + if (!this.recordUndo) { + json['recordUndo'] = this.recordUndo; + } + return json; + } + + /** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ + fromJson(json) { + super.fromJson(json); + this.oldXml = Xml.textToDom(json['oldXml']); + this.ids = json['ids']; + this.wasShadow = + json['wasShadow'] || this.oldXml.tagName.toLowerCase() === 'shadow'; + this.oldJson = /** @type {!blocks.State} */ (json['oldJson']); + if (json['recordUndo'] !== undefined) { + this.recordUndo = json['recordUndo']; + } + } + + /** + * Run a deletion event. + * @param {boolean} forward True if run forward, false if run backward (undo). + */ + run(forward) { + const workspace = this.getEventWorkspace_(); + if (forward) { + for (let i = 0; i < this.ids.length; i++) { + const id = this.ids[i]; + const block = workspace.getBlockById(id); + if (block) { + block.dispose(false); + } else if (id === this.blockId) { + // Only complain about root-level block. + console.warn('Can\'t delete non-existent block: ' + id); + } + } + } else { + blocks.append(this.oldJson, workspace); + } + } +} registry.register(registry.Type.EVENT, eventUtils.DELETE, BlockDelete); diff --git a/core/events/events_block_drag.js b/core/events/events_block_drag.js index 432246eb8..9c4850e93 100644 --- a/core/events/events_block_drag.js +++ b/core/events/events_block_drag.js @@ -16,7 +16,6 @@ goog.module('Blockly.Events.BlockDrag'); const eventUtils = goog.require('Blockly.Events.utils'); -const object = goog.require('Blockly.utils.object'); const registry = goog.require('Blockly.registry'); /* eslint-disable-next-line no-unused-vars */ const {Block} = goog.requireType('Blockly.Block'); @@ -25,63 +24,65 @@ const {UiBase} = goog.require('Blockly.Events.UiBase'); /** * Class for a block drag event. - * @param {!Block=} opt_block The top block in the stack that is being - * dragged. Undefined for a blank event. - * @param {boolean=} opt_isStart Whether this is the start of a block drag. - * Undefined for a blank event. - * @param {!Array=} opt_blocks The blocks affected by this - * drag. Undefined for a blank event. * @extends {UiBase} - * @constructor * @alias Blockly.Events.BlockDrag */ -const BlockDrag = function(opt_block, opt_isStart, opt_blocks) { - const workspaceId = opt_block ? opt_block.workspace.id : undefined; - BlockDrag.superClass_.constructor.call(this, workspaceId); - this.blockId = opt_block ? opt_block.id : null; +class BlockDrag extends UiBase { + /** + * @param {!Block=} opt_block The top block in the stack that is being + * dragged. Undefined for a blank event. + * @param {boolean=} opt_isStart Whether this is the start of a block drag. + * Undefined for a blank event. + * @param {!Array=} opt_blocks The blocks affected by this + * drag. Undefined for a blank event. + */ + constructor(opt_block, opt_isStart, opt_blocks) { + const workspaceId = opt_block ? opt_block.workspace.id : undefined; + super(workspaceId); + this.blockId = opt_block ? opt_block.id : null; + + /** + * Whether this is the start of a block drag. + * @type {boolean|undefined} + */ + this.isStart = opt_isStart; + + /** + * The blocks affected by this drag event. + * @type {!Array|undefined} + */ + this.blocks = opt_blocks; + + /** + * Type of this event. + * @type {string} + */ + this.type = eventUtils.BLOCK_DRAG; + } /** - * Whether this is the start of a block drag. - * @type {boolean|undefined} + * Encode the event as JSON. + * @return {!Object} JSON representation. */ - this.isStart = opt_isStart; + toJson() { + const json = super.toJson(); + json['isStart'] = this.isStart; + json['blockId'] = this.blockId; + json['blocks'] = this.blocks; + return json; + } /** - * The blocks affected by this drag event. - * @type {!Array|undefined} + * Decode the JSON event. + * @param {!Object} json JSON representation. */ - this.blocks = opt_blocks; -}; -object.inherits(BlockDrag, UiBase); - -/** - * Type of this event. - * @type {string} - */ -BlockDrag.prototype.type = eventUtils.BLOCK_DRAG; - -/** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ -BlockDrag.prototype.toJson = function() { - const json = BlockDrag.superClass_.toJson.call(this); - json['isStart'] = this.isStart; - json['blockId'] = this.blockId; - json['blocks'] = this.blocks; - return json; -}; - -/** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ -BlockDrag.prototype.fromJson = function(json) { - BlockDrag.superClass_.fromJson.call(this, json); - this.isStart = json['isStart']; - this.blockId = json['blockId']; - this.blocks = json['blocks']; -}; + fromJson(json) { + super.fromJson(json); + this.isStart = json['isStart']; + this.blockId = json['blockId']; + this.blocks = json['blocks']; + } +} registry.register(registry.Type.EVENT, eventUtils.BLOCK_DRAG, BlockDrag); diff --git a/core/events/events_block_move.js b/core/events/events_block_move.js index a90d74bd2..41e11a646 100644 --- a/core/events/events_block_move.js +++ b/core/events/events_block_move.js @@ -16,7 +16,6 @@ goog.module('Blockly.Events.BlockMove'); const eventUtils = goog.require('Blockly.Events.utils'); -const object = goog.require('Blockly.utils.object'); const registry = goog.require('Blockly.registry'); const {BlockBase} = goog.require('Blockly.Events.BlockBase'); /* eslint-disable-next-line no-unused-vars */ @@ -27,168 +26,176 @@ const {Coordinate} = goog.require('Blockly.utils.Coordinate'); /** * Class for a block move event. Created before the move. - * @param {!Block=} opt_block The moved block. Undefined for a blank - * event. * @extends {BlockBase} - * @constructor * @alias Blockly.Events.BlockMove */ -const BlockMove = function(opt_block) { - BlockMove.superClass_.constructor.call(this, opt_block); - if (!opt_block) { - return; // Blank event to be populated by fromJson. - } - if (opt_block.isShadow()) { - // Moving shadow blocks is handled via disconnection. - this.recordUndo = false; - } +class BlockMove extends BlockBase { + /** + * @param {!Block=} opt_block The moved block. Undefined for a blank + * event. + */ + constructor(opt_block) { + super(opt_block); - const location = this.currentLocation_(); - this.oldParentId = location.parentId; - this.oldInputName = location.inputName; - this.oldCoordinate = location.coordinate; -}; -object.inherits(BlockMove, BlockBase); + /** + * Type of this event. + * @type {string} + */ + this.type = eventUtils.BLOCK_MOVE; -/** - * Type of this event. - * @type {string} - */ -BlockMove.prototype.type = eventUtils.BLOCK_MOVE; - -/** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ -BlockMove.prototype.toJson = function() { - const json = BlockMove.superClass_.toJson.call(this); - if (this.newParentId) { - json['newParentId'] = this.newParentId; - } - if (this.newInputName) { - json['newInputName'] = this.newInputName; - } - if (this.newCoordinate) { - json['newCoordinate'] = Math.round(this.newCoordinate.x) + ',' + - Math.round(this.newCoordinate.y); - } - if (!this.recordUndo) { - json['recordUndo'] = this.recordUndo; - } - return json; -}; - -/** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ -BlockMove.prototype.fromJson = function(json) { - BlockMove.superClass_.fromJson.call(this, json); - this.newParentId = json['newParentId']; - this.newInputName = json['newInputName']; - if (json['newCoordinate']) { - const xy = json['newCoordinate'].split(','); - this.newCoordinate = new Coordinate(Number(xy[0]), Number(xy[1])); - } - if (json['recordUndo'] !== undefined) { - this.recordUndo = json['recordUndo']; - } -}; - -/** - * Record the block's new location. Called after the move. - */ -BlockMove.prototype.recordNew = function() { - const location = this.currentLocation_(); - this.newParentId = location.parentId; - this.newInputName = location.inputName; - this.newCoordinate = location.coordinate; -}; - -/** - * Returns the parentId and input if the block is connected, - * or the XY location if disconnected. - * @return {!Object} Collection of location info. - * @private - */ -BlockMove.prototype.currentLocation_ = function() { - const workspace = this.getEventWorkspace_(); - const block = workspace.getBlockById(this.blockId); - const location = {}; - const parent = block.getParent(); - if (parent) { - location.parentId = parent.id; - const input = parent.getInputWithBlock(block); - if (input) { - location.inputName = input.name; + if (!opt_block) { + return; // Blank event to be populated by fromJson. + } + if (opt_block.isShadow()) { + // Moving shadow blocks is handled via disconnection. + this.recordUndo = false; } - } else { - location.coordinate = block.getRelativeToSurfaceXY(); - } - return location; -}; -/** - * Does this event record any change of state? - * @return {boolean} False if something changed. - */ -BlockMove.prototype.isNull = function() { - return this.oldParentId === this.newParentId && - this.oldInputName === this.newInputName && - Coordinate.equals(this.oldCoordinate, this.newCoordinate); -}; + const location = this.currentLocation_(); + this.oldParentId = location.parentId; + this.oldInputName = location.inputName; + this.oldCoordinate = location.coordinate; -/** - * Run a move event. - * @param {boolean} forward True if run forward, false if run backward (undo). - */ -BlockMove.prototype.run = function(forward) { - const workspace = this.getEventWorkspace_(); - const block = workspace.getBlockById(this.blockId); - if (!block) { - console.warn('Can\'t move non-existent block: ' + this.blockId); - return; + this.newParentId = null; + this.newInputName = null; + this.newCoordinate = null; } - const parentId = forward ? this.newParentId : this.oldParentId; - const inputName = forward ? this.newInputName : this.oldInputName; - const coordinate = forward ? this.newCoordinate : this.oldCoordinate; - let parentBlock; - if (parentId) { - parentBlock = workspace.getBlockById(parentId); - if (!parentBlock) { - console.warn('Can\'t connect to non-existent block: ' + parentId); + + /** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ + toJson() { + const json = super.toJson(); + if (this.newParentId) { + json['newParentId'] = this.newParentId; + } + if (this.newInputName) { + json['newInputName'] = this.newInputName; + } + if (this.newCoordinate) { + json['newCoordinate'] = Math.round(this.newCoordinate.x) + ',' + + Math.round(this.newCoordinate.y); + } + if (!this.recordUndo) { + json['recordUndo'] = this.recordUndo; + } + return json; + } + + /** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ + fromJson(json) { + super.fromJson(json); + this.newParentId = json['newParentId']; + this.newInputName = json['newInputName']; + if (json['newCoordinate']) { + const xy = json['newCoordinate'].split(','); + this.newCoordinate = new Coordinate(Number(xy[0]), Number(xy[1])); + } + if (json['recordUndo'] !== undefined) { + this.recordUndo = json['recordUndo']; + } + } + + /** + * Record the block's new location. Called after the move. + */ + recordNew() { + const location = this.currentLocation_(); + this.newParentId = location.parentId; + this.newInputName = location.inputName; + this.newCoordinate = location.coordinate; + } + + /** + * Returns the parentId and input if the block is connected, + * or the XY location if disconnected. + * @return {!Object} Collection of location info. + * @private + */ + currentLocation_() { + const workspace = this.getEventWorkspace_(); + const block = workspace.getBlockById(this.blockId); + const location = {}; + const parent = block.getParent(); + if (parent) { + location.parentId = parent.id; + const input = parent.getInputWithBlock(block); + if (input) { + location.inputName = input.name; + } + } else { + location.coordinate = block.getRelativeToSurfaceXY(); + } + return location; + } + + /** + * Does this event record any change of state? + * @return {boolean} False if something changed. + */ + isNull() { + return this.oldParentId === this.newParentId && + this.oldInputName === this.newInputName && + Coordinate.equals(this.oldCoordinate, this.newCoordinate); + } + + /** + * Run a move event. + * @param {boolean} forward True if run forward, false if run backward (undo). + */ + run(forward) { + const workspace = this.getEventWorkspace_(); + const block = workspace.getBlockById(this.blockId); + if (!block) { + console.warn('Can\'t move non-existent block: ' + this.blockId); return; } - } - if (block.getParent()) { - block.unplug(); - } - if (coordinate) { - const xy = block.getRelativeToSurfaceXY(); - block.moveBy(coordinate.x - xy.x, coordinate.y - xy.y); - } else { - let blockConnection = block.outputConnection; - if (!blockConnection || - (block.previousConnection && block.previousConnection.isConnected())) { - blockConnection = block.previousConnection; - } - let parentConnection; - const connectionType = blockConnection.type; - if (inputName) { - const input = parentBlock.getInput(inputName); - if (input) { - parentConnection = input.connection; + const parentId = forward ? this.newParentId : this.oldParentId; + const inputName = forward ? this.newInputName : this.oldInputName; + const coordinate = forward ? this.newCoordinate : this.oldCoordinate; + let parentBlock; + if (parentId) { + parentBlock = workspace.getBlockById(parentId); + if (!parentBlock) { + console.warn('Can\'t connect to non-existent block: ' + parentId); + return; } - } else if (connectionType === ConnectionType.PREVIOUS_STATEMENT) { - parentConnection = parentBlock.nextConnection; } - if (parentConnection) { - blockConnection.connect(parentConnection); + if (block.getParent()) { + block.unplug(); + } + if (coordinate) { + const xy = block.getRelativeToSurfaceXY(); + block.moveBy(coordinate.x - xy.x, coordinate.y - xy.y); } else { - console.warn('Can\'t connect to non-existent input: ' + inputName); + let blockConnection = block.outputConnection; + if (!blockConnection || + (block.previousConnection && + block.previousConnection.isConnected())) { + blockConnection = block.previousConnection; + } + let parentConnection; + const connectionType = blockConnection.type; + if (inputName) { + const input = parentBlock.getInput(inputName); + if (input) { + parentConnection = input.connection; + } + } else if (connectionType === ConnectionType.PREVIOUS_STATEMENT) { + parentConnection = parentBlock.nextConnection; + } + if (parentConnection) { + blockConnection.connect(parentConnection); + } else { + console.warn('Can\'t connect to non-existent input: ' + inputName); + } } } -}; +} registry.register(registry.Type.EVENT, eventUtils.MOVE, BlockMove); diff --git a/core/events/events_bubble_open.js b/core/events/events_bubble_open.js index 9c76fd751..93d068790 100644 --- a/core/events/events_bubble_open.js +++ b/core/events/events_bubble_open.js @@ -16,7 +16,6 @@ goog.module('Blockly.Events.BubbleOpen'); const eventUtils = goog.require('Blockly.Events.utils'); -const object = goog.require('Blockly.utils.object'); const registry = goog.require('Blockly.registry'); /* eslint-disable-next-line no-unused-vars */ const {BlockSvg} = goog.requireType('Blockly.BlockSvg'); @@ -25,64 +24,66 @@ const {UiBase} = goog.require('Blockly.Events.UiBase'); /** * Class for a bubble open event. - * @param {BlockSvg} opt_block The associated block. Undefined for a - * blank event. - * @param {boolean=} opt_isOpen Whether the bubble is opening (false if - * closing). Undefined for a blank event. - * @param {string=} opt_bubbleType The type of bubble. One of 'mutator', - * 'comment' - * or 'warning'. Undefined for a blank event. * @extends {UiBase} - * @constructor * @alias Blockly.Events.BubbleOpen */ -const BubbleOpen = function(opt_block, opt_isOpen, opt_bubbleType) { - const workspaceId = opt_block ? opt_block.workspace.id : undefined; - BubbleOpen.superClass_.constructor.call(this, workspaceId); - this.blockId = opt_block ? opt_block.id : null; +class BubbleOpen extends UiBase { + /** + * @param {BlockSvg} opt_block The associated block. Undefined for a + * blank event. + * @param {boolean=} opt_isOpen Whether the bubble is opening (false if + * closing). Undefined for a blank event. + * @param {string=} opt_bubbleType The type of bubble. One of 'mutator', + * 'comment' + * or 'warning'. Undefined for a blank event. + */ + constructor(opt_block, opt_isOpen, opt_bubbleType) { + const workspaceId = opt_block ? opt_block.workspace.id : undefined; + super(workspaceId); + this.blockId = opt_block ? opt_block.id : null; + + /** + * Whether the bubble is opening (false if closing). + * @type {boolean|undefined} + */ + this.isOpen = opt_isOpen; + + /** + * The type of bubble. One of 'mutator', 'comment', or 'warning'. + * @type {string|undefined} + */ + this.bubbleType = opt_bubbleType; + + /** + * Type of this event. + * @type {string} + */ + this.type = eventUtils.BUBBLE_OPEN; + } /** - * Whether the bubble is opening (false if closing). - * @type {boolean|undefined} + * Encode the event as JSON. + * @return {!Object} JSON representation. */ - this.isOpen = opt_isOpen; + toJson() { + const json = super.toJson(); + json['isOpen'] = this.isOpen; + json['bubbleType'] = this.bubbleType; + json['blockId'] = this.blockId; + return json; + } /** - * The type of bubble. One of 'mutator', 'comment', or 'warning'. - * @type {string|undefined} + * Decode the JSON event. + * @param {!Object} json JSON representation. */ - this.bubbleType = opt_bubbleType; -}; -object.inherits(BubbleOpen, UiBase); - -/** - * Type of this event. - * @type {string} - */ -BubbleOpen.prototype.type = eventUtils.BUBBLE_OPEN; - -/** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ -BubbleOpen.prototype.toJson = function() { - const json = BubbleOpen.superClass_.toJson.call(this); - json['isOpen'] = this.isOpen; - json['bubbleType'] = this.bubbleType; - json['blockId'] = this.blockId; - return json; -}; - -/** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ -BubbleOpen.prototype.fromJson = function(json) { - BubbleOpen.superClass_.fromJson.call(this, json); - this.isOpen = json['isOpen']; - this.bubbleType = json['bubbleType']; - this.blockId = json['blockId']; -}; + fromJson(json) { + super.fromJson(json); + this.isOpen = json['isOpen']; + this.bubbleType = json['bubbleType']; + this.blockId = json['blockId']; + } +} registry.register(registry.Type.EVENT, eventUtils.BUBBLE_OPEN, BubbleOpen); diff --git a/core/events/events_click.js b/core/events/events_click.js index 0bc9aa972..f61d83a4c 100644 --- a/core/events/events_click.js +++ b/core/events/events_click.js @@ -16,7 +16,6 @@ goog.module('Blockly.Events.Click'); const eventUtils = goog.require('Blockly.Events.utils'); -const object = goog.require('Blockly.utils.object'); const registry = goog.require('Blockly.registry'); /* eslint-disable-next-line no-unused-vars */ const {Block} = goog.requireType('Blockly.Block'); @@ -25,58 +24,63 @@ const {UiBase} = goog.require('Blockly.Events.UiBase'); /** * Class for a click event. - * @param {?Block=} opt_block The affected block. Null for click events - * that do not have an associated block (i.e. workspace click). Undefined - * for a blank event. - * @param {?string=} opt_workspaceId The workspace identifier for this event. - * Not used if block is passed. Undefined for a blank event. - * @param {string=} opt_targetType The type of element targeted by this click - * event. Undefined for a blank event. * @extends {UiBase} - * @constructor * @alias Blockly.Events.Click */ -const Click = function(opt_block, opt_workspaceId, opt_targetType) { - const workspaceId = opt_block ? opt_block.workspace.id : opt_workspaceId; - Click.superClass_.constructor.call(this, workspaceId); - this.blockId = opt_block ? opt_block.id : null; +class Click extends UiBase { + /** + * @param {?Block=} opt_block The affected block. Null for click events + * that do not have an associated block (i.e. workspace click). Undefined + * for a blank event. + * @param {?string=} opt_workspaceId The workspace identifier for this event. + * Not used if block is passed. Undefined for a blank event. + * @param {string=} opt_targetType The type of element targeted by this click + * event. Undefined for a blank event. + */ + constructor(opt_block, opt_workspaceId, opt_targetType) { + let workspaceId = opt_block ? opt_block.workspace.id : opt_workspaceId; + if (workspaceId === null) { + workspaceId = undefined; + } + super(workspaceId); + this.blockId = opt_block ? opt_block.id : null; + + /** + * The type of element targeted by this click event. + * @type {string|undefined} + */ + this.targetType = opt_targetType; + + /** + * Type of this event. + * @type {string} + */ + this.type = eventUtils.CLICK; + } /** - * The type of element targeted by this click event. - * @type {string|undefined} + * Encode the event as JSON. + * @return {!Object} JSON representation. */ - this.targetType = opt_targetType; -}; -object.inherits(Click, UiBase); - -/** - * Type of this event. - * @type {string} - */ -Click.prototype.type = eventUtils.CLICK; - -/** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ -Click.prototype.toJson = function() { - const json = Click.superClass_.toJson.call(this); - json['targetType'] = this.targetType; - if (this.blockId) { - json['blockId'] = this.blockId; + toJson() { + const json = super.toJson(); + json['targetType'] = this.targetType; + if (this.blockId) { + json['blockId'] = this.blockId; + } + return json; } - return json; -}; -/** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ -Click.prototype.fromJson = function(json) { - Click.superClass_.fromJson.call(this, json); - this.targetType = json['targetType']; - this.blockId = json['blockId']; -}; + /** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ + fromJson(json) { + super.fromJson(json); + this.targetType = json['targetType']; + this.blockId = json['blockId']; + } +} registry.register(registry.Type.EVENT, eventUtils.CLICK, Click); diff --git a/core/events/events_comment_base.js b/core/events/events_comment_base.js index 63de88dfe..5e1a65b99 100644 --- a/core/events/events_comment_base.js +++ b/core/events/events_comment_base.js @@ -15,11 +15,10 @@ */ goog.module('Blockly.Events.CommentBase'); -const AbstractEvents = goog.require('Blockly.Events.Abstract'); const Xml = goog.require('Blockly.Xml'); const eventUtils = goog.require('Blockly.Events.utils'); -const object = goog.require('Blockly.utils.object'); const utilsXml = goog.require('Blockly.utils.xml'); +const {Abstract: AbstractEvent} = goog.require('Blockly.Events.Abstract'); /* eslint-disable-next-line no-unused-vars */ const {CommentCreate} = goog.requireType('Blockly.Events.CommentCreate'); /* eslint-disable-next-line no-unused-vars */ @@ -30,89 +29,93 @@ const {WorkspaceComment} = goog.requireType('Blockly.WorkspaceComment'); /** * Abstract class for a comment event. - * @param {!WorkspaceComment=} opt_comment The comment this event - * corresponds to. Undefined for a blank event. - * @extends {AbstractEvents} - * @constructor + * @extends {AbstractEvent} * @alias Blockly.Events.CommentBase */ -const CommentBase = function(opt_comment) { +class CommentBase extends AbstractEvent { /** - * Whether or not an event is blank. - * @type {boolean} + * @param {!WorkspaceComment=} opt_comment The comment this event + * corresponds to. Undefined for a blank event. */ - this.isBlank = typeof opt_comment === 'undefined'; + constructor(opt_comment) { + super(); + /** + * Whether or not an event is blank. + * @type {boolean} + */ + this.isBlank = typeof opt_comment === 'undefined'; - /** - * The ID of the comment this event pertains to. - * @type {string} - */ - this.commentId = this.isBlank ? '' : opt_comment.id; + /** + * The ID of the comment this event pertains to. + * @type {string} + */ + this.commentId = this.isBlank ? '' : opt_comment.id; - /** - * The workspace identifier for this event. - * @type {string} - */ - this.workspaceId = this.isBlank ? '' : opt_comment.workspace.id; + /** + * The workspace identifier for this event. + * @type {string} + */ + this.workspaceId = this.isBlank ? '' : opt_comment.workspace.id; - /** - * The event group id for the group this event belongs to. Groups define - * events that should be treated as an single action from the user's - * perspective, and should be undone together. - * @type {string} - */ - this.group = eventUtils.getGroup(); + /** + * The event group id for the group this event belongs to. Groups define + * events that should be treated as an single action from the user's + * perspective, and should be undone together. + * @type {string} + */ + this.group = eventUtils.getGroup(); - /** - * Sets whether the event should be added to the undo stack. - * @type {boolean} - */ - this.recordUndo = eventUtils.getRecordUndo(); -}; -object.inherits(CommentBase, AbstractEvents); - -/** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ -CommentBase.prototype.toJson = function() { - const json = CommentBase.superClass_.toJson.call(this); - if (this.commentId) { - json['commentId'] = this.commentId; + /** + * Sets whether the event should be added to the undo stack. + * @type {boolean} + */ + this.recordUndo = eventUtils.getRecordUndo(); } - return json; -}; -/** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ -CommentBase.prototype.fromJson = function(json) { - CommentBase.superClass_.fromJson.call(this, json); - this.commentId = json['commentId']; -}; + /** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ + toJson() { + const json = super.toJson(); + if (this.commentId) { + json['commentId'] = this.commentId; + } + return json; + } -/** - * Helper function for Comment[Create|Delete] - * @param {!CommentCreate|!CommentDelete} event - * The event to run. - * @param {boolean} create if True then Create, if False then Delete - */ -CommentBase.CommentCreateDeleteHelper = function(event, create) { - const workspace = event.getEventWorkspace_(); - if (create) { - const xmlElement = utilsXml.createElement('xml'); - xmlElement.appendChild(event.xml); - Xml.domToWorkspace(xmlElement, workspace); - } else { - const comment = workspace.getCommentById(event.commentId); - if (comment) { - comment.dispose(); + /** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ + fromJson(json) { + super.fromJson(json); + this.commentId = json['commentId']; + } + + /** + * Helper function for Comment[Create|Delete] + * @param {!CommentCreate|!CommentDelete} event + * The event to run. + * @param {boolean} create if True then Create, if False then Delete + */ + static CommentCreateDeleteHelper(event, create) { + const workspace = event.getEventWorkspace_(); + if (create) { + const xmlElement = utilsXml.createElement('xml'); + xmlElement.appendChild(event.xml); + Xml.domToWorkspace(xmlElement, workspace); } else { - // Only complain about root-level block. - console.warn('Can\'t uncreate non-existent comment: ' + event.commentId); + const comment = workspace.getCommentById(event.commentId); + if (comment) { + comment.dispose(); + } else { + // Only complain about root-level block. + console.warn( + 'Can\'t uncreate non-existent comment: ' + event.commentId); + } } } -}; +} exports.CommentBase = CommentBase; diff --git a/core/events/events_comment_change.js b/core/events/events_comment_change.js index 1f6b44ba3..1053ad636 100644 --- a/core/events/events_comment_change.js +++ b/core/events/events_comment_change.js @@ -16,7 +16,6 @@ goog.module('Blockly.Events.CommentChange'); const eventUtils = goog.require('Blockly.Events.utils'); -const object = goog.require('Blockly.utils.object'); const registry = goog.require('Blockly.registry'); const {CommentBase} = goog.require('Blockly.Events.CommentBase'); /* eslint-disable-next-line no-unused-vars */ @@ -25,77 +24,80 @@ const {WorkspaceComment} = goog.requireType('Blockly.WorkspaceComment'); /** * Class for a comment change event. - * @param {!WorkspaceComment=} opt_comment The comment that is being - * changed. Undefined for a blank event. - * @param {string=} opt_oldContents Previous contents of the comment. - * @param {string=} opt_newContents New contents of the comment. * @extends {CommentBase} - * @constructor * @alias Blockly.Events.CommentChange */ -const CommentChange = function(opt_comment, opt_oldContents, opt_newContents) { - CommentChange.superClass_.constructor.call(this, opt_comment); - if (!opt_comment) { - return; // Blank event to be populated by fromJson. +class CommentChange extends CommentBase { + /** + * @param {!WorkspaceComment=} opt_comment The comment that is being + * changed. Undefined for a blank event. + * @param {string=} opt_oldContents Previous contents of the comment. + * @param {string=} opt_newContents New contents of the comment. + */ + constructor(opt_comment, opt_oldContents, opt_newContents) { + super(opt_comment); + + /** + * Type of this event. + * @type {string} + */ + this.type = eventUtils.COMMENT_CHANGE; + + if (!opt_comment) { + return; // Blank event to be populated by fromJson. + } + + this.oldContents_ = + typeof opt_oldContents === 'undefined' ? '' : opt_oldContents; + this.newContents_ = + typeof opt_newContents === 'undefined' ? '' : opt_newContents; } - this.oldContents_ = - typeof opt_oldContents === 'undefined' ? '' : opt_oldContents; - this.newContents_ = - typeof opt_newContents === 'undefined' ? '' : opt_newContents; -}; -object.inherits(CommentChange, CommentBase); - -/** - * Type of this event. - * @type {string} - */ -CommentChange.prototype.type = eventUtils.COMMENT_CHANGE; - -/** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ -CommentChange.prototype.toJson = function() { - const json = CommentChange.superClass_.toJson.call(this); - json['oldContents'] = this.oldContents_; - json['newContents'] = this.newContents_; - return json; -}; - -/** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ -CommentChange.prototype.fromJson = function(json) { - CommentChange.superClass_.fromJson.call(this, json); - this.oldContents_ = json['oldContents']; - this.newContents_ = json['newContents']; -}; - -/** - * Does this event record any change of state? - * @return {boolean} False if something changed. - */ -CommentChange.prototype.isNull = function() { - return this.oldContents_ === this.newContents_; -}; - -/** - * Run a change event. - * @param {boolean} forward True if run forward, false if run backward (undo). - */ -CommentChange.prototype.run = function(forward) { - const workspace = this.getEventWorkspace_(); - const comment = workspace.getCommentById(this.commentId); - if (!comment) { - console.warn('Can\'t change non-existent comment: ' + this.commentId); - return; + /** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ + toJson() { + const json = super.toJson(); + json['oldContents'] = this.oldContents_; + json['newContents'] = this.newContents_; + return json; } - const contents = forward ? this.newContents_ : this.oldContents_; - comment.setContent(contents); -}; + /** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ + fromJson(json) { + super.fromJson(json); + this.oldContents_ = json['oldContents']; + this.newContents_ = json['newContents']; + } + + /** + * Does this event record any change of state? + * @return {boolean} False if something changed. + */ + isNull() { + return this.oldContents_ === this.newContents_; + } + + /** + * Run a change event. + * @param {boolean} forward True if run forward, false if run backward (undo). + */ + run(forward) { + const workspace = this.getEventWorkspace_(); + const comment = workspace.getCommentById(this.commentId); + if (!comment) { + console.warn('Can\'t change non-existent comment: ' + this.commentId); + return; + } + const contents = forward ? this.newContents_ : this.oldContents_; + + comment.setContent(contents); + } +} registry.register( registry.Type.EVENT, eventUtils.COMMENT_CHANGE, CommentChange); diff --git a/core/events/events_comment_create.js b/core/events/events_comment_create.js index 5d9cb8d79..86c72f6d6 100644 --- a/core/events/events_comment_create.js +++ b/core/events/events_comment_create.js @@ -17,7 +17,6 @@ goog.module('Blockly.Events.CommentCreate'); const Xml = goog.require('Blockly.Xml'); const eventUtils = goog.require('Blockly.Events.utils'); -const object = goog.require('Blockly.utils.object'); const registry = goog.require('Blockly.registry'); const {CommentBase} = goog.require('Blockly.Events.CommentBase'); /* eslint-disable-next-line no-unused-vars */ @@ -26,55 +25,58 @@ const {WorkspaceComment} = goog.requireType('Blockly.WorkspaceComment'); /** * Class for a comment creation event. - * @param {!WorkspaceComment=} opt_comment The created comment. - * Undefined for a blank event. * @extends {CommentBase} - * @constructor * @alias Blockly.Events.CommentCreate */ -const CommentCreate = function(opt_comment) { - CommentCreate.superClass_.constructor.call(this, opt_comment); - if (!opt_comment) { - return; // Blank event to be populated by fromJson. +class CommentCreate extends CommentBase { + /** + * @param {!WorkspaceComment=} opt_comment The created comment. + * Undefined for a blank event. + */ + constructor(opt_comment) { + super(opt_comment); + + /** + * Type of this event. + * @type {string} + */ + this.type = eventUtils.COMMENT_CREATE; + + if (!opt_comment) { + return; // Blank event to be populated by fromJson. + } + + this.xml = opt_comment.toXmlWithXY(); } - this.xml = opt_comment.toXmlWithXY(); -}; -object.inherits(CommentCreate, CommentBase); + // TODO (#1266): "Full" and "minimal" serialization. + /** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ + toJson() { + const json = super.toJson(); + json['xml'] = Xml.domToText(this.xml); + return json; + } -/** - * Type of this event. - * @type {string} - */ -CommentCreate.prototype.type = eventUtils.COMMENT_CREATE; + /** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ + fromJson(json) { + super.fromJson(json); + this.xml = Xml.textToDom(json['xml']); + } -/** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ -// TODO (#1266): "Full" and "minimal" serialization. -CommentCreate.prototype.toJson = function() { - const json = CommentCreate.superClass_.toJson.call(this); - json['xml'] = Xml.domToText(this.xml); - return json; -}; - -/** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ -CommentCreate.prototype.fromJson = function(json) { - CommentCreate.superClass_.fromJson.call(this, json); - this.xml = Xml.textToDom(json['xml']); -}; - -/** - * Run a creation event. - * @param {boolean} forward True if run forward, false if run backward (undo). - */ -CommentCreate.prototype.run = function(forward) { - CommentBase.CommentCreateDeleteHelper(this, forward); -}; + /** + * Run a creation event. + * @param {boolean} forward True if run forward, false if run backward (undo). + */ + run(forward) { + CommentBase.CommentCreateDeleteHelper(this, forward); + } +} registry.register( registry.Type.EVENT, eventUtils.COMMENT_CREATE, CommentCreate); diff --git a/core/events/events_comment_delete.js b/core/events/events_comment_delete.js index 54e1df4ba..795e919ff 100644 --- a/core/events/events_comment_delete.js +++ b/core/events/events_comment_delete.js @@ -16,7 +16,6 @@ goog.module('Blockly.Events.CommentDelete'); const eventUtils = goog.require('Blockly.Events.utils'); -const object = goog.require('Blockly.utils.object'); const registry = goog.require('Blockly.registry'); const {CommentBase} = goog.require('Blockly.Events.CommentBase'); /* eslint-disable-next-line no-unused-vars */ @@ -25,53 +24,56 @@ const {WorkspaceComment} = goog.requireType('Blockly.WorkspaceComment'); /** * Class for a comment deletion event. - * @param {!WorkspaceComment=} opt_comment The deleted comment. - * Undefined for a blank event. * @extends {CommentBase} - * @constructor * @alias Blockly.Events.CommentDelete */ -const CommentDelete = function(opt_comment) { - CommentDelete.superClass_.constructor.call(this, opt_comment); - if (!opt_comment) { - return; // Blank event to be populated by fromJson. +class CommentDelete extends CommentBase { + /** + * @param {!WorkspaceComment=} opt_comment The deleted comment. + * Undefined for a blank event. + */ + constructor(opt_comment) { + super(opt_comment); + + /** + * Type of this event. + * @type {string} + */ + this.type = eventUtils.COMMENT_DELETE; + + if (!opt_comment) { + return; // Blank event to be populated by fromJson. + } + + this.xml = opt_comment.toXmlWithXY(); } - this.xml = opt_comment.toXmlWithXY(); -}; -object.inherits(CommentDelete, CommentBase); + // TODO (#1266): "Full" and "minimal" serialization. + /** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ + toJson() { + const json = super.toJson(); + return json; + } -/** - * Type of this event. - * @type {string} - */ -CommentDelete.prototype.type = eventUtils.COMMENT_DELETE; + /** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ + fromJson(json) { + super.fromJson(json); + } -/** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ -// TODO (#1266): "Full" and "minimal" serialization. -CommentDelete.prototype.toJson = function() { - const json = CommentDelete.superClass_.toJson.call(this); - return json; -}; - -/** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ -CommentDelete.prototype.fromJson = function(json) { - CommentDelete.superClass_.fromJson.call(this, json); -}; - -/** - * Run a creation event. - * @param {boolean} forward True if run forward, false if run backward (undo). - */ -CommentDelete.prototype.run = function(forward) { - CommentBase.CommentCreateDeleteHelper(this, !forward); -}; + /** + * Run a creation event. + * @param {boolean} forward True if run forward, false if run backward (undo). + */ + run(forward) { + CommentBase.CommentCreateDeleteHelper(this, !forward); + } +} registry.register( registry.Type.EVENT, eventUtils.COMMENT_DELETE, CommentDelete); diff --git a/core/events/events_comment_move.js b/core/events/events_comment_move.js index 51ba01ab6..cade6586a 100644 --- a/core/events/events_comment_move.js +++ b/core/events/events_comment_move.js @@ -16,7 +16,6 @@ goog.module('Blockly.Events.CommentMove'); const eventUtils = goog.require('Blockly.Events.utils'); -const object = goog.require('Blockly.utils.object'); const registry = goog.require('Blockly.registry'); const {CommentBase} = goog.require('Blockly.Events.CommentBase'); const {Coordinate} = goog.require('Blockly.utils.Coordinate'); @@ -26,129 +25,132 @@ const {WorkspaceComment} = goog.requireType('Blockly.WorkspaceComment'); /** * Class for a comment move event. Created before the move. - * @param {!WorkspaceComment=} opt_comment The comment that is being - * moved. Undefined for a blank event. * @extends {CommentBase} - * @constructor * @alias Blockly.Events.CommentMove */ -const CommentMove = function(opt_comment) { - CommentMove.superClass_.constructor.call(this, opt_comment); - if (!opt_comment) { - return; // Blank event to be populated by fromJson. +class CommentMove extends CommentBase { + /** + * @param {!WorkspaceComment=} opt_comment The comment that is being + * moved. Undefined for a blank event. + */ + constructor(opt_comment) { + super(opt_comment); + + /** + * Type of this event. + * @type {string} + */ + this.type = eventUtils.COMMENT_MOVE; + + if (!opt_comment) { + return; // Blank event to be populated by fromJson. + } + + /** + * The comment that is being moved. Will be cleared after recording the new + * location. + * @type {WorkspaceComment} + */ + this.comment_ = opt_comment; + + /** + * The location before the move, in workspace coordinates. + * @type {!Coordinate} + */ + this.oldCoordinate_ = opt_comment.getXY(); + + /** + * The location after the move, in workspace coordinates. + * @type {Coordinate} + */ + this.newCoordinate_ = null; } /** - * The comment that is being moved. Will be cleared after recording the new - * location. - * @type {WorkspaceComment} + * Record the comment's new location. Called after the move. Can only be + * called once. */ - this.comment_ = opt_comment; + recordNew() { + if (!this.comment_) { + throw Error( + 'Tried to record the new position of a comment on the ' + + 'same event twice.'); + } + this.newCoordinate_ = this.comment_.getXY(); + this.comment_ = null; + } /** - * The location before the move, in workspace coordinates. - * @type {!Coordinate} + * Override the location before the move. Use this if you don't create the + * event until the end of the move, but you know the original location. + * @param {!Coordinate} xy The location before the move, + * in workspace coordinates. */ - this.oldCoordinate_ = opt_comment.getXY(); + setOldCoordinate(xy) { + this.oldCoordinate_ = xy; + } + + // TODO (#1266): "Full" and "minimal" serialization. + /** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ + toJson() { + const json = super.toJson(); + if (this.oldCoordinate_) { + json['oldCoordinate'] = Math.round(this.oldCoordinate_.x) + ',' + + Math.round(this.oldCoordinate_.y); + } + if (this.newCoordinate_) { + json['newCoordinate'] = Math.round(this.newCoordinate_.x) + ',' + + Math.round(this.newCoordinate_.y); + } + return json; + } /** - * The location after the move, in workspace coordinates. - * @type {Coordinate} + * Decode the JSON event. + * @param {!Object} json JSON representation. */ - this.newCoordinate_ = null; -}; -object.inherits(CommentMove, CommentBase); + fromJson(json) { + super.fromJson(json); -/** - * Record the comment's new location. Called after the move. Can only be - * called once. - */ -CommentMove.prototype.recordNew = function() { - if (!this.comment_) { - throw Error( - 'Tried to record the new position of a comment on the ' + - 'same event twice.'); - } - this.newCoordinate_ = this.comment_.getXY(); - this.comment_ = null; -}; - -/** - * Type of this event. - * @type {string} - */ -CommentMove.prototype.type = eventUtils.COMMENT_MOVE; - -/** - * Override the location before the move. Use this if you don't create the - * event until the end of the move, but you know the original location. - * @param {!Coordinate} xy The location before the move, - * in workspace coordinates. - */ -CommentMove.prototype.setOldCoordinate = function(xy) { - this.oldCoordinate_ = xy; -}; - -/** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ -// TODO (#1266): "Full" and "minimal" serialization. -CommentMove.prototype.toJson = function() { - const json = CommentMove.superClass_.toJson.call(this); - if (this.oldCoordinate_) { - json['oldCoordinate'] = Math.round(this.oldCoordinate_.x) + ',' + - Math.round(this.oldCoordinate_.y); - } - if (this.newCoordinate_) { - json['newCoordinate'] = Math.round(this.newCoordinate_.x) + ',' + - Math.round(this.newCoordinate_.y); - } - return json; -}; - -/** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ -CommentMove.prototype.fromJson = function(json) { - CommentMove.superClass_.fromJson.call(this, json); - - if (json['oldCoordinate']) { - const xy = json['oldCoordinate'].split(','); - this.oldCoordinate_ = new Coordinate(Number(xy[0]), Number(xy[1])); - } - if (json['newCoordinate']) { - const xy = json['newCoordinate'].split(','); - this.newCoordinate_ = new Coordinate(Number(xy[0]), Number(xy[1])); - } -}; - -/** - * Does this event record any change of state? - * @return {boolean} False if something changed. - */ -CommentMove.prototype.isNull = function() { - return Coordinate.equals(this.oldCoordinate_, this.newCoordinate_); -}; - -/** - * Run a move event. - * @param {boolean} forward True if run forward, false if run backward (undo). - */ -CommentMove.prototype.run = function(forward) { - const workspace = this.getEventWorkspace_(); - const comment = workspace.getCommentById(this.commentId); - if (!comment) { - console.warn('Can\'t move non-existent comment: ' + this.commentId); - return; + if (json['oldCoordinate']) { + const xy = json['oldCoordinate'].split(','); + this.oldCoordinate_ = new Coordinate(Number(xy[0]), Number(xy[1])); + } + if (json['newCoordinate']) { + const xy = json['newCoordinate'].split(','); + this.newCoordinate_ = new Coordinate(Number(xy[0]), Number(xy[1])); + } } - const target = forward ? this.newCoordinate_ : this.oldCoordinate_; - // TODO: Check if the comment is being dragged, and give up if so. - const current = comment.getXY(); - comment.moveBy(target.x - current.x, target.y - current.y); -}; + /** + * Does this event record any change of state? + * @return {boolean} False if something changed. + */ + isNull() { + return Coordinate.equals(this.oldCoordinate_, this.newCoordinate_); + } + + /** + * Run a move event. + * @param {boolean} forward True if run forward, false if run backward (undo). + */ + run(forward) { + const workspace = this.getEventWorkspace_(); + const comment = workspace.getCommentById(this.commentId); + if (!comment) { + console.warn('Can\'t move non-existent comment: ' + this.commentId); + return; + } + + const target = forward ? this.newCoordinate_ : this.oldCoordinate_; + // TODO: Check if the comment is being dragged, and give up if so. + const current = comment.getXY(); + comment.moveBy(target.x - current.x, target.y - current.y); + } +} registry.register(registry.Type.EVENT, eventUtils.COMMENT_MOVE, CommentMove); diff --git a/core/events/events_marker_move.js b/core/events/events_marker_move.js index 97f473d95..8961919fc 100644 --- a/core/events/events_marker_move.js +++ b/core/events/events_marker_move.js @@ -16,7 +16,6 @@ goog.module('Blockly.Events.MarkerMove'); const eventUtils = goog.require('Blockly.Events.utils'); -const object = goog.require('Blockly.utils.object'); const registry = goog.require('Blockly.registry'); const {ASTNode} = goog.require('Blockly.ASTNode'); /* eslint-disable-next-line no-unused-vars */ @@ -28,81 +27,83 @@ const {Workspace} = goog.requireType('Blockly.Workspace'); /** * Class for a marker move event. - * @param {?Block=} opt_block The affected block. Null if current node - * is of type workspace. Undefined for a blank event. - * @param {boolean=} isCursor Whether this is a cursor event. Undefined for a - * blank event. - * @param {?ASTNode=} opt_oldNode The old node the marker used to be on. - * Undefined for a blank event. - * @param {!ASTNode=} opt_newNode The new node the marker is now on. - * Undefined for a blank event. * @extends {UiBase} - * @constructor * @alias Blockly.Events.MarkerMove */ -const MarkerMove = function(opt_block, isCursor, opt_oldNode, opt_newNode) { - let workspaceId = opt_block ? opt_block.workspace.id : undefined; - if (opt_newNode && opt_newNode.getType() === ASTNode.types.WORKSPACE) { - workspaceId = (/** @type {!Workspace} */ (opt_newNode.getLocation())).id; +class MarkerMove extends UiBase { + /** + * @param {?Block=} opt_block The affected block. Null if current node + * is of type workspace. Undefined for a blank event. + * @param {boolean=} isCursor Whether this is a cursor event. Undefined for a + * blank event. + * @param {?ASTNode=} opt_oldNode The old node the marker used to be on. + * Undefined for a blank event. + * @param {!ASTNode=} opt_newNode The new node the marker is now on. + * Undefined for a blank event. + */ + constructor(opt_block, isCursor, opt_oldNode, opt_newNode) { + let workspaceId = opt_block ? opt_block.workspace.id : undefined; + if (opt_newNode && opt_newNode.getType() === ASTNode.types.WORKSPACE) { + workspaceId = (/** @type {!Workspace} */ (opt_newNode.getLocation())).id; + } + super(workspaceId); + + /** + * The workspace identifier for this event. + * @type {?string} + */ + this.blockId = opt_block ? opt_block.id : null; + + /** + * The old node the marker used to be on. + * @type {?ASTNode|undefined} + */ + this.oldNode = opt_oldNode; + + /** + * The new node the marker is now on. + * @type {ASTNode|undefined} + */ + this.newNode = opt_newNode; + + /** + * Whether this is a cursor event. + * @type {boolean|undefined} + */ + this.isCursor = isCursor; + + /** + * Type of this event. + * @type {string} + */ + this.type = eventUtils.MARKER_MOVE; } - MarkerMove.superClass_.constructor.call(this, workspaceId); /** - * The workspace identifier for this event. - * @type {?string} + * Encode the event as JSON. + * @return {!Object} JSON representation. */ - this.blockId = opt_block ? opt_block.id : null; + toJson() { + const json = super.toJson(); + json['isCursor'] = this.isCursor; + json['blockId'] = this.blockId; + json['oldNode'] = this.oldNode; + json['newNode'] = this.newNode; + return json; + } /** - * The old node the marker used to be on. - * @type {?ASTNode|undefined} + * Decode the JSON event. + * @param {!Object} json JSON representation. */ - this.oldNode = opt_oldNode; - - /** - * The new node the marker is now on. - * @type {ASTNode|undefined} - */ - this.newNode = opt_newNode; - - /** - * Whether this is a cursor event. - * @type {boolean|undefined} - */ - this.isCursor = isCursor; -}; -object.inherits(MarkerMove, UiBase); - -/** - * Type of this event. - * @type {string} - */ -MarkerMove.prototype.type = eventUtils.MARKER_MOVE; - -/** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ -MarkerMove.prototype.toJson = function() { - const json = MarkerMove.superClass_.toJson.call(this); - json['isCursor'] = this.isCursor; - json['blockId'] = this.blockId; - json['oldNode'] = this.oldNode; - json['newNode'] = this.newNode; - return json; -}; - -/** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ -MarkerMove.prototype.fromJson = function(json) { - MarkerMove.superClass_.fromJson.call(this, json); - this.isCursor = json['isCursor']; - this.blockId = json['blockId']; - this.oldNode = json['oldNode']; - this.newNode = json['newNode']; -}; + fromJson(json) { + super.fromJson(json); + this.isCursor = json['isCursor']; + this.blockId = json['blockId']; + this.oldNode = json['oldNode']; + this.newNode = json['newNode']; + } +} registry.register(registry.Type.EVENT, eventUtils.MARKER_MOVE, MarkerMove); diff --git a/core/events/events_selected.js b/core/events/events_selected.js index 7009a8549..0ec33eb61 100644 --- a/core/events/events_selected.js +++ b/core/events/events_selected.js @@ -16,66 +16,67 @@ goog.module('Blockly.Events.Selected'); const eventUtils = goog.require('Blockly.Events.utils'); -const object = goog.require('Blockly.utils.object'); const registry = goog.require('Blockly.registry'); const {UiBase} = goog.require('Blockly.Events.UiBase'); /** * Class for a selected event. - * @param {?string=} opt_oldElementId The ID of the previously selected - * element. Null if no element last selected. Undefined for a blank event. - * @param {?string=} opt_newElementId The ID of the selected element. Null if no - * element currently selected (deselect). Undefined for a blank event. - * @param {string=} opt_workspaceId The workspace identifier for this event. - * Null if no element previously selected. Undefined for a blank event. * @extends {UiBase} - * @constructor * @alias Blockly.Events.Selected */ -const Selected = function(opt_oldElementId, opt_newElementId, opt_workspaceId) { - Selected.superClass_.constructor.call(this, opt_workspaceId); +class Selected extends UiBase { + /** + * @param {?string=} opt_oldElementId The ID of the previously selected + * element. Null if no element last selected. Undefined for a blank event. + * @param {?string=} opt_newElementId The ID of the selected element. Null if + * no element currently selected (deselect). Undefined for a blank event. + * @param {string=} opt_workspaceId The workspace identifier for this event. + * Null if no element previously selected. Undefined for a blank event. + */ + constructor(opt_oldElementId, opt_newElementId, opt_workspaceId) { + super(opt_workspaceId); + + /** + * The id of the last selected element. + * @type {?string|undefined} + */ + this.oldElementId = opt_oldElementId; + + /** + * The id of the selected element. + * @type {?string|undefined} + */ + this.newElementId = opt_newElementId; + + /** + * Type of this event. + * @type {string} + */ + this.type = eventUtils.SELECTED; + } /** - * The id of the last selected element. - * @type {?string|undefined} + * Encode the event as JSON. + * @return {!Object} JSON representation. */ - this.oldElementId = opt_oldElementId; + toJson() { + const json = super.toJson(); + json['oldElementId'] = this.oldElementId; + json['newElementId'] = this.newElementId; + return json; + } /** - * The id of the selected element. - * @type {?string|undefined} + * Decode the JSON event. + * @param {!Object} json JSON representation. */ - this.newElementId = opt_newElementId; -}; -object.inherits(Selected, UiBase); - -/** - * Type of this event. - * @type {string} - */ -Selected.prototype.type = eventUtils.SELECTED; - -/** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ -Selected.prototype.toJson = function() { - const json = Selected.superClass_.toJson.call(this); - json['oldElementId'] = this.oldElementId; - json['newElementId'] = this.newElementId; - return json; -}; - -/** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ -Selected.prototype.fromJson = function(json) { - Selected.superClass_.fromJson.call(this, json); - this.oldElementId = json['oldElementId']; - this.newElementId = json['newElementId']; -}; + fromJson(json) { + super.fromJson(json); + this.oldElementId = json['oldElementId']; + this.newElementId = json['newElementId']; + } +} registry.register(registry.Type.EVENT, eventUtils.SELECTED, Selected); diff --git a/core/events/events_theme_change.js b/core/events/events_theme_change.js index e1cba44c2..a3c436372 100644 --- a/core/events/events_theme_change.js +++ b/core/events/events_theme_change.js @@ -16,55 +16,56 @@ goog.module('Blockly.Events.ThemeChange'); const eventUtils = goog.require('Blockly.Events.utils'); -const object = goog.require('Blockly.utils.object'); const registry = goog.require('Blockly.registry'); const {UiBase} = goog.require('Blockly.Events.UiBase'); /** * Class for a theme change event. - * @param {string=} opt_themeName The theme name. Undefined for a blank event. - * @param {string=} opt_workspaceId The workspace identifier for this event. - * event. Undefined for a blank event. * @extends {UiBase} - * @constructor * @alias Blockly.Events.ThemeChange */ -const ThemeChange = function(opt_themeName, opt_workspaceId) { - ThemeChange.superClass_.constructor.call(this, opt_workspaceId); +class ThemeChange extends UiBase { + /** + * @param {string=} opt_themeName The theme name. Undefined for a blank event. + * @param {string=} opt_workspaceId The workspace identifier for this event. + * event. Undefined for a blank event. + */ + constructor(opt_themeName, opt_workspaceId) { + super(opt_workspaceId); + + /** + * The theme name. + * @type {string|undefined} + */ + this.themeName = opt_themeName; + + /** + * Type of this event. + * @type {string} + */ + this.type = eventUtils.THEME_CHANGE; + } /** - * The theme name. - * @type {string|undefined} + * Encode the event as JSON. + * @return {!Object} JSON representation. */ - this.themeName = opt_themeName; -}; -object.inherits(ThemeChange, UiBase); + toJson() { + const json = super.toJson(); + json['themeName'] = this.themeName; + return json; + } -/** - * Type of this event. - * @type {string} - */ -ThemeChange.prototype.type = eventUtils.THEME_CHANGE; - -/** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ -ThemeChange.prototype.toJson = function() { - const json = ThemeChange.superClass_.toJson.call(this); - json['themeName'] = this.themeName; - return json; -}; - -/** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ -ThemeChange.prototype.fromJson = function(json) { - ThemeChange.superClass_.fromJson.call(this, json); - this.themeName = json['themeName']; -}; + /** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ + fromJson(json) { + super.fromJson(json); + this.themeName = json['themeName']; + } +} registry.register(registry.Type.EVENT, eventUtils.THEME_CHANGE, ThemeChange); diff --git a/core/events/events_toolbox_item_select.js b/core/events/events_toolbox_item_select.js index f2ba9bf19..202c92ea8 100644 --- a/core/events/events_toolbox_item_select.js +++ b/core/events/events_toolbox_item_select.js @@ -16,66 +16,67 @@ goog.module('Blockly.Events.ToolboxItemSelect'); const eventUtils = goog.require('Blockly.Events.utils'); -const object = goog.require('Blockly.utils.object'); const registry = goog.require('Blockly.registry'); const {UiBase} = goog.require('Blockly.Events.UiBase'); /** * Class for a toolbox item select event. - * @param {?string=} opt_oldItem The previously selected toolbox item. Undefined - * for a blank event. - * @param {?string=} opt_newItem The newly selected toolbox item. Undefined for - * a blank event. - * @param {string=} opt_workspaceId The workspace identifier for this event. - * Undefined for a blank event. * @extends {UiBase} - * @constructor * @alias Blockly.Events.ToolboxItemSelect */ -const ToolboxItemSelect = function(opt_oldItem, opt_newItem, opt_workspaceId) { - ToolboxItemSelect.superClass_.constructor.call(this, opt_workspaceId); +class ToolboxItemSelect extends UiBase { + /** + * @param {?string=} opt_oldItem The previously selected toolbox item. + * Undefined for a blank event. + * @param {?string=} opt_newItem The newly selected toolbox item. Undefined + * for a blank event. + * @param {string=} opt_workspaceId The workspace identifier for this event. + * Undefined for a blank event. + */ + constructor(opt_oldItem, opt_newItem, opt_workspaceId) { + super(opt_workspaceId); + + /** + * The previously selected toolbox item. + * @type {?string|undefined} + */ + this.oldItem = opt_oldItem; + + /** + * The newly selected toolbox item. + * @type {?string|undefined} + */ + this.newItem = opt_newItem; + + /** + * Type of this event. + * @type {string} + */ + this.type = eventUtils.TOOLBOX_ITEM_SELECT; + } /** - * The previously selected toolbox item. - * @type {?string|undefined} + * Encode the event as JSON. + * @return {!Object} JSON representation. */ - this.oldItem = opt_oldItem; + toJson() { + const json = super.toJson(); + json['oldItem'] = this.oldItem; + json['newItem'] = this.newItem; + return json; + } /** - * The newly selected toolbox item. - * @type {?string|undefined} + * Decode the JSON event. + * @param {!Object} json JSON representation. */ - this.newItem = opt_newItem; -}; -object.inherits(ToolboxItemSelect, UiBase); - -/** - * Type of this event. - * @type {string} - */ -ToolboxItemSelect.prototype.type = eventUtils.TOOLBOX_ITEM_SELECT; - -/** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ -ToolboxItemSelect.prototype.toJson = function() { - const json = ToolboxItemSelect.superClass_.toJson.call(this); - json['oldItem'] = this.oldItem; - json['newItem'] = this.newItem; - return json; -}; - -/** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ -ToolboxItemSelect.prototype.fromJson = function(json) { - ToolboxItemSelect.superClass_.fromJson.call(this, json); - this.oldItem = json['oldItem']; - this.newItem = json['newItem']; -}; + fromJson(json) { + super.fromJson(json); + this.oldItem = json['oldItem']; + this.newItem = json['newItem']; + } +} registry.register( registry.Type.EVENT, eventUtils.TOOLBOX_ITEM_SELECT, ToolboxItemSelect); diff --git a/core/events/events_trashcan_open.js b/core/events/events_trashcan_open.js index 21fd0d3b3..8c4d375bf 100644 --- a/core/events/events_trashcan_open.js +++ b/core/events/events_trashcan_open.js @@ -16,56 +16,57 @@ goog.module('Blockly.Events.TrashcanOpen'); const eventUtils = goog.require('Blockly.Events.utils'); -const object = goog.require('Blockly.utils.object'); const registry = goog.require('Blockly.registry'); const {UiBase} = goog.require('Blockly.Events.UiBase'); /** * Class for a trashcan open event. - * @param {boolean=} opt_isOpen Whether the trashcan flyout is opening (false if - * opening). Undefined for a blank event. - * @param {string=} opt_workspaceId The workspace identifier for this event. - * Undefined for a blank event. * @extends {UiBase} - * @constructor * @alias Blockly.Events.TrashcanOpen */ -const TrashcanOpen = function(opt_isOpen, opt_workspaceId) { - TrashcanOpen.superClass_.constructor.call(this, opt_workspaceId); +class TrashcanOpen extends UiBase { + /** + * @param {boolean=} opt_isOpen Whether the trashcan flyout is opening (false + * if opening). Undefined for a blank event. + * @param {string=} opt_workspaceId The workspace identifier for this event. + * Undefined for a blank event. + */ + constructor(opt_isOpen, opt_workspaceId) { + super(opt_workspaceId); + + /** + * Whether the trashcan flyout is opening (false if closing). + * @type {boolean|undefined} + */ + this.isOpen = opt_isOpen; + + /** + * Type of this event. + * @type {string} + */ + this.type = eventUtils.TRASHCAN_OPEN; + } /** - * Whether the trashcan flyout is opening (false if closing). - * @type {boolean|undefined} + * Encode the event as JSON. + * @return {!Object} JSON representation. */ - this.isOpen = opt_isOpen; -}; -object.inherits(TrashcanOpen, UiBase); + toJson() { + const json = super.toJson(); + json['isOpen'] = this.isOpen; + return json; + } -/** - * Type of this event. - * @type {string} - */ -TrashcanOpen.prototype.type = eventUtils.TRASHCAN_OPEN; - -/** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ -TrashcanOpen.prototype.toJson = function() { - const json = TrashcanOpen.superClass_.toJson.call(this); - json['isOpen'] = this.isOpen; - return json; -}; - -/** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ -TrashcanOpen.prototype.fromJson = function(json) { - TrashcanOpen.superClass_.fromJson.call(this, json); - this.isOpen = json['isOpen']; -}; + /** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ + fromJson(json) { + super.fromJson(json); + this.isOpen = json['isOpen']; + } +} registry.register(registry.Type.EVENT, eventUtils.TRASHCAN_OPEN, TrashcanOpen); diff --git a/core/events/events_ui.js b/core/events/events_ui.js index b505d3b20..275655a36 100644 --- a/core/events/events_ui.js +++ b/core/events/events_ui.js @@ -18,7 +18,6 @@ goog.module('Blockly.Events.Ui'); const eventUtils = goog.require('Blockly.Events.utils'); -const object = goog.require('Blockly.utils.object'); const registry = goog.require('Blockly.registry'); /* eslint-disable-next-line no-unused-vars */ const {Block} = goog.requireType('Blockly.Block'); @@ -27,60 +26,62 @@ const {UiBase} = goog.require('Blockly.Events.UiBase'); /** * Class for a UI event. - * @param {?Block=} opt_block The affected block. Null for UI events - * that do not have an associated block. Undefined for a blank event. - * @param {string=} opt_element One of 'selected', 'comment', 'mutatorOpen', - * etc. - * @param {*=} opt_oldValue Previous value of element. - * @param {*=} opt_newValue New value of element. * @extends {UiBase} * @deprecated December 2020. Instead use a more specific UI event. - * @constructor * @alias Blockly.Events.Ui */ -const Ui = function(opt_block, opt_element, opt_oldValue, opt_newValue) { - const workspaceId = opt_block ? opt_block.workspace.id : undefined; - Ui.superClass_.constructor.call(this, workspaceId); +class Ui extends UiBase { + /** + * @param {?Block=} opt_block The affected block. Null for UI events + * that do not have an associated block. Undefined for a blank event. + * @param {string=} opt_element One of 'selected', 'comment', 'mutatorOpen', + * etc. + * @param {*=} opt_oldValue Previous value of element. + * @param {*=} opt_newValue New value of element. + */ + constructor(opt_block, opt_element, opt_oldValue, opt_newValue) { + const workspaceId = opt_block ? opt_block.workspace.id : undefined; + super(workspaceId); - this.blockId = opt_block ? opt_block.id : null; - this.element = typeof opt_element === 'undefined' ? '' : opt_element; - this.oldValue = typeof opt_oldValue === 'undefined' ? '' : opt_oldValue; - this.newValue = typeof opt_newValue === 'undefined' ? '' : opt_newValue; -}; -object.inherits(Ui, UiBase); + this.blockId = opt_block ? opt_block.id : null; + this.element = typeof opt_element === 'undefined' ? '' : opt_element; + this.oldValue = typeof opt_oldValue === 'undefined' ? '' : opt_oldValue; + this.newValue = typeof opt_newValue === 'undefined' ? '' : opt_newValue; -/** - * Type of this event. - * @type {string} - */ -Ui.prototype.type = eventUtils.UI; - -/** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ -Ui.prototype.toJson = function() { - const json = Ui.superClass_.toJson.call(this); - json['element'] = this.element; - if (this.newValue !== undefined) { - json['newValue'] = this.newValue; + /** + * Type of this event. + * @type {string} + */ + this.type = eventUtils.UI; } - if (this.blockId) { - json['blockId'] = this.blockId; - } - return json; -}; -/** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ -Ui.prototype.fromJson = function(json) { - Ui.superClass_.fromJson.call(this, json); - this.element = json['element']; - this.newValue = json['newValue']; - this.blockId = json['blockId']; -}; + /** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ + toJson() { + const json = super.toJson(); + json['element'] = this.element; + if (this.newValue !== undefined) { + json['newValue'] = this.newValue; + } + if (this.blockId) { + json['blockId'] = this.blockId; + } + return json; + } + + /** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ + fromJson(json) { + super.fromJson(json); + this.element = json['element']; + this.newValue = json['newValue']; + this.blockId = json['blockId']; + } +} registry.register(registry.Type.EVENT, eventUtils.UI, Ui); diff --git a/core/events/events_ui_base.js b/core/events/events_ui_base.js index 1618f2876..3df25abcf 100644 --- a/core/events/events_ui_base.js +++ b/core/events/events_ui_base.js @@ -17,8 +17,7 @@ */ goog.module('Blockly.Events.UiBase'); -const Abstract = goog.require('Blockly.Events.Abstract'); -const object = goog.require('Blockly.utils.object'); +const {Abstract: AbstractEvent} = goog.require('Blockly.Events.Abstract'); /** @@ -27,36 +26,38 @@ const object = goog.require('Blockly.utils.object'); * editing to work (e.g. scrolling the workspace, zooming, opening toolbox * categories). * UI events do not undo or redo. - * @param {string=} opt_workspaceId The workspace identifier for this event. - * Undefined for a blank event. - * @extends {Abstract} - * @constructor + * @extends {AbstractEvent} * @alias Blockly.Events.UiBase */ -const UiBase = function(opt_workspaceId) { - UiBase.superClass_.constructor.call(this); - +class UiBase extends AbstractEvent { /** - * Whether or not the event is blank (to be populated by fromJson). - * @type {boolean} + * @param {string=} opt_workspaceId The workspace identifier for this event. + * Undefined for a blank event. */ - this.isBlank = typeof opt_workspaceId === 'undefined'; + constructor(opt_workspaceId) { + super(); - /** - * The workspace identifier for this event. - * @type {string} - */ - this.workspaceId = opt_workspaceId ? opt_workspaceId : ''; + /** + * Whether or not the event is blank (to be populated by fromJson). + * @type {boolean} + */ + this.isBlank = typeof opt_workspaceId === 'undefined'; - // UI events do not undo or redo. - this.recordUndo = false; -}; -object.inherits(UiBase, Abstract); + /** + * The workspace identifier for this event. + * @type {string} + */ + this.workspaceId = opt_workspaceId ? opt_workspaceId : ''; -/** - * Whether or not the event is a UI event. - * @type {boolean} - */ -UiBase.prototype.isUiEvent = true; + // UI events do not undo or redo. + this.recordUndo = false; + + /** + * Whether or not the event is a UI event. + * @type {boolean} + */ + this.isUiEvent = true; + } +} exports.UiBase = UiBase; diff --git a/core/events/events_var_base.js b/core/events/events_var_base.js index fd919a061..35d792df1 100644 --- a/core/events/events_var_base.js +++ b/core/events/events_var_base.js @@ -15,55 +15,56 @@ */ goog.module('Blockly.Events.VarBase'); -const Abstract = goog.require('Blockly.Events.Abstract'); -const object = goog.require('Blockly.utils.object'); +const {Abstract: AbstractEvent} = goog.require('Blockly.Events.Abstract'); /* eslint-disable-next-line no-unused-vars */ const {VariableModel} = goog.requireType('Blockly.VariableModel'); /** * Abstract class for a variable event. - * @param {!VariableModel=} opt_variable The variable this event - * corresponds to. Undefined for a blank event. - * @extends {Abstract} - * @constructor + * @extends {AbstractEvent} * @alias Blockly.Events.VarBase */ -const VarBase = function(opt_variable) { - VarBase.superClass_.constructor.call(this); - this.isBlank = typeof opt_variable === 'undefined'; +class VarBase extends AbstractEvent { + /** + * @param {!VariableModel=} opt_variable The variable this event + * corresponds to. Undefined for a blank event. + */ + constructor(opt_variable) { + super(); + this.isBlank = typeof opt_variable === 'undefined'; + + /** + * The variable id for the variable this event pertains to. + * @type {string} + */ + this.varId = this.isBlank ? '' : opt_variable.getId(); + + /** + * The workspace identifier for this event. + * @type {string} + */ + this.workspaceId = this.isBlank ? '' : opt_variable.workspace.id; + } /** - * The variable id for the variable this event pertains to. - * @type {string} + * Encode the event as JSON. + * @return {!Object} JSON representation. */ - this.varId = this.isBlank ? '' : opt_variable.getId(); + toJson() { + const json = super.toJson(); + json['varId'] = this.varId; + return json; + } /** - * The workspace identifier for this event. - * @type {string} + * Decode the JSON event. + * @param {!Object} json JSON representation. */ - this.workspaceId = this.isBlank ? '' : opt_variable.workspace.id; -}; -object.inherits(VarBase, Abstract); - -/** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ -VarBase.prototype.toJson = function() { - const json = VarBase.superClass_.toJson.call(this); - json['varId'] = this.varId; - return json; -}; - -/** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ -VarBase.prototype.fromJson = function(json) { - VarBase.superClass_.toJson.call(this); - this.varId = json['varId']; -}; + fromJson(json) { + super.fromJson(json); + this.varId = json['varId']; + } +} exports.VarBase = VarBase; diff --git a/core/events/events_var_create.js b/core/events/events_var_create.js index f924fd98d..13df20aad 100644 --- a/core/events/events_var_create.js +++ b/core/events/events_var_create.js @@ -16,7 +16,6 @@ goog.module('Blockly.Events.VarCreate'); const eventUtils = goog.require('Blockly.Events.utils'); -const object = goog.require('Blockly.utils.object'); const registry = goog.require('Blockly.registry'); const {VarBase} = goog.require('Blockly.Events.VarBase'); /* eslint-disable-next-line no-unused-vars */ @@ -25,62 +24,65 @@ const {VariableModel} = goog.requireType('Blockly.VariableModel'); /** * Class for a variable creation event. - * @param {!VariableModel=} opt_variable The created variable. Undefined - * for a blank event. * @extends {VarBase} - * @constructor * @alias Blockly.Events.VarCreate */ -const VarCreate = function(opt_variable) { - VarCreate.superClass_.constructor.call(this, opt_variable); - if (!opt_variable) { - return; // Blank event to be populated by fromJson. +class VarCreate extends VarBase { + /** + * @param {!VariableModel=} opt_variable The created variable. Undefined + * for a blank event. + */ + constructor(opt_variable) { + super(opt_variable); + + /** + * Type of this event. + * @type {string} + */ + this.type = eventUtils.VAR_CREATE; + + if (!opt_variable) { + return; // Blank event to be populated by fromJson. + } + + this.varType = opt_variable.type; + this.varName = opt_variable.name; } - this.varType = opt_variable.type; - this.varName = opt_variable.name; -}; -object.inherits(VarCreate, VarBase); - -/** - * Type of this event. - * @type {string} - */ -VarCreate.prototype.type = eventUtils.VAR_CREATE; - -/** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ -VarCreate.prototype.toJson = function() { - const json = VarCreate.superClass_.toJson.call(this); - json['varType'] = this.varType; - json['varName'] = this.varName; - return json; -}; - -/** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ -VarCreate.prototype.fromJson = function(json) { - VarCreate.superClass_.fromJson.call(this, json); - this.varType = json['varType']; - this.varName = json['varName']; -}; - -/** - * Run a variable creation event. - * @param {boolean} forward True if run forward, false if run backward (undo). - */ -VarCreate.prototype.run = function(forward) { - const workspace = this.getEventWorkspace_(); - if (forward) { - workspace.createVariable(this.varName, this.varType, this.varId); - } else { - workspace.deleteVariableById(this.varId); + /** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ + toJson() { + const json = super.toJson(); + json['varType'] = this.varType; + json['varName'] = this.varName; + return json; } -}; + + /** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ + fromJson(json) { + super.fromJson(json); + this.varType = json['varType']; + this.varName = json['varName']; + } + + /** + * Run a variable creation event. + * @param {boolean} forward True if run forward, false if run backward (undo). + */ + run(forward) { + const workspace = this.getEventWorkspace_(); + if (forward) { + workspace.createVariable(this.varName, this.varType, this.varId); + } else { + workspace.deleteVariableById(this.varId); + } + } +} registry.register(registry.Type.EVENT, eventUtils.VAR_CREATE, VarCreate); diff --git a/core/events/events_var_delete.js b/core/events/events_var_delete.js index 2c8b34fb2..0f23f49c8 100644 --- a/core/events/events_var_delete.js +++ b/core/events/events_var_delete.js @@ -16,7 +16,6 @@ goog.module('Blockly.Events.VarDelete'); const eventUtils = goog.require('Blockly.Events.utils'); -const object = goog.require('Blockly.utils.object'); const registry = goog.require('Blockly.registry'); const {VarBase} = goog.require('Blockly.Events.VarBase'); /* eslint-disable-next-line no-unused-vars */ @@ -25,62 +24,65 @@ const {VariableModel} = goog.requireType('Blockly.VariableModel'); /** * Class for a variable deletion event. - * @param {!VariableModel=} opt_variable The deleted variable. Undefined - * for a blank event. * @extends {VarBase} - * @constructor * @alias Blockly.Events.VarDelete */ -const VarDelete = function(opt_variable) { - VarDelete.superClass_.constructor.call(this, opt_variable); - if (!opt_variable) { - return; // Blank event to be populated by fromJson. +class VarDelete extends VarBase { + /** + * @param {!VariableModel=} opt_variable The deleted variable. Undefined + * for a blank event. + */ + constructor(opt_variable) { + super(opt_variable); + + /** + * Type of this event. + * @type {string} + */ + this.type = eventUtils.VAR_DELETE; + + if (!opt_variable) { + return; // Blank event to be populated by fromJson. + } + + this.varType = opt_variable.type; + this.varName = opt_variable.name; } - this.varType = opt_variable.type; - this.varName = opt_variable.name; -}; -object.inherits(VarDelete, VarBase); - -/** - * Type of this event. - * @type {string} - */ -VarDelete.prototype.type = eventUtils.VAR_DELETE; - -/** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ -VarDelete.prototype.toJson = function() { - const json = VarDelete.superClass_.toJson.call(this); - json['varType'] = this.varType; - json['varName'] = this.varName; - return json; -}; - -/** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ -VarDelete.prototype.fromJson = function(json) { - VarDelete.superClass_.fromJson.call(this, json); - this.varType = json['varType']; - this.varName = json['varName']; -}; - -/** - * Run a variable deletion event. - * @param {boolean} forward True if run forward, false if run backward (undo). - */ -VarDelete.prototype.run = function(forward) { - const workspace = this.getEventWorkspace_(); - if (forward) { - workspace.deleteVariableById(this.varId); - } else { - workspace.createVariable(this.varName, this.varType, this.varId); + /** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ + toJson() { + const json = super.toJson(); + json['varType'] = this.varType; + json['varName'] = this.varName; + return json; } -}; + + /** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ + fromJson(json) { + super.fromJson(json); + this.varType = json['varType']; + this.varName = json['varName']; + } + + /** + * Run a variable deletion event. + * @param {boolean} forward True if run forward, false if run backward (undo). + */ + run(forward) { + const workspace = this.getEventWorkspace_(); + if (forward) { + workspace.deleteVariableById(this.varId); + } else { + workspace.createVariable(this.varName, this.varType, this.varId); + } + } +} registry.register(registry.Type.EVENT, eventUtils.VAR_DELETE, VarDelete); diff --git a/core/events/events_var_rename.js b/core/events/events_var_rename.js index 97d8fd882..cca540b94 100644 --- a/core/events/events_var_rename.js +++ b/core/events/events_var_rename.js @@ -16,7 +16,6 @@ goog.module('Blockly.Events.VarRename'); const eventUtils = goog.require('Blockly.Events.utils'); -const object = goog.require('Blockly.utils.object'); const registry = goog.require('Blockly.registry'); const {VarBase} = goog.require('Blockly.Events.VarBase'); /* eslint-disable-next-line no-unused-vars */ @@ -25,63 +24,66 @@ const {VariableModel} = goog.requireType('Blockly.VariableModel'); /** * Class for a variable rename event. - * @param {!VariableModel=} opt_variable The renamed variable. Undefined - * for a blank event. - * @param {string=} newName The new name the variable will be changed to. * @extends {VarBase} - * @constructor * @alias Blockly.Events.VarRename */ -const VarRename = function(opt_variable, newName) { - VarRename.superClass_.constructor.call(this, opt_variable); - if (!opt_variable) { - return; // Blank event to be populated by fromJson. +class VarRename extends VarBase { + /** + * @param {!VariableModel=} opt_variable The renamed variable. Undefined + * for a blank event. + * @param {string=} newName The new name the variable will be changed to. + */ + constructor(opt_variable, newName) { + super(opt_variable); + + /** + * Type of this event. + * @type {string} + */ + this.type = eventUtils.VAR_RENAME; + + if (!opt_variable) { + return; // Blank event to be populated by fromJson. + } + + this.oldName = opt_variable.name; + this.newName = typeof newName === 'undefined' ? '' : newName; } - this.oldName = opt_variable.name; - this.newName = typeof newName === 'undefined' ? '' : newName; -}; -object.inherits(VarRename, VarBase); - -/** - * Type of this event. - * @type {string} - */ -VarRename.prototype.type = eventUtils.VAR_RENAME; - -/** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ -VarRename.prototype.toJson = function() { - const json = VarRename.superClass_.toJson.call(this); - json['oldName'] = this.oldName; - json['newName'] = this.newName; - return json; -}; - -/** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ -VarRename.prototype.fromJson = function(json) { - VarRename.superClass_.fromJson.call(this, json); - this.oldName = json['oldName']; - this.newName = json['newName']; -}; - -/** - * Run a variable rename event. - * @param {boolean} forward True if run forward, false if run backward (undo). - */ -VarRename.prototype.run = function(forward) { - const workspace = this.getEventWorkspace_(); - if (forward) { - workspace.renameVariableById(this.varId, this.newName); - } else { - workspace.renameVariableById(this.varId, this.oldName); + /** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ + toJson() { + const json = super.toJson(); + json['oldName'] = this.oldName; + json['newName'] = this.newName; + return json; } -}; + + /** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ + fromJson(json) { + super.fromJson(json); + this.oldName = json['oldName']; + this.newName = json['newName']; + } + + /** + * Run a variable rename event. + * @param {boolean} forward True if run forward, false if run backward (undo). + */ + run(forward) { + const workspace = this.getEventWorkspace_(); + if (forward) { + workspace.renameVariableById(this.varId, this.newName); + } else { + workspace.renameVariableById(this.varId, this.oldName); + } + } +} registry.register(registry.Type.EVENT, eventUtils.VAR_RENAME, VarRename); diff --git a/core/events/events_viewport.js b/core/events/events_viewport.js index 71ac3d3c7..bb5cda5a5 100644 --- a/core/events/events_viewport.js +++ b/core/events/events_viewport.js @@ -16,89 +16,90 @@ goog.module('Blockly.Events.ViewportChange'); const eventUtils = goog.require('Blockly.Events.utils'); -const object = goog.require('Blockly.utils.object'); const registry = goog.require('Blockly.registry'); const {UiBase} = goog.require('Blockly.Events.UiBase'); /** * Class for a viewport change event. - * @param {number=} opt_top Top-edge of the visible portion of the workspace, - * relative to the workspace origin. Undefined for a blank event. - * @param {number=} opt_left Left-edge of the visible portion of the workspace, - * relative to the workspace origin. Undefined for a blank event. - * @param {number=} opt_scale The scale of the workspace. Undefined for a blank - * event. - * @param {string=} opt_workspaceId The workspace identifier for this event. - * Undefined for a blank event. - * @param {number=} opt_oldScale The old scale of the workspace. Undefined for a - * blank event. * @extends {UiBase} - * @constructor * @alias Blockly.Events.ViewportChange */ -const ViewportChange = function( - opt_top, opt_left, opt_scale, opt_workspaceId, opt_oldScale) { - ViewportChange.superClass_.constructor.call(this, opt_workspaceId); +class ViewportChange extends UiBase { + /** + * @param {number=} opt_top Top-edge of the visible portion of the workspace, + * relative to the workspace origin. Undefined for a blank event. + * @param {number=} opt_left Left-edge of the visible portion of the + * workspace relative to the workspace origin. Undefined for a blank + * event. + * @param {number=} opt_scale The scale of the workspace. Undefined for a + * blank event. + * @param {string=} opt_workspaceId The workspace identifier for this event. + * Undefined for a blank event. + * @param {number=} opt_oldScale The old scale of the workspace. Undefined for + * a blank event. + */ + constructor(opt_top, opt_left, opt_scale, opt_workspaceId, opt_oldScale) { + super(opt_workspaceId); + + /** + * Top-edge of the visible portion of the workspace, relative to the + * workspace origin. + * @type {number|undefined} + */ + this.viewTop = opt_top; + + /** + * Left-edge of the visible portion of the workspace, relative to the + * workspace origin. + * @type {number|undefined} + */ + this.viewLeft = opt_left; + + /** + * The scale of the workspace. + * @type {number|undefined} + */ + this.scale = opt_scale; + + /** + * The old scale of the workspace. + * @type {number|undefined} + */ + this.oldScale = opt_oldScale; + + /** + * Type of this event. + * @type {string} + */ + this.type = eventUtils.VIEWPORT_CHANGE; + } /** - * Top-edge of the visible portion of the workspace, relative to the workspace - * origin. - * @type {number|undefined} + * Encode the event as JSON. + * @return {!Object} JSON representation. */ - this.viewTop = opt_top; + toJson() { + const json = super.toJson(); + json['viewTop'] = this.viewTop; + json['viewLeft'] = this.viewLeft; + json['scale'] = this.scale; + json['oldScale'] = this.oldScale; + return json; + } /** - * Left-edge of the visible portion of the workspace, relative to the - * workspace origin. - * @type {number|undefined} + * Decode the JSON event. + * @param {!Object} json JSON representation. */ - this.viewLeft = opt_left; - - /** - * The scale of the workspace. - * @type {number|undefined} - */ - this.scale = opt_scale; - - /** - * The old scale of the workspace. - * @type {number|undefined} - */ - this.oldScale = opt_oldScale; -}; -object.inherits(ViewportChange, UiBase); - -/** - * Type of this event. - * @type {string} - */ -ViewportChange.prototype.type = eventUtils.VIEWPORT_CHANGE; - -/** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ -ViewportChange.prototype.toJson = function() { - const json = ViewportChange.superClass_.toJson.call(this); - json['viewTop'] = this.viewTop; - json['viewLeft'] = this.viewLeft; - json['scale'] = this.scale; - json['oldScale'] = this.oldScale; - return json; -}; - -/** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ -ViewportChange.prototype.fromJson = function(json) { - ViewportChange.superClass_.fromJson.call(this, json); - this.viewTop = json['viewTop']; - this.viewLeft = json['viewLeft']; - this.scale = json['scale']; - this.oldScale = json['oldScale']; -}; + fromJson(json) { + super.fromJson(json); + this.viewTop = json['viewTop']; + this.viewLeft = json['viewLeft']; + this.scale = json['scale']; + this.oldScale = json['oldScale']; + } +} registry.register( registry.Type.EVENT, eventUtils.VIEWPORT_CHANGE, ViewportChange); diff --git a/core/events/utils.js b/core/events/utils.js index c405490b7..dfd2d4f06 100644 --- a/core/events/utils.js +++ b/core/events/utils.js @@ -17,11 +17,13 @@ */ goog.module('Blockly.Events.utils'); -/* eslint-disable-next-line no-unused-vars */ -const Abstract = goog.requireType('Blockly.Events.Abstract'); const idGenerator = goog.require('Blockly.utils.idGenerator'); const registry = goog.require('Blockly.registry'); /* eslint-disable-next-line no-unused-vars */ +const {Abstract} = goog.requireType('Blockly.Events.Abstract'); +/* eslint-disable-next-line no-unused-vars */ +const {BlockChange} = goog.requireType('Blockly.Events.BlockChange'); +/* eslint-disable-next-line no-unused-vars */ const {BlockCreate} = goog.requireType('Blockly.Events.BlockCreate'); /* eslint-disable-next-line no-unused-vars */ const {BlockMove} = goog.requireType('Blockly.Events.BlockMove'); @@ -32,7 +34,11 @@ const {CommentCreate} = goog.requireType('Blockly.Events.CommentCreate'); /* eslint-disable-next-line no-unused-vars */ const {CommentMove} = goog.requireType('Blockly.Events.CommentMove'); /* eslint-disable-next-line no-unused-vars */ +const {ViewportChange} = goog.requireType('Blockly.Events.ViewportChange'); +/* eslint-disable-next-line no-unused-vars */ const {Workspace} = goog.requireType('Blockly.Workspace'); +/* eslint-disable-next-line no-unused-vars */ +const {WorkspaceSvg} = goog.requireType('Blockly.WorkspaceSvg'); /** @@ -307,6 +313,7 @@ exports.BUMP_EVENTS = BUMP_EVENTS; /** * List of events queued for firing. + * @type {!Array} */ const FIRE_QUEUE = []; @@ -365,7 +372,9 @@ const filter = function(queueIn, forward) { if (!event.isNull()) { // Treat all UI events as the same type in hash table. const eventType = event.isUiEvent ? UI : event.type; - const key = [eventType, event.blockId, event.workspaceId].join(' '); + // TODO(#5927): Ceck whether `blockId` exists before accessing it. + const blockId = /** @type {*} */ (event).blockId; + const key = [eventType, blockId, event.workspaceId].join(' '); const lastEntry = hash[key]; const lastEvent = lastEntry ? lastEntry.event : null; @@ -376,22 +385,25 @@ const filter = function(queueIn, forward) { hash[key] = {event: event, index: i}; mergedQueue.push(event); } else if (event.type === MOVE && lastEntry.index === i - 1) { + const moveEvent = /** @type {!BlockMove} */ (event); // Merge move events. - lastEvent.newParentId = event.newParentId; - lastEvent.newInputName = event.newInputName; - lastEvent.newCoordinate = event.newCoordinate; + lastEvent.newParentId = moveEvent.newParentId; + lastEvent.newInputName = moveEvent.newInputName; + lastEvent.newCoordinate = moveEvent.newCoordinate; lastEntry.index = i; } else if ( event.type === CHANGE && event.element === lastEvent.element && event.name === lastEvent.name) { + const changeEvent = /** @type {!BlockChange} */ (event); // Merge change events. - lastEvent.newValue = event.newValue; + lastEvent.newValue = changeEvent.newValue; } else if (event.type === VIEWPORT_CHANGE) { + const viewportEvent = /** @type {!ViewportChange} */ (event); // Merge viewport change events. - lastEvent.viewTop = event.viewTop; - lastEvent.viewLeft = event.viewLeft; - lastEvent.scale = event.scale; - lastEvent.oldScale = event.oldScale; + lastEvent.viewTop = viewportEvent.viewTop; + lastEvent.viewLeft = viewportEvent.viewLeft; + lastEvent.scale = viewportEvent.scale; + lastEvent.oldScale = viewportEvent.oldScale; } else if (event.type === CLICK && lastEvent.type === BUBBLE_OPEN) { // Drop click events caused by opening/closing bubbles. } else { @@ -546,12 +558,15 @@ exports.get = get; */ const disableOrphans = function(event) { if (event.type === MOVE || event.type === CREATE) { - if (!event.workspaceId) { + const blockEvent = /** @type {!BlockMove|!BlockCreate} */ (event); + if (!blockEvent.workspaceId) { return; } const {Workspace} = goog.module.get('Blockly.Workspace'); - const eventWorkspace = Workspace.getById(event.workspaceId); - let block = eventWorkspace.getBlockById(event.blockId); + const eventWorkspace = + /** @type {!WorkspaceSvg} */ ( + Workspace.getById(blockEvent.workspaceId)); + let block = eventWorkspace.getBlockById(blockEvent.blockId); if (block) { // Changing blocks as part of this event shouldn't be undoable. const initialUndoFlag = recordUndo; diff --git a/core/events/workspace_events.js b/core/events/workspace_events.js index 726a114c0..d2ad15714 100644 --- a/core/events/workspace_events.js +++ b/core/events/workspace_events.js @@ -15,10 +15,9 @@ */ goog.module('Blockly.Events.FinishedLoading'); -const Abstract = goog.require('Blockly.Events.Abstract'); const eventUtils = goog.require('Blockly.Events.utils'); -const object = goog.require('Blockly.utils.object'); const registry = goog.require('Blockly.registry'); +const {Abstract: AbstractEvent} = goog.require('Blockly.Events.Abstract'); /* eslint-disable-next-line no-unused-vars */ const {Workspace} = goog.requireType('Blockly.Workspace'); @@ -28,70 +27,65 @@ const {Workspace} = goog.requireType('Blockly.Workspace'); * Used to notify the developer when the workspace has finished loading (i.e * domToWorkspace). * Finished loading events do not record undo or redo. - * @param {!Workspace=} opt_workspace The workspace that has finished - * loading. Undefined for a blank event. - * @extends {Abstract} - * @constructor + * @extends {AbstractEvent} * @alias Blockly.Events.FinishedLoading */ -const FinishedLoading = function(opt_workspace) { +class FinishedLoading extends AbstractEvent { /** - * Whether or not the event is blank (to be populated by fromJson). - * @type {boolean} + * @param {!Workspace=} opt_workspace The workspace that has finished + * loading. Undefined for a blank event. */ - this.isBlank = typeof opt_workspace === 'undefined'; + constructor(opt_workspace) { + super(); + /** + * Whether or not the event is blank (to be populated by fromJson). + * @type {boolean} + */ + this.isBlank = typeof opt_workspace === 'undefined'; - /** - * The workspace identifier for this event. - * @type {string} - */ - this.workspaceId = opt_workspace ? opt_workspace.id : ''; + /** + * The workspace identifier for this event. + * @type {string} + */ + this.workspaceId = opt_workspace ? opt_workspace.id : ''; - /** - * The event group ID for the group this event belongs to. Groups define - * events that should be treated as an single action from the user's - * perspective, and should be undone together. - * @type {string} - */ - this.group = eventUtils.getGroup(); + // Workspace events do not undo or redo. + this.recordUndo = false; - // Workspace events do not undo or redo. - this.recordUndo = false; -}; -object.inherits(FinishedLoading, Abstract); - -/** - * Type of this event. - * @type {string} - */ -FinishedLoading.prototype.type = eventUtils.FINISHED_LOADING; - -/** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ -FinishedLoading.prototype.toJson = function() { - const json = { - 'type': this.type, - }; - if (this.group) { - json['group'] = this.group; + /** + * Type of this event. + * @type {string} + */ + this.type = eventUtils.FINISHED_LOADING; } - if (this.workspaceId) { - json['workspaceId'] = this.workspaceId; - } - return json; -}; -/** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ -FinishedLoading.prototype.fromJson = function(json) { - this.isBlank = false; - this.workspaceId = json['workspaceId']; - this.group = json['group']; -}; + /** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ + toJson() { + const json = { + 'type': this.type, + }; + if (this.group) { + json['group'] = this.group; + } + if (this.workspaceId) { + json['workspaceId'] = this.workspaceId; + } + return json; + } + + /** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ + fromJson(json) { + this.isBlank = false; + this.workspaceId = json['workspaceId']; + this.group = json['group']; + } +} registry.register( registry.Type.EVENT, eventUtils.FINISHED_LOADING, FinishedLoading); diff --git a/core/extensions.js b/core/extensions.js index d0c219d4f..a05e05d61 100644 --- a/core/extensions.js +++ b/core/extensions.js @@ -24,6 +24,7 @@ goog.module('Blockly.Extensions'); const parsing = goog.require('Blockly.utils.parsing'); /* eslint-disable-next-line no-unused-vars */ const {Block} = goog.requireType('Blockly.Block'); +const {FieldDropdown} = goog.require('Blockly.FieldDropdown'); goog.requireType('Blockly.Mutator'); @@ -454,7 +455,7 @@ exports.buildTooltipForDropdown = buildTooltipForDropdown; const checkDropdownOptionsInTable = function(block, dropdownName, lookupTable) { // Validate all dropdown options have values. const dropdown = block.getField(dropdownName); - if (!dropdown.isOptionListDynamic()) { + if (dropdown instanceof FieldDropdown && !dropdown.isOptionListDynamic()) { const options = dropdown.getOptions(); for (let i = 0; i < options.length; i++) { const optionKey = options[i][1]; // label, then value @@ -512,11 +513,11 @@ exports.buildTooltipWithFieldText = buildTooltipWithFieldText; * @this {Block} */ const extensionParentTooltip = function() { - this.tooltipWhenNotConnected = this.tooltip; + const tooltipWhenNotConnected = this.tooltip; this.setTooltip(function() { const parent = this.getParent(); return (parent && parent.getInputsInline() && parent.tooltip) || - this.tooltipWhenNotConnected; + tooltipWhenNotConnected; }.bind(this)); }; register('parent_tooltip_when_inline', extensionParentTooltip); diff --git a/core/field.js b/core/field.js index f1fd06b2f..a70d08944 100644 --- a/core/field.js +++ b/core/field.js @@ -24,6 +24,7 @@ const WidgetDiv = goog.require('Blockly.WidgetDiv'); const Xml = goog.require('Blockly.Xml'); const browserEvents = goog.require('Blockly.browserEvents'); const dom = goog.require('Blockly.utils.dom'); +const dropDownDiv = goog.require('Blockly.dropDownDiv'); const eventUtils = goog.require('Blockly.Events.utils'); const parsing = goog.require('Blockly.utils.parsing'); const style = goog.require('Blockly.utils.style'); @@ -37,7 +38,6 @@ const {Block} = goog.requireType('Blockly.Block'); const {ConstantProvider} = goog.requireType('Blockly.blockRendering.ConstantProvider'); /* eslint-disable-next-line no-unused-vars */ const {Coordinate} = goog.requireType('Blockly.utils.Coordinate'); -const {DropDownDiv} = goog.require('Blockly.DropDownDiv'); /* eslint-disable-next-line no-unused-vars */ const {IASTNodeLocationSvg} = goog.require('Blockly.IASTNodeLocationSvg'); /* eslint-disable-next-line no-unused-vars */ @@ -50,6 +50,7 @@ const {IRegistrable} = goog.require('Blockly.IRegistrable'); const {Input} = goog.requireType('Blockly.Input'); const {MarkerManager} = goog.require('Blockly.MarkerManager'); const {Rect} = goog.require('Blockly.utils.Rect'); +const {Sentinel} = goog.require('Blockly.utils.Sentinel'); /* eslint-disable-next-line no-unused-vars */ const {ShortcutRegistry} = goog.requireType('Blockly.ShortcutRegistry'); const {Size} = goog.require('Blockly.utils.Size'); @@ -64,114 +65,1198 @@ goog.require('Blockly.Gesture'); /** * Abstract class for an editable field. - * @param {*} value The initial value of the field. - * @param {?Function=} opt_validator A function that is called to validate - * changes to the field's value. Takes in a value & returns a validated - * value, or null to abort the change. - * @param {Object=} opt_config A map of options used to configure the field. See - * the individual field's documentation for a list of properties this - * parameter supports. - * @constructor - * @abstract * @implements {IASTNodeLocationSvg} * @implements {IASTNodeLocationWithBlock} * @implements {IKeyboardAccessible} * @implements {IRegistrable} + * @abstract * @alias Blockly.Field */ -const Field = function(value, opt_validator, opt_config) { +class Field { /** - * A generic value possessed by the field. - * Should generally be non-null, only null when the field is created. - * @type {*} - * @protected + * @param {*} value The initial value of the field. + * Also accepts Field.SKIP_SETUP if you wish to skip setup (only used by + * subclasses that want to handle configuration and setting the field + * value after their own constructors have run). + * @param {?Function=} opt_validator A function that is called to validate + * changes to the field's value. Takes in a value & returns a validated + * value, or null to abort the change. + * @param {Object=} opt_config A map of options used to configure the field. + * Refer to the individual field's documentation for a list of properties + * this parameter supports. */ - this.value_ = this.DEFAULT_VALUE; + constructor(value, opt_validator, opt_config) { + /** + * Name of field. Unique within each block. + * Static labels are usually unnamed. + * @type {string|undefined} + */ + this.name = undefined; + + /** + * A generic value possessed by the field. + * Should generally be non-null, only null when the field is created. + * @type {*} + * @protected + */ + this.value_ = + /** @type {typeof Field} */ (new.target).prototype.DEFAULT_VALUE; + + /** + * Validation function called when user edits an editable field. + * @type {Function} + * @protected + */ + this.validator_ = null; + + /** + * Used to cache the field's tooltip value if setTooltip is called when the + * field is not yet initialized. Is *not* guaranteed to be accurate. + * @type {?Tooltip.TipInfo} + * @private + */ + this.tooltip_ = null; + + /** + * The size of the area rendered by the field. + * @type {!Size} + * @protected + */ + this.size_ = new Size(0, 0); + + /** + * Holds the cursors svg element when the cursor is attached to the field. + * This is null if there is no cursor on the field. + * @type {SVGElement} + * @private + */ + this.cursorSvg_ = null; + + /** + * Holds the markers svg element when the marker is attached to the field. + * This is null if there is no marker on the field. + * @type {SVGElement} + * @private + */ + this.markerSvg_ = null; + + /** + * The rendered field's SVG group element. + * @type {SVGGElement} + * @protected + */ + this.fieldGroup_ = null; + + /** + * The rendered field's SVG border element. + * @type {SVGRectElement} + * @protected + */ + this.borderRect_ = null; + + /** + * The rendered field's SVG text element. + * @type {SVGTextElement} + * @protected + */ + this.textElement_ = null; + + /** + * The rendered field's text content element. + * @type {Text} + * @protected + */ + this.textContent_ = null; + + /** + * Mouse down event listener data. + * @type {?browserEvents.Data} + * @private + */ + this.mouseDownWrapper_ = null; + + /** + * Constants associated with the source block's renderer. + * @type {ConstantProvider} + * @protected + */ + this.constants_ = null; + + /** + * Has this field been disposed of? + * @type {boolean} + * @package + */ + this.disposed = false; + + /** + * Maximum characters of text to display before adding an ellipsis. + * @type {number} + */ + this.maxDisplayLength = 50; + + /** + * Block this field is attached to. Starts as null, then set in init. + * @type {Block} + * @protected + */ + this.sourceBlock_ = null; + + /** + * Does this block need to be re-rendered? + * @type {boolean} + * @protected + */ + this.isDirty_ = true; + + /** + * Is the field visible, or hidden due to the block being collapsed? + * @type {boolean} + * @protected + */ + this.visible_ = true; + + /** + * Can the field value be changed using the editor on an editable block? + * @type {boolean} + * @protected + */ + this.enabled_ = true; + + /** + * The element the click handler is bound to. + * @type {Element} + * @protected + */ + this.clickTarget_ = null; + + /** + * The prefix field. + * @type {?string} + * @package + */ + this.prefixField = null; + + /** + * The suffix field. + * @type {?string} + * @package + */ + this.suffixField = null; + + /** + * Editable fields usually show some sort of UI indicating they are + * editable. They will also be saved by the serializer. + * @type {boolean} + */ + this.EDITABLE = true; + + /** + * Serializable fields are saved by the serializer, non-serializable fields + * are not. Editable fields should also be serializable. This is not the + * case by default so that SERIALIZABLE is backwards compatible. + * @type {boolean} + */ + this.SERIALIZABLE = false; + + /** + * Mouse cursor style when over the hotspot that initiates the editor. + * @type {string} + */ + this.CURSOR = ''; + + if (value === Field.SKIP_SETUP) return; + if (opt_config) this.configure_(opt_config); + this.setValue(value); + if (opt_validator) this.setValidator(opt_validator); + } /** - * Validation function called when user edits an editable field. - * @type {Function} + * Process the configuration map passed to the field. + * @param {!Object} config A map of options used to configure the field. See + * the individual field's documentation for a list of properties this + * parameter supports. * @protected */ - this.validator_ = null; + configure_(config) { + let tooltip = config['tooltip']; + if (typeof tooltip === 'string') { + tooltip = parsing.replaceMessageReferences(config['tooltip']); + } + tooltip && this.setTooltip(tooltip); + + // TODO (#2884): Possibly add CSS class config option. + // TODO (#2885): Possibly add cursor config option. + } /** - * Used to cache the field's tooltip value if setTooltip is called when the - * field is not yet initialized. Is *not* guaranteed to be accurate. - * @type {?Tooltip.TipInfo} + * Attach this field to a block. + * @param {!Block} block The block containing this field. + */ + setSourceBlock(block) { + if (this.sourceBlock_) { + throw Error('Field already bound to a block'); + } + this.sourceBlock_ = block; + } + + /** + * Get the renderer constant provider. + * @return {?ConstantProvider} The renderer constant + * provider. + */ + getConstants() { + if (!this.constants_ && this.sourceBlock_ && this.sourceBlock_.workspace && + this.sourceBlock_.workspace.rendered) { + this.constants_ = + /** @type {!WorkspaceSvg} */ (this.sourceBlock_.workspace) + .getRenderer() + .getConstants(); + } + return this.constants_; + } + + /** + * Get the block this field is attached to. + * @return {Block} The block containing this field. + */ + getSourceBlock() { + return this.sourceBlock_; + } + + /** + * Initialize everything to render this field. Override + * methods initModel and initView rather than this method. + * @package + * @final + */ + init() { + if (this.fieldGroup_) { + // Field has already been initialized once. + return; + } + this.fieldGroup_ = dom.createSvgElement(Svg.G, {}, null); + if (!this.isVisible()) { + this.fieldGroup_.style.display = 'none'; + } + const sourceBlockSvg = /** @type {!BlockSvg} **/ (this.sourceBlock_); + sourceBlockSvg.getSvgRoot().appendChild(this.fieldGroup_); + this.initView(); + this.updateEditable(); + this.setTooltip(this.tooltip_); + this.bindEvents_(); + this.initModel(); + } + + /** + * Create the block UI for this field. + * @package + */ + initView() { + this.createBorderRect_(); + this.createTextElement_(); + } + + /** + * Initializes the model of the field after it has been installed on a block. + * No-op by default. + * @package + */ + initModel() {} + + /** + * Create a field border rect element. Not to be overridden by subclasses. + * Instead modify the result of the function inside initView, or create a + * separate function to call. + * @protected + */ + createBorderRect_() { + this.borderRect_ = dom.createSvgElement( + Svg.RECT, { + 'rx': this.getConstants().FIELD_BORDER_RECT_RADIUS, + 'ry': this.getConstants().FIELD_BORDER_RECT_RADIUS, + 'x': 0, + 'y': 0, + 'height': this.size_.height, + 'width': this.size_.width, + 'class': 'blocklyFieldRect', + }, + this.fieldGroup_); + } + + /** + * Create a field text element. Not to be overridden by subclasses. Instead + * modify the result of the function inside initView, or create a separate + * function to call. + * @protected + */ + createTextElement_() { + this.textElement_ = dom.createSvgElement( + Svg.TEXT, { + 'class': 'blocklyText', + }, + this.fieldGroup_); + if (this.getConstants().FIELD_TEXT_BASELINE_CENTER) { + this.textElement_.setAttribute('dominant-baseline', 'central'); + } + this.textContent_ = document.createTextNode(''); + this.textElement_.appendChild(this.textContent_); + } + + /** + * Bind events to the field. Can be overridden by subclasses if they need to + * do custom input handling. + * @protected + */ + bindEvents_() { + Tooltip.bindMouseEvents(this.getClickTarget_()); + this.mouseDownWrapper_ = browserEvents.conditionalBind( + this.getClickTarget_(), 'mousedown', this, this.onMouseDown_); + } + + /** + * Sets the field's value based on the given XML element. Should only be + * called by Blockly.Xml. + * @param {!Element} fieldElement The element containing info about the + * field's state. + * @package + */ + fromXml(fieldElement) { + this.setValue(fieldElement.textContent); + } + + /** + * Serializes this field's value to XML. Should only be called by Blockly.Xml. + * @param {!Element} fieldElement The element to populate with info about the + * field's state. + * @return {!Element} The element containing info about the field's state. + * @package + */ + toXml(fieldElement) { + fieldElement.textContent = this.getValue(); + return fieldElement; + } + + /** + * Saves this fields value as something which can be serialized to JSON. + * Should only be called by the serialization system. + * @param {boolean=} _doFullSerialization If true, this signals to the field + * that if it normally just saves a reference to some state (eg variable + * fields) it should instead serialize the full state of the thing being + * referenced. + * @return {*} JSON serializable state. + * @package + */ + saveState(_doFullSerialization) { + const legacyState = this.saveLegacyState(Field); + if (legacyState !== null) { + return legacyState; + } + return this.getValue(); + } + + /** + * Sets the field's state based on the given state value. Should only be + * called by the serialization system. + * @param {*} state The state we want to apply to the field. + * @package + */ + loadState(state) { + if (this.loadLegacyState(Field, state)) { + return; + } + this.setValue(state); + } + + /** + * Returns a stringified version of the XML state, if it should be used. + * Otherwise this returns null, to signal the field should use its own + * serialization. + * @param {*} callingClass The class calling this method. + * Used to see if `this` has overridden any relevant hooks. + * @return {?string} The stringified version of the XML state, or null. + * @protected + */ + saveLegacyState(callingClass) { + if (callingClass.prototype.saveState === this.saveState && + callingClass.prototype.toXml !== this.toXml) { + const elem = utilsXml.createElement('field'); + elem.setAttribute('name', this.name || ''); + const text = Xml.domToText(this.toXml(elem)); + return text.replace( + ' xmlns="https://developers.google.com/blockly/xml"', ''); + } + // Either they called this on purpose from their saveState, or they have + // no implementations of either hook. Just do our thing. + return null; + } + + /** + * Loads the given state using either the old XML hoooks, if they should be + * used. Returns true to indicate loading has been handled, false otherwise. + * @param {*} callingClass The class calling this method. + * Used to see if `this` has overridden any relevant hooks. + * @param {*} state The state to apply to the field. + * @return {boolean} Whether the state was applied or not. + */ + loadLegacyState(callingClass, state) { + if (callingClass.prototype.loadState === this.loadState && + callingClass.prototype.fromXml !== this.fromXml) { + this.fromXml(Xml.textToDom(/** @type {string} */ (state))); + return true; + } + // Either they called this on purpose from their loadState, or they have + // no implementations of either hook. Just do our thing. + return false; + } + + /** + * Dispose of all DOM objects and events belonging to this editable field. + * @package + */ + dispose() { + dropDownDiv.hideIfOwner(this); + WidgetDiv.hideIfOwner(this); + Tooltip.unbindMouseEvents(this.getClickTarget_()); + + if (this.mouseDownWrapper_) { + browserEvents.unbind(this.mouseDownWrapper_); + } + + dom.removeNode(this.fieldGroup_); + + this.disposed = true; + } + + /** + * Add or remove the UI indicating if this field is editable or not. + */ + updateEditable() { + const group = this.fieldGroup_; + if (!this.EDITABLE || !group) { + return; + } + if (this.enabled_ && this.sourceBlock_.isEditable()) { + dom.addClass(group, 'blocklyEditableText'); + dom.removeClass(group, 'blocklyNonEditableText'); + group.style.cursor = this.CURSOR; + } else { + dom.addClass(group, 'blocklyNonEditableText'); + dom.removeClass(group, 'blocklyEditableText'); + group.style.cursor = ''; + } + } + + /** + * Set whether this field's value can be changed using the editor when the + * source block is editable. + * @param {boolean} enabled True if enabled. + */ + setEnabled(enabled) { + this.enabled_ = enabled; + this.updateEditable(); + } + + /** + * Check whether this field's value can be changed using the editor when the + * source block is editable. + * @return {boolean} Whether this field is enabled. + */ + isEnabled() { + return this.enabled_; + } + + /** + * Check whether this field defines the showEditor_ function. + * @return {boolean} Whether this field is clickable. + */ + isClickable() { + return this.enabled_ && !!this.sourceBlock_ && + this.sourceBlock_.isEditable() && + this.showEditor_ !== Field.prototype.showEditor_; + } + + /** + * Check whether this field is currently editable. Some fields are never + * EDITABLE (e.g. text labels). Other fields may be EDITABLE but may exist on + * non-editable blocks or be currently disabled. + * @return {boolean} Whether this field is currently enabled, editable and on + * an editable block. + */ + isCurrentlyEditable() { + return this.enabled_ && this.EDITABLE && !!this.sourceBlock_ && + this.sourceBlock_.isEditable(); + } + + /** + * Check whether this field should be serialized by the XML renderer. + * Handles the logic for backwards compatibility and incongruous states. + * @return {boolean} Whether this field should be serialized or not. + */ + isSerializable() { + let isSerializable = false; + if (this.name) { + if (this.SERIALIZABLE) { + isSerializable = true; + } else if (this.EDITABLE) { + console.warn( + 'Detected an editable field that was not serializable.' + + ' Please define SERIALIZABLE property as true on all editable custom' + + ' fields. Proceeding with serialization.'); + isSerializable = true; + } + } + return isSerializable; + } + + /** + * Gets whether this editable field is visible or not. + * @return {boolean} True if visible. + */ + isVisible() { + return this.visible_; + } + + /** + * Sets whether this editable field is visible or not. Should only be called + * by input.setVisible. + * @param {boolean} visible True if visible. + * @package + */ + setVisible(visible) { + if (this.visible_ === visible) { + return; + } + this.visible_ = visible; + const root = this.getSvgRoot(); + if (root) { + root.style.display = visible ? 'block' : 'none'; + } + } + + /** + * Sets a new validation function for editable fields, or clears a previously + * set validator. + * + * The validator function takes in the new field value, and returns + * validated value. The validated value could be the input value, a modified + * version of the input value, or null to abort the change. + * + * If the function does not return anything (or returns undefined) the new + * value is accepted as valid. This is to allow for fields using the + * validated function as a field-level change event notification. + * + * @param {Function} handler The validator function + * or null to clear a previous validator. + */ + setValidator(handler) { + this.validator_ = handler; + } + + /** + * Gets the validation function for editable fields, or null if not set. + * @return {?Function} Validation function, or null. + */ + getValidator() { + return this.validator_; + } + + /** + * Gets the group element for this editable field. + * Used for measuring the size and for positioning. + * @return {!SVGGElement} The group element. + */ + getSvgRoot() { + return /** @type {!SVGGElement} */ (this.fieldGroup_); + } + + /** + * Updates the field to match the colour/style of the block. Should only be + * called by BlockSvg.applyColour(). + * @package + */ + applyColour() { + // Non-abstract sub-classes may wish to implement this. See FieldDropdown. + } + + /** + * Used by getSize() to move/resize any DOM elements, and get the new size. + * + * All rendering that has an effect on the size/shape of the block should be + * done here, and should be triggered by getSize(). + * @protected + */ + render_() { + if (this.textContent_) { + this.textContent_.nodeValue = this.getDisplayText_(); + } + this.updateSize_(); + } + + /** + * Calls showEditor_ when the field is clicked if the field is clickable. + * Do not override. + * @param {Event=} opt_e Optional mouse event that triggered the field to + * open, or undefined if triggered programmatically. + * @package + * @final + */ + showEditor(opt_e) { + if (this.isClickable()) { + this.showEditor_(opt_e); + } + } + + /** + * A developer hook to create an editor for the field. This is no-op by + * default, and must be overriden to create an editor. + * @param {Event=} _e Optional mouse event that triggered the field to + * open, or undefined if triggered programmatically. + * @return {void} + * @protected + */ + showEditor_(_e) { + // NOP + } + + /** + * Updates the size of the field based on the text. + * @param {number=} opt_margin margin to use when positioning the text + * element. + * @protected + */ + updateSize_(opt_margin) { + const constants = this.getConstants(); + const xOffset = opt_margin !== undefined ? + opt_margin : + (this.borderRect_ ? this.getConstants().FIELD_BORDER_RECT_X_PADDING : + 0); + let totalWidth = xOffset * 2; + let totalHeight = constants.FIELD_TEXT_HEIGHT; + + let contentWidth = 0; + if (this.textElement_) { + contentWidth = dom.getFastTextWidth( + this.textElement_, constants.FIELD_TEXT_FONTSIZE, + constants.FIELD_TEXT_FONTWEIGHT, constants.FIELD_TEXT_FONTFAMILY); + totalWidth += contentWidth; + } + if (this.borderRect_) { + totalHeight = Math.max(totalHeight, constants.FIELD_BORDER_RECT_HEIGHT); + } + + this.size_.height = totalHeight; + this.size_.width = totalWidth; + + this.positionTextElement_(xOffset, contentWidth); + this.positionBorderRect_(); + } + + /** + * Position a field's text element after a size change. This handles both LTR + * and RTL positioning. + * @param {number} xOffset x offset to use when positioning the text element. + * @param {number} contentWidth The content width. + * @protected + */ + positionTextElement_(xOffset, contentWidth) { + if (!this.textElement_) { + return; + } + const constants = this.getConstants(); + const halfHeight = this.size_.height / 2; + + this.textElement_.setAttribute( + 'x', + this.sourceBlock_.RTL ? this.size_.width - contentWidth - xOffset : + xOffset); + this.textElement_.setAttribute( + 'y', + constants.FIELD_TEXT_BASELINE_CENTER ? + halfHeight : + halfHeight - constants.FIELD_TEXT_HEIGHT / 2 + + constants.FIELD_TEXT_BASELINE); + } + + /** + * Position a field's border rect after a size change. + * @protected + */ + positionBorderRect_() { + if (!this.borderRect_) { + return; + } + this.borderRect_.setAttribute('width', this.size_.width); + this.borderRect_.setAttribute('height', this.size_.height); + this.borderRect_.setAttribute( + 'rx', this.getConstants().FIELD_BORDER_RECT_RADIUS); + this.borderRect_.setAttribute( + 'ry', this.getConstants().FIELD_BORDER_RECT_RADIUS); + } + + /** + * Returns the height and width of the field. + * + * This should *in general* be the only place render_ gets called from. + * @return {!Size} Height and width. + */ + getSize() { + if (!this.isVisible()) { + return new Size(0, 0); + } + + if (this.isDirty_) { + this.render_(); + this.isDirty_ = false; + } else if (this.visible_ && this.size_.width === 0) { + // If the field is not visible the width will be 0 as well, one of the + // problems with the old system. + console.warn( + 'Deprecated use of setting size_.width to 0 to rerender a' + + ' field. Set field.isDirty_ to true instead.'); + this.render_(); + } + return this.size_; + } + + /** + * Returns the bounding box of the rendered field, accounting for workspace + * scaling. + * @return {!Rect} An object with top, bottom, left, and right in + * pixels relative to the top left corner of the page (window + * coordinates). + * @package + */ + getScaledBBox() { + let scaledWidth; + let scaledHeight; + let xy; + if (!this.borderRect_) { + // Browsers are inconsistent in what they return for a bounding box. + // - Webkit / Blink: fill-box / object bounding box + // - Gecko / Triden / EdgeHTML: stroke-box + const bBox = this.sourceBlock_.getHeightWidth(); + const scale = + /** @type {!WorkspaceSvg} */ (this.sourceBlock_.workspace).scale; + xy = this.getAbsoluteXY_(); + scaledWidth = bBox.width * scale; + scaledHeight = bBox.height * scale; + + if (userAgent.GECKO) { + xy.x += 1.5 * scale; + xy.y += 1.5 * scale; + scaledWidth += 1 * scale; + scaledHeight += 1 * scale; + } else { + if (!userAgent.EDGE && !userAgent.IE) { + xy.x -= 0.5 * scale; + xy.y -= 0.5 * scale; + } + scaledWidth += 1 * scale; + scaledHeight += 1 * scale; + } + } else { + const bBox = this.borderRect_.getBoundingClientRect(); + xy = style.getPageOffset(this.borderRect_); + scaledWidth = bBox.width; + scaledHeight = bBox.height; + } + return new Rect(xy.y, xy.y + scaledHeight, xy.x, xy.x + scaledWidth); + } + + /** + * Get the text from this field to display on the block. May differ from + * ``getText`` due to ellipsis, and other formatting. + * @return {string} Text to display. + * @protected + */ + getDisplayText_() { + let text = this.getText(); + if (!text) { + // Prevent the field from disappearing if empty. + return Field.NBSP; + } + if (text.length > this.maxDisplayLength) { + // Truncate displayed string and add an ellipsis ('...'). + text = text.substring(0, this.maxDisplayLength - 2) + '\u2026'; + } + // Replace whitespace with non-breaking spaces so the text doesn't collapse. + text = text.replace(/\s/g, Field.NBSP); + if (this.sourceBlock_ && this.sourceBlock_.RTL) { + // The SVG is LTR, force text to be RTL. + text += '\u200F'; + } + return text; + } + + /** + * Get the text from this field. + * Override getText_ to provide a different behavior than simply casting the + * value to a string. + * @return {string} Current text. + * @final + */ + getText() { + // this.getText_ was intended so that devs don't have to remember to call + // super when overriding how the text of the field is generated. (#2910) + const text = this.getText_(); + if (text !== null) return String(text); + return String(this.getValue()); + } + + /** + * A developer hook to override the returned text of this field. + * Override if the text representation of the value of this field + * is not just a string cast of its value. + * Return null to resort to a string cast. + * @return {?string} Current text or null. + * @protected + */ + getText_() { + return null; + } + + /** + * Force a rerender of the block that this field is installed on, which will + * rerender this field and adjust for any sizing changes. + * Other fields on the same block will not rerender, because their sizes have + * already been recorded. + * @package + */ + markDirty() { + this.isDirty_ = true; + this.constants_ = null; + } + + /** + * Force a rerender of the block that this field is installed on, which will + * rerender this field and adjust for any sizing changes. + * Other fields on the same block will not rerender, because their sizes have + * already been recorded. + * @package + */ + forceRerender() { + this.isDirty_ = true; + if (this.sourceBlock_ && this.sourceBlock_.rendered) { + this.sourceBlock_.render(); + this.sourceBlock_.bumpNeighbours(); + this.updateMarkers_(); + } + } + + /** + * Used to change the value of the field. Handles validation and events. + * Subclasses should override doClassValidation_ and doValueUpdate_ rather + * than this method. + * @param {*} newValue New value. + * @final + */ + setValue(newValue) { + const doLogging = false; + if (newValue === null) { + doLogging && console.log('null, return'); + // Not a valid value to check. + return; + } + + let validatedValue = this.doClassValidation_(newValue); + // Class validators might accidentally forget to return, we'll ignore that. + newValue = this.processValidation_(newValue, validatedValue); + if (newValue instanceof Error) { + doLogging && console.log('invalid class validation, return'); + return; + } + + const localValidator = this.getValidator(); + if (localValidator) { + validatedValue = localValidator.call(this, newValue); + // Local validators might accidentally forget to return, we'll ignore + // that. + newValue = this.processValidation_(newValue, validatedValue); + if (newValue instanceof Error) { + doLogging && console.log('invalid local validation, return'); + return; + } + } + const source = this.sourceBlock_; + if (source && source.disposed) { + doLogging && console.log('source disposed, return'); + return; + } + const oldValue = this.getValue(); + if (oldValue === newValue) { + doLogging && console.log('same, doValueUpdate_, return'); + this.doValueUpdate_(newValue); + return; + } + + this.doValueUpdate_(newValue); + if (source && eventUtils.isEnabled()) { + eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))( + source, 'field', this.name || null, oldValue, newValue)); + } + if (this.isDirty_) { + this.forceRerender(); + } + doLogging && console.log(this.value_); + } + + /** + * Process the result of validation. + * @param {*} newValue New value. + * @param {*} validatedValue Validated value. + * @return {*} New value, or an Error object. * @private */ - this.tooltip_ = null; + processValidation_(newValue, validatedValue) { + if (validatedValue === null) { + this.doValueInvalid_(newValue); + if (this.isDirty_) { + this.forceRerender(); + } + return Error(); + } + if (validatedValue !== undefined) { + newValue = validatedValue; + } + return newValue; + } /** - * The size of the area rendered by the field. - * @type {!Size} + * Get the current value of the field. + * @return {*} Current value. + */ + getValue() { + return this.value_; + } + + /** + * Used to validate a value. Returns input by default. Can be overridden by + * subclasses, see FieldDropdown. + * @param {*=} opt_newValue The value to be validated. + * @return {*} The validated value, same as input by default. * @protected */ - this.size_ = new Size(0, 0); + doClassValidation_(opt_newValue) { + if (opt_newValue === null || opt_newValue === undefined) { + return null; + } + return opt_newValue; + } /** - * Holds the cursors svg element when the cursor is attached to the field. - * This is null if there is no cursor on the field. - * @type {SVGElement} - * @private - */ - this.cursorSvg_ = null; - - /** - * Holds the markers svg element when the marker is attached to the field. - * This is null if there is no marker on the field. - * @type {SVGElement} - * @private - */ - this.markerSvg_ = null; - - /** - * The rendered field's SVG group element. - * @type {SVGGElement} + * Used to update the value of a field. Can be overridden by subclasses to do + * custom storage of values/updating of external things. + * @param {*} newValue The value to be saved. * @protected */ - this.fieldGroup_ = null; + doValueUpdate_(newValue) { + this.value_ = newValue; + this.isDirty_ = true; + } /** - * The rendered field's SVG border element. - * @type {SVGRectElement} + * Used to notify the field an invalid value was input. Can be overridden by + * subclasses, see FieldTextInput. + * No-op by default. + * @param {*} _invalidValue The input value that was determined to be invalid. * @protected */ - this.borderRect_ = null; + doValueInvalid_(_invalidValue) { + // NOP + } /** - * The rendered field's SVG text element. - * @type {SVGTextElement} + * Handle a mouse down event on a field. + * @param {!Event} e Mouse down event. * @protected */ - this.textElement_ = null; + onMouseDown_(e) { + if (!this.sourceBlock_ || !this.sourceBlock_.workspace) { + return; + } + const gesture = + /** @type {!WorkspaceSvg} */ (this.sourceBlock_.workspace) + .getGesture(e); + if (gesture) { + gesture.setStartField(this); + } + } /** - * The rendered field's text content element. - * @type {Text} + * Sets the tooltip for this field. + * @param {?Tooltip.TipInfo} newTip The + * text for the tooltip, a function that returns the text for the tooltip, + * a parent object whose tooltip will be used, or null to display the tooltip + * of the parent block. To not display a tooltip pass the empty string. + */ + setTooltip(newTip) { + if (!newTip && newTip !== '') { // If null or undefined. + newTip = this.sourceBlock_; + } + const clickTarget = this.getClickTarget_(); + if (clickTarget) { + clickTarget.tooltip = newTip; + } else { + // Field has not been initialized yet. + this.tooltip_ = newTip; + } + } + + /** + * Returns the tooltip text for this field. + * @return {string} The tooltip text for this field. + */ + getTooltip() { + const clickTarget = this.getClickTarget_(); + if (clickTarget) { + return Tooltip.getTooltipOfObject(clickTarget); + } + // Field has not been initialized yet. Return stashed this.tooltip_ value. + return Tooltip.getTooltipOfObject({tooltip: this.tooltip_}); + } + + /** + * The element to bind the click handler to. If not set explicitly, defaults + * to the SVG root of the field. When this element is + * clicked on an editable field, the editor will open. + * @return {!Element} Element to bind click handler to. * @protected */ - this.textContent_ = null; + getClickTarget_() { + return this.clickTarget_ || this.getSvgRoot(); + } /** - * Mouse down event listener data. - * @type {?browserEvents.Data} - * @private - */ - this.mouseDownWrapper_ = null; - - /** - * Constants associated with the source block's renderer. - * @type {ConstantProvider} + * Return the absolute coordinates of the top-left corner of this field. + * The origin (0,0) is the top-left corner of the page body. + * @return {!Coordinate} Object with .x and .y properties. * @protected */ - this.constants_ = null; + getAbsoluteXY_() { + return style.getPageOffset( + /** @type {!SVGRectElement} */ (this.getClickTarget_())); + } - opt_config && this.configure_(opt_config); - this.setValue(value); - opt_validator && this.setValidator(opt_validator); -}; + /** + * Whether this field references any Blockly variables. If true it may need + * to be handled differently during serialization and deserialization. + * Subclasses may override this. + * @return {boolean} True if this field has any variable references. + * @package + */ + referencesVariables() { + return false; + } + + /** + * Refresh the variable name referenced by this field if this field references + * variables. + * @package + */ + refreshVariableName() { + // NOP + } + + /** + * Search through the list of inputs and their fields in order to find the + * parent input of a field. + * @return {Input} The input that the field belongs to. + * @package + */ + getParentInput() { + let parentInput = null; + const block = this.sourceBlock_; + const inputs = block.inputList; + + for (let idx = 0; idx < block.inputList.length; idx++) { + const input = inputs[idx]; + const fieldRows = input.fieldRow; + for (let j = 0; j < fieldRows.length; j++) { + if (fieldRows[j] === this) { + parentInput = input; + break; + } + } + } + return parentInput; + } + + /** + * Returns whether or not we should flip the field in RTL. + * @return {boolean} True if we should flip in RTL. + */ + getFlipRtl() { + return false; + } + + /** + * Returns whether or not the field is tab navigable. + * @return {boolean} True if the field is tab navigable. + */ + isTabNavigable() { + return false; + } + + /** + * Handles the given keyboard shortcut. + * @param {!ShortcutRegistry.KeyboardShortcut} _shortcut The shortcut to be + * handled. + * @return {boolean} True if the shortcut has been handled, false otherwise. + * @public + */ + onShortcut(_shortcut) { + return false; + } + + /** + * Add the cursor SVG to this fields SVG group. + * @param {SVGElement} cursorSvg The SVG root of the cursor to be added to the + * field group. + * @package + */ + setCursorSvg(cursorSvg) { + if (!cursorSvg) { + this.cursorSvg_ = null; + return; + } + + this.fieldGroup_.appendChild(cursorSvg); + this.cursorSvg_ = cursorSvg; + } + + /** + * Add the marker SVG to this fields SVG group. + * @param {SVGElement} markerSvg The SVG root of the marker to be added to the + * field group. + * @package + */ + setMarkerSvg(markerSvg) { + if (!markerSvg) { + this.markerSvg_ = null; + return; + } + + this.fieldGroup_.appendChild(markerSvg); + this.markerSvg_ = markerSvg; + } + + /** + * Redraw any attached marker or cursor svgs if needed. + * @protected + */ + updateMarkers_() { + const workspace = + /** @type {!WorkspaceSvg} */ (this.sourceBlock_.workspace); + if (workspace.keyboardAccessibilityMode && this.cursorSvg_) { + workspace.getCursor().draw(); + } + if (workspace.keyboardAccessibilityMode && this.markerSvg_) { + // TODO(#4592): Update all markers on the field. + workspace.getMarker(MarkerManager.LOCAL_MARKER).draw(); + } + } +} /** * The default value for this field. @@ -180,82 +1265,6 @@ const Field = function(value, opt_validator, opt_config) { */ Field.prototype.DEFAULT_VALUE = null; -/** - * Name of field. Unique within each block. - * Static labels are usually unnamed. - * @type {string|undefined} - */ -Field.prototype.name = undefined; - -/** - * Has this field been disposed of? - * @type {boolean} - * @package - */ -Field.prototype.disposed = false; - -/** - * Maximum characters of text to display before adding an ellipsis. - * @type {number} - */ -Field.prototype.maxDisplayLength = 50; - -/** - * Block this field is attached to. Starts as null, then set in init. - * @type {Block} - * @protected - */ -Field.prototype.sourceBlock_ = null; - -/** - * Does this block need to be re-rendered? - * @type {boolean} - * @protected - */ -Field.prototype.isDirty_ = true; - -/** - * Is the field visible, or hidden due to the block being collapsed? - * @type {boolean} - * @protected - */ -Field.prototype.visible_ = true; - -/** - * Can the field value be changed using the editor on an editable block? - * @type {boolean} - * @protected - */ -Field.prototype.enabled_ = true; - -/** - * The element the click handler is bound to. - * @type {Element} - * @protected - */ -Field.prototype.clickTarget_ = null; - -/** - * A developer hook to override the returned text of this field. - * Override if the text representation of the value of this field - * is not just a string cast of its value. - * Return null to resort to a string cast. - * @return {?string} Current text. Return null to resort to a string cast. - * @protected - */ -Field.prototype.getText_; - -/** - * An optional method that can be defined to show an editor when the field is - * clicked. Blockly will automatically set the field as clickable if this - * method is defined. - * @param {Event=} opt_e Optional mouse event that triggered the field to open, - * or undefined if triggered programmatically. - * @return {void} - * @protected - */ -Field.prototype.showEditor_; - /** * Non-breaking space. * @const @@ -263,956 +1272,11 @@ Field.prototype.showEditor_; Field.NBSP = '\u00A0'; /** - * Editable fields usually show some sort of UI indicating they are editable. - * They will also be saved by the XML renderer. - * @type {boolean} + * A value used to signal when a field's constructor should *not* set the + * field's value or run configure_, and should allow a subclass to do that + * instead. + * @const */ -Field.prototype.EDITABLE = true; - -/** - * Serializable fields are saved by the XML renderer, non-serializable fields - * are not. Editable fields should also be serializable. This is not the - * case by default so that SERIALIZABLE is backwards compatible. - * @type {boolean} - */ -Field.prototype.SERIALIZABLE = false; - -/** - * Process the configuration map passed to the field. - * @param {!Object} config A map of options used to configure the field. See - * the individual field's documentation for a list of properties this - * parameter supports. - * @protected - */ -Field.prototype.configure_ = function(config) { - let tooltip = config['tooltip']; - if (typeof tooltip === 'string') { - tooltip = parsing.replaceMessageReferences(config['tooltip']); - } - tooltip && this.setTooltip(tooltip); - - // TODO (#2884): Possibly add CSS class config option. - // TODO (#2885): Possibly add cursor config option. -}; - -/** - * Attach this field to a block. - * @param {!Block} block The block containing this field. - */ -Field.prototype.setSourceBlock = function(block) { - if (this.sourceBlock_) { - throw Error('Field already bound to a block'); - } - this.sourceBlock_ = block; -}; - -/** - * Get the renderer constant provider. - * @return {?ConstantProvider} The renderer constant - * provider. - */ -Field.prototype.getConstants = function() { - if (!this.constants_ && this.sourceBlock_ && this.sourceBlock_.workspace && - this.sourceBlock_.workspace.rendered) { - this.constants_ = this.sourceBlock_.workspace.getRenderer().getConstants(); - } - return this.constants_; -}; - -/** - * Get the block this field is attached to. - * @return {Block} The block containing this field. - */ -Field.prototype.getSourceBlock = function() { - return this.sourceBlock_; -}; - -/** - * Initialize everything to render this field. Override - * methods initModel and initView rather than this method. - * @package - */ -Field.prototype.init = function() { - if (this.fieldGroup_) { - // Field has already been initialized once. - return; - } - this.fieldGroup_ = dom.createSvgElement(Svg.G, {}, null); - if (!this.isVisible()) { - this.fieldGroup_.style.display = 'none'; - } - const sourceBlockSvg = /** @type {!BlockSvg} **/ (this.sourceBlock_); - sourceBlockSvg.getSvgRoot().appendChild(this.fieldGroup_); - this.initView(); - this.updateEditable(); - this.setTooltip(this.tooltip_); - this.bindEvents_(); - this.initModel(); -}; - -/** - * Create the block UI for this field. - * @package - */ -Field.prototype.initView = function() { - this.createBorderRect_(); - this.createTextElement_(); -}; - -/** - * Initializes the model of the field after it has been installed on a block. - * No-op by default. - * @package - */ -Field.prototype.initModel = function() {}; - -/** - * Create a field border rect element. Not to be overridden by subclasses. - * Instead modify the result of the function inside initView, or create a - * separate function to call. - * @protected - */ -Field.prototype.createBorderRect_ = function() { - this.borderRect_ = dom.createSvgElement( - Svg.RECT, { - 'rx': this.getConstants().FIELD_BORDER_RECT_RADIUS, - 'ry': this.getConstants().FIELD_BORDER_RECT_RADIUS, - 'x': 0, - 'y': 0, - 'height': this.size_.height, - 'width': this.size_.width, - 'class': 'blocklyFieldRect', - }, - this.fieldGroup_); -}; - -/** - * Create a field text element. Not to be overridden by subclasses. Instead - * modify the result of the function inside initView, or create a separate - * function to call. - * @protected - */ -Field.prototype.createTextElement_ = function() { - this.textElement_ = dom.createSvgElement( - Svg.TEXT, { - 'class': 'blocklyText', - }, - this.fieldGroup_); - if (this.getConstants().FIELD_TEXT_BASELINE_CENTER) { - this.textElement_.setAttribute('dominant-baseline', 'central'); - } - this.textContent_ = document.createTextNode(''); - this.textElement_.appendChild(this.textContent_); -}; - -/** - * Bind events to the field. Can be overridden by subclasses if they need to do - * custom input handling. - * @protected - */ -Field.prototype.bindEvents_ = function() { - Tooltip.bindMouseEvents(this.getClickTarget_()); - this.mouseDownWrapper_ = browserEvents.conditionalBind( - this.getClickTarget_(), 'mousedown', this, this.onMouseDown_); -}; - -/** - * Sets the field's value based on the given XML element. Should only be called - * by Blockly.Xml. - * @param {!Element} fieldElement The element containing info about the - * field's state. - * @package - */ -Field.prototype.fromXml = function(fieldElement) { - this.setValue(fieldElement.textContent); -}; - -/** - * Serializes this field's value to XML. Should only be called by Blockly.Xml. - * @param {!Element} fieldElement The element to populate with info about the - * field's state. - * @return {!Element} The element containing info about the field's state. - * @package - */ -Field.prototype.toXml = function(fieldElement) { - fieldElement.textContent = this.getValue(); - return fieldElement; -}; - -/** - * Saves this fields value as something which can be serialized to JSON. Should - * only be called by the serialization system. - * @param {boolean=} _doFullSerialization If true, this signals to the field - * that if it normally just saves a reference to some state (eg variable - * fields) it should instead serialize the full state of the thing being - * referenced. - * @return {*} JSON serializable state. - * @package - */ -Field.prototype.saveState = function(_doFullSerialization) { - const legacyState = this.saveLegacyState(Field); - if (legacyState !== null) { - return legacyState; - } - return this.getValue(); -}; - -/** - * Sets the field's state based on the given state value. Should only be called - * by the serialization system. - * @param {*} state The state we want to apply to the field. - * @package - */ -Field.prototype.loadState = function(state) { - if (this.loadLegacyState(Field, state)) { - return; - } - this.setValue(state); -}; - -/** - * Returns a stringified version of the XML state, if it should be used. - * Otherwise this returns null, to signal the field should use its own - * serialization. - * @param {*} callingClass The class calling this method. - * Used to see if `this` has overridden any relevant hooks. - * @return {?string} The stringified version of the XML state, or null. - * @protected - */ -Field.prototype.saveLegacyState = function(callingClass) { - if (callingClass.prototype.saveState === this.saveState && - callingClass.prototype.toXml !== this.toXml) { - const elem = utilsXml.createElement('field'); - elem.setAttribute('name', this.name || ''); - const text = Xml.domToText(this.toXml(elem)); - return text.replace( - ' xmlns="https://developers.google.com/blockly/xml"', ''); - } - // Either they called this on purpose from their saveState, or they have - // no implementations of either hook. Just do our thing. - return null; -}; - -/** - * Loads the given state using either the old XML hoooks, if they should be - * used. Returns true to indicate loading has been handled, false otherwise. - * @param {*} callingClass The class calling this method. - * Used to see if `this` has overridden any relevant hooks. - * @param {*} state The state to apply to the field. - * @return {boolean} Whether the state was applied or not. - */ -Field.prototype.loadLegacyState = function(callingClass, state) { - if (callingClass.prototype.loadState === this.loadState && - callingClass.prototype.fromXml !== this.fromXml) { - this.fromXml(Xml.textToDom(/** @type {string} */ (state))); - return true; - } - // Either they called this on purpose from their loadState, or they have - // no implementations of either hook. Just do our thing. - return false; -}; - -/** - * Dispose of all DOM objects and events belonging to this editable field. - * @package - */ -Field.prototype.dispose = function() { - DropDownDiv.hideIfOwner(this); - WidgetDiv.hideIfOwner(this); - Tooltip.unbindMouseEvents(this.getClickTarget_()); - - if (this.mouseDownWrapper_) { - browserEvents.unbind(this.mouseDownWrapper_); - } - - dom.removeNode(this.fieldGroup_); - - this.disposed = true; -}; - -/** - * Add or remove the UI indicating if this field is editable or not. - */ -Field.prototype.updateEditable = function() { - const group = this.fieldGroup_; - if (!this.EDITABLE || !group) { - return; - } - if (this.enabled_ && this.sourceBlock_.isEditable()) { - dom.addClass(group, 'blocklyEditableText'); - dom.removeClass(group, 'blocklyNonEditableText'); - group.style.cursor = this.CURSOR; - } else { - dom.addClass(group, 'blocklyNonEditableText'); - dom.removeClass(group, 'blocklyEditableText'); - group.style.cursor = ''; - } -}; - -/** - * Set whether this field's value can be changed using the editor when the - * source block is editable. - * @param {boolean} enabled True if enabled. - */ -Field.prototype.setEnabled = function(enabled) { - this.enabled_ = enabled; - this.updateEditable(); -}; - -/** - * Check whether this field's value can be changed using the editor when the - * source block is editable. - * @return {boolean} Whether this field is enabled. - */ -Field.prototype.isEnabled = function() { - return this.enabled_; -}; - -/** - * Check whether this field defines the showEditor_ function. - * @return {boolean} Whether this field is clickable. - */ -Field.prototype.isClickable = function() { - return this.enabled_ && !!this.sourceBlock_ && - this.sourceBlock_.isEditable() && !!this.showEditor_ && - (typeof this.showEditor_ === 'function'); -}; - -/** - * Check whether this field is currently editable. Some fields are never - * EDITABLE (e.g. text labels). Other fields may be EDITABLE but may exist on - * non-editable blocks or be currently disabled. - * @return {boolean} Whether this field is currently enabled, editable and on - * an editable block. - */ -Field.prototype.isCurrentlyEditable = function() { - return this.enabled_ && this.EDITABLE && !!this.sourceBlock_ && - this.sourceBlock_.isEditable(); -}; - -/** - * Check whether this field should be serialized by the XML renderer. - * Handles the logic for backwards compatibility and incongruous states. - * @return {boolean} Whether this field should be serialized or not. - */ -Field.prototype.isSerializable = function() { - let isSerializable = false; - if (this.name) { - if (this.SERIALIZABLE) { - isSerializable = true; - } else if (this.EDITABLE) { - console.warn( - 'Detected an editable field that was not serializable.' + - ' Please define SERIALIZABLE property as true on all editable custom' + - ' fields. Proceeding with serialization.'); - isSerializable = true; - } - } - return isSerializable; -}; - -/** - * Gets whether this editable field is visible or not. - * @return {boolean} True if visible. - */ -Field.prototype.isVisible = function() { - return this.visible_; -}; - -/** - * Sets whether this editable field is visible or not. Should only be called - * by input.setVisible. - * @param {boolean} visible True if visible. - * @package - */ -Field.prototype.setVisible = function(visible) { - if (this.visible_ === visible) { - return; - } - this.visible_ = visible; - const root = this.getSvgRoot(); - if (root) { - root.style.display = visible ? 'block' : 'none'; - } -}; - -/** - * Sets a new validation function for editable fields, or clears a previously - * set validator. - * - * The validator function takes in the new field value, and returns - * validated value. The validated value could be the input value, a modified - * version of the input value, or null to abort the change. - * - * If the function does not return anything (or returns undefined) the new - * value is accepted as valid. This is to allow for fields using the - * validated function as a field-level change event notification. - * - * @param {Function} handler The validator function - * or null to clear a previous validator. - */ -Field.prototype.setValidator = function(handler) { - this.validator_ = handler; -}; - -/** - * Gets the validation function for editable fields, or null if not set. - * @return {?Function} Validation function, or null. - */ -Field.prototype.getValidator = function() { - return this.validator_; -}; - -/** - * Gets the group element for this editable field. - * Used for measuring the size and for positioning. - * @return {!SVGGElement} The group element. - */ -Field.prototype.getSvgRoot = function() { - return /** @type {!SVGGElement} */ (this.fieldGroup_); -}; - -/** - * Updates the field to match the colour/style of the block. Should only be - * called by BlockSvg.applyColour(). - * @package - */ -Field.prototype.applyColour = function() { - // Non-abstract sub-classes may wish to implement this. See FieldDropdown. -}; - -/** - * Used by getSize() to move/resize any DOM elements, and get the new size. - * - * All rendering that has an effect on the size/shape of the block should be - * done here, and should be triggered by getSize(). - * @protected - */ -Field.prototype.render_ = function() { - if (this.textContent_) { - this.textContent_.nodeValue = this.getDisplayText_(); - } - this.updateSize_(); -}; - -/** - * Show an editor when the field is clicked only if the field is clickable. - * @param {Event=} opt_e Optional mouse event that triggered the field to open, - * or undefined if triggered programmatically. - * @package - */ -Field.prototype.showEditor = function(opt_e) { - if (this.isClickable()) { - this.showEditor_(opt_e); - } -}; - -/** - * Updates the size of the field based on the text. - * @param {number=} opt_margin margin to use when positioning the text element. - * @protected - */ -Field.prototype.updateSize_ = function(opt_margin) { - const constants = this.getConstants(); - const xOffset = opt_margin !== undefined ? - opt_margin : - (this.borderRect_ ? this.getConstants().FIELD_BORDER_RECT_X_PADDING : 0); - let totalWidth = xOffset * 2; - let totalHeight = constants.FIELD_TEXT_HEIGHT; - - let contentWidth = 0; - if (this.textElement_) { - contentWidth = dom.getFastTextWidth( - this.textElement_, constants.FIELD_TEXT_FONTSIZE, - constants.FIELD_TEXT_FONTWEIGHT, constants.FIELD_TEXT_FONTFAMILY); - totalWidth += contentWidth; - } - if (this.borderRect_) { - totalHeight = Math.max(totalHeight, constants.FIELD_BORDER_RECT_HEIGHT); - } - - this.size_.height = totalHeight; - this.size_.width = totalWidth; - - this.positionTextElement_(xOffset, contentWidth); - this.positionBorderRect_(); -}; - -/** - * Position a field's text element after a size change. This handles both LTR - * and RTL positioning. - * @param {number} xOffset x offset to use when positioning the text element. - * @param {number} contentWidth The content width. - * @protected - */ -Field.prototype.positionTextElement_ = function(xOffset, contentWidth) { - if (!this.textElement_) { - return; - } - const constants = this.getConstants(); - const halfHeight = this.size_.height / 2; - - this.textElement_.setAttribute( - 'x', - this.sourceBlock_.RTL ? this.size_.width - contentWidth - xOffset : - xOffset); - this.textElement_.setAttribute( - 'y', - constants.FIELD_TEXT_BASELINE_CENTER ? halfHeight : - halfHeight - - constants.FIELD_TEXT_HEIGHT / 2 + constants.FIELD_TEXT_BASELINE); -}; - -/** - * Position a field's border rect after a size change. - * @protected - */ -Field.prototype.positionBorderRect_ = function() { - if (!this.borderRect_) { - return; - } - this.borderRect_.setAttribute('width', this.size_.width); - this.borderRect_.setAttribute('height', this.size_.height); - this.borderRect_.setAttribute( - 'rx', this.getConstants().FIELD_BORDER_RECT_RADIUS); - this.borderRect_.setAttribute( - 'ry', this.getConstants().FIELD_BORDER_RECT_RADIUS); -}; - - -/** - * Returns the height and width of the field. - * - * This should *in general* be the only place render_ gets called from. - * @return {!Size} Height and width. - */ -Field.prototype.getSize = function() { - if (!this.isVisible()) { - return new Size(0, 0); - } - - if (this.isDirty_) { - this.render_(); - this.isDirty_ = false; - } else if (this.visible_ && this.size_.width === 0) { - // If the field is not visible the width will be 0 as well, one of the - // problems with the old system. - console.warn( - 'Deprecated use of setting size_.width to 0 to rerender a' + - ' field. Set field.isDirty_ to true instead.'); - this.render_(); - } - return this.size_; -}; - -/** - * Returns the bounding box of the rendered field, accounting for workspace - * scaling. - * @return {!Rect} An object with top, bottom, left, and right in - * pixels relative to the top left corner of the page (window coordinates). - * @package - */ -Field.prototype.getScaledBBox = function() { - let scaledWidth; - let scaledHeight; - let xy; - if (!this.borderRect_) { - // Browsers are inconsistent in what they return for a bounding box. - // - Webkit / Blink: fill-box / object bounding box - // - Gecko / Triden / EdgeHTML: stroke-box - const bBox = this.sourceBlock_.getHeightWidth(); - const scale = this.sourceBlock_.workspace.scale; - xy = this.getAbsoluteXY_(); - scaledWidth = bBox.width * scale; - scaledHeight = bBox.height * scale; - - if (userAgent.GECKO) { - xy.x += 1.5 * scale; - xy.y += 1.5 * scale; - scaledWidth += 1 * scale; - scaledHeight += 1 * scale; - } else { - if (!userAgent.EDGE && !userAgent.IE) { - xy.x -= 0.5 * scale; - xy.y -= 0.5 * scale; - } - scaledWidth += 1 * scale; - scaledHeight += 1 * scale; - } - } else { - const bBox = this.borderRect_.getBoundingClientRect(); - xy = style.getPageOffset(this.borderRect_); - scaledWidth = bBox.width; - scaledHeight = bBox.height; - } - return new Rect(xy.y, xy.y + scaledHeight, xy.x, xy.x + scaledWidth); -}; - -/** - * Get the text from this field to display on the block. May differ from - * ``getText`` due to ellipsis, and other formatting. - * @return {string} Text to display. - * @protected - */ -Field.prototype.getDisplayText_ = function() { - let text = this.getText(); - if (!text) { - // Prevent the field from disappearing if empty. - return Field.NBSP; - } - if (text.length > this.maxDisplayLength) { - // Truncate displayed string and add an ellipsis ('...'). - text = text.substring(0, this.maxDisplayLength - 2) + '\u2026'; - } - // Replace whitespace with non-breaking spaces so the text doesn't collapse. - text = text.replace(/\s/g, Field.NBSP); - if (this.sourceBlock_ && this.sourceBlock_.RTL) { - // The SVG is LTR, force text to be RTL. - text += '\u200F'; - } - return text; -}; - -/** - * Get the text from this field. - * @return {string} Current text. - */ -Field.prototype.getText = function() { - if (this.getText_) { - const text = this.getText_.call(this); - if (text !== null) { - return String(text); - } - } - return String(this.getValue()); -}; - -/** - * Force a rerender of the block that this field is installed on, which will - * rerender this field and adjust for any sizing changes. - * Other fields on the same block will not rerender, because their sizes have - * already been recorded. - * @package - */ -Field.prototype.markDirty = function() { - this.isDirty_ = true; - this.constants_ = null; -}; - -/** - * Force a rerender of the block that this field is installed on, which will - * rerender this field and adjust for any sizing changes. - * Other fields on the same block will not rerender, because their sizes have - * already been recorded. - * @package - */ -Field.prototype.forceRerender = function() { - this.isDirty_ = true; - if (this.sourceBlock_ && this.sourceBlock_.rendered) { - this.sourceBlock_.render(); - this.sourceBlock_.bumpNeighbours(); - this.updateMarkers_(); - } -}; - -/** - * Used to change the value of the field. Handles validation and events. - * Subclasses should override doClassValidation_ and doValueUpdate_ rather - * than this method. - * @param {*} newValue New value. - */ -Field.prototype.setValue = function(newValue) { - const doLogging = false; - if (newValue === null) { - doLogging && console.log('null, return'); - // Not a valid value to check. - return; - } - - let validatedValue = this.doClassValidation_(newValue); - // Class validators might accidentally forget to return, we'll ignore that. - newValue = this.processValidation_(newValue, validatedValue); - if (newValue instanceof Error) { - doLogging && console.log('invalid class validation, return'); - return; - } - - const localValidator = this.getValidator(); - if (localValidator) { - validatedValue = localValidator.call(this, newValue); - // Local validators might accidentally forget to return, we'll ignore that. - newValue = this.processValidation_(newValue, validatedValue); - if (newValue instanceof Error) { - doLogging && console.log('invalid local validation, return'); - return; - } - } - const source = this.sourceBlock_; - if (source && source.disposed) { - doLogging && console.log('source disposed, return'); - return; - } - const oldValue = this.getValue(); - if (oldValue === newValue) { - doLogging && console.log('same, doValueUpdate_, return'); - this.doValueUpdate_(newValue); - return; - } - - this.doValueUpdate_(newValue); - if (source && eventUtils.isEnabled()) { - eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))( - source, 'field', this.name || null, oldValue, newValue)); - } - if (this.isDirty_) { - this.forceRerender(); - } - doLogging && console.log(this.value_); -}; - -/** - * Process the result of validation. - * @param {*} newValue New value. - * @param {*} validatedValue Validated value. - * @return {*} New value, or an Error object. - * @private - */ -Field.prototype.processValidation_ = function(newValue, validatedValue) { - if (validatedValue === null) { - this.doValueInvalid_(newValue); - if (this.isDirty_) { - this.forceRerender(); - } - return Error(); - } - if (validatedValue !== undefined) { - newValue = validatedValue; - } - return newValue; -}; - -/** - * Get the current value of the field. - * @return {*} Current value. - */ -Field.prototype.getValue = function() { - return this.value_; -}; - -/** - * Used to validate a value. Returns input by default. Can be overridden by - * subclasses, see FieldDropdown. - * @param {*=} opt_newValue The value to be validated. - * @return {*} The validated value, same as input by default. - * @protected - */ -Field.prototype.doClassValidation_ = function(opt_newValue) { - if (opt_newValue === null || opt_newValue === undefined) { - return null; - } - return opt_newValue; -}; - -/** - * Used to update the value of a field. Can be overridden by subclasses to do - * custom storage of values/updating of external things. - * @param {*} newValue The value to be saved. - * @protected - */ -Field.prototype.doValueUpdate_ = function(newValue) { - this.value_ = newValue; - this.isDirty_ = true; -}; - -/** - * Used to notify the field an invalid value was input. Can be overridden by - * subclasses, see FieldTextInput. - * No-op by default. - * @param {*} _invalidValue The input value that was determined to be invalid. - * @protected - */ -Field.prototype.doValueInvalid_ = function(_invalidValue) { - // NOP -}; - -/** - * Handle a mouse down event on a field. - * @param {!Event} e Mouse down event. - * @protected - */ -Field.prototype.onMouseDown_ = function(e) { - if (!this.sourceBlock_ || !this.sourceBlock_.workspace) { - return; - } - const gesture = this.sourceBlock_.workspace.getGesture(e); - if (gesture) { - gesture.setStartField(this); - } -}; - -/** - * Sets the tooltip for this field. - * @param {?Tooltip.TipInfo} newTip The - * text for the tooltip, a function that returns the text for the tooltip, a - * parent object whose tooltip will be used, or null to display the tooltip - * of the parent block. To not display a tooltip pass the empty string. - */ -Field.prototype.setTooltip = function(newTip) { - if (!newTip && newTip !== '') { // If null or undefined. - newTip = this.sourceBlock_; - } - const clickTarget = this.getClickTarget_(); - if (clickTarget) { - clickTarget.tooltip = newTip; - } else { - // Field has not been initialized yet. - this.tooltip_ = newTip; - } -}; - -/** - * Returns the tooltip text for this field. - * @return {string} The tooltip text for this field. - */ -Field.prototype.getTooltip = function() { - const clickTarget = this.getClickTarget_(); - if (clickTarget) { - return Tooltip.getTooltipOfObject(clickTarget); - } - // Field has not been initialized yet. Return stashed this.tooltip_ value. - return Tooltip.getTooltipOfObject({tooltip: this.tooltip_}); -}; - -/** - * The element to bind the click handler to. If not set explicitly, defaults - * to the SVG root of the field. When this element is - * clicked on an editable field, the editor will open. - * @return {!Element} Element to bind click handler to. - * @protected - */ -Field.prototype.getClickTarget_ = function() { - return this.clickTarget_ || this.getSvgRoot(); -}; - -/** - * Return the absolute coordinates of the top-left corner of this field. - * The origin (0,0) is the top-left corner of the page body. - * @return {!Coordinate} Object with .x and .y properties. - * @protected - */ -Field.prototype.getAbsoluteXY_ = function() { - return style.getPageOffset( - /** @type {!SVGRectElement} */ (this.getClickTarget_())); -}; - -/** - * Whether this field references any Blockly variables. If true it may need to - * be handled differently during serialization and deserialization. Subclasses - * may override this. - * @return {boolean} True if this field has any variable references. - * @package - */ -Field.prototype.referencesVariables = function() { - return false; -}; - -/** - * Search through the list of inputs and their fields in order to find the - * parent input of a field. - * @return {Input} The input that the field belongs to. - * @package - */ -Field.prototype.getParentInput = function() { - let parentInput = null; - const block = this.sourceBlock_; - const inputs = block.inputList; - - for (let idx = 0; idx < block.inputList.length; idx++) { - const input = inputs[idx]; - const fieldRows = input.fieldRow; - for (let j = 0; j < fieldRows.length; j++) { - if (fieldRows[j] === this) { - parentInput = input; - break; - } - } - } - return parentInput; -}; - -/** - * Returns whether or not we should flip the field in RTL. - * @return {boolean} True if we should flip in RTL. - */ -Field.prototype.getFlipRtl = function() { - return false; -}; - -/** - * Returns whether or not the field is tab navigable. - * @return {boolean} True if the field is tab navigable. - */ -Field.prototype.isTabNavigable = function() { - return false; -}; - -/** - * Handles the given keyboard shortcut. - * @param {!ShortcutRegistry.KeyboardShortcut} _shortcut The shortcut to be - * handled. - * @return {boolean} True if the shortcut has been handled, false otherwise. - * @public - */ -Field.prototype.onShortcut = function(_shortcut) { - return false; -}; - -/** - * Add the cursor SVG to this fields SVG group. - * @param {SVGElement} cursorSvg The SVG root of the cursor to be added to the - * field group. - * @package - */ -Field.prototype.setCursorSvg = function(cursorSvg) { - if (!cursorSvg) { - this.cursorSvg_ = null; - return; - } - - this.fieldGroup_.appendChild(cursorSvg); - this.cursorSvg_ = cursorSvg; -}; - -/** - * Add the marker SVG to this fields SVG group. - * @param {SVGElement} markerSvg The SVG root of the marker to be added to the - * field group. - * @package - */ -Field.prototype.setMarkerSvg = function(markerSvg) { - if (!markerSvg) { - this.markerSvg_ = null; - return; - } - - this.fieldGroup_.appendChild(markerSvg); - this.markerSvg_ = markerSvg; -}; - -/** - * Redraw any attached marker or cursor svgs if needed. - * @protected - */ -Field.prototype.updateMarkers_ = function() { - const workspace = - /** @type {!WorkspaceSvg} */ (this.sourceBlock_.workspace); - if (workspace.keyboardAccessibilityMode && this.cursorSvg_) { - workspace.getCursor().draw(); - } - if (workspace.keyboardAccessibilityMode && this.markerSvg_) { - // TODO(#4592): Update all markers on the field. - workspace.getMarker(MarkerManager.LOCAL_MARKER).draw(); - } -}; +Field.SKIP_SETUP = new Sentinel(); exports.Field = Field; diff --git a/core/field_angle.js b/core/field_angle.js index 8454f7900..ed7bf07c0 100644 --- a/core/field_angle.js +++ b/core/field_angle.js @@ -19,110 +19,493 @@ const Css = goog.require('Blockly.Css'); const WidgetDiv = goog.require('Blockly.WidgetDiv'); const browserEvents = goog.require('Blockly.browserEvents'); const dom = goog.require('Blockly.utils.dom'); +const dropDownDiv = goog.require('Blockly.dropDownDiv'); const fieldRegistry = goog.require('Blockly.fieldRegistry'); const math = goog.require('Blockly.utils.math'); -const object = goog.require('Blockly.utils.object'); const userAgent = goog.require('Blockly.utils.userAgent'); -const {DropDownDiv} = goog.require('Blockly.DropDownDiv'); +const {Field} = goog.require('Blockly.Field'); const {FieldTextInput} = goog.require('Blockly.FieldTextInput'); const {KeyCodes} = goog.require('Blockly.utils.KeyCodes'); +/* eslint-disable-next-line no-unused-vars */ +const {Sentinel} = goog.requireType('Blockly.utils.Sentinel'); const {Svg} = goog.require('Blockly.utils.Svg'); /** * Class for an editable angle field. - * @param {string|number=} opt_value The initial value of the field. Should cast - * to a number. Defaults to 0. - * @param {Function=} opt_validator A function that is called to validate - * changes to the field's value. Takes in a number & returns a - * validated number, or null to abort the change. - * @param {Object=} opt_config A map of options used to configure the field. - * See the [field creation documentation]{@link - * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/angle#creation} - * for a list of properties this parameter supports. * @extends {FieldTextInput} - * @constructor * @alias Blockly.FieldAngle */ -const FieldAngle = function(opt_value, opt_validator, opt_config) { +class FieldAngle extends FieldTextInput { /** - * Should the angle increase as the angle picker is moved clockwise (true) - * or counterclockwise (false) - * @see FieldAngle.CLOCKWISE - * @type {boolean} + * @param {(string|number|!Sentinel)=} opt_value The initial value of + * the field. Should cast to a number. Defaults to 0. + * Also accepts Field.SKIP_SETUP if you wish to skip setup (only used by + * subclasses that want to handle configuration and setting the field + * value after their own constructors have run). + * @param {Function=} opt_validator A function that is called to validate + * changes to the field's value. Takes in a number & returns a + * validated number, or null to abort the change. + * @param {Object=} opt_config A map of options used to configure the field. + * See the [field creation documentation]{@link + * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/angle#creation} + * for a list of properties this parameter supports. + */ + constructor(opt_value, opt_validator, opt_config) { + super(Field.SKIP_SETUP); + + /** + * Should the angle increase as the angle picker is moved clockwise (true) + * or counterclockwise (false) + * @see FieldAngle.CLOCKWISE + * @type {boolean} + * @private + */ + this.clockwise_ = FieldAngle.CLOCKWISE; + + /** + * The offset of zero degrees (and all other angles). + * @see FieldAngle.OFFSET + * @type {number} + * @private + */ + this.offset_ = FieldAngle.OFFSET; + + /** + * The maximum angle to allow before wrapping. + * @see FieldAngle.WRAP + * @type {number} + * @private + */ + this.wrap_ = FieldAngle.WRAP; + + /** + * The amount to round angles to when using a mouse or keyboard nav input. + * @see FieldAngle.ROUND + * @type {number} + * @private + */ + this.round_ = FieldAngle.ROUND; + + /** + * The angle picker's SVG element. + * @type {?SVGElement} + * @private + */ + this.editor_ = null; + + /** + * The angle picker's gauge path depending on the value. + * @type {?SVGElement} + */ + this.gauge_ = null; + + /** + * The angle picker's line drawn representing the value's angle. + * @type {?SVGElement} + */ + this.line_ = null; + + /** + * The degree symbol for this field. + * @type {SVGTSpanElement} + * @protected + */ + this.symbol_ = null; + + /** + * Wrapper click event data. + * @type {?browserEvents.Data} + * @private + */ + this.clickWrapper_ = null; + + /** + * Surface click event data. + * @type {?browserEvents.Data} + * @private + */ + this.clickSurfaceWrapper_ = null; + + /** + * Surface mouse move event data. + * @type {?browserEvents.Data} + * @private + */ + this.moveSurfaceWrapper_ = null; + + /** + * Serializable fields are saved by the serializer, non-serializable fields + * are not. Editable fields should also be serializable. + * @type {boolean} + */ + this.SERIALIZABLE = true; + + if (opt_value === Field.SKIP_SETUP) return; + if (opt_config) this.configure_(opt_config); + this.setValue(opt_value); + if (opt_validator) this.setValidator(opt_validator); + } + + /** + * Configure the field based on the given map of options. + * @param {!Object} config A map of options to configure the field based on. + * @protected + * @override + */ + configure_(config) { + super.configure_(config); + + switch (config['mode']) { + case 'compass': + this.clockwise_ = true; + this.offset_ = 90; + break; + case 'protractor': + // This is the default mode, so we could do nothing. But just to + // future-proof, we'll set it anyway. + this.clockwise_ = false; + this.offset_ = 0; + break; + } + + // Allow individual settings to override the mode setting. + const clockwise = config['clockwise']; + if (typeof clockwise === 'boolean') { + this.clockwise_ = clockwise; + } + + // If these are passed as null then we should leave them on the default. + let offset = config['offset']; + if (offset !== null) { + offset = Number(offset); + if (!isNaN(offset)) { + this.offset_ = offset; + } + } + let wrap = config['wrap']; + if (wrap !== null) { + wrap = Number(wrap); + if (!isNaN(wrap)) { + this.wrap_ = wrap; + } + } + let round = config['round']; + if (round !== null) { + round = Number(round); + if (!isNaN(round)) { + this.round_ = round; + } + } + } + + /** + * Create the block UI for this field. + * @package + */ + initView() { + super.initView(); + // Add the degree symbol to the left of the number, even in RTL (issue + // #2380) + this.symbol_ = dom.createSvgElement(Svg.TSPAN, {}, null); + this.symbol_.appendChild(document.createTextNode('\u00B0')); + this.textElement_.appendChild(this.symbol_); + } + + /** + * Updates the graph when the field rerenders. + * @protected + * @override + */ + render_() { + super.render_(); + this.updateGraph_(); + } + + /** + * Create and show the angle field's editor. + * @param {Event=} opt_e Optional mouse event that triggered the field to + * open, or undefined if triggered programmatically. + * @protected + */ + showEditor_(opt_e) { + // Mobile browsers have issues with in-line textareas (focus & keyboards). + const noFocus = userAgent.MOBILE || userAgent.ANDROID || userAgent.IPAD; + super.showEditor_(opt_e, noFocus); + + this.dropdownCreate_(); + dropDownDiv.getContentDiv().appendChild(this.editor_); + + dropDownDiv.setColour( + this.sourceBlock_.style.colourPrimary, + this.sourceBlock_.style.colourTertiary); + + dropDownDiv.showPositionedByField(this, this.dropdownDispose_.bind(this)); + + this.updateGraph_(); + } + + /** + * Create the angle dropdown editor. * @private */ - this.clockwise_ = FieldAngle.CLOCKWISE; + dropdownCreate_() { + const svg = dom.createSvgElement( + Svg.SVG, { + 'xmlns': dom.SVG_NS, + 'xmlns:html': dom.HTML_NS, + 'xmlns:xlink': dom.XLINK_NS, + 'version': '1.1', + 'height': (FieldAngle.HALF * 2) + 'px', + 'width': (FieldAngle.HALF * 2) + 'px', + 'style': 'touch-action: none', + }, + null); + const circle = dom.createSvgElement( + Svg.CIRCLE, { + 'cx': FieldAngle.HALF, + 'cy': FieldAngle.HALF, + 'r': FieldAngle.RADIUS, + 'class': 'blocklyAngleCircle', + }, + svg); + this.gauge_ = + dom.createSvgElement(Svg.PATH, {'class': 'blocklyAngleGauge'}, svg); + this.line_ = dom.createSvgElement( + Svg.LINE, { + 'x1': FieldAngle.HALF, + 'y1': FieldAngle.HALF, + 'class': 'blocklyAngleLine', + }, + svg); + // Draw markers around the edge. + for (let angle = 0; angle < 360; angle += 15) { + dom.createSvgElement( + Svg.LINE, { + 'x1': FieldAngle.HALF + FieldAngle.RADIUS, + 'y1': FieldAngle.HALF, + 'x2': FieldAngle.HALF + FieldAngle.RADIUS - + (angle % 45 === 0 ? 10 : 5), + 'y2': FieldAngle.HALF, + 'class': 'blocklyAngleMarks', + 'transform': 'rotate(' + angle + ',' + FieldAngle.HALF + ',' + + FieldAngle.HALF + ')', + }, + svg); + } + + // The angle picker is different from other fields in that it updates on + // mousemove even if it's not in the middle of a drag. In future we may + // change this behaviour. + this.clickWrapper_ = + browserEvents.conditionalBind(svg, 'click', this, this.hide_); + // On touch devices, the picker's value is only updated with a drag. Add + // a click handler on the drag surface to update the value if the surface + // is clicked. + this.clickSurfaceWrapper_ = browserEvents.conditionalBind( + circle, 'click', this, this.onMouseMove_, true, true); + this.moveSurfaceWrapper_ = browserEvents.conditionalBind( + circle, 'mousemove', this, this.onMouseMove_, true, true); + this.editor_ = svg; + } /** - * The offset of zero degrees (and all other angles). - * @see FieldAngle.OFFSET - * @type {number} + * Disposes of events and DOM-references belonging to the angle editor. * @private */ - this.offset_ = FieldAngle.OFFSET; + dropdownDispose_() { + if (this.clickWrapper_) { + browserEvents.unbind(this.clickWrapper_); + this.clickWrapper_ = null; + } + if (this.clickSurfaceWrapper_) { + browserEvents.unbind(this.clickSurfaceWrapper_); + this.clickSurfaceWrapper_ = null; + } + if (this.moveSurfaceWrapper_) { + browserEvents.unbind(this.moveSurfaceWrapper_); + this.moveSurfaceWrapper_ = null; + } + this.gauge_ = null; + this.line_ = null; + } /** - * The maximum angle to allow before wrapping. - * @see FieldAngle.WRAP - * @type {number} + * Hide the editor. * @private */ - this.wrap_ = FieldAngle.WRAP; + hide_() { + dropDownDiv.hideIfOwner(this); + WidgetDiv.hide(); + } /** - * The amount to round angles to when using a mouse or keyboard nav input. - * @see FieldAngle.ROUND - * @type {number} + * Set the angle to match the mouse's position. + * @param {!Event} e Mouse move event. + * @protected + */ + onMouseMove_(e) { + // Calculate angle. + const bBox = this.gauge_.ownerSVGElement.getBoundingClientRect(); + const dx = e.clientX - bBox.left - FieldAngle.HALF; + const dy = e.clientY - bBox.top - FieldAngle.HALF; + let angle = Math.atan(-dy / dx); + if (isNaN(angle)) { + // This shouldn't happen, but let's not let this error propagate further. + return; + } + angle = math.toDegrees(angle); + // 0: East, 90: North, 180: West, 270: South. + if (dx < 0) { + angle += 180; + } else if (dy > 0) { + angle += 360; + } + + // Do offsetting. + if (this.clockwise_) { + angle = this.offset_ + 360 - angle; + } else { + angle = 360 - (this.offset_ - angle); + } + + this.displayMouseOrKeyboardValue_(angle); + } + + /** + * Handles and displays values that are input via mouse or arrow key input. + * These values need to be rounded and wrapped before being displayed so + * that the text input's value is appropriate. + * @param {number} angle New angle. * @private */ - this.round_ = FieldAngle.ROUND; - - FieldAngle.superClass_.constructor.call( - this, opt_value, opt_validator, opt_config); + displayMouseOrKeyboardValue_(angle) { + if (this.round_) { + angle = Math.round(angle / this.round_) * this.round_; + } + angle = this.wrapValue_(angle); + if (angle !== this.value_) { + this.setEditorValue_(angle); + } + } /** - * The angle picker's SVG element. - * @type {?SVGElement} + * Redraw the graph with the current angle. * @private */ - this.editor_ = null; + updateGraph_() { + if (!this.gauge_) { + return; + } + // Always display the input (i.e. getText) even if it is invalid. + let angleDegrees = Number(this.getText()) + this.offset_; + angleDegrees %= 360; + let angleRadians = math.toRadians(angleDegrees); + const path = ['M ', FieldAngle.HALF, ',', FieldAngle.HALF]; + let x2 = FieldAngle.HALF; + let y2 = FieldAngle.HALF; + if (!isNaN(angleRadians)) { + const clockwiseFlag = Number(this.clockwise_); + const angle1 = math.toRadians(this.offset_); + const x1 = Math.cos(angle1) * FieldAngle.RADIUS; + const y1 = Math.sin(angle1) * -FieldAngle.RADIUS; + if (clockwiseFlag) { + angleRadians = 2 * angle1 - angleRadians; + } + x2 += Math.cos(angleRadians) * FieldAngle.RADIUS; + y2 -= Math.sin(angleRadians) * FieldAngle.RADIUS; + // Don't ask how the flag calculations work. They just do. + let largeFlag = + Math.abs(Math.floor((angleRadians - angle1) / Math.PI) % 2); + if (clockwiseFlag) { + largeFlag = 1 - largeFlag; + } + path.push( + ' l ', x1, ',', y1, ' A ', FieldAngle.RADIUS, ',', FieldAngle.RADIUS, + ' 0 ', largeFlag, ' ', clockwiseFlag, ' ', x2, ',', y2, ' z'); + } + this.gauge_.setAttribute('d', path.join('')); + this.line_.setAttribute('x2', x2); + this.line_.setAttribute('y2', y2); + } /** - * The angle picker's gauge path depending on the value. - * @type {?SVGElement} + * Handle key down to the editor. + * @param {!Event} e Keyboard event. + * @protected + * @override */ - this.gauge_ = null; + onHtmlInputKeyDown_(e) { + super.onHtmlInputKeyDown_(e); + + let multiplier; + if (e.keyCode === KeyCodes.LEFT) { + // decrement (increment in RTL) + multiplier = this.sourceBlock_.RTL ? 1 : -1; + } else if (e.keyCode === KeyCodes.RIGHT) { + // increment (decrement in RTL) + multiplier = this.sourceBlock_.RTL ? -1 : 1; + } else if (e.keyCode === KeyCodes.DOWN) { + // decrement + multiplier = -1; + } else if (e.keyCode === KeyCodes.UP) { + // increment + multiplier = 1; + } + if (multiplier) { + const value = /** @type {number} */ (this.getValue()); + this.displayMouseOrKeyboardValue_(value + (multiplier * this.round_)); + e.preventDefault(); + e.stopPropagation(); + } + } /** - * The angle picker's line drawn representing the value's angle. - * @type {?SVGElement} + * Ensure that the input value is a valid angle. + * @param {*=} opt_newValue The input value. + * @return {?number} A valid angle, or null if invalid. + * @protected + * @override */ - this.line_ = null; + doClassValidation_(opt_newValue) { + const value = Number(opt_newValue); + if (isNaN(value) || !isFinite(value)) { + return null; + } + return this.wrapValue_(value); + } /** - * Wrapper click event data. - * @type {?browserEvents.Data} + * Wraps the value so that it is in the range (-360 + wrap, wrap). + * @param {number} value The value to wrap. + * @return {number} The wrapped value. * @private */ - this.clickWrapper_ = null; + wrapValue_(value) { + value %= 360; + if (value < 0) { + value += 360; + } + if (value > this.wrap_) { + value -= 360; + } + return value; + } /** - * Surface click event data. - * @type {?browserEvents.Data} - * @private + * Construct a FieldAngle from a JSON arg object. + * @param {!Object} options A JSON object with options (angle). + * @return {!FieldAngle} The new field instance. + * @package + * @nocollapse + * @override */ - this.clickSurfaceWrapper_ = null; - - /** - * Surface mouse move event data. - * @type {?browserEvents.Data} - * @private - */ - this.moveSurfaceWrapper_ = null; -}; -object.inherits(FieldAngle, FieldTextInput); - + static fromJson(options) { + // `this` might be a subclass of FieldAngle if that class doesn't override + // the static fromJson method. + return new this(options['angle'], undefined, options); + } +} /** * The default value for this field. @@ -131,26 +514,6 @@ object.inherits(FieldAngle, FieldTextInput); */ FieldAngle.prototype.DEFAULT_VALUE = 0; -/** - * Construct a FieldAngle from a JSON arg object. - * @param {!Object} options A JSON object with options (angle). - * @return {!FieldAngle} The new field instance. - * @package - * @nocollapse - */ -FieldAngle.fromJson = function(options) { - // `this` might be a subclass of FieldAngle if that class doesn't override - // the static fromJson method. - return new this(options['angle'], undefined, options); -}; - -/** - * Serializable fields are saved by the XML renderer, non-serializable fields - * are not. Editable fields should also be serializable. - * @type {boolean} - */ -FieldAngle.prototype.SERIALIZABLE = true; - /** * The default amount to round angles to when using a mouse or keyboard nav * input. Must be a positive integer to support keyboard navigation. @@ -193,377 +556,34 @@ FieldAngle.WRAP = 360; */ FieldAngle.RADIUS = FieldAngle.HALF - 1; -/** - * Configure the field based on the given map of options. - * @param {!Object} config A map of options to configure the field based on. - * @protected - * @override - */ -FieldAngle.prototype.configure_ = function(config) { - FieldAngle.superClass_.configure_.call(this, config); - - switch (config['mode']) { - case 'compass': - this.clockwise_ = true; - this.offset_ = 90; - break; - case 'protractor': - // This is the default mode, so we could do nothing. But just to - // future-proof, we'll set it anyway. - this.clockwise_ = false; - this.offset_ = 0; - break; - } - - // Allow individual settings to override the mode setting. - const clockwise = config['clockwise']; - if (typeof clockwise === 'boolean') { - this.clockwise_ = clockwise; - } - - // If these are passed as null then we should leave them on the default. - let offset = config['offset']; - if (offset !== null) { - offset = Number(offset); - if (!isNaN(offset)) { - this.offset_ = offset; - } - } - let wrap = config['wrap']; - if (wrap !== null) { - wrap = Number(wrap); - if (!isNaN(wrap)) { - this.wrap_ = wrap; - } - } - let round = config['round']; - if (round !== null) { - round = Number(round); - if (!isNaN(round)) { - this.round_ = round; - } - } -}; - -/** - * Create the block UI for this field. - * @package - */ -FieldAngle.prototype.initView = function() { - FieldAngle.superClass_.initView.call(this); - // Add the degree symbol to the left of the number, even in RTL (issue #2380) - this.symbol_ = dom.createSvgElement(Svg.TSPAN, {}, null); - this.symbol_.appendChild(document.createTextNode('\u00B0')); - this.textElement_.appendChild(this.symbol_); -}; - -/** - * Updates the graph when the field rerenders. - * @protected - * @override - */ -FieldAngle.prototype.render_ = function() { - FieldAngle.superClass_.render_.call(this); - this.updateGraph_(); -}; - -/** - * Create and show the angle field's editor. - * @param {Event=} opt_e Optional mouse event that triggered the field to open, - * or undefined if triggered programmatically. - * @protected - */ -FieldAngle.prototype.showEditor_ = function(opt_e) { - // Mobile browsers have issues with in-line textareas (focus & keyboards). - const noFocus = userAgent.MOBILE || userAgent.ANDROID || userAgent.IPAD; - FieldAngle.superClass_.showEditor_.call(this, opt_e, noFocus); - - this.dropdownCreate_(); - DropDownDiv.getContentDiv().appendChild(this.editor_); - - DropDownDiv.setColour( - this.sourceBlock_.style.colourPrimary, - this.sourceBlock_.style.colourTertiary); - - DropDownDiv.showPositionedByField(this, this.dropdownDispose_.bind(this)); - - this.updateGraph_(); -}; - -/** - * Create the angle dropdown editor. - * @private - */ -FieldAngle.prototype.dropdownCreate_ = function() { - const svg = dom.createSvgElement( - Svg.SVG, { - 'xmlns': dom.SVG_NS, - 'xmlns:html': dom.HTML_NS, - 'xmlns:xlink': dom.XLINK_NS, - 'version': '1.1', - 'height': (FieldAngle.HALF * 2) + 'px', - 'width': (FieldAngle.HALF * 2) + 'px', - 'style': 'touch-action: none', - }, - null); - const circle = dom.createSvgElement( - Svg.CIRCLE, { - 'cx': FieldAngle.HALF, - 'cy': FieldAngle.HALF, - 'r': FieldAngle.RADIUS, - 'class': 'blocklyAngleCircle', - }, - svg); - this.gauge_ = - dom.createSvgElement(Svg.PATH, {'class': 'blocklyAngleGauge'}, svg); - this.line_ = dom.createSvgElement( - Svg.LINE, { - 'x1': FieldAngle.HALF, - 'y1': FieldAngle.HALF, - 'class': 'blocklyAngleLine', - }, - svg); - // Draw markers around the edge. - for (let angle = 0; angle < 360; angle += 15) { - dom.createSvgElement( - Svg.LINE, { - 'x1': FieldAngle.HALF + FieldAngle.RADIUS, - 'y1': FieldAngle.HALF, - 'x2': - FieldAngle.HALF + FieldAngle.RADIUS - (angle % 45 === 0 ? 10 : 5), - 'y2': FieldAngle.HALF, - 'class': 'blocklyAngleMarks', - 'transform': 'rotate(' + angle + ',' + FieldAngle.HALF + ',' + - FieldAngle.HALF + ')', - }, - svg); - } - - // The angle picker is different from other fields in that it updates on - // mousemove even if it's not in the middle of a drag. In future we may - // change this behaviour. - this.clickWrapper_ = - browserEvents.conditionalBind(svg, 'click', this, this.hide_); - // On touch devices, the picker's value is only updated with a drag. Add - // a click handler on the drag surface to update the value if the surface - // is clicked. - this.clickSurfaceWrapper_ = browserEvents.conditionalBind( - circle, 'click', this, this.onMouseMove_, true, true); - this.moveSurfaceWrapper_ = browserEvents.conditionalBind( - circle, 'mousemove', this, this.onMouseMove_, true, true); - this.editor_ = svg; -}; - -/** - * Disposes of events and DOM-references belonging to the angle editor. - * @private - */ -FieldAngle.prototype.dropdownDispose_ = function() { - if (this.clickWrapper_) { - browserEvents.unbind(this.clickWrapper_); - this.clickWrapper_ = null; - } - if (this.clickSurfaceWrapper_) { - browserEvents.unbind(this.clickSurfaceWrapper_); - this.clickSurfaceWrapper_ = null; - } - if (this.moveSurfaceWrapper_) { - browserEvents.unbind(this.moveSurfaceWrapper_); - this.moveSurfaceWrapper_ = null; - } - this.gauge_ = null; - this.line_ = null; -}; - -/** - * Hide the editor. - * @private - */ -FieldAngle.prototype.hide_ = function() { - DropDownDiv.hideIfOwner(this); - WidgetDiv.hide(); -}; - -/** - * Set the angle to match the mouse's position. - * @param {!Event} e Mouse move event. - * @protected - */ -FieldAngle.prototype.onMouseMove_ = function(e) { - // Calculate angle. - const bBox = this.gauge_.ownerSVGElement.getBoundingClientRect(); - const dx = e.clientX - bBox.left - FieldAngle.HALF; - const dy = e.clientY - bBox.top - FieldAngle.HALF; - let angle = Math.atan(-dy / dx); - if (isNaN(angle)) { - // This shouldn't happen, but let's not let this error propagate further. - return; - } - angle = math.toDegrees(angle); - // 0: East, 90: North, 180: West, 270: South. - if (dx < 0) { - angle += 180; - } else if (dy > 0) { - angle += 360; - } - - // Do offsetting. - if (this.clockwise_) { - angle = this.offset_ + 360 - angle; - } else { - angle = 360 - (this.offset_ - angle); - } - - this.displayMouseOrKeyboardValue_(angle); -}; - -/** - * Handles and displays values that are input via mouse or arrow key input. - * These values need to be rounded and wrapped before being displayed so - * that the text input's value is appropriate. - * @param {number} angle New angle. - * @private - */ -FieldAngle.prototype.displayMouseOrKeyboardValue_ = function(angle) { - if (this.round_) { - angle = Math.round(angle / this.round_) * this.round_; - } - angle = this.wrapValue_(angle); - if (angle !== this.value_) { - this.setEditorValue_(angle); - } -}; - -/** - * Redraw the graph with the current angle. - * @private - */ -FieldAngle.prototype.updateGraph_ = function() { - if (!this.gauge_) { - return; - } - // Always display the input (i.e. getText) even if it is invalid. - let angleDegrees = Number(this.getText()) + this.offset_; - angleDegrees %= 360; - let angleRadians = math.toRadians(angleDegrees); - const path = ['M ', FieldAngle.HALF, ',', FieldAngle.HALF]; - let x2 = FieldAngle.HALF; - let y2 = FieldAngle.HALF; - if (!isNaN(angleRadians)) { - const clockwiseFlag = Number(this.clockwise_); - const angle1 = math.toRadians(this.offset_); - const x1 = Math.cos(angle1) * FieldAngle.RADIUS; - const y1 = Math.sin(angle1) * -FieldAngle.RADIUS; - if (clockwiseFlag) { - angleRadians = 2 * angle1 - angleRadians; - } - x2 += Math.cos(angleRadians) * FieldAngle.RADIUS; - y2 -= Math.sin(angleRadians) * FieldAngle.RADIUS; - // Don't ask how the flag calculations work. They just do. - let largeFlag = Math.abs(Math.floor((angleRadians - angle1) / Math.PI) % 2); - if (clockwiseFlag) { - largeFlag = 1 - largeFlag; - } - path.push( - ' l ', x1, ',', y1, ' A ', FieldAngle.RADIUS, ',', FieldAngle.RADIUS, - ' 0 ', largeFlag, ' ', clockwiseFlag, ' ', x2, ',', y2, ' z'); - } - this.gauge_.setAttribute('d', path.join('')); - this.line_.setAttribute('x2', x2); - this.line_.setAttribute('y2', y2); -}; - -/** - * Handle key down to the editor. - * @param {!Event} e Keyboard event. - * @protected - * @override - */ -FieldAngle.prototype.onHtmlInputKeyDown_ = function(e) { - FieldAngle.superClass_.onHtmlInputKeyDown_.call(this, e); - - let multiplier; - if (e.keyCode === KeyCodes.LEFT) { - // decrement (increment in RTL) - multiplier = this.sourceBlock_.RTL ? 1 : -1; - } else if (e.keyCode === KeyCodes.RIGHT) { - // increment (decrement in RTL) - multiplier = this.sourceBlock_.RTL ? -1 : 1; - } else if (e.keyCode === KeyCodes.DOWN) { - // decrement - multiplier = -1; - } else if (e.keyCode === KeyCodes.UP) { - // increment - multiplier = 1; - } - if (multiplier) { - const value = /** @type {number} */ (this.getValue()); - this.displayMouseOrKeyboardValue_(value + (multiplier * this.round_)); - e.preventDefault(); - e.stopPropagation(); - } -}; - -/** - * Ensure that the input value is a valid angle. - * @param {*=} opt_newValue The input value. - * @return {?number} A valid angle, or null if invalid. - * @protected - * @override - */ -FieldAngle.prototype.doClassValidation_ = function(opt_newValue) { - const value = Number(opt_newValue); - if (isNaN(value) || !isFinite(value)) { - return null; - } - return this.wrapValue_(value); -}; - -/** - * Wraps the value so that it is in the range (-360 + wrap, wrap). - * @param {number} value The value to wrap. - * @return {number} The wrapped value. - * @private - */ -FieldAngle.prototype.wrapValue_ = function(value) { - value %= 360; - if (value < 0) { - value += 360; - } - if (value > this.wrap_) { - value -= 360; - } - return value; -}; - /** * CSS for angle field. See css.js for use. */ Css.register(` - .blocklyAngleCircle { - stroke: #444; - stroke-width: 1; - fill: #ddd; - fill-opacity: .8; - } +.blocklyAngleCircle { + stroke: #444; + stroke-width: 1; + fill: #ddd; + fill-opacity: .8; +} - .blocklyAngleMarks { - stroke: #444; - stroke-width: 1; - } +.blocklyAngleMarks { + stroke: #444; + stroke-width: 1; +} - .blocklyAngleGauge { - fill: #f88; - fill-opacity: .8; - pointer-events: none; - } +.blocklyAngleGauge { + fill: #f88; + fill-opacity: .8; + pointer-events: none; +} - .blocklyAngleLine { - stroke: #f00; - stroke-width: 2; - stroke-linecap: round; - pointer-events: none; - } +.blocklyAngleLine { + stroke: #f00; + stroke-width: 2; + stroke-linecap: round; + pointer-events: none; +} `); fieldRegistry.register('field_angle', FieldAngle); diff --git a/core/field_checkbox.js b/core/field_checkbox.js index ddbe38711..457b01ed8 100644 --- a/core/field_checkbox.js +++ b/core/field_checkbox.js @@ -17,41 +17,224 @@ goog.module('Blockly.FieldCheckbox'); const dom = goog.require('Blockly.utils.dom'); const fieldRegistry = goog.require('Blockly.fieldRegistry'); -const object = goog.require('Blockly.utils.object'); const {Field} = goog.require('Blockly.Field'); +/* eslint-disable-next-line no-unused-vars */ +const {Sentinel} = goog.requireType('Blockly.utils.Sentinel'); /** @suppress {extraRequire} */ goog.require('Blockly.Events.BlockChange'); /** * Class for a checkbox field. - * @param {string|boolean=} opt_value The initial value of the field. Should - * either be 'TRUE', 'FALSE' or a boolean. Defaults to 'FALSE'. - * @param {Function=} opt_validator A function that is called to validate - * changes to the field's value. Takes in a value ('TRUE' or 'FALSE') & - * returns a validated value ('TRUE' or 'FALSE'), or null to abort the - * change. - * @param {Object=} opt_config A map of options used to configure the field. - * See the [field creation documentation]{@link - * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/checkbox#creation} - * for a list of properties this parameter supports. * @extends {Field} - * @constructor * @alias Blockly.FieldCheckbox */ -const FieldCheckbox = function(opt_value, opt_validator, opt_config) { +class FieldCheckbox extends Field { /** - * Character for the check mark. Used to apply a different check mark - * character to individual fields. - * @type {?string} + * @param {(string|boolean|!Sentinel)=} opt_value The initial value of + * the field. Should either be 'TRUE', 'FALSE' or a boolean. Defaults to + * 'FALSE'. + * Also accepts Field.SKIP_SETUP if you wish to skip setup (only used by + * subclasses that want to handle configuration and setting the field + * value after their own constructors have run). + * @param {Function=} opt_validator A function that is called to validate + * changes to the field's value. Takes in a value ('TRUE' or 'FALSE') & + * returns a validated value ('TRUE' or 'FALSE'), or null to abort the + * change. + * @param {Object=} opt_config A map of options used to configure the field. + * See the [field creation documentation]{@link + * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/checkbox#creation} + * for a list of properties this parameter supports. + */ + constructor(opt_value, opt_validator, opt_config) { + super(Field.SKIP_SETUP); + + /** + * Character for the check mark. Used to apply a different check mark + * character to individual fields. + * @type {string} + * @private + */ + this.checkChar_ = FieldCheckbox.CHECK_CHAR; + + /** + * Serializable fields are saved by the serializer, non-serializable fields + * are not. Editable fields should also be serializable. + * @type {boolean} + */ + this.SERIALIZABLE = true; + + /** + * Mouse cursor style when over the hotspot that initiates editability. + * @type {string} + */ + this.CURSOR = 'default'; + + if (opt_value === Field.SKIP_SETUP) return; + if (opt_config) this.configure_(opt_config); + this.setValue(opt_value); + if (opt_validator) this.setValidator(opt_validator); + } + + /** + * Configure the field based on the given map of options. + * @param {!Object} config A map of options to configure the field based on. + * @protected + * @override + */ + configure_(config) { + super.configure_(config); + if (config['checkCharacter']) { + this.checkChar_ = config['checkCharacter']; + } + } + + /** + * Saves this field's value. + * @return {*} The boolean value held by this field. + * @override + * @package + */ + saveState() { + const legacyState = this.saveLegacyState(FieldCheckbox); + if (legacyState !== null) { + return legacyState; + } + return this.getValueBoolean(); + } + + /** + * Create the block UI for this checkbox. + * @package + */ + initView() { + super.initView(); + + dom.addClass( + /** @type {!SVGTextElement} **/ (this.textElement_), 'blocklyCheckbox'); + this.textElement_.style.display = this.value_ ? 'block' : 'none'; + } + + /** + * @override + */ + render_() { + if (this.textContent_) { + this.textContent_.nodeValue = this.getDisplayText_(); + } + this.updateSize_(this.getConstants().FIELD_CHECKBOX_X_OFFSET); + } + + /** + * @override + */ + getDisplayText_() { + return this.checkChar_; + } + + /** + * Set the character used for the check mark. + * @param {?string} character The character to use for the check mark, or + * null to use the default. + */ + setCheckCharacter(character) { + this.checkChar_ = character || FieldCheckbox.CHECK_CHAR; + this.forceRerender(); + } + + /** + * Toggle the state of the checkbox on click. + * @protected + */ + showEditor_() { + this.setValue(!this.value_); + } + + /** + * Ensure that the input value is valid ('TRUE' or 'FALSE'). + * @param {*=} opt_newValue The input value. + * @return {?string} A valid value ('TRUE' or 'FALSE), or null if invalid. + * @protected + */ + doClassValidation_(opt_newValue) { + if (opt_newValue === true || opt_newValue === 'TRUE') { + return 'TRUE'; + } + if (opt_newValue === false || opt_newValue === 'FALSE') { + return 'FALSE'; + } + return null; + } + + /** + * Update the value of the field, and update the checkElement. + * @param {*} newValue The value to be saved. The default validator guarantees + * that this is a either 'TRUE' or 'FALSE'. + * @protected + */ + doValueUpdate_(newValue) { + this.value_ = this.convertValueToBool_(newValue); + // Update visual. + if (this.textElement_) { + this.textElement_.style.display = this.value_ ? 'block' : 'none'; + } + } + + /** + * Get the value of this field, either 'TRUE' or 'FALSE'. + * @return {string} The value of this field. + */ + getValue() { + return this.value_ ? 'TRUE' : 'FALSE'; + } + + /** + * Get the boolean value of this field. + * @return {boolean} The boolean value of this field. + */ + getValueBoolean() { + return /** @type {boolean} */ (this.value_); + } + + /** + * Get the text of this field. Used when the block is collapsed. + * @return {string} Text representing the value of this field + * ('true' or 'false'). + */ + getText() { + return String(this.convertValueToBool_(this.value_)); + } + + /** + * Convert a value into a pure boolean. + * + * Converts 'TRUE' to true and 'FALSE' to false correctly, everything else + * is cast to a boolean. + * @param {*} value The value to convert. + * @return {boolean} The converted value. * @private */ - this.checkChar_ = null; + convertValueToBool_(value) { + if (typeof value === 'string') { + return value === 'TRUE'; + } else { + return !!value; + } + } - FieldCheckbox.superClass_.constructor.call( - this, opt_value, opt_validator, opt_config); -}; -object.inherits(FieldCheckbox, Field); + /** + * Construct a FieldCheckbox from a JSON arg object. + * @param {!Object} options A JSON object with options (checked). + * @return {!FieldCheckbox} The new field instance. + * @package + * @nocollapse + */ + static fromJson(options) { + // `this` might be a subclass of FieldCheckbox if that class doesn't + // 'override' the static fromJson method. + return new this(options['checked'], undefined, options); + } +} /** * The default value for this field. @@ -60,19 +243,6 @@ object.inherits(FieldCheckbox, Field); */ FieldCheckbox.prototype.DEFAULT_VALUE = false; -/** - * Construct a FieldCheckbox from a JSON arg object. - * @param {!Object} options A JSON object with options (checked). - * @return {!FieldCheckbox} The new field instance. - * @package - * @nocollapse - */ -FieldCheckbox.fromJson = function(options) { - // `this` might be a subclass of FieldCheckbox if that class doesn't override - // the static fromJson method. - return new this(options['checked'], undefined, options); -}; - /** * Default character for the checkmark. * @type {string} @@ -80,164 +250,6 @@ FieldCheckbox.fromJson = function(options) { */ FieldCheckbox.CHECK_CHAR = '\u2713'; -/** - * Serializable fields are saved by the XML renderer, non-serializable fields - * are not. Editable fields should also be serializable. - * @type {boolean} - */ -FieldCheckbox.prototype.SERIALIZABLE = true; - -/** - * Mouse cursor style when over the hotspot that initiates editability. - */ -FieldCheckbox.prototype.CURSOR = 'default'; - -/** - * Configure the field based on the given map of options. - * @param {!Object} config A map of options to configure the field based on. - * @protected - * @override - */ -FieldCheckbox.prototype.configure_ = function(config) { - FieldCheckbox.superClass_.configure_.call(this, config); - if (config['checkCharacter']) { - this.checkChar_ = config['checkCharacter']; - } -}; - -/** - * Saves this field's value. - * @return {*} The boolean value held by this field. - * @override - * @package - */ -FieldCheckbox.prototype.saveState = function() { - const legacyState = this.saveLegacyState(FieldCheckbox); - if (legacyState !== null) { - return legacyState; - } - return this.getValueBoolean(); -}; - -/** - * Create the block UI for this checkbox. - * @package - */ -FieldCheckbox.prototype.initView = function() { - FieldCheckbox.superClass_.initView.call(this); - - dom.addClass( - /** @type {!SVGTextElement} **/ (this.textElement_), 'blocklyCheckbox'); - this.textElement_.style.display = this.value_ ? 'block' : 'none'; -}; - -/** - * @override - */ -FieldCheckbox.prototype.render_ = function() { - if (this.textContent_) { - this.textContent_.nodeValue = this.getDisplayText_(); - } - this.updateSize_(this.getConstants().FIELD_CHECKBOX_X_OFFSET); -}; - -/** - * @override - */ -FieldCheckbox.prototype.getDisplayText_ = function() { - return this.checkChar_ || FieldCheckbox.CHECK_CHAR; -}; - -/** - * Set the character used for the check mark. - * @param {?string} character The character to use for the check mark, or - * null to use the default. - */ -FieldCheckbox.prototype.setCheckCharacter = function(character) { - this.checkChar_ = character; - this.forceRerender(); -}; - -/** - * Toggle the state of the checkbox on click. - * @protected - */ -FieldCheckbox.prototype.showEditor_ = function() { - this.setValue(!this.value_); -}; - -/** - * Ensure that the input value is valid ('TRUE' or 'FALSE'). - * @param {*=} opt_newValue The input value. - * @return {?string} A valid value ('TRUE' or 'FALSE), or null if invalid. - * @protected - */ -FieldCheckbox.prototype.doClassValidation_ = function(opt_newValue) { - if (opt_newValue === true || opt_newValue === 'TRUE') { - return 'TRUE'; - } - if (opt_newValue === false || opt_newValue === 'FALSE') { - return 'FALSE'; - } - return null; -}; - -/** - * Update the value of the field, and update the checkElement. - * @param {*} newValue The value to be saved. The default validator guarantees - * that this is a either 'TRUE' or 'FALSE'. - * @protected - */ -FieldCheckbox.prototype.doValueUpdate_ = function(newValue) { - this.value_ = this.convertValueToBool_(newValue); - // Update visual. - if (this.textElement_) { - this.textElement_.style.display = this.value_ ? 'block' : 'none'; - } -}; - -/** - * Get the value of this field, either 'TRUE' or 'FALSE'. - * @return {string} The value of this field. - */ -FieldCheckbox.prototype.getValue = function() { - return this.value_ ? 'TRUE' : 'FALSE'; -}; - -/** - * Get the boolean value of this field. - * @return {boolean} The boolean value of this field. - */ -FieldCheckbox.prototype.getValueBoolean = function() { - return /** @type {boolean} */ (this.value_); -}; - -/** - * Get the text of this field. Used when the block is collapsed. - * @return {string} Text representing the value of this field - * ('true' or 'false'). - */ -FieldCheckbox.prototype.getText = function() { - return String(this.convertValueToBool_(this.value_)); -}; - -/** - * Convert a value into a pure boolean. - * - * Converts 'TRUE' to true and 'FALSE' to false correctly, everything else - * is cast to a boolean. - * @param {*} value The value to convert. - * @return {boolean} The converted value. - * @private - */ -FieldCheckbox.prototype.convertValueToBool_ = function(value) { - if (typeof value === 'string') { - return value === 'TRUE'; - } else { - return !!value; - } -}; - fieldRegistry.register('field_checkbox', FieldCheckbox); exports.FieldCheckbox = FieldCheckbox; diff --git a/core/field_colour.js b/core/field_colour.js index 4b4c037a9..02095f25b 100644 --- a/core/field_colour.js +++ b/core/field_colour.js @@ -20,12 +20,13 @@ const aria = goog.require('Blockly.utils.aria'); const browserEvents = goog.require('Blockly.browserEvents'); const colour = goog.require('Blockly.utils.colour'); const dom = goog.require('Blockly.utils.dom'); +const dropDownDiv = goog.require('Blockly.dropDownDiv'); const fieldRegistry = goog.require('Blockly.fieldRegistry'); const idGenerator = goog.require('Blockly.utils.idGenerator'); -const object = goog.require('Blockly.utils.object'); -const {DropDownDiv} = goog.require('Blockly.DropDownDiv'); const {Field} = goog.require('Blockly.Field'); const {KeyCodes} = goog.require('Blockly.utils.KeyCodes'); +/* eslint-disable-next-line no-unused-vars */ +const {Sentinel} = goog.requireType('Blockly.utils.Sentinel'); const {Size} = goog.require('Blockly.utils.Size'); /** @suppress {extraRequire} */ goog.require('Blockly.Events.BlockChange'); @@ -33,219 +34,539 @@ goog.require('Blockly.Events.BlockChange'); /** * Class for a colour input field. - * @param {string=} opt_value The initial value of the field. Should be in - * '#rrggbb' format. Defaults to the first value in the default colour array. - * @param {Function=} opt_validator A function that is called to validate - * changes to the field's value. Takes in a colour string & returns a - * validated colour string ('#rrggbb' format), or null to abort the - * change.Blockly. - * @param {Object=} opt_config A map of options used to configure the field. - * See the [field creation documentation]{@link - * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/colour} - * for a list of properties this parameter supports. * @extends {Field} - * @constructor * @alias Blockly.FieldColour */ -const FieldColour = function(opt_value, opt_validator, opt_config) { - FieldColour.superClass_.constructor.call( - this, opt_value, opt_validator, opt_config); - +class FieldColour extends Field { /** - * The field's colour picker element. - * @type {?Element} - * @private + * @param {(string|!Sentinel)=} opt_value The initial value of the + * field. Should be in '#rrggbb' format. Defaults to the first value in + * the default colour array. + * Also accepts Field.SKIP_SETUP if you wish to skip setup (only used by + * subclasses that want to handle configuration and setting the field + * value after their own constructors have run). + * @param {Function=} opt_validator A function that is called to validate + * changes to the field's value. Takes in a colour string & returns a + * validated colour string ('#rrggbb' format), or null to abort the + * change.Blockly. + * @param {Object=} opt_config A map of options used to configure the field. + * See the [field creation documentation]{@link + * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/colour} + * for a list of properties this parameter supports. */ - this.picker_ = null; + constructor(opt_value, opt_validator, opt_config) { + super(Field.SKIP_SETUP); - /** - * Index of the currently highlighted element. - * @type {?number} - * @private - */ - this.highlightedIndex_ = null; + /** + * The field's colour picker element. + * @type {?Element} + * @private + */ + this.picker_ = null; - /** - * Mouse click event data. - * @type {?browserEvents.Data} - * @private - */ - this.onClickWrapper_ = null; + /** + * Index of the currently highlighted element. + * @type {?number} + * @private + */ + this.highlightedIndex_ = null; - /** - * Mouse move event data. - * @type {?browserEvents.Data} - * @private - */ - this.onMouseMoveWrapper_ = null; + /** + * Mouse click event data. + * @type {?browserEvents.Data} + * @private + */ + this.onClickWrapper_ = null; - /** - * Mouse enter event data. - * @type {?browserEvents.Data} - * @private - */ - this.onMouseEnterWrapper_ = null; + /** + * Mouse move event data. + * @type {?browserEvents.Data} + * @private + */ + this.onMouseMoveWrapper_ = null; - /** - * Mouse leave event data. - * @type {?browserEvents.Data} - * @private - */ - this.onMouseLeaveWrapper_ = null; + /** + * Mouse enter event data. + * @type {?browserEvents.Data} + * @private + */ + this.onMouseEnterWrapper_ = null; - /** - * Key down event data. - * @type {?browserEvents.Data} - * @private - */ - this.onKeyDownWrapper_ = null; -}; -object.inherits(FieldColour, Field); + /** + * Mouse leave event data. + * @type {?browserEvents.Data} + * @private + */ + this.onMouseLeaveWrapper_ = null; -/** - * Construct a FieldColour from a JSON arg object. - * @param {!Object} options A JSON object with options (colour). - * @return {!FieldColour} The new field instance. - * @package - * @nocollapse - */ -FieldColour.fromJson = function(options) { - // `this` might be a subclass of FieldColour if that class doesn't override - // the static fromJson method. - return new this(options['colour'], undefined, options); -}; + /** + * Key down event data. + * @type {?browserEvents.Data} + * @private + */ + this.onKeyDownWrapper_ = null; -/** - * Serializable fields are saved by the XML renderer, non-serializable fields - * are not. Editable fields should also be serializable. - * @type {boolean} - */ -FieldColour.prototype.SERIALIZABLE = true; + /** + * Serializable fields are saved by the serializer, non-serializable fields + * are not. Editable fields should also be serializable. + * @type {boolean} + */ + this.SERIALIZABLE = true; -/** - * Mouse cursor style when over the hotspot that initiates the editor. - */ -FieldColour.prototype.CURSOR = 'default'; + /** + * Mouse cursor style when over the hotspot that initiates the editor. + * @type {string} + */ + this.CURSOR = 'default'; -/** - * Used to tell if the field needs to be rendered the next time the block is - * rendered. Colour fields are statically sized, and only need to be - * rendered at initialization. - * @type {boolean} - * @protected - */ -FieldColour.prototype.isDirty_ = false; + /** + * Used to tell if the field needs to be rendered the next time the block is + * rendered. Colour fields are statically sized, and only need to be + * rendered at initialization. + * @type {boolean} + * @protected + */ + this.isDirty_ = false; -/** - * Array of colours used by this field. If null, use the global list. - * @type {Array} - * @private - */ -FieldColour.prototype.colours_ = null; + /** + * Array of colours used by this field. If null, use the global list. + * @type {Array} + * @private + */ + this.colours_ = null; -/** - * Array of colour tooltips used by this field. If null, use the global list. - * @type {Array} - * @private - */ -FieldColour.prototype.titles_ = null; + /** + * Array of colour tooltips used by this field. If null, use the global + * list. + * @type {Array} + * @private + */ + this.titles_ = null; -/** - * Number of colour columns used by this field. If 0, use the global setting. - * By default use the global constants for columns. - * @type {number} - * @private - */ -FieldColour.prototype.columns_ = 0; + /** + * Number of colour columns used by this field. If 0, use the global + * setting. By default use the global constants for columns. + * @type {number} + * @private + */ + this.columns_ = 0; -/** - * Configure the field based on the given map of options. - * @param {!Object} config A map of options to configure the field based on. - * @protected - * @override - */ -FieldColour.prototype.configure_ = function(config) { - FieldColour.superClass_.configure_.call(this, config); - if (config['colourOptions']) { - this.colours_ = config['colourOptions']; - this.titles_ = config['colourTitles']; + if (opt_value === Field.SKIP_SETUP) return; + if (opt_config) this.configure_(opt_config); + this.setValue(opt_value); + if (opt_validator) this.setValidator(opt_validator); } - if (config['columns']) { - this.columns_ = config['columns']; - } -}; -/** - * Create the block UI for this colour field. - * @package - */ -FieldColour.prototype.initView = function() { - this.size_ = new Size( - this.getConstants().FIELD_COLOUR_DEFAULT_WIDTH, - this.getConstants().FIELD_COLOUR_DEFAULT_HEIGHT); - if (!this.getConstants().FIELD_COLOUR_FULL_BLOCK) { - this.createBorderRect_(); - this.borderRect_.style['fillOpacity'] = '1'; - } else { - this.clickTarget_ = this.sourceBlock_.getSvgRoot(); - } -}; - -/** - * @override - */ -FieldColour.prototype.applyColour = function() { - if (!this.getConstants().FIELD_COLOUR_FULL_BLOCK) { - if (this.borderRect_) { - this.borderRect_.style.fill = /** @type {string} */ (this.getValue()); + /** + * Configure the field based on the given map of options. + * @param {!Object} config A map of options to configure the field based on. + * @protected + * @override + */ + configure_(config) { + super.configure_(config); + if (config['colourOptions']) { + this.colours_ = config['colourOptions']; + this.titles_ = config['colourTitles']; + } + if (config['columns']) { + this.columns_ = config['columns']; } - } else { - this.sourceBlock_.pathObject.svgPath.setAttribute('fill', this.getValue()); - this.sourceBlock_.pathObject.svgPath.setAttribute('stroke', '#fff'); } -}; -/** - * Ensure that the input value is a valid colour. - * @param {*=} opt_newValue The input value. - * @return {?string} A valid colour, or null if invalid. - * @protected - */ -FieldColour.prototype.doClassValidation_ = function(opt_newValue) { - if (typeof opt_newValue !== 'string') { - return null; + /** + * Create the block UI for this colour field. + * @package + */ + initView() { + this.size_ = new Size( + this.getConstants().FIELD_COLOUR_DEFAULT_WIDTH, + this.getConstants().FIELD_COLOUR_DEFAULT_HEIGHT); + if (!this.getConstants().FIELD_COLOUR_FULL_BLOCK) { + this.createBorderRect_(); + this.borderRect_.style['fillOpacity'] = '1'; + } else { + this.clickTarget_ = this.sourceBlock_.getSvgRoot(); + } } - return colour.parse(opt_newValue); -}; -/** - * Update the value of this colour field, and update the displayed colour. - * @param {*} newValue The value to be saved. The default validator guarantees - * that this is a colour in '#rrggbb' format. - * @protected - */ -FieldColour.prototype.doValueUpdate_ = function(newValue) { - this.value_ = newValue; - if (this.borderRect_) { - this.borderRect_.style.fill = /** @type {string} */ (newValue); - } else if (this.sourceBlock_ && this.sourceBlock_.rendered) { - this.sourceBlock_.pathObject.svgPath.setAttribute('fill', newValue); - this.sourceBlock_.pathObject.svgPath.setAttribute('stroke', '#fff'); + /** + * @override + */ + applyColour() { + if (!this.getConstants().FIELD_COLOUR_FULL_BLOCK) { + if (this.borderRect_) { + this.borderRect_.style.fill = /** @type {string} */ (this.getValue()); + } + } else { + this.sourceBlock_.pathObject.svgPath.setAttribute( + 'fill', this.getValue()); + this.sourceBlock_.pathObject.svgPath.setAttribute('stroke', '#fff'); + } } -}; -/** - * Get the text for this field. Used when the block is collapsed. - * @return {string} Text representing the value of this field. - */ -FieldColour.prototype.getText = function() { - let colour = /** @type {string} */ (this.value_); - // Try to use #rgb format if possible, rather than #rrggbb. - if (/^#(.)\1(.)\2(.)\3$/.test(colour)) { - colour = '#' + colour[1] + colour[3] + colour[5]; + /** + * Ensure that the input value is a valid colour. + * @param {*=} opt_newValue The input value. + * @return {?string} A valid colour, or null if invalid. + * @protected + */ + doClassValidation_(opt_newValue) { + if (typeof opt_newValue !== 'string') { + return null; + } + return colour.parse(opt_newValue); } - return colour; -}; + + /** + * Update the value of this colour field, and update the displayed colour. + * @param {*} newValue The value to be saved. The default validator guarantees + * that this is a colour in '#rrggbb' format. + * @protected + */ + doValueUpdate_(newValue) { + this.value_ = newValue; + if (this.borderRect_) { + this.borderRect_.style.fill = /** @type {string} */ (newValue); + } else if (this.sourceBlock_ && this.sourceBlock_.rendered) { + this.sourceBlock_.pathObject.svgPath.setAttribute('fill', newValue); + this.sourceBlock_.pathObject.svgPath.setAttribute('stroke', '#fff'); + } + } + + /** + * Get the text for this field. Used when the block is collapsed. + * @return {string} Text representing the value of this field. + */ + getText() { + let colour = /** @type {string} */ (this.value_); + // Try to use #rgb format if possible, rather than #rrggbb. + if (/^#(.)\1(.)\2(.)\3$/.test(colour)) { + colour = '#' + colour[1] + colour[3] + colour[5]; + } + return colour; + } + + /** + * Set a custom colour grid for this field. + * @param {Array} colours Array of colours for this block, + * or null to use default (FieldColour.COLOURS). + * @param {Array=} opt_titles Optional array of colour tooltips, + * or null to use default (FieldColour.TITLES). + * @return {!FieldColour} Returns itself (for method chaining). + */ + setColours(colours, opt_titles) { + this.colours_ = colours; + if (opt_titles) { + this.titles_ = opt_titles; + } + return this; + } + + /** + * Set a custom grid size for this field. + * @param {number} columns Number of columns for this block, + * or 0 to use default (FieldColour.COLUMNS). + * @return {!FieldColour} Returns itself (for method chaining). + */ + setColumns(columns) { + this.columns_ = columns; + return this; + } + + /** + * Create and show the colour field's editor. + * @protected + */ + showEditor_() { + this.dropdownCreate_(); + dropDownDiv.getContentDiv().appendChild(this.picker_); + + dropDownDiv.showPositionedByField(this, this.dropdownDispose_.bind(this)); + + // Focus so we can start receiving keyboard events. + this.picker_.focus({preventScroll: true}); + } + + /** + * Handle a click on a colour cell. + * @param {!MouseEvent} e Mouse event. + * @private + */ + onClick_(e) { + const cell = /** @type {!Element} */ (e.target); + const colour = cell && cell.label; + if (colour !== null) { + this.setValue(colour); + dropDownDiv.hideIfOwner(this); + } + } + + /** + * Handle a key down event. Navigate around the grid with the + * arrow keys. Enter selects the highlighted colour. + * @param {!KeyboardEvent} e Keyboard event. + * @private + */ + onKeyDown_(e) { + let handled = false; + if (e.keyCode === KeyCodes.UP) { + this.moveHighlightBy_(0, -1); + handled = true; + } else if (e.keyCode === KeyCodes.DOWN) { + this.moveHighlightBy_(0, 1); + handled = true; + } else if (e.keyCode === KeyCodes.LEFT) { + this.moveHighlightBy_(-1, 0); + handled = true; + } else if (e.keyCode === KeyCodes.RIGHT) { + this.moveHighlightBy_(1, 0); + handled = true; + } else if (e.keyCode === KeyCodes.ENTER) { + // Select the highlighted colour. + const highlighted = this.getHighlighted_(); + if (highlighted) { + const colour = highlighted && highlighted.label; + if (colour !== null) { + this.setValue(colour); + } + } + dropDownDiv.hideWithoutAnimation(); + handled = true; + } + if (handled) { + e.stopPropagation(); + } + } + + /** + * Move the currently highlighted position by dx and dy. + * @param {number} dx Change of x + * @param {number} dy Change of y + * @private + */ + moveHighlightBy_(dx, dy) { + const colours = this.colours_ || FieldColour.COLOURS; + const columns = this.columns_ || FieldColour.COLUMNS; + + // Get the current x and y coordinates + let x = this.highlightedIndex_ % columns; + let y = Math.floor(this.highlightedIndex_ / columns); + + // Add the offset + x += dx; + y += dy; + + if (dx < 0) { + // Move left one grid cell, even in RTL. + // Loop back to the end of the previous row if we have room. + if (x < 0 && y > 0) { + x = columns - 1; + y--; + } else if (x < 0) { + x = 0; + } + } else if (dx > 0) { + // Move right one grid cell, even in RTL. + // Loop to the start of the next row, if there's room. + if (x > columns - 1 && y < Math.floor(colours.length / columns) - 1) { + x = 0; + y++; + } else if (x > columns - 1) { + x--; + } + } else if (dy < 0) { + // Move up one grid cell, stop at the top. + if (y < 0) { + y = 0; + } + } else if (dy > 0) { + // Move down one grid cell, stop at the bottom. + if (y > Math.floor(colours.length / columns) - 1) { + y = Math.floor(colours.length / columns) - 1; + } + } + + // Move the highlight to the new coordinates. + const cell = + /** @type {!Element} */ (this.picker_.childNodes[y].childNodes[x]); + const index = (y * columns) + x; + this.setHighlightedCell_(cell, index); + } + + /** + * Handle a mouse move event. Highlight the hovered colour. + * @param {!MouseEvent} e Mouse event. + * @private + */ + onMouseMove_(e) { + const cell = /** @type {!Element} */ (e.target); + const index = cell && Number(cell.getAttribute('data-index')); + if (index !== null && index !== this.highlightedIndex_) { + this.setHighlightedCell_(cell, index); + } + } + + /** + * Handle a mouse enter event. Focus the picker. + * @private + */ + onMouseEnter_() { + this.picker_.focus({preventScroll: true}); + } + + /** + * Handle a mouse leave event. Blur the picker and unhighlight + * the currently highlighted colour. + * @private + */ + onMouseLeave_() { + this.picker_.blur(); + const highlighted = this.getHighlighted_(); + if (highlighted) { + dom.removeClass(highlighted, 'blocklyColourHighlighted'); + } + } + + /** + * Returns the currently highlighted item (if any). + * @return {?HTMLElement} Highlighted item (null if none). + * @private + */ + getHighlighted_() { + const columns = this.columns_ || FieldColour.COLUMNS; + const x = this.highlightedIndex_ % columns; + const y = Math.floor(this.highlightedIndex_ / columns); + const row = this.picker_.childNodes[y]; + if (!row) { + return null; + } + const col = /** @type {HTMLElement} */ (row.childNodes[x]); + return col; + } + + /** + * Update the currently highlighted cell. + * @param {!Element} cell the new cell to highlight + * @param {number} index the index of the new cell + * @private + */ + setHighlightedCell_(cell, index) { + // Unhighlight the current item. + const highlighted = this.getHighlighted_(); + if (highlighted) { + dom.removeClass(highlighted, 'blocklyColourHighlighted'); + } + // Highlight new item. + dom.addClass(cell, 'blocklyColourHighlighted'); + // Set new highlighted index. + this.highlightedIndex_ = index; + + // Update accessibility roles. + aria.setState( + /** @type {!Element} */ (this.picker_), aria.State.ACTIVEDESCENDANT, + cell.getAttribute('id')); + } + + /** + * Create a colour picker dropdown editor. + * @private + */ + dropdownCreate_() { + const columns = this.columns_ || FieldColour.COLUMNS; + const colours = this.colours_ || FieldColour.COLOURS; + const titles = this.titles_ || FieldColour.TITLES; + const selectedColour = this.getValue(); + // Create the palette. + const table = document.createElement('table'); + table.className = 'blocklyColourTable'; + table.tabIndex = 0; + table.dir = 'ltr'; + aria.setRole(table, aria.Role.GRID); + aria.setState(table, aria.State.EXPANDED, true); + aria.setState( + table, aria.State.ROWCOUNT, Math.floor(colours.length / columns)); + aria.setState(table, aria.State.COLCOUNT, columns); + let row; + for (let i = 0; i < colours.length; i++) { + if (i % columns === 0) { + row = document.createElement('tr'); + aria.setRole(row, aria.Role.ROW); + table.appendChild(row); + } + const cell = document.createElement('td'); + row.appendChild(cell); + cell.label = colours[i]; // This becomes the value, if clicked. + cell.title = titles[i] || colours[i]; + cell.id = idGenerator.getNextUniqueId(); + cell.setAttribute('data-index', i); + aria.setRole(cell, aria.Role.GRIDCELL); + aria.setState(cell, aria.State.LABEL, colours[i]); + aria.setState(cell, aria.State.SELECTED, colours[i] === selectedColour); + cell.style.backgroundColor = colours[i]; + if (colours[i] === selectedColour) { + cell.className = 'blocklyColourSelected'; + this.highlightedIndex_ = i; + } + } + + // Configure event handler on the table to listen for any event in a cell. + this.onClickWrapper_ = browserEvents.conditionalBind( + table, 'click', this, this.onClick_, true); + this.onMouseMoveWrapper_ = browserEvents.conditionalBind( + table, 'mousemove', this, this.onMouseMove_, true); + this.onMouseEnterWrapper_ = browserEvents.conditionalBind( + table, 'mouseenter', this, this.onMouseEnter_, true); + this.onMouseLeaveWrapper_ = browserEvents.conditionalBind( + table, 'mouseleave', this, this.onMouseLeave_, true); + this.onKeyDownWrapper_ = + browserEvents.conditionalBind(table, 'keydown', this, this.onKeyDown_); + + this.picker_ = table; + } + + /** + * Disposes of events and DOM-references belonging to the colour editor. + * @private + */ + dropdownDispose_() { + if (this.onClickWrapper_) { + browserEvents.unbind(this.onClickWrapper_); + this.onClickWrapper_ = null; + } + if (this.onMouseMoveWrapper_) { + browserEvents.unbind(this.onMouseMoveWrapper_); + this.onMouseMoveWrapper_ = null; + } + if (this.onMouseEnterWrapper_) { + browserEvents.unbind(this.onMouseEnterWrapper_); + this.onMouseEnterWrapper_ = null; + } + if (this.onMouseLeaveWrapper_) { + browserEvents.unbind(this.onMouseLeaveWrapper_); + this.onMouseLeaveWrapper_ = null; + } + if (this.onKeyDownWrapper_) { + browserEvents.unbind(this.onKeyDownWrapper_); + this.onKeyDownWrapper_ = null; + } + this.picker_ = null; + this.highlightedIndex_ = null; + } + + /** + * Construct a FieldColour from a JSON arg object. + * @param {!Object} options A JSON object with options (colour). + * @return {!FieldColour} The new field instance. + * @package + * @nocollapse + */ + static fromJson(options) { + // `this` might be a subclass of FieldColour if that class doesn't override + // the static fromJson method. + return new this(options['colour'], undefined, options); + } +} /** * An array of colour strings for the palette. @@ -357,345 +678,38 @@ FieldColour.TITLES = []; */ FieldColour.COLUMNS = 7; -/** - * Set a custom colour grid for this field. - * @param {Array} colours Array of colours for this block, - * or null to use default (FieldColour.COLOURS). - * @param {Array=} opt_titles Optional array of colour tooltips, - * or null to use default (FieldColour.TITLES). - * @return {!FieldColour} Returns itself (for method chaining). - */ -FieldColour.prototype.setColours = function(colours, opt_titles) { - this.colours_ = colours; - if (opt_titles) { - this.titles_ = opt_titles; - } - return this; -}; - -/** - * Set a custom grid size for this field. - * @param {number} columns Number of columns for this block, - * or 0 to use default (FieldColour.COLUMNS). - * @return {!FieldColour} Returns itself (for method chaining). - */ -FieldColour.prototype.setColumns = function(columns) { - this.columns_ = columns; - return this; -}; - -/** - * Create and show the colour field's editor. - * @protected - */ -FieldColour.prototype.showEditor_ = function() { - this.dropdownCreate_(); - DropDownDiv.getContentDiv().appendChild(this.picker_); - - DropDownDiv.showPositionedByField(this, this.dropdownDispose_.bind(this)); - - // Focus so we can start receiving keyboard events. - this.picker_.focus({preventScroll: true}); -}; - -/** - * Handle a click on a colour cell. - * @param {!MouseEvent} e Mouse event. - * @private - */ -FieldColour.prototype.onClick_ = function(e) { - const cell = /** @type {!Element} */ (e.target); - const colour = cell && cell.label; - if (colour !== null) { - this.setValue(colour); - DropDownDiv.hideIfOwner(this); - } -}; - -/** - * Handle a key down event. Navigate around the grid with the - * arrow keys. Enter selects the highlighted colour. - * @param {!KeyboardEvent} e Keyboard event. - * @private - */ -FieldColour.prototype.onKeyDown_ = function(e) { - let handled = false; - if (e.keyCode === KeyCodes.UP) { - this.moveHighlightBy_(0, -1); - handled = true; - } else if (e.keyCode === KeyCodes.DOWN) { - this.moveHighlightBy_(0, 1); - handled = true; - } else if (e.keyCode === KeyCodes.LEFT) { - this.moveHighlightBy_(-1, 0); - handled = true; - } else if (e.keyCode === KeyCodes.RIGHT) { - this.moveHighlightBy_(1, 0); - handled = true; - } else if (e.keyCode === KeyCodes.ENTER) { - // Select the highlighted colour. - const highlighted = this.getHighlighted_(); - if (highlighted) { - const colour = highlighted && highlighted.label; - if (colour !== null) { - this.setValue(colour); - } - } - DropDownDiv.hideWithoutAnimation(); - handled = true; - } - if (handled) { - e.stopPropagation(); - } -}; - -/** - * Move the currently highlighted position by dx and dy. - * @param {number} dx Change of x - * @param {number} dy Change of y - * @private - */ -FieldColour.prototype.moveHighlightBy_ = function(dx, dy) { - const colours = this.colours_ || FieldColour.COLOURS; - const columns = this.columns_ || FieldColour.COLUMNS; - - // Get the current x and y coordinates - let x = this.highlightedIndex_ % columns; - let y = Math.floor(this.highlightedIndex_ / columns); - - // Add the offset - x += dx; - y += dy; - - if (dx < 0) { - // Move left one grid cell, even in RTL. - // Loop back to the end of the previous row if we have room. - if (x < 0 && y > 0) { - x = columns - 1; - y--; - } else if (x < 0) { - x = 0; - } - } else if (dx > 0) { - // Move right one grid cell, even in RTL. - // Loop to the start of the next row, if there's room. - if (x > columns - 1 && y < Math.floor(colours.length / columns) - 1) { - x = 0; - y++; - } else if (x > columns - 1) { - x--; - } - } else if (dy < 0) { - // Move up one grid cell, stop at the top. - if (y < 0) { - y = 0; - } - } else if (dy > 0) { - // Move down one grid cell, stop at the bottom. - if (y > Math.floor(colours.length / columns) - 1) { - y = Math.floor(colours.length / columns) - 1; - } - } - - // Move the highlight to the new coordinates. - const cell = - /** @type {!Element} */ (this.picker_.childNodes[y].childNodes[x]); - const index = (y * columns) + x; - this.setHighlightedCell_(cell, index); -}; - -/** - * Handle a mouse move event. Highlight the hovered colour. - * @param {!MouseEvent} e Mouse event. - * @private - */ -FieldColour.prototype.onMouseMove_ = function(e) { - const cell = /** @type {!Element} */ (e.target); - const index = cell && Number(cell.getAttribute('data-index')); - if (index !== null && index !== this.highlightedIndex_) { - this.setHighlightedCell_(cell, index); - } -}; - -/** - * Handle a mouse enter event. Focus the picker. - * @private - */ -FieldColour.prototype.onMouseEnter_ = function() { - this.picker_.focus({preventScroll: true}); -}; - -/** - * Handle a mouse leave event. Blur the picker and unhighlight - * the currently highlighted colour. - * @private - */ -FieldColour.prototype.onMouseLeave_ = function() { - this.picker_.blur(); - const highlighted = this.getHighlighted_(); - if (highlighted) { - dom.removeClass(highlighted, 'blocklyColourHighlighted'); - } -}; - -/** - * Returns the currently highlighted item (if any). - * @return {?HTMLElement} Highlighted item (null if none). - * @private - */ -FieldColour.prototype.getHighlighted_ = function() { - const columns = this.columns_ || FieldColour.COLUMNS; - const x = this.highlightedIndex_ % columns; - const y = Math.floor(this.highlightedIndex_ / columns); - const row = this.picker_.childNodes[y]; - if (!row) { - return null; - } - const col = /** @type {HTMLElement} */ (row.childNodes[x]); - return col; -}; - -/** - * Update the currently highlighted cell. - * @param {!Element} cell the new cell to highlight - * @param {number} index the index of the new cell - * @private - */ -FieldColour.prototype.setHighlightedCell_ = function(cell, index) { - // Unhighlight the current item. - const highlighted = this.getHighlighted_(); - if (highlighted) { - dom.removeClass(highlighted, 'blocklyColourHighlighted'); - } - // Highlight new item. - dom.addClass(cell, 'blocklyColourHighlighted'); - // Set new highlighted index. - this.highlightedIndex_ = index; - - // Update accessibility roles. - aria.setState( - /** @type {!Element} */ (this.picker_), aria.State.ACTIVEDESCENDANT, - cell.getAttribute('id')); -}; - -/** - * Create a colour picker dropdown editor. - * @private - */ -FieldColour.prototype.dropdownCreate_ = function() { - const columns = this.columns_ || FieldColour.COLUMNS; - const colours = this.colours_ || FieldColour.COLOURS; - const titles = this.titles_ || FieldColour.TITLES; - const selectedColour = this.getValue(); - // Create the palette. - const table = document.createElement('table'); - table.className = 'blocklyColourTable'; - table.tabIndex = 0; - table.dir = 'ltr'; - aria.setRole(table, aria.Role.GRID); - aria.setState(table, aria.State.EXPANDED, true); - aria.setState( - table, aria.State.ROWCOUNT, Math.floor(colours.length / columns)); - aria.setState(table, aria.State.COLCOUNT, columns); - let row; - for (let i = 0; i < colours.length; i++) { - if (i % columns === 0) { - row = document.createElement('tr'); - aria.setRole(row, aria.Role.ROW); - table.appendChild(row); - } - const cell = document.createElement('td'); - row.appendChild(cell); - cell.label = colours[i]; // This becomes the value, if clicked. - cell.title = titles[i] || colours[i]; - cell.id = idGenerator.getNextUniqueId(); - cell.setAttribute('data-index', i); - aria.setRole(cell, aria.Role.GRIDCELL); - aria.setState(cell, aria.State.LABEL, colours[i]); - aria.setState(cell, aria.State.SELECTED, colours[i] === selectedColour); - cell.style.backgroundColor = colours[i]; - if (colours[i] === selectedColour) { - cell.className = 'blocklyColourSelected'; - this.highlightedIndex_ = i; - } - } - - // Configure event handler on the table to listen for any event in a cell. - this.onClickWrapper_ = - browserEvents.conditionalBind(table, 'click', this, this.onClick_, true); - this.onMouseMoveWrapper_ = browserEvents.conditionalBind( - table, 'mousemove', this, this.onMouseMove_, true); - this.onMouseEnterWrapper_ = browserEvents.conditionalBind( - table, 'mouseenter', this, this.onMouseEnter_, true); - this.onMouseLeaveWrapper_ = browserEvents.conditionalBind( - table, 'mouseleave', this, this.onMouseLeave_, true); - this.onKeyDownWrapper_ = - browserEvents.conditionalBind(table, 'keydown', this, this.onKeyDown_); - - this.picker_ = table; -}; - -/** - * Disposes of events and DOM-references belonging to the colour editor. - * @private - */ -FieldColour.prototype.dropdownDispose_ = function() { - if (this.onClickWrapper_) { - browserEvents.unbind(this.onClickWrapper_); - this.onClickWrapper_ = null; - } - if (this.onMouseMoveWrapper_) { - browserEvents.unbind(this.onMouseMoveWrapper_); - this.onMouseMoveWrapper_ = null; - } - if (this.onMouseEnterWrapper_) { - browserEvents.unbind(this.onMouseEnterWrapper_); - this.onMouseEnterWrapper_ = null; - } - if (this.onMouseLeaveWrapper_) { - browserEvents.unbind(this.onMouseLeaveWrapper_); - this.onMouseLeaveWrapper_ = null; - } - if (this.onKeyDownWrapper_) { - browserEvents.unbind(this.onKeyDownWrapper_); - this.onKeyDownWrapper_ = null; - } - this.picker_ = null; - this.highlightedIndex_ = null; -}; - /** * CSS for colour picker. See css.js for use. */ Css.register(` - .blocklyColourTable { - border-collapse: collapse; - display: block; - outline: none; - padding: 1px; - } +.blocklyColourTable { + border-collapse: collapse; + display: block; + outline: none; + padding: 1px; +} - .blocklyColourTable>tr>td { - border: .5px solid #888; - box-sizing: border-box; - cursor: pointer; - display: inline-block; - height: 20px; - padding: 0; - width: 20px; - } +.blocklyColourTable>tr>td { + border: .5px solid #888; + box-sizing: border-box; + cursor: pointer; + display: inline-block; + height: 20px; + padding: 0; + width: 20px; +} - .blocklyColourTable>tr>td.blocklyColourHighlighted { - border-color: #eee; - box-shadow: 2px 2px 7px 2px rgba(0,0,0,.3); - position: relative; - } +.blocklyColourTable>tr>td.blocklyColourHighlighted { + border-color: #eee; + box-shadow: 2px 2px 7px 2px rgba(0,0,0,.3); + position: relative; +} - .blocklyColourSelected, .blocklyColourSelected:hover { - border-color: #eee !important; - outline: 1px solid #333; - position: relative; - } +.blocklyColourSelected, .blocklyColourSelected:hover { + border-color: #eee !important; + outline: 1px solid #333; + position: relative; +} `); fieldRegistry.register('field_colour', FieldColour); diff --git a/core/field_dropdown.js b/core/field_dropdown.js index 618ecd7b6..98d2c14d7 100644 --- a/core/field_dropdown.js +++ b/core/field_dropdown.js @@ -21,121 +21,691 @@ goog.module('Blockly.FieldDropdown'); const aria = goog.require('Blockly.utils.aria'); const dom = goog.require('Blockly.utils.dom'); +const dropDownDiv = goog.require('Blockly.dropDownDiv'); const fieldRegistry = goog.require('Blockly.fieldRegistry'); -const object = goog.require('Blockly.utils.object'); const parsing = goog.require('Blockly.utils.parsing'); const userAgent = goog.require('Blockly.utils.userAgent'); const utilsString = goog.require('Blockly.utils.string'); const {Coordinate} = goog.require('Blockly.utils.Coordinate'); -const {DropDownDiv} = goog.require('Blockly.DropDownDiv'); const {Field} = goog.require('Blockly.Field'); const {MenuItem} = goog.require('Blockly.MenuItem'); const {Menu} = goog.require('Blockly.Menu'); +/* eslint-disable-next-line no-unused-vars */ +const {Sentinel} = goog.requireType('Blockly.utils.Sentinel'); const {Svg} = goog.require('Blockly.utils.Svg'); /** * Class for an editable dropdown field. - * @param {(!Array|!Function)} menuGenerator A non-empty array of - * options for a dropdown list, or a function which generates these options. - * @param {Function=} opt_validator A function that is called to validate - * changes to the field's value. Takes in a language-neutral dropdown - * option & returns a validated language-neutral dropdown option, or null to - * abort the change. - * @param {Object=} opt_config A map of options used to configure the field. - * See the [field creation documentation]{@link - * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/dropdown#creation} - * for a list of properties this parameter supports. * @extends {Field} - * @constructor - * @throws {TypeError} If `menuGenerator` options are incorrectly structured. * @alias Blockly.FieldDropdown */ -const FieldDropdown = function(menuGenerator, opt_validator, opt_config) { - if (typeof menuGenerator !== 'function') { - validateOptions(menuGenerator); +class FieldDropdown extends Field { + /** + * @param {(!Array|!Function|!Sentinel)} menuGenerator + * A non-empty array of options for a dropdown list, or a function which + * generates these options. + * Also accepts Field.SKIP_SETUP if you wish to skip setup (only used by + * subclasses that want to handle configuration and setting the field + * value after their own constructors have run). + * @param {Function=} opt_validator A function that is called to validate + * changes to the field's value. Takes in a language-neutral dropdown + * option & returns a validated language-neutral dropdown option, or null + * to abort the change. + * @param {Object=} opt_config A map of options used to configure the field. + * See the [field creation documentation]{@link + * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/dropdown#creation} + * for a list of properties this parameter supports. + * @throws {TypeError} If `menuGenerator` options are incorrectly structured. + */ + constructor(menuGenerator, opt_validator, opt_config) { + super(Field.SKIP_SETUP); + + /** + * A reference to the currently selected menu item. + * @type {?MenuItem} + * @private + */ + this.selectedMenuItem_ = null; + + /** + * The dropdown menu. + * @type {?Menu} + * @protected + */ + this.menu_ = null; + + /** + * SVG image element if currently selected option is an image, or null. + * @type {?SVGImageElement} + * @private + */ + this.imageElement_ = null; + + /** + * Tspan based arrow element. + * @type {?SVGTSpanElement} + * @private + */ + this.arrow_ = null; + + /** + * SVG based arrow element. + * @type {?SVGElement} + * @private + */ + this.svgArrow_ = null; + + /** + * Serializable fields are saved by the serializer, non-serializable fields + * are not. Editable fields should also be serializable. + * @type {boolean} + */ + this.SERIALIZABLE = true; + + /** + * Mouse cursor style when over the hotspot that initiates the editor. + * @type {string} + */ + this.CURSOR = 'default'; + + + // If we pass SKIP_SETUP, don't do *anything* with the menu generator. + if (menuGenerator === Field.SKIP_SETUP) return; + + if (Array.isArray(menuGenerator)) { + validateOptions(menuGenerator); + } + + /** + * An array of options for a dropdown list, + * or a function which generates these options. + * @type {(!Array|!function(this:FieldDropdown): !Array)} + * @protected + */ + this.menuGenerator_ = + /** + * @type {(!Array| + * !function(this:FieldDropdown):!Array)} + */ + (menuGenerator); + + /** + * A cache of the most recently generated options. + * @type {Array>} + * @private + */ + this.generatedOptions_ = null; + + /** + * The prefix field label, of common words set after options are trimmed. + * @type {?string} + * @package + */ + this.prefixField = null; + + /** + * The suffix field label, of common words set after options are trimmed. + * @type {?string} + * @package + */ + this.suffixField = null; + + this.trimOptions_(); + + /** + * The currently selected option. The field is initialized with the + * first option selected. + * @type {!Array} + * @private + */ + this.selectedOption_ = this.getOptions(false)[0]; + + if (opt_config) this.configure_(opt_config); + this.setValue(this.selectedOption_[1]); + if (opt_validator) this.setValidator(opt_validator); } /** - * An array of options for a dropdown list, - * or a function which generates these options. - * @type {(!Array| - * !function(this:FieldDropdown): !Array)} - * @protected - */ - this.menuGenerator_ = menuGenerator; - - /** - * A cache of the most recently generated options. - * @type {Array>} - * @private - */ - this.generatedOptions_ = null; - - /** - * The prefix field label, of common words set after options are trimmed. - * @type {?string} + * Sets the field's value based on the given XML element. Should only be + * called by Blockly.Xml. + * @param {!Element} fieldElement The element containing info about the + * field's state. * @package */ - this.prefixField = null; + fromXml(fieldElement) { + if (this.isOptionListDynamic()) { + this.getOptions(false); + } + this.setValue(fieldElement.textContent); + } /** - * The suffix field label, of common words set after options are trimmed. - * @type {?string} + * Sets the field's value based on the given state. + * @param {*} state The state to apply to the dropdown field. + * @override * @package */ - this.suffixField = null; - - this.trimOptions_(); + loadState(state) { + if (this.loadLegacyState(FieldDropdown, state)) { + return; + } + if (this.isOptionListDynamic()) { + this.getOptions(false); + } + this.setValue(state); + } /** - * The currently selected option. The field is initialized with the - * first option selected. - * @type {!Object} - * @private + * Create the block UI for this dropdown. + * @package */ - this.selectedOption_ = this.getOptions(false)[0]; + initView() { + if (this.shouldAddBorderRect_()) { + this.createBorderRect_(); + } else { + this.clickTarget_ = this.sourceBlock_.getSvgRoot(); + } + this.createTextElement_(); - // Call parent's constructor. - FieldDropdown.superClass_.constructor.call( - this, this.selectedOption_[1], opt_validator, opt_config); + this.imageElement_ = dom.createSvgElement(Svg.IMAGE, {}, this.fieldGroup_); + + if (this.getConstants().FIELD_DROPDOWN_SVG_ARROW) { + this.createSVGArrow_(); + } else { + this.createTextArrow_(); + } + + if (this.borderRect_) { + dom.addClass(this.borderRect_, 'blocklyDropdownRect'); + } + } /** - * A reference to the currently selected menu item. - * @type {?MenuItem} - * @private - */ - this.selectedMenuItem_ = null; - - /** - * The dropdown menu. - * @type {?Menu} + * Whether or not the dropdown should add a border rect. + * @return {boolean} True if the dropdown field should add a border rect. * @protected */ - this.menu_ = null; + shouldAddBorderRect_() { + return !this.getConstants().FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW || + (this.getConstants().FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW && + !this.sourceBlock_.isShadow()); + } /** - * SVG image element if currently selected option is an image, or null. - * @type {?SVGImageElement} - * @private + * Create a tspan based arrow. + * @protected */ - this.imageElement_ = null; + createTextArrow_() { + this.arrow_ = dom.createSvgElement(Svg.TSPAN, {}, this.textElement_); + this.arrow_.appendChild(document.createTextNode( + this.sourceBlock_.RTL ? FieldDropdown.ARROW_CHAR + ' ' : + ' ' + FieldDropdown.ARROW_CHAR)); + if (this.sourceBlock_.RTL) { + this.textElement_.insertBefore(this.arrow_, this.textContent_); + } else { + this.textElement_.appendChild(this.arrow_); + } + } /** - * Tspan based arrow element. - * @type {?SVGTSpanElement} - * @private + * Create an SVG based arrow. + * @protected */ - this.arrow_ = null; + createSVGArrow_() { + this.svgArrow_ = dom.createSvgElement( + Svg.IMAGE, { + 'height': this.getConstants().FIELD_DROPDOWN_SVG_ARROW_SIZE + 'px', + 'width': this.getConstants().FIELD_DROPDOWN_SVG_ARROW_SIZE + 'px', + }, + this.fieldGroup_); + this.svgArrow_.setAttributeNS( + dom.XLINK_NS, 'xlink:href', + this.getConstants().FIELD_DROPDOWN_SVG_ARROW_DATAURI); + } /** - * SVG based arrow element. - * @type {?SVGElement} + * Create a dropdown menu under the text. + * @param {Event=} opt_e Optional mouse event that triggered the field to + * open, or undefined if triggered programmatically. + * @protected + */ + showEditor_(opt_e) { + this.dropdownCreate_(); + if (opt_e && typeof opt_e.clientX === 'number') { + this.menu_.openingCoords = new Coordinate(opt_e.clientX, opt_e.clientY); + } else { + this.menu_.openingCoords = null; + } + + // Remove any pre-existing elements in the dropdown. + dropDownDiv.clearContent(); + // Element gets created in render. + this.menu_.render(dropDownDiv.getContentDiv()); + const menuElement = /** @type {!Element} */ (this.menu_.getElement()); + dom.addClass(menuElement, 'blocklyDropdownMenu'); + + if (this.getConstants().FIELD_DROPDOWN_COLOURED_DIV) { + const primaryColour = (this.sourceBlock_.isShadow()) ? + this.sourceBlock_.getParent().getColour() : + this.sourceBlock_.getColour(); + const borderColour = (this.sourceBlock_.isShadow()) ? + this.sourceBlock_.getParent().style.colourTertiary : + this.sourceBlock_.style.colourTertiary; + dropDownDiv.setColour(primaryColour, borderColour); + } + + dropDownDiv.showPositionedByField(this, this.dropdownDispose_.bind(this)); + + // Focusing needs to be handled after the menu is rendered and positioned. + // Otherwise it will cause a page scroll to get the misplaced menu in + // view. See issue #1329. + this.menu_.focus(); + + if (this.selectedMenuItem_) { + this.menu_.setHighlighted(this.selectedMenuItem_); + } + + this.applyColour(); + } + + /** + * Create the dropdown editor. * @private */ - this.svgArrow_ = null; -}; -object.inherits(FieldDropdown, Field); + dropdownCreate_() { + const menu = new Menu(); + menu.setRole(aria.Role.LISTBOX); + this.menu_ = menu; + + const options = this.getOptions(false); + this.selectedMenuItem_ = null; + for (let i = 0; i < options.length; i++) { + let content = options[i][0]; // Human-readable text or image. + const value = options[i][1]; // Language-neutral value. + if (typeof content === 'object') { + // An image, not text. + const image = new Image(content['width'], content['height']); + image.src = content['src']; + image.alt = content['alt'] || ''; + content = image; + } + const menuItem = new MenuItem(content, value); + menuItem.setRole(aria.Role.OPTION); + menuItem.setRightToLeft(this.sourceBlock_.RTL); + menuItem.setCheckable(true); + menu.addChild(menuItem); + menuItem.setChecked(value === this.value_); + if (value === this.value_) { + this.selectedMenuItem_ = menuItem; + } + menuItem.onAction(this.handleMenuActionEvent_, this); + } + } + + /** + * Disposes of events and DOM-references belonging to the dropdown editor. + * @private + */ + dropdownDispose_() { + if (this.menu_) { + this.menu_.dispose(); + } + this.menu_ = null; + this.selectedMenuItem_ = null; + this.applyColour(); + } + + /** + * Handle an action in the dropdown menu. + * @param {!MenuItem} menuItem The MenuItem selected within menu. + * @private + */ + handleMenuActionEvent_(menuItem) { + dropDownDiv.hideIfOwner(this, true); + this.onItemSelected_(/** @type {!Menu} */ (this.menu_), menuItem); + } + + /** + * Handle the selection of an item in the dropdown menu. + * @param {!Menu} menu The Menu component clicked. + * @param {!MenuItem} menuItem The MenuItem selected within menu. + * @protected + */ + onItemSelected_(menu, menuItem) { + this.setValue(menuItem.getValue()); + } + + /** + * Factor out common words in statically defined options. + * Create prefix and/or suffix labels. + * @private + */ + trimOptions_() { + const options = this.menuGenerator_; + if (!Array.isArray(options)) { + return; + } + let hasImages = false; + + // Localize label text and image alt text. + for (let i = 0; i < options.length; i++) { + const label = options[i][0]; + if (typeof label === 'string') { + options[i][0] = parsing.replaceMessageReferences(label); + } else { + if (label.alt !== null) { + options[i][0].alt = parsing.replaceMessageReferences(label.alt); + } + hasImages = true; + } + } + if (hasImages || options.length < 2) { + return; // Do nothing if too few items or at least one label is an image. + } + const strings = []; + for (let i = 0; i < options.length; i++) { + strings.push(options[i][0]); + } + const shortest = utilsString.shortestStringLength(strings); + const prefixLength = utilsString.commonWordPrefix(strings, shortest); + const suffixLength = utilsString.commonWordSuffix(strings, shortest); + if (!prefixLength && !suffixLength) { + return; + } + if (shortest <= prefixLength + suffixLength) { + // One or more strings will entirely vanish if we proceed. Abort. + return; + } + if (prefixLength) { + this.prefixField = strings[0].substring(0, prefixLength - 1); + } + if (suffixLength) { + this.suffixField = strings[0].substr(1 - suffixLength); + } + + this.menuGenerator_ = + FieldDropdown.applyTrim_(options, prefixLength, suffixLength); + } + + /** + * @return {boolean} True if the option list is generated by a function. + * Otherwise false. + */ + isOptionListDynamic() { + return typeof this.menuGenerator_ === 'function'; + } + + /** + * Return a list of the options for this dropdown. + * @param {boolean=} opt_useCache For dynamic options, whether or not to use + * the cached options or to re-generate them. + * @return {!Array} A non-empty array of option tuples: + * (human-readable text or image, language-neutral name). + * @throws {TypeError} If generated options are incorrectly structured. + */ + getOptions(opt_useCache) { + if (this.isOptionListDynamic()) { + if (!this.generatedOptions_ || !opt_useCache) { + this.generatedOptions_ = this.menuGenerator_.call(this); + validateOptions(this.generatedOptions_); + } + return this.generatedOptions_; + } + return /** @type {!Array>} */ (this.menuGenerator_); + } + + /** + * Ensure that the input value is a valid language-neutral option. + * @param {*=} opt_newValue The input value. + * @return {?string} A valid language-neutral option, or null if invalid. + * @protected + */ + doClassValidation_(opt_newValue) { + let isValueValid = false; + const options = this.getOptions(true); + for (let i = 0, option; (option = options[i]); i++) { + // Options are tuples of human-readable text and language-neutral values. + if (option[1] === opt_newValue) { + isValueValid = true; + break; + } + } + if (!isValueValid) { + if (this.sourceBlock_) { + console.warn( + 'Cannot set the dropdown\'s value to an unavailable option.' + + ' Block type: ' + this.sourceBlock_.type + + ', Field name: ' + this.name + ', Value: ' + opt_newValue); + } + return null; + } + return /** @type {string} */ (opt_newValue); + } + + /** + * Update the value of this dropdown field. + * @param {*} newValue The value to be saved. The default validator guarantees + * that this is one of the valid dropdown options. + * @protected + */ + doValueUpdate_(newValue) { + super.doValueUpdate_(newValue); + const options = this.getOptions(true); + for (let i = 0, option; (option = options[i]); i++) { + if (option[1] === this.value_) { + this.selectedOption_ = option; + } + } + } + + /** + * Updates the dropdown arrow to match the colour/style of the block. + * @package + */ + applyColour() { + if (this.borderRect_) { + this.borderRect_.setAttribute( + 'stroke', this.sourceBlock_.style.colourTertiary); + if (this.menu_) { + this.borderRect_.setAttribute( + 'fill', this.sourceBlock_.style.colourTertiary); + } else { + this.borderRect_.setAttribute('fill', 'transparent'); + } + } + // Update arrow's colour. + if (this.sourceBlock_ && this.arrow_) { + if (this.sourceBlock_.isShadow()) { + this.arrow_.style.fill = this.sourceBlock_.style.colourSecondary; + } else { + this.arrow_.style.fill = this.sourceBlock_.style.colourPrimary; + } + } + } + + /** + * Draws the border with the correct width. + * @protected + */ + render_() { + // Hide both elements. + this.textContent_.nodeValue = ''; + this.imageElement_.style.display = 'none'; + + // Show correct element. + const option = this.selectedOption_ && this.selectedOption_[0]; + if (option && typeof option === 'object') { + this.renderSelectedImage_( + /** @type {!ImageProperties} */ (option)); + } else { + this.renderSelectedText_(); + } + + this.positionBorderRect_(); + } + + /** + * Renders the selected option, which must be an image. + * @param {!ImageProperties} imageJson Selected + * option that must be an image. + * @private + */ + renderSelectedImage_(imageJson) { + this.imageElement_.style.display = ''; + this.imageElement_.setAttributeNS( + dom.XLINK_NS, 'xlink:href', imageJson.src); + this.imageElement_.setAttribute('height', imageJson.height); + this.imageElement_.setAttribute('width', imageJson.width); + + const imageHeight = Number(imageJson.height); + const imageWidth = Number(imageJson.width); + + // Height and width include the border rect. + const hasBorder = !!this.borderRect_; + const height = Math.max( + hasBorder ? this.getConstants().FIELD_DROPDOWN_BORDER_RECT_HEIGHT : 0, + imageHeight + IMAGE_Y_PADDING); + const xPadding = + hasBorder ? this.getConstants().FIELD_BORDER_RECT_X_PADDING : 0; + let arrowWidth = 0; + if (this.svgArrow_) { + arrowWidth = this.positionSVGArrow_( + imageWidth + xPadding, + height / 2 - this.getConstants().FIELD_DROPDOWN_SVG_ARROW_SIZE / 2); + } else { + arrowWidth = dom.getFastTextWidth( + /** @type {!SVGTSpanElement} */ (this.arrow_), + this.getConstants().FIELD_TEXT_FONTSIZE, + this.getConstants().FIELD_TEXT_FONTWEIGHT, + this.getConstants().FIELD_TEXT_FONTFAMILY); + } + this.size_.width = imageWidth + arrowWidth + xPadding * 2; + this.size_.height = height; + + let arrowX = 0; + if (this.sourceBlock_.RTL) { + const imageX = xPadding + arrowWidth; + this.imageElement_.setAttribute('x', imageX); + } else { + arrowX = imageWidth + arrowWidth; + this.textElement_.setAttribute('text-anchor', 'end'); + this.imageElement_.setAttribute('x', xPadding); + } + this.imageElement_.setAttribute('y', height / 2 - imageHeight / 2); + + this.positionTextElement_(arrowX + xPadding, imageWidth + arrowWidth); + } + + /** + * Renders the selected option, which must be text. + * @private + */ + renderSelectedText_() { + // Retrieves the selected option to display through getText_. + this.textContent_.nodeValue = this.getDisplayText_(); + dom.addClass( + /** @type {!Element} */ (this.textElement_), 'blocklyDropdownText'); + this.textElement_.setAttribute('text-anchor', 'start'); + + // Height and width include the border rect. + const hasBorder = !!this.borderRect_; + const height = Math.max( + hasBorder ? this.getConstants().FIELD_DROPDOWN_BORDER_RECT_HEIGHT : 0, + this.getConstants().FIELD_TEXT_HEIGHT); + const textWidth = dom.getFastTextWidth( + this.textElement_, this.getConstants().FIELD_TEXT_FONTSIZE, + this.getConstants().FIELD_TEXT_FONTWEIGHT, + this.getConstants().FIELD_TEXT_FONTFAMILY); + const xPadding = + hasBorder ? this.getConstants().FIELD_BORDER_RECT_X_PADDING : 0; + let arrowWidth = 0; + if (this.svgArrow_) { + arrowWidth = this.positionSVGArrow_( + textWidth + xPadding, + height / 2 - this.getConstants().FIELD_DROPDOWN_SVG_ARROW_SIZE / 2); + } + this.size_.width = textWidth + arrowWidth + xPadding * 2; + this.size_.height = height; + + this.positionTextElement_(xPadding, textWidth); + } + + /** + * Position a drop-down arrow at the appropriate location at render-time. + * @param {number} x X position the arrow is being rendered at, in px. + * @param {number} y Y position the arrow is being rendered at, in px. + * @return {number} Amount of space the arrow is taking up, in px. + * @private + */ + positionSVGArrow_(x, y) { + if (!this.svgArrow_) { + return 0; + } + const hasBorder = !!this.borderRect_; + const xPadding = + hasBorder ? this.getConstants().FIELD_BORDER_RECT_X_PADDING : 0; + const textPadding = this.getConstants().FIELD_DROPDOWN_SVG_ARROW_PADDING; + const svgArrowSize = this.getConstants().FIELD_DROPDOWN_SVG_ARROW_SIZE; + const arrowX = this.sourceBlock_.RTL ? xPadding : x + textPadding; + this.svgArrow_.setAttribute( + 'transform', 'translate(' + arrowX + ',' + y + ')'); + return svgArrowSize + textPadding; + } + + /** + * Use the `getText_` developer hook to override the field's text + * representation. Get the selected option text. If the selected option is an + * image we return the image alt text. + * @return {?string} Selected option text. + * @protected + * @override + */ + getText_() { + if (!this.selectedOption_) { + return null; + } + const option = this.selectedOption_[0]; + if (typeof option === 'object') { + return option['alt']; + } + return option; + } + + /** + * Construct a FieldDropdown from a JSON arg object. + * @param {!Object} options A JSON object with options (options). + * @return {!FieldDropdown} The new field instance. + * @package + * @nocollapse + */ + static fromJson(options) { + // `this` might be a subclass of FieldDropdown if that class doesn't + // override the static fromJson method. + return new this(options['options'], undefined, options); + } + + /** + * Use the calculated prefix and suffix lengths to trim all of the options in + * the given array. + * @param {!Array} options Array of option tuples: + * (human-readable text or image, language-neutral name). + * @param {number} prefixLength The length of the common prefix. + * @param {number} suffixLength The length of the common suffix + * @return {!Array} A new array with all of the option text trimmed. + */ + static applyTrim_(options, prefixLength, suffixLength) { + const newOptions = []; + // Remove the prefix and suffix from the options. + for (let i = 0; i < options.length; i++) { + let text = options[i][0]; + const value = options[i][1]; + text = text.substring(prefixLength, text.length - suffixLength); + newOptions[i] = [text, value]; + } + return newOptions; + } +} /** * Dropdown image properties. @@ -146,57 +716,7 @@ object.inherits(FieldDropdown, Field); * height:number * }} */ -FieldDropdown.ImageProperties; - -/** - * Construct a FieldDropdown from a JSON arg object. - * @param {!Object} options A JSON object with options (options). - * @return {!FieldDropdown} The new field instance. - * @package - * @nocollapse - */ -FieldDropdown.fromJson = function(options) { - // `this` might be a subclass of FieldDropdown if that class doesn't override - // the static fromJson method. - return new this(options['options'], undefined, options); -}; - -/** - * Sets the field's value based on the given XML element. Should only be - * called by Blockly.Xml. - * @param {!Element} fieldElement The element containing info about the - * field's state. - * @package - */ -FieldDropdown.prototype.fromXml = function(fieldElement) { - if (this.isOptionListDynamic()) { - this.getOptions(false); - } - this.setValue(fieldElement.textContent); -}; - -/** - * Sets the field's value based on the given state. - * @param {*} state The state to apply to the dropdown field. - * @override - * @package - */ -FieldDropdown.prototype.loadState = function(state) { - if (this.loadLegacyState(FieldDropdown, state)) { - return; - } - if (this.isOptionListDynamic()) { - this.getOptions(false); - } - this.setValue(state); -}; - -/** - * Serializable fields are saved by the XML renderer, non-serializable fields - * are not. Editable fields should also be serializable. - * @type {boolean} - */ -FieldDropdown.prototype.SERIALIZABLE = true; +let ImageProperties; // eslint-disable-line no-unused-vars /** * Horizontal distance that a checkmark overhangs the dropdown. @@ -228,507 +748,6 @@ const IMAGE_Y_PADDING = IMAGE_Y_OFFSET * 2; */ FieldDropdown.ARROW_CHAR = userAgent.ANDROID ? '\u25BC' : '\u25BE'; -/** - * Mouse cursor style when over the hotspot that initiates the editor. - */ -FieldDropdown.prototype.CURSOR = 'default'; - -/** - * Create the block UI for this dropdown. - * @package - */ -FieldDropdown.prototype.initView = function() { - if (this.shouldAddBorderRect_()) { - this.createBorderRect_(); - } else { - this.clickTarget_ = this.sourceBlock_.getSvgRoot(); - } - this.createTextElement_(); - - this.imageElement_ = dom.createSvgElement(Svg.IMAGE, {}, this.fieldGroup_); - - if (this.getConstants().FIELD_DROPDOWN_SVG_ARROW) { - this.createSVGArrow_(); - } else { - this.createTextArrow_(); - } - - if (this.borderRect_) { - dom.addClass(this.borderRect_, 'blocklyDropdownRect'); - } -}; - -/** - * Whether or not the dropdown should add a border rect. - * @return {boolean} True if the dropdown field should add a border rect. - * @protected - */ -FieldDropdown.prototype.shouldAddBorderRect_ = function() { - return !this.getConstants().FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW || - (this.getConstants().FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW && - !this.sourceBlock_.isShadow()); -}; - -/** - * Create a tspan based arrow. - * @protected - */ -FieldDropdown.prototype.createTextArrow_ = function() { - this.arrow_ = dom.createSvgElement(Svg.TSPAN, {}, this.textElement_); - this.arrow_.appendChild(document.createTextNode( - this.sourceBlock_.RTL ? FieldDropdown.ARROW_CHAR + ' ' : - ' ' + FieldDropdown.ARROW_CHAR)); - if (this.sourceBlock_.RTL) { - this.textElement_.insertBefore(this.arrow_, this.textContent_); - } else { - this.textElement_.appendChild(this.arrow_); - } -}; - -/** - * Create an SVG based arrow. - * @protected - */ -FieldDropdown.prototype.createSVGArrow_ = function() { - this.svgArrow_ = dom.createSvgElement( - Svg.IMAGE, { - 'height': this.getConstants().FIELD_DROPDOWN_SVG_ARROW_SIZE + 'px', - 'width': this.getConstants().FIELD_DROPDOWN_SVG_ARROW_SIZE + 'px', - }, - this.fieldGroup_); - this.svgArrow_.setAttributeNS( - dom.XLINK_NS, 'xlink:href', - this.getConstants().FIELD_DROPDOWN_SVG_ARROW_DATAURI); -}; - -/** - * Create a dropdown menu under the text. - * @param {Event=} opt_e Optional mouse event that triggered the field to open, - * or undefined if triggered programmatically. - * @protected - */ -FieldDropdown.prototype.showEditor_ = function(opt_e) { - this.dropdownCreate_(); - if (opt_e && typeof opt_e.clientX === 'number') { - this.menu_.openingCoords = new Coordinate(opt_e.clientX, opt_e.clientY); - } else { - this.menu_.openingCoords = null; - } - - // Remove any pre-existing elements in the dropdown. - DropDownDiv.clearContent(); - // Element gets created in render. - this.menu_.render(DropDownDiv.getContentDiv()); - const menuElement = /** @type {!Element} */ (this.menu_.getElement()); - dom.addClass(menuElement, 'blocklyDropdownMenu'); - - if (this.getConstants().FIELD_DROPDOWN_COLOURED_DIV) { - const primaryColour = (this.sourceBlock_.isShadow()) ? - this.sourceBlock_.getParent().getColour() : - this.sourceBlock_.getColour(); - const borderColour = (this.sourceBlock_.isShadow()) ? - this.sourceBlock_.getParent().style.colourTertiary : - this.sourceBlock_.style.colourTertiary; - DropDownDiv.setColour(primaryColour, borderColour); - } - - DropDownDiv.showPositionedByField(this, this.dropdownDispose_.bind(this)); - - // Focusing needs to be handled after the menu is rendered and positioned. - // Otherwise it will cause a page scroll to get the misplaced menu in - // view. See issue #1329. - this.menu_.focus(); - - if (this.selectedMenuItem_) { - this.menu_.setHighlighted(this.selectedMenuItem_); - } - - this.applyColour(); -}; - -/** - * Create the dropdown editor. - * @private - */ -FieldDropdown.prototype.dropdownCreate_ = function() { - const menu = new Menu(); - menu.setRole(aria.Role.LISTBOX); - this.menu_ = menu; - - const options = this.getOptions(false); - this.selectedMenuItem_ = null; - for (let i = 0; i < options.length; i++) { - let content = options[i][0]; // Human-readable text or image. - const value = options[i][1]; // Language-neutral value. - if (typeof content === 'object') { - // An image, not text. - const image = new Image(content['width'], content['height']); - image.src = content['src']; - image.alt = content['alt'] || ''; - content = image; - } - const menuItem = new MenuItem(content, value); - menuItem.setRole(aria.Role.OPTION); - menuItem.setRightToLeft(this.sourceBlock_.RTL); - menuItem.setCheckable(true); - menu.addChild(menuItem); - menuItem.setChecked(value === this.value_); - if (value === this.value_) { - this.selectedMenuItem_ = menuItem; - } - menuItem.onAction(this.handleMenuActionEvent_, this); - } -}; - -/** - * Disposes of events and DOM-references belonging to the dropdown editor. - * @private - */ -FieldDropdown.prototype.dropdownDispose_ = function() { - if (this.menu_) { - this.menu_.dispose(); - } - this.menu_ = null; - this.selectedMenuItem_ = null; - this.applyColour(); -}; - -/** - * Handle an action in the dropdown menu. - * @param {!MenuItem} menuItem The MenuItem selected within menu. - * @private - */ -FieldDropdown.prototype.handleMenuActionEvent_ = function(menuItem) { - DropDownDiv.hideIfOwner(this, true); - this.onItemSelected_(/** @type {!Menu} */ (this.menu_), menuItem); -}; - -/** - * Handle the selection of an item in the dropdown menu. - * @param {!Menu} menu The Menu component clicked. - * @param {!MenuItem} menuItem The MenuItem selected within menu. - * @protected - */ -FieldDropdown.prototype.onItemSelected_ = function(menu, menuItem) { - this.setValue(menuItem.getValue()); -}; - -/** - * Factor out common words in statically defined options. - * Create prefix and/or suffix labels. - * @private - */ -FieldDropdown.prototype.trimOptions_ = function() { - const options = this.menuGenerator_; - if (!Array.isArray(options)) { - return; - } - let hasImages = false; - - // Localize label text and image alt text. - for (let i = 0; i < options.length; i++) { - const label = options[i][0]; - if (typeof label === 'string') { - options[i][0] = parsing.replaceMessageReferences(label); - } else { - if (label.alt !== null) { - options[i][0].alt = parsing.replaceMessageReferences(label.alt); - } - hasImages = true; - } - } - if (hasImages || options.length < 2) { - return; // Do nothing if too few items or at least one label is an image. - } - const strings = []; - for (let i = 0; i < options.length; i++) { - strings.push(options[i][0]); - } - const shortest = utilsString.shortestStringLength(strings); - const prefixLength = utilsString.commonWordPrefix(strings, shortest); - const suffixLength = utilsString.commonWordSuffix(strings, shortest); - if (!prefixLength && !suffixLength) { - return; - } - if (shortest <= prefixLength + suffixLength) { - // One or more strings will entirely vanish if we proceed. Abort. - return; - } - if (prefixLength) { - this.prefixField = strings[0].substring(0, prefixLength - 1); - } - if (suffixLength) { - this.suffixField = strings[0].substr(1 - suffixLength); - } - - this.menuGenerator_ = - FieldDropdown.applyTrim_(options, prefixLength, suffixLength); -}; - -/** - * Use the calculated prefix and suffix lengths to trim all of the options in - * the given array. - * @param {!Array} options Array of option tuples: - * (human-readable text or image, language-neutral name). - * @param {number} prefixLength The length of the common prefix. - * @param {number} suffixLength The length of the common suffix - * @return {!Array} A new array with all of the option text trimmed. - */ -FieldDropdown.applyTrim_ = function(options, prefixLength, suffixLength) { - const newOptions = []; - // Remove the prefix and suffix from the options. - for (let i = 0; i < options.length; i++) { - let text = options[i][0]; - const value = options[i][1]; - text = text.substring(prefixLength, text.length - suffixLength); - newOptions[i] = [text, value]; - } - return newOptions; -}; - -/** - * @return {boolean} True if the option list is generated by a function. - * Otherwise false. - */ -FieldDropdown.prototype.isOptionListDynamic = function() { - return typeof this.menuGenerator_ === 'function'; -}; - -/** - * Return a list of the options for this dropdown. - * @param {boolean=} opt_useCache For dynamic options, whether or not to use the - * cached options or to re-generate them. - * @return {!Array} A non-empty array of option tuples: - * (human-readable text or image, language-neutral name). - * @throws {TypeError} If generated options are incorrectly structured. - */ -FieldDropdown.prototype.getOptions = function(opt_useCache) { - if (this.isOptionListDynamic()) { - if (!this.generatedOptions_ || !opt_useCache) { - this.generatedOptions_ = this.menuGenerator_.call(this); - validateOptions(this.generatedOptions_); - } - return this.generatedOptions_; - } - return /** @type {!Array>} */ (this.menuGenerator_); -}; - -/** - * Ensure that the input value is a valid language-neutral option. - * @param {*=} opt_newValue The input value. - * @return {?string} A valid language-neutral option, or null if invalid. - * @protected - */ -FieldDropdown.prototype.doClassValidation_ = function(opt_newValue) { - let isValueValid = false; - const options = this.getOptions(true); - for (let i = 0, option; (option = options[i]); i++) { - // Options are tuples of human-readable text and language-neutral values. - if (option[1] === opt_newValue) { - isValueValid = true; - break; - } - } - if (!isValueValid) { - if (this.sourceBlock_) { - console.warn( - 'Cannot set the dropdown\'s value to an unavailable option.' + - ' Block type: ' + this.sourceBlock_.type + - ', Field name: ' + this.name + ', Value: ' + opt_newValue); - } - return null; - } - return /** @type {string} */ (opt_newValue); -}; - -/** - * Update the value of this dropdown field. - * @param {*} newValue The value to be saved. The default validator guarantees - * that this is one of the valid dropdown options. - * @protected - */ -FieldDropdown.prototype.doValueUpdate_ = function(newValue) { - FieldDropdown.superClass_.doValueUpdate_.call(this, newValue); - const options = this.getOptions(true); - for (let i = 0, option; (option = options[i]); i++) { - if (option[1] === this.value_) { - this.selectedOption_ = option; - } - } -}; - -/** - * Updates the dropdown arrow to match the colour/style of the block. - * @package - */ -FieldDropdown.prototype.applyColour = function() { - if (this.borderRect_) { - this.borderRect_.setAttribute( - 'stroke', this.sourceBlock_.style.colourTertiary); - if (this.menu_) { - this.borderRect_.setAttribute( - 'fill', this.sourceBlock_.style.colourTertiary); - } else { - this.borderRect_.setAttribute('fill', 'transparent'); - } - } - // Update arrow's colour. - if (this.sourceBlock_ && this.arrow_) { - if (this.sourceBlock_.isShadow()) { - this.arrow_.style.fill = this.sourceBlock_.style.colourSecondary; - } else { - this.arrow_.style.fill = this.sourceBlock_.style.colourPrimary; - } - } -}; - -/** - * Draws the border with the correct width. - * @protected - */ -FieldDropdown.prototype.render_ = function() { - // Hide both elements. - this.textContent_.nodeValue = ''; - this.imageElement_.style.display = 'none'; - - // Show correct element. - const option = this.selectedOption_ && this.selectedOption_[0]; - if (option && typeof option === 'object') { - this.renderSelectedImage_( - /** @type {!FieldDropdown.ImageProperties} */ (option)); - } else { - this.renderSelectedText_(); - } - - this.positionBorderRect_(); -}; - -/** - * Renders the selected option, which must be an image. - * @param {!FieldDropdown.ImageProperties} imageJson Selected - * option that must be an image. - * @private - */ -FieldDropdown.prototype.renderSelectedImage_ = function(imageJson) { - this.imageElement_.style.display = ''; - this.imageElement_.setAttributeNS(dom.XLINK_NS, 'xlink:href', imageJson.src); - this.imageElement_.setAttribute('height', imageJson.height); - this.imageElement_.setAttribute('width', imageJson.width); - - const imageHeight = Number(imageJson.height); - const imageWidth = Number(imageJson.width); - - // Height and width include the border rect. - const hasBorder = !!this.borderRect_; - const height = Math.max( - hasBorder ? this.getConstants().FIELD_DROPDOWN_BORDER_RECT_HEIGHT : 0, - imageHeight + IMAGE_Y_PADDING); - const xPadding = - hasBorder ? this.getConstants().FIELD_BORDER_RECT_X_PADDING : 0; - let arrowWidth = 0; - if (this.svgArrow_) { - arrowWidth = this.positionSVGArrow_( - imageWidth + xPadding, - height / 2 - this.getConstants().FIELD_DROPDOWN_SVG_ARROW_SIZE / 2); - } else { - arrowWidth = dom.getFastTextWidth( - /** @type {!SVGTSpanElement} */ (this.arrow_), - this.getConstants().FIELD_TEXT_FONTSIZE, - this.getConstants().FIELD_TEXT_FONTWEIGHT, - this.getConstants().FIELD_TEXT_FONTFAMILY); - } - this.size_.width = imageWidth + arrowWidth + xPadding * 2; - this.size_.height = height; - - let arrowX = 0; - if (this.sourceBlock_.RTL) { - const imageX = xPadding + arrowWidth; - this.imageElement_.setAttribute('x', imageX); - } else { - arrowX = imageWidth + arrowWidth; - this.textElement_.setAttribute('text-anchor', 'end'); - this.imageElement_.setAttribute('x', xPadding); - } - this.imageElement_.setAttribute('y', height / 2 - imageHeight / 2); - - this.positionTextElement_(arrowX + xPadding, imageWidth + arrowWidth); -}; - -/** - * Renders the selected option, which must be text. - * @private - */ -FieldDropdown.prototype.renderSelectedText_ = function() { - // Retrieves the selected option to display through getText_. - this.textContent_.nodeValue = this.getDisplayText_(); - dom.addClass( - /** @type {!Element} */ (this.textElement_), 'blocklyDropdownText'); - this.textElement_.setAttribute('text-anchor', 'start'); - - // Height and width include the border rect. - const hasBorder = !!this.borderRect_; - const height = Math.max( - hasBorder ? this.getConstants().FIELD_DROPDOWN_BORDER_RECT_HEIGHT : 0, - this.getConstants().FIELD_TEXT_HEIGHT); - const textWidth = dom.getFastTextWidth( - this.textElement_, this.getConstants().FIELD_TEXT_FONTSIZE, - this.getConstants().FIELD_TEXT_FONTWEIGHT, - this.getConstants().FIELD_TEXT_FONTFAMILY); - const xPadding = - hasBorder ? this.getConstants().FIELD_BORDER_RECT_X_PADDING : 0; - let arrowWidth = 0; - if (this.svgArrow_) { - arrowWidth = this.positionSVGArrow_( - textWidth + xPadding, - height / 2 - this.getConstants().FIELD_DROPDOWN_SVG_ARROW_SIZE / 2); - } - this.size_.width = textWidth + arrowWidth + xPadding * 2; - this.size_.height = height; - - this.positionTextElement_(xPadding, textWidth); -}; - -/** - * Position a drop-down arrow at the appropriate location at render-time. - * @param {number} x X position the arrow is being rendered at, in px. - * @param {number} y Y position the arrow is being rendered at, in px. - * @return {number} Amount of space the arrow is taking up, in px. - * @private - */ -FieldDropdown.prototype.positionSVGArrow_ = function(x, y) { - if (!this.svgArrow_) { - return 0; - } - const hasBorder = !!this.borderRect_; - const xPadding = - hasBorder ? this.getConstants().FIELD_BORDER_RECT_X_PADDING : 0; - const textPadding = this.getConstants().FIELD_DROPDOWN_SVG_ARROW_PADDING; - const svgArrowSize = this.getConstants().FIELD_DROPDOWN_SVG_ARROW_SIZE; - const arrowX = this.sourceBlock_.RTL ? xPadding : x + textPadding; - this.svgArrow_.setAttribute( - 'transform', 'translate(' + arrowX + ',' + y + ')'); - return svgArrowSize + textPadding; -}; - -/** - * Use the `getText_` developer hook to override the field's text - * representation. Get the selected option text. If the selected option is an - * image we return the image alt text. - * @return {?string} Selected option text. - * @protected - * @override - */ -FieldDropdown.prototype.getText_ = function() { - if (!this.selectedOption_) { - return null; - } - const option = this.selectedOption_[0]; - if (typeof option === 'object') { - return option['alt']; - } - return option; -}; - /** * Validates the data structure to be processed as an options list. * @param {?} options The proposed dropdown options. diff --git a/core/field_image.js b/core/field_image.js index 25cf1c5d1..fde679911 100644 --- a/core/field_image.js +++ b/core/field_image.js @@ -17,108 +17,272 @@ goog.module('Blockly.FieldImage'); const dom = goog.require('Blockly.utils.dom'); const fieldRegistry = goog.require('Blockly.fieldRegistry'); -const object = goog.require('Blockly.utils.object'); const parsing = goog.require('Blockly.utils.parsing'); const {Field} = goog.require('Blockly.Field'); +/* eslint-disable-next-line no-unused-vars */ +const {Sentinel} = goog.requireType('Blockly.utils.Sentinel'); const {Size} = goog.require('Blockly.utils.Size'); const {Svg} = goog.require('Blockly.utils.Svg'); /** * Class for an image on a block. - * @param {string} src The URL of the image. - * @param {!(string|number)} width Width of the image. - * @param {!(string|number)} height Height of the image. - * @param {string=} opt_alt Optional alt text for when block is collapsed. - * @param {function(!FieldImage)=} opt_onClick Optional function to be - * called when the image is clicked. If opt_onClick is defined, opt_alt must - * also be defined. - * @param {boolean=} opt_flipRtl Whether to flip the icon in RTL. - * @param {Object=} opt_config A map of options used to configure the field. - * See the [field creation documentation]{@link - * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/image#creation} - * for a list of properties this parameter supports. * @extends {Field} - * @constructor * @alias Blockly.FieldImage */ -const FieldImage = function( - src, width, height, opt_alt, opt_onClick, opt_flipRtl, opt_config) { - // Return early. - if (!src) { - throw Error('Src value of an image field is required'); - } - src = parsing.replaceMessageReferences(src); - const imageHeight = Number(parsing.replaceMessageReferences(height)); - const imageWidth = Number(parsing.replaceMessageReferences(width)); - if (isNaN(imageHeight) || isNaN(imageWidth)) { - throw Error( - 'Height and width values of an image field must cast to' + - ' numbers.'); - } - if (imageHeight <= 0 || imageWidth <= 0) { - throw Error( - 'Height and width values of an image field must be greater' + - ' than 0.'); - } - - // Initialize configurable properties. +class FieldImage extends Field { /** - * Whether to flip this image in RTL. - * @type {boolean} - * @private + * @param {string|!Sentinel} src The URL of the image. + * Also accepts Field.SKIP_SETUP if you wish to skip setup (only used by + * subclasses that want to handle configuration and setting the field + * value after their own constructors have run). + * @param {!(string|number)} width Width of the image. + * @param {!(string|number)} height Height of the image. + * @param {string=} opt_alt Optional alt text for when block is collapsed. + * @param {function(!FieldImage)=} opt_onClick Optional function to be + * called when the image is clicked. If opt_onClick is defined, opt_alt + * must also be defined. + * @param {boolean=} opt_flipRtl Whether to flip the icon in RTL. + * @param {Object=} opt_config A map of options used to configure the field. + * See the [field creation documentation]{@link + * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/image#creation} + * for a list of properties this parameter supports. */ - this.flipRtl_ = false; + constructor( + src, width, height, opt_alt, opt_onClick, opt_flipRtl, opt_config) { + super(Field.SKIP_SETUP); - /** - * Alt text of this image. - * @type {string} - * @private - */ - this.altText_ = ''; + // Return early. + if (!src) { + throw Error('Src value of an image field is required'); + } + const imageHeight = Number(parsing.replaceMessageReferences(height)); + const imageWidth = Number(parsing.replaceMessageReferences(width)); + if (isNaN(imageHeight) || isNaN(imageWidth)) { + throw Error( + 'Height and width values of an image field must cast to' + + ' numbers.'); + } + if (imageHeight <= 0 || imageWidth <= 0) { + throw Error( + 'Height and width values of an image field must be greater' + + ' than 0.'); + } - FieldImage.superClass_.constructor.call(this, src, null, opt_config); + /** + * The size of the area rendered by the field. + * @type {Size} + * @protected + * @override + */ + this.size_ = new Size(imageWidth, imageHeight + FieldImage.Y_PADDING); - if (!opt_config) { // If the config wasn't passed, do old configuration. - this.flipRtl_ = !!opt_flipRtl; - this.altText_ = parsing.replaceMessageReferences(opt_alt) || ''; + /** + * Store the image height, since it is different from the field height. + * @type {number} + * @private + */ + this.imageHeight_ = imageHeight; + + /** + * The function to be called when this field is clicked. + * @type {?function(!FieldImage)} + * @private + */ + this.clickHandler_ = null; + + if (typeof opt_onClick === 'function') { + this.clickHandler_ = opt_onClick; + } + + /** + * The rendered field's image element. + * @type {SVGImageElement} + * @private + */ + this.imageElement_ = null; + + /** + * Editable fields usually show some sort of UI indicating they are + * editable. This field should not. + * @type {boolean} + * @const + */ + this.EDITABLE = false; + + /** + * Used to tell if the field needs to be rendered the next time the block is + * rendered. Image fields are statically sized, and only need to be + * rendered at initialization. + * @type {boolean} + * @protected + */ + this.isDirty_ = false; + + /** + * Whether to flip this image in RTL. + * @type {boolean} + * @private + */ + this.flipRtl_ = false; + + /** + * Alt text of this image. + * @type {string} + * @private + */ + this.altText_ = ''; + + if (src === Field.SKIP_SETUP) return; + + if (opt_config) { + this.configure_(opt_config); + } else { + this.flipRtl_ = !!opt_flipRtl; + this.altText_ = parsing.replaceMessageReferences(opt_alt) || ''; + } + this.setValue(parsing.replaceMessageReferences(src)); } - // Initialize other properties. /** - * The size of the area rendered by the field. - * @type {Size} + * Configure the field based on the given map of options. + * @param {!Object} config A map of options to configure the field based on. * @protected * @override */ - this.size_ = new Size(imageWidth, imageHeight + FieldImage.Y_PADDING); - - /** - * Store the image height, since it is different from the field height. - * @type {number} - * @private - */ - this.imageHeight_ = imageHeight; - - /** - * The function to be called when this field is clicked. - * @type {?function(!FieldImage)} - * @private - */ - this.clickHandler_ = null; - - if (typeof opt_onClick === 'function') { - this.clickHandler_ = opt_onClick; + configure_(config) { + super.configure_(config); + this.flipRtl_ = !!config['flipRtl']; + this.altText_ = parsing.replaceMessageReferences(config['alt']) || ''; } /** - * The rendered field's image element. - * @type {SVGImageElement} - * @private + * Create the block UI for this image. + * @package */ - this.imageElement_ = null; -}; -object.inherits(FieldImage, Field); + initView() { + this.imageElement_ = dom.createSvgElement( + Svg.IMAGE, { + 'height': this.imageHeight_ + 'px', + 'width': this.size_.width + 'px', + 'alt': this.altText_, + }, + this.fieldGroup_); + this.imageElement_.setAttributeNS( + dom.XLINK_NS, 'xlink:href', /** @type {string} */ (this.value_)); + + if (this.clickHandler_) { + this.imageElement_.style.cursor = 'pointer'; + } + } + + /** + * @override + */ + updateSize_() { + // NOP + } + + /** + * Ensure that the input value (the source URL) is a string. + * @param {*=} opt_newValue The input value. + * @return {?string} A string, or null if invalid. + * @protected + */ + doClassValidation_(opt_newValue) { + if (typeof opt_newValue !== 'string') { + return null; + } + return opt_newValue; + } + + /** + * Update the value of this image field, and update the displayed image. + * @param {*} newValue The value to be saved. The default validator guarantees + * that this is a string. + * @protected + */ + doValueUpdate_(newValue) { + this.value_ = newValue; + if (this.imageElement_) { + this.imageElement_.setAttributeNS( + dom.XLINK_NS, 'xlink:href', String(this.value_)); + } + } + + /** + * Get whether to flip this image in RTL + * @return {boolean} True if we should flip in RTL. + * @override + */ + getFlipRtl() { + return this.flipRtl_; + } + + /** + * Set the alt text of this image. + * @param {?string} alt New alt text. + * @public + */ + setAlt(alt) { + if (alt === this.altText_) { + return; + } + this.altText_ = alt || ''; + if (this.imageElement_) { + this.imageElement_.setAttribute('alt', this.altText_); + } + } + + /** + * If field click is called, and click handler defined, + * call the handler. + * @protected + */ + showEditor_() { + if (this.clickHandler_) { + this.clickHandler_(this); + } + } + + /** + * Set the function that is called when this image is clicked. + * @param {?function(!FieldImage)} func The function that is called + * when the image is clicked, or null to remove. + */ + setOnClickHandler(func) { + this.clickHandler_ = func; + } + + /** + * Use the `getText_` developer hook to override the field's text + * representation. + * Return the image alt text instead. + * @return {?string} The image alt text. + * @protected + * @override + */ + getText_() { + return this.altText_; + } + + /** + * Construct a FieldImage from a JSON arg object, + * dereferencing any string table references. + * @param {!Object} options A JSON object with options (src, width, height, + * alt, and flipRtl). + * @return {!FieldImage} The new field instance. + * @package + * @nocollapse + */ + static fromJson(options) { + // `this` might be a subclass of FieldImage if that class doesn't override + // the static fromJson method. + return new this( + options['src'], options['width'], options['height'], undefined, + undefined, undefined, options); + } +} /** * The default value for this field. @@ -127,23 +291,6 @@ object.inherits(FieldImage, Field); */ FieldImage.prototype.DEFAULT_VALUE = ''; -/** - * Construct a FieldImage from a JSON arg object, - * dereferencing any string table references. - * @param {!Object} options A JSON object with options (src, width, height, - * alt, and flipRtl). - * @return {!FieldImage} The new field instance. - * @package - * @nocollapse - */ -FieldImage.fromJson = function(options) { - // `this` might be a subclass of FieldImage if that class doesn't override - // the static fromJson method. - return new this( - options['src'], options['width'], options['height'], undefined, undefined, - undefined, options); -}; - /** * Vertical padding below the image, which is included in the reported height of * the field. @@ -152,144 +299,6 @@ FieldImage.fromJson = function(options) { */ FieldImage.Y_PADDING = 1; -/** - * Editable fields usually show some sort of UI indicating they are - * editable. This field should not. - * @type {boolean} - */ -FieldImage.prototype.EDITABLE = false; - -/** - * Used to tell if the field needs to be rendered the next time the block is - * rendered. Image fields are statically sized, and only need to be - * rendered at initialization. - * @type {boolean} - * @protected - */ -FieldImage.prototype.isDirty_ = false; - -/** - * Configure the field based on the given map of options. - * @param {!Object} config A map of options to configure the field based on. - * @protected - * @override - */ -FieldImage.prototype.configure_ = function(config) { - FieldImage.superClass_.configure_.call(this, config); - this.flipRtl_ = !!config['flipRtl']; - this.altText_ = parsing.replaceMessageReferences(config['alt']) || ''; -}; - -/** - * Create the block UI for this image. - * @package - */ -FieldImage.prototype.initView = function() { - this.imageElement_ = dom.createSvgElement( - Svg.IMAGE, { - 'height': this.imageHeight_ + 'px', - 'width': this.size_.width + 'px', - 'alt': this.altText_, - }, - this.fieldGroup_); - this.imageElement_.setAttributeNS( - dom.XLINK_NS, 'xlink:href', /** @type {string} */ (this.value_)); - - if (this.clickHandler_) { - this.imageElement_.style.cursor = 'pointer'; - } -}; - -/** - * @override - */ -FieldImage.prototype.updateSize_ = function() { - // NOP -}; - -/** - * Ensure that the input value (the source URL) is a string. - * @param {*=} opt_newValue The input value. - * @return {?string} A string, or null if invalid. - * @protected - */ -FieldImage.prototype.doClassValidation_ = function(opt_newValue) { - if (typeof opt_newValue !== 'string') { - return null; - } - return opt_newValue; -}; - -/** - * Update the value of this image field, and update the displayed image. - * @param {*} newValue The value to be saved. The default validator guarantees - * that this is a string. - * @protected - */ -FieldImage.prototype.doValueUpdate_ = function(newValue) { - this.value_ = newValue; - if (this.imageElement_) { - this.imageElement_.setAttributeNS( - dom.XLINK_NS, 'xlink:href', String(this.value_)); - } -}; - -/** - * Get whether to flip this image in RTL - * @return {boolean} True if we should flip in RTL. - * @override - */ -FieldImage.prototype.getFlipRtl = function() { - return this.flipRtl_; -}; - -/** - * Set the alt text of this image. - * @param {?string} alt New alt text. - * @public - */ -FieldImage.prototype.setAlt = function(alt) { - if (alt === this.altText_) { - return; - } - this.altText_ = alt || ''; - if (this.imageElement_) { - this.imageElement_.setAttribute('alt', this.altText_); - } -}; - -/** - * If field click is called, and click handler defined, - * call the handler. - * @protected - */ -FieldImage.prototype.showEditor_ = function() { - if (this.clickHandler_) { - this.clickHandler_(this); - } -}; - -/** - * Set the function that is called when this image is clicked. - * @param {?function(!FieldImage)} func The function that is called - * when the image is clicked, or null to remove. - */ -FieldImage.prototype.setOnClickHandler = function(func) { - this.clickHandler_ = func; -}; - -/** - * Use the `getText_` developer hook to override the field's text - * representation. - * Return the image alt text instead. - * @return {?string} The image alt text. - * @protected - * @override - */ -FieldImage.prototype.getText_ = function() { - return this.altText_; -}; - fieldRegistry.register('field_image', FieldImage); exports.FieldImage = FieldImage; diff --git a/core/field_label.js b/core/field_label.js index b9195d803..f30c0de82 100644 --- a/core/field_label.js +++ b/core/field_label.js @@ -19,39 +19,123 @@ goog.module('Blockly.FieldLabel'); const dom = goog.require('Blockly.utils.dom'); const fieldRegistry = goog.require('Blockly.fieldRegistry'); -const object = goog.require('Blockly.utils.object'); const parsing = goog.require('Blockly.utils.parsing'); const {Field} = goog.require('Blockly.Field'); +/* eslint-disable-next-line no-unused-vars */ +const {Sentinel} = goog.requireType('Blockly.utils.Sentinel'); /** * Class for a non-editable, non-serializable text field. - * @param {string=} opt_value The initial value of the field. Should cast to a - * string. Defaults to an empty string if null or undefined. - * @param {string=} opt_class Optional CSS class for the field's text. - * @param {Object=} opt_config A map of options used to configure the field. - * See the [field creation documentation]{@link - * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/label#creation} - * for a list of properties this parameter supports. * @extends {Field} - * @constructor * @alias Blockly.FieldLabel */ -const FieldLabel = function(opt_value, opt_class, opt_config) { +class FieldLabel extends Field { /** - * The html class name to use for this field. - * @type {?string} - * @private + * @param {(string|!Sentinel)=} opt_value The initial value of the + * field. Should cast to a string. Defaults to an empty string if null or + * undefined. + * Also accepts Field.SKIP_SETUP if you wish to skip setup (only used by + * subclasses that want to handle configuration and setting the field + * value after their own constructors have run). + * @param {string=} opt_class Optional CSS class for the field's text. + * @param {Object=} opt_config A map of options used to configure the field. + * See the [field creation documentation]{@link + * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/label#creation} + * for a list of properties this parameter supports. */ - this.class_ = null; + constructor(opt_value, opt_class, opt_config) { + super(Field.SKIP_SETUP); - FieldLabel.superClass_.constructor.call(this, opt_value, null, opt_config); + /** + * The html class name to use for this field. + * @type {?string} + * @private + */ + this.class_ = null; - if (!opt_config) { // If the config was not passed use old configuration. - this.class_ = opt_class || null; + /** + * Editable fields usually show some sort of UI indicating they are + * editable. This field should not. + * @type {boolean} + */ + this.EDITABLE = false; + + if (opt_value === Field.SKIP_SETUP) return; + if (opt_config) { + this.configure_(opt_config); + } else { + this.class_ = opt_class || null; + } + this.setValue(opt_value); } -}; -object.inherits(FieldLabel, Field); + + /** + * @override + */ + configure_(config) { + super.configure_(config); + this.class_ = config['class']; + } + + /** + * Create block UI for this label. + * @package + */ + initView() { + this.createTextElement_(); + if (this.class_) { + dom.addClass( + /** @type {!SVGTextElement} */ (this.textElement_), this.class_); + } + } + + /** + * Ensure that the input value casts to a valid string. + * @param {*=} opt_newValue The input value. + * @return {?string} A valid string, or null if invalid. + * @protected + */ + doClassValidation_(opt_newValue) { + if (opt_newValue === null || opt_newValue === undefined) { + return null; + } + return String(opt_newValue); + } + + /** + * Set the CSS class applied to the field's textElement_. + * @param {?string} cssClass The new CSS class name, or null to remove. + */ + setClass(cssClass) { + if (this.textElement_) { + // This check isn't necessary, but it's faster than letting removeClass + // figure it out. + if (this.class_) { + dom.removeClass(this.textElement_, this.class_); + } + if (cssClass) { + dom.addClass(this.textElement_, cssClass); + } + } + this.class_ = cssClass; + } + + /** + * Construct a FieldLabel from a JSON arg object, + * dereferencing any string table references. + * @param {!Object} options A JSON object with options (text, and class). + * @return {!FieldLabel} The new field instance. + * @package + * @nocollapse + */ + static fromJson(options) { + const text = parsing.replaceMessageReferences(options['text']); + // `this` might be a subclass of FieldLabel if that class doesn't override + // the static fromJson method. + return new this(text, undefined, options); + } +} /** * The default value for this field. @@ -60,79 +144,6 @@ object.inherits(FieldLabel, Field); */ FieldLabel.prototype.DEFAULT_VALUE = ''; -/** - * Construct a FieldLabel from a JSON arg object, - * dereferencing any string table references. - * @param {!Object} options A JSON object with options (text, and class). - * @return {!FieldLabel} The new field instance. - * @package - * @nocollapse - */ -FieldLabel.fromJson = function(options) { - const text = parsing.replaceMessageReferences(options['text']); - // `this` might be a subclass of FieldLabel if that class doesn't override - // the static fromJson method. - return new this(text, undefined, options); -}; - -/** - * Editable fields usually show some sort of UI indicating they are - * editable. This field should not. - * @type {boolean} - */ -FieldLabel.prototype.EDITABLE = false; - -/** - * @override - */ -FieldLabel.prototype.configure_ = function(config) { - FieldLabel.superClass_.configure_.call(this, config); - this.class_ = config['class']; -}; - -/** - * Create block UI for this label. - * @package - */ -FieldLabel.prototype.initView = function() { - this.createTextElement_(); - if (this.class_) { - dom.addClass( - /** @type {!SVGTextElement} */ (this.textElement_), this.class_); - } -}; - -/** - * Ensure that the input value casts to a valid string. - * @param {*=} opt_newValue The input value. - * @return {?string} A valid string, or null if invalid. - * @protected - */ -FieldLabel.prototype.doClassValidation_ = function(opt_newValue) { - if (opt_newValue === null || opt_newValue === undefined) { - return null; - } - return String(opt_newValue); -}; - -/** - * Set the CSS class applied to the field's textElement_. - * @param {?string} cssClass The new CSS class name, or null to remove. - */ -FieldLabel.prototype.setClass = function(cssClass) { - if (this.textElement_) { - // This check isn't necessary, but it's faster than letting removeClass - // figure it out. - if (this.class_) { - dom.removeClass(this.textElement_, this.class_); - } - if (cssClass) { - dom.addClass(this.textElement_, cssClass); - } - } - this.class_ = cssClass; -}; - fieldRegistry.register('field_label', FieldLabel); exports.FieldLabel = FieldLabel; diff --git a/core/field_label_serializable.js b/core/field_label_serializable.js index 4c4990f03..e243648fd 100644 --- a/core/field_label_serializable.js +++ b/core/field_label_serializable.js @@ -20,59 +20,60 @@ goog.module('Blockly.FieldLabelSerializable'); const fieldRegistry = goog.require('Blockly.fieldRegistry'); -const object = goog.require('Blockly.utils.object'); const parsing = goog.require('Blockly.utils.parsing'); const {FieldLabel} = goog.require('Blockly.FieldLabel'); /** * Class for a non-editable, serializable text field. - * @param {*} opt_value The initial value of the field. Should cast to a - * string. Defaults to an empty string if null or undefined. - * @param {string=} opt_class Optional CSS class for the field's text. - * @param {Object=} opt_config A map of options used to configure the field. - * See the [field creation documentation]{@link - * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/label-serializable#creation} - * for a list of properties this parameter supports. * @extends {FieldLabel} - * @constructor - * * @alias Blockly.FieldLabelSerializable */ -const FieldLabelSerializable = function(opt_value, opt_class, opt_config) { - FieldLabelSerializable.superClass_.constructor.call( - this, opt_value, opt_class, opt_config); -}; -object.inherits(FieldLabelSerializable, FieldLabel); +class FieldLabelSerializable extends FieldLabel { + /** + * @param {string=} opt_value The initial value of the field. Should cast to a + * string. Defaults to an empty string if null or undefined. + * @param {string=} opt_class Optional CSS class for the field's text. + * @param {Object=} opt_config A map of options used to configure the field. + * See the [field creation documentation]{@link + * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/label-serializable#creation} + * for a list of properties this parameter supports. + */ + constructor(opt_value, opt_class, opt_config) { + super(String(opt_value ?? ''), opt_class, opt_config); -/** - * Construct a FieldLabelSerializable from a JSON arg object, - * dereferencing any string table references. - * @param {!Object} options A JSON object with options (text, and class). - * @return {!FieldLabelSerializable} The new field instance. - * @package - * @nocollapse - */ -FieldLabelSerializable.fromJson = function(options) { - const text = parsing.replaceMessageReferences(options['text']); - // `this` might be a subclass of FieldLabelSerializable if that class doesn't - // override the static fromJson method. - return new this(text, undefined, options); -}; + /** + * Editable fields usually show some sort of UI indicating they are + * editable. This field should not. + * @type {boolean} + */ + this.EDITABLE = false; -/** - * Editable fields usually show some sort of UI indicating they are - * editable. This field should not. - * @type {boolean} - */ -FieldLabelSerializable.prototype.EDITABLE = false; + /** + * Serializable fields are saved by the XML renderer, non-serializable + * fields are not. This field should be serialized, but only edited + * programmatically. + * @type {boolean} + */ + this.SERIALIZABLE = true; + } -/** - * Serializable fields are saved by the XML renderer, non-serializable fields - * are not. This field should be serialized, but only edited programmatically. - * @type {boolean} - */ -FieldLabelSerializable.prototype.SERIALIZABLE = true; + /** + * Construct a FieldLabelSerializable from a JSON arg object, + * dereferencing any string table references. + * @param {!Object} options A JSON object with options (text, and class). + * @return {!FieldLabelSerializable} The new field instance. + * @package + * @nocollapse + * @override + */ + static fromJson(options) { + const text = parsing.replaceMessageReferences(options['text']); + // `this` might be a subclass of FieldLabelSerializable if that class + // doesn't override the static fromJson method. + return new this(text, undefined, options); + } +} fieldRegistry.register('field_label_serializable', FieldLabelSerializable); diff --git a/core/field_multilineinput.js b/core/field_multilineinput.js index 5496bdef8..0e9ec36e5 100644 --- a/core/field_multilineinput.js +++ b/core/field_multilineinput.js @@ -20,428 +20,442 @@ const WidgetDiv = goog.require('Blockly.WidgetDiv'); const aria = goog.require('Blockly.utils.aria'); const dom = goog.require('Blockly.utils.dom'); const fieldRegistry = goog.require('Blockly.fieldRegistry'); -const object = goog.require('Blockly.utils.object'); const parsing = goog.require('Blockly.utils.parsing'); const userAgent = goog.require('Blockly.utils.userAgent'); const {FieldTextInput} = goog.require('Blockly.FieldTextInput'); const {Field} = goog.require('Blockly.Field'); const {KeyCodes} = goog.require('Blockly.utils.KeyCodes'); +/* eslint-disable-next-line no-unused-vars */ +const {Sentinel} = goog.requireType('Blockly.utils.Sentinel'); const {Svg} = goog.require('Blockly.utils.Svg'); /** * Class for an editable text area field. - * @param {string=} opt_value The initial content of the field. Should cast to a - * string. Defaults to an empty string if null or undefined. - * @param {Function=} opt_validator An optional function that is called - * to validate any constraints on what the user entered. Takes the new - * text as an argument and returns either the accepted text, a replacement - * text, or null to abort the change. - * @param {Object=} opt_config A map of options used to configure the field. - * See the [field creation documentation]{@link - * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/multiline-text-input#creation} - * for a list of properties this parameter supports. * @extends {FieldTextInput} - * @constructor * @alias Blockly.FieldMultilineInput */ -const FieldMultilineInput = function(opt_value, opt_validator, opt_config) { - FieldMultilineInput.superClass_.constructor.call( - this, opt_value, opt_validator, opt_config); - +class FieldMultilineInput extends FieldTextInput { /** - * The SVG group element that will contain a text element for each text row - * when initialized. - * @type {SVGGElement} + * @param {(string|!Sentinel)=} opt_value The initial content of the + * field. Should cast to a string. Defaults to an empty string if null or + * undefined. + * Also accepts Field.SKIP_SETUP if you wish to skip setup (only used by + * subclasses that want to handle configuration and setting the field + * value after their own constructors have run). + * @param {Function=} opt_validator An optional function that is called + * to validate any constraints on what the user entered. Takes the new + * text as an argument and returns either the accepted text, a replacement + * text, or null to abort the change. + * @param {Object=} opt_config A map of options used to configure the field. + * See the [field creation documentation]{@link + * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/multiline-text-input#creation} + * for a list of properties this parameter supports. */ - this.textGroup_ = null; + constructor(opt_value, opt_validator, opt_config) { + super(Field.SKIP_SETUP); + + /** + * The SVG group element that will contain a text element for each text row + * when initialized. + * @type {SVGGElement} + */ + this.textGroup_ = null; + + /** + * Defines the maximum number of lines of field. + * If exceeded, scrolling functionality is enabled. + * @type {number} + * @protected + */ + this.maxLines_ = Infinity; + + /** + * Whether Y overflow is currently occurring. + * @type {boolean} + * @protected + */ + this.isOverflowedY_ = false; + + if (opt_value === Field.SKIP_SETUP) return; + if (opt_config) this.configure_(opt_config); + this.setValue(opt_value); + if (opt_validator) this.setValidator(opt_validator); + } /** - * Defines the maximum number of lines of field. - * If exceeded, scrolling functionality is enabled. - * @type {number} + * @override + */ + configure_(config) { + super.configure_(config); + config.maxLines && this.setMaxLines(config.maxLines); + } + + /** + * Serializes this field's value to XML. Should only be called by Blockly.Xml. + * @param {!Element} fieldElement The element to populate with info about the + * field's state. + * @return {!Element} The element containing info about the field's state. + * @package + */ + toXml(fieldElement) { + // Replace '\n' characters with HTML-escaped equivalent ' '. This is + // needed so the plain-text representation of the XML produced by + // `Blockly.Xml.domToText` will appear on a single line (this is a + // limitation of the plain-text format). + fieldElement.textContent = this.getValue().replace(/\n/g, ' '); + return fieldElement; + } + + /** + * Sets the field's value based on the given XML element. Should only be + * called by Blockly.Xml. + * @param {!Element} fieldElement The element containing info about the + * field's state. + * @package + */ + fromXml(fieldElement) { + this.setValue(fieldElement.textContent.replace(/ /g, '\n')); + } + + /** + * Saves this field's value. + * @return {*} The state of this field. + * @package + */ + saveState() { + const legacyState = this.saveLegacyState(FieldMultilineInput); + if (legacyState !== null) { + return legacyState; + } + return this.getValue(); + } + + /** + * Sets the field's value based on the given state. + * @param {*} state The state of the variable to assign to this variable + * field. + * @override + * @package + */ + loadState(state) { + if (this.loadLegacyState(Field, state)) { + return; + } + this.setValue(state); + } + + /** + * Create the block UI for this field. + * @package + */ + initView() { + this.createBorderRect_(); + this.textGroup_ = dom.createSvgElement( + Svg.G, { + 'class': 'blocklyEditableText', + }, + this.fieldGroup_); + } + + /** + * Get the text from this field as displayed on screen. May differ from + * getText due to ellipsis, and other formatting. + * @return {string} Currently displayed text. + * @protected + * @override + */ + getDisplayText_() { + let textLines = this.getText(); + if (!textLines) { + // Prevent the field from disappearing if empty. + return Field.NBSP; + } + const lines = textLines.split('\n'); + textLines = ''; + const displayLinesNumber = + this.isOverflowedY_ ? this.maxLines_ : lines.length; + for (let i = 0; i < displayLinesNumber; i++) { + let text = lines[i]; + if (text.length > this.maxDisplayLength) { + // Truncate displayed string and add an ellipsis ('...'). + text = text.substring(0, this.maxDisplayLength - 4) + '...'; + } else if (this.isOverflowedY_ && i === displayLinesNumber - 1) { + text = text.substring(0, text.length - 3) + '...'; + } + // Replace whitespace with non-breaking spaces so the text doesn't + // collapse. + text = text.replace(/\s/g, Field.NBSP); + + textLines += text; + if (i !== displayLinesNumber - 1) { + textLines += '\n'; + } + } + if (this.sourceBlock_.RTL) { + // The SVG is LTR, force value to be RTL. + textLines += '\u200F'; + } + return textLines; + } + + /** + * Called by setValue if the text input is valid. Updates the value of the + * field, and updates the text of the field if it is not currently being + * edited (i.e. handled by the htmlInput_). Is being redefined here to update + * overflow state of the field. + * @param {*} newValue The value to be saved. The default validator guarantees + * that this is a string. * @protected */ - this.maxLines_ = Infinity; + doValueUpdate_(newValue) { + super.doValueUpdate_(newValue); + this.isOverflowedY_ = this.value_.split('\n').length > this.maxLines_; + } /** - * Whether Y overflow is currently occurring. - * @type {boolean} + * Updates the text of the textElement. * @protected */ - this.isOverflowedY_ = false; -}; -object.inherits(FieldMultilineInput, FieldTextInput); - -/** - * @override - */ -FieldMultilineInput.prototype.configure_ = function(config) { - FieldMultilineInput.superClass_.configure_.call(this, config); - config.maxLines && this.setMaxLines(config.maxLines); -}; - -/** - * Construct a FieldMultilineInput from a JSON arg object, - * dereferencing any string table references. - * @param {!Object} options A JSON object with options (text, and spellcheck). - * @return {!FieldMultilineInput} The new field instance. - * @package - * @nocollapse - */ -FieldMultilineInput.fromJson = function(options) { - const text = parsing.replaceMessageReferences(options['text']); - // `this` might be a subclass of FieldMultilineInput if that class doesn't - // override the static fromJson method. - return new this(text, undefined, options); -}; - -/** - * Serializes this field's value to XML. Should only be called by Blockly.Xml. - * @param {!Element} fieldElement The element to populate with info about the - * field's state. - * @return {!Element} The element containing info about the field's state. - * @package - */ -FieldMultilineInput.prototype.toXml = function(fieldElement) { - // Replace '\n' characters with HTML-escaped equivalent ' '. This is - // needed so the plain-text representation of the XML produced by - // `Blockly.Xml.domToText` will appear on a single line (this is a limitation - // of the plain-text format). - fieldElement.textContent = this.getValue().replace(/\n/g, ' '); - return fieldElement; -}; - -/** - * Sets the field's value based on the given XML element. Should only be - * called by Blockly.Xml. - * @param {!Element} fieldElement The element containing info about the - * field's state. - * @package - */ -FieldMultilineInput.prototype.fromXml = function(fieldElement) { - this.setValue(fieldElement.textContent.replace(/ /g, '\n')); -}; - -/** - * Saves this field's value. - * @return {*} The state of this field. - * @package - */ -FieldMultilineInput.prototype.saveState = function() { - const legacyState = this.saveLegacyState(FieldMultilineInput); - if (legacyState !== null) { - return legacyState; - } - return this.getValue(); -}; - -/** - * Sets the field's value based on the given state. - * @param {*} state The state of the variable to assign to this variable field. - * @override - * @package - */ -FieldMultilineInput.prototype.loadState = function(state) { - if (this.loadLegacyState(Field, state)) { - return; - } - this.setValue(state); -}; - -/** - * Create the block UI for this field. - * @package - */ -FieldMultilineInput.prototype.initView = function() { - this.createBorderRect_(); - this.textGroup_ = dom.createSvgElement( - Svg.G, { - 'class': 'blocklyEditableText', - }, - this.fieldGroup_); -}; - -/** - * Get the text from this field as displayed on screen. May differ from getText - * due to ellipsis, and other formatting. - * @return {string} Currently displayed text. - * @protected - * @override - */ -FieldMultilineInput.prototype.getDisplayText_ = function() { - let textLines = this.getText(); - if (!textLines) { - // Prevent the field from disappearing if empty. - return Field.NBSP; - } - const lines = textLines.split('\n'); - textLines = ''; - const displayLinesNumber = - this.isOverflowedY_ ? this.maxLines_ : lines.length; - for (let i = 0; i < displayLinesNumber; i++) { - let text = lines[i]; - if (text.length > this.maxDisplayLength) { - // Truncate displayed string and add an ellipsis ('...'). - text = text.substring(0, this.maxDisplayLength - 4) + '...'; - } else if (this.isOverflowedY_ && i === displayLinesNumber - 1) { - text = text.substring(0, text.length - 3) + '...'; + render_() { + // Remove all text group children. + let currentChild; + while ((currentChild = this.textGroup_.firstChild)) { + this.textGroup_.removeChild(currentChild); } - // Replace whitespace with non-breaking spaces so the text doesn't collapse. - text = text.replace(/\s/g, Field.NBSP); - textLines += text; - if (i !== displayLinesNumber - 1) { - textLines += '\n'; + // Add in text elements into the group. + const lines = this.getDisplayText_().split('\n'); + let y = 0; + for (let i = 0; i < lines.length; i++) { + const lineHeight = this.getConstants().FIELD_TEXT_HEIGHT + + this.getConstants().FIELD_BORDER_RECT_Y_PADDING; + const span = dom.createSvgElement( + Svg.TEXT, { + 'class': 'blocklyText blocklyMultilineText', + 'x': this.getConstants().FIELD_BORDER_RECT_X_PADDING, + 'y': y + this.getConstants().FIELD_BORDER_RECT_Y_PADDING, + 'dy': this.getConstants().FIELD_TEXT_BASELINE, + }, + this.textGroup_); + span.appendChild(document.createTextNode(lines[i])); + y += lineHeight; + } + + if (this.isBeingEdited_) { + const htmlInput = /** @type {!HTMLElement} */ (this.htmlInput_); + if (this.isOverflowedY_) { + dom.addClass(htmlInput, 'blocklyHtmlTextAreaInputOverflowedY'); + } else { + dom.removeClass(htmlInput, 'blocklyHtmlTextAreaInputOverflowedY'); + } + } + + this.updateSize_(); + + if (this.isBeingEdited_) { + if (this.sourceBlock_.RTL) { + // in RTL, we need to let the browser reflow before resizing + // in order to get the correct bounding box of the borderRect + // avoiding issue #2777. + setTimeout(this.resizeEditor_.bind(this), 0); + } else { + this.resizeEditor_(); + } + const htmlInput = /** @type {!HTMLElement} */ (this.htmlInput_); + if (!this.isTextValid_) { + dom.addClass(htmlInput, 'blocklyInvalidInput'); + aria.setState(htmlInput, aria.State.INVALID, true); + } else { + dom.removeClass(htmlInput, 'blocklyInvalidInput'); + aria.setState(htmlInput, aria.State.INVALID, false); + } } } - if (this.sourceBlock_.RTL) { - // The SVG is LTR, force value to be RTL. - textLines += '\u200F'; - } - return textLines; -}; -/** - * Called by setValue if the text input is valid. Updates the value of the - * field, and updates the text of the field if it is not currently being - * edited (i.e. handled by the htmlInput_). Is being redefined here to update - * overflow state of the field. - * @param {*} newValue The value to be saved. The default validator guarantees - * that this is a string. - * @protected - */ -FieldMultilineInput.prototype.doValueUpdate_ = function(newValue) { - FieldMultilineInput.superClass_.doValueUpdate_.call(this, newValue); - this.isOverflowedY_ = this.value_.split('\n').length > this.maxLines_; -}; + /** + * Updates the size of the field based on the text. + * @protected + */ + updateSize_() { + const nodes = this.textGroup_.childNodes; + let totalWidth = 0; + let totalHeight = 0; + for (let i = 0; i < nodes.length; i++) { + const tspan = /** @type {!Element} */ (nodes[i]); + const textWidth = dom.getTextWidth(tspan); + if (textWidth > totalWidth) { + totalWidth = textWidth; + } + totalHeight += this.getConstants().FIELD_TEXT_HEIGHT + + (i > 0 ? this.getConstants().FIELD_BORDER_RECT_Y_PADDING : 0); + } + if (this.isBeingEdited_) { + // The default width is based on the longest line in the display text, + // but when it's being edited, width should be calculated based on the + // absolute longest line, even if it would be truncated after editing. + // Otherwise we would get wrong editor width when there are more + // lines than this.maxLines_. + const actualEditorLines = this.value_.split('\n'); + const dummyTextElement = dom.createSvgElement( + Svg.TEXT, {'class': 'blocklyText blocklyMultilineText'}); + const fontSize = this.getConstants().FIELD_TEXT_FONTSIZE; + const fontWeight = this.getConstants().FIELD_TEXT_FONTWEIGHT; + const fontFamily = this.getConstants().FIELD_TEXT_FONTFAMILY; -/** - * Updates the text of the textElement. - * @protected - */ -FieldMultilineInput.prototype.render_ = function() { - // Remove all text group children. - let currentChild; - while ((currentChild = this.textGroup_.firstChild)) { - this.textGroup_.removeChild(currentChild); + for (let i = 0; i < actualEditorLines.length; i++) { + if (actualEditorLines[i].length > this.maxDisplayLength) { + actualEditorLines[i] = + actualEditorLines[i].substring(0, this.maxDisplayLength); + } + dummyTextElement.textContent = actualEditorLines[i]; + const lineWidth = dom.getFastTextWidth( + dummyTextElement, fontSize, fontWeight, fontFamily); + if (lineWidth > totalWidth) { + totalWidth = lineWidth; + } + } + + const scrollbarWidth = + this.htmlInput_.offsetWidth - this.htmlInput_.clientWidth; + totalWidth += scrollbarWidth; + } + if (this.borderRect_) { + totalHeight += this.getConstants().FIELD_BORDER_RECT_Y_PADDING * 2; + totalWidth += this.getConstants().FIELD_BORDER_RECT_X_PADDING * 2; + this.borderRect_.setAttribute('width', totalWidth); + this.borderRect_.setAttribute('height', totalHeight); + } + this.size_.width = totalWidth; + this.size_.height = totalHeight; + + this.positionBorderRect_(); } - // Add in text elements into the group. - const lines = this.getDisplayText_().split('\n'); - let y = 0; - for (let i = 0; i < lines.length; i++) { + /** + * Show the inline free-text editor on top of the text. + * Overrides the default behaviour to force rerender in order to + * correct block size, based on editor text. + * @param {Event=} _opt_e Optional mouse event that triggered the field to + * open, or undefined if triggered programmatically. + * @param {boolean=} opt_quietInput True if editor should be created without + * focus. Defaults to false. + * @override + */ + showEditor_(_opt_e, opt_quietInput) { + super.showEditor_(_opt_e, opt_quietInput); + this.forceRerender(); + } + + /** + * Create the text input editor widget. + * @return {!HTMLTextAreaElement} The newly created text input editor. + * @protected + */ + widgetCreate_() { + const div = WidgetDiv.getDiv(); + const scale = this.workspace_.getScale(); + + const htmlInput = + /** @type {HTMLTextAreaElement} */ (document.createElement('textarea')); + htmlInput.className = 'blocklyHtmlInput blocklyHtmlTextAreaInput'; + htmlInput.setAttribute('spellcheck', this.spellcheck_); + const fontSize = (this.getConstants().FIELD_TEXT_FONTSIZE * scale) + 'pt'; + div.style.fontSize = fontSize; + htmlInput.style.fontSize = fontSize; + const borderRadius = (FieldTextInput.BORDERRADIUS * scale) + 'px'; + htmlInput.style.borderRadius = borderRadius; + const paddingX = this.getConstants().FIELD_BORDER_RECT_X_PADDING * scale; + const paddingY = + this.getConstants().FIELD_BORDER_RECT_Y_PADDING * scale / 2; + htmlInput.style.padding = paddingY + 'px ' + paddingX + 'px ' + paddingY + + 'px ' + paddingX + 'px'; const lineHeight = this.getConstants().FIELD_TEXT_HEIGHT + this.getConstants().FIELD_BORDER_RECT_Y_PADDING; - const span = dom.createSvgElement( - Svg.TEXT, { - 'class': 'blocklyText blocklyMultilineText', - 'x': this.getConstants().FIELD_BORDER_RECT_X_PADDING, - 'y': y + this.getConstants().FIELD_BORDER_RECT_Y_PADDING, - 'dy': this.getConstants().FIELD_TEXT_BASELINE, - }, - this.textGroup_); - span.appendChild(document.createTextNode(lines[i])); - y += lineHeight; - } + htmlInput.style.lineHeight = (lineHeight * scale) + 'px'; - if (this.isBeingEdited_) { - const htmlInput = /** @type {!HTMLElement} */ (this.htmlInput_); - if (this.isOverflowedY_) { - dom.addClass(htmlInput, 'blocklyHtmlTextAreaInputOverflowedY'); - } else { - dom.removeClass(htmlInput, 'blocklyHtmlTextAreaInputOverflowedY'); - } - } + div.appendChild(htmlInput); - this.updateSize_(); - - if (this.isBeingEdited_) { - if (this.sourceBlock_.RTL) { - // in RTL, we need to let the browser reflow before resizing - // in order to get the correct bounding box of the borderRect - // avoiding issue #2777. + htmlInput.value = htmlInput.defaultValue = this.getEditorText_(this.value_); + htmlInput.untypedDefaultValue_ = this.value_; + htmlInput.oldValue_ = null; + if (userAgent.GECKO) { + // In FF, ensure the browser reflows before resizing to avoid issue #2777. setTimeout(this.resizeEditor_.bind(this), 0); } else { this.resizeEditor_(); } - const htmlInput = /** @type {!HTMLElement} */ (this.htmlInput_); - if (!this.isTextValid_) { - dom.addClass(htmlInput, 'blocklyInvalidInput'); - aria.setState(htmlInput, aria.State.INVALID, true); - } else { - dom.removeClass(htmlInput, 'blocklyInvalidInput'); - aria.setState(htmlInput, aria.State.INVALID, false); + + this.bindInputEvents_(htmlInput); + + return htmlInput; + } + + /** + * Sets the maxLines config for this field. + * @param {number} maxLines Defines the maximum number of lines allowed, + * before scrolling functionality is enabled. + */ + setMaxLines(maxLines) { + if (typeof maxLines === 'number' && maxLines > 0 && + maxLines !== this.maxLines_) { + this.maxLines_ = maxLines; + this.forceRerender(); } } -}; -/** - * Updates the size of the field based on the text. - * @protected - */ -FieldMultilineInput.prototype.updateSize_ = function() { - const nodes = this.textGroup_.childNodes; - let totalWidth = 0; - let totalHeight = 0; - for (let i = 0; i < nodes.length; i++) { - const tspan = /** @type {!Element} */ (nodes[i]); - const textWidth = dom.getTextWidth(tspan); - if (textWidth > totalWidth) { - totalWidth = textWidth; + /** + * Returns the maxLines config of this field. + * @return {number} The maxLines config value. + */ + getMaxLines() { + return this.maxLines_; + } + + /** + * Handle key down to the editor. Override the text input definition of this + * so as to not close the editor when enter is typed in. + * @param {!Event} e Keyboard event. + * @protected + */ + onHtmlInputKeyDown_(e) { + if (e.keyCode !== KeyCodes.ENTER) { + super.onHtmlInputKeyDown_(e); } - totalHeight += this.getConstants().FIELD_TEXT_HEIGHT + - (i > 0 ? this.getConstants().FIELD_BORDER_RECT_Y_PADDING : 0); - } - if (this.isBeingEdited_) { - // The default width is based on the longest line in the display text, - // but when it's being edited, width should be calculated based on the - // absolute longest line, even if it would be truncated after editing. - // Otherwise we would get wrong editor width when there are more - // lines than this.maxLines_. - const actualEditorLines = this.value_.split('\n'); - const dummyTextElement = dom.createSvgElement( - Svg.TEXT, {'class': 'blocklyText blocklyMultilineText'}); - const fontSize = this.getConstants().FIELD_TEXT_FONTSIZE; - const fontWeight = this.getConstants().FIELD_TEXT_FONTWEIGHT; - const fontFamily = this.getConstants().FIELD_TEXT_FONTFAMILY; - - for (let i = 0; i < actualEditorLines.length; i++) { - if (actualEditorLines[i].length > this.maxDisplayLength) { - actualEditorLines[i] = - actualEditorLines[i].substring(0, this.maxDisplayLength); - } - dummyTextElement.textContent = actualEditorLines[i]; - const lineWidth = dom.getFastTextWidth( - dummyTextElement, fontSize, fontWeight, fontFamily); - if (lineWidth > totalWidth) { - totalWidth = lineWidth; - } - } - - const scrollbarWidth = - this.htmlInput_.offsetWidth - this.htmlInput_.clientWidth; - totalWidth += scrollbarWidth; - } - if (this.borderRect_) { - totalHeight += this.getConstants().FIELD_BORDER_RECT_Y_PADDING * 2; - totalWidth += this.getConstants().FIELD_BORDER_RECT_X_PADDING * 2; - this.borderRect_.setAttribute('width', totalWidth); - this.borderRect_.setAttribute('height', totalHeight); - } - this.size_.width = totalWidth; - this.size_.height = totalHeight; - - this.positionBorderRect_(); -}; - -/** - * Show the inline free-text editor on top of the text. - * Overrides the default behaviour to force rerender in order to - * correct block size, based on editor text. - * @param {Event=} _opt_e Optional mouse event that triggered the field to open, - * or undefined if triggered programmatically. - * @param {boolean=} opt_quietInput True if editor should be created without - * focus. Defaults to false. - * @override - */ -FieldMultilineInput.prototype.showEditor_ = function(_opt_e, opt_quietInput) { - FieldMultilineInput.superClass_.showEditor_.call( - this, _opt_e, opt_quietInput); - this.forceRerender(); -}; - -/** - * Create the text input editor widget. - * @return {!HTMLTextAreaElement} The newly created text input editor. - * @protected - */ -FieldMultilineInput.prototype.widgetCreate_ = function() { - const div = WidgetDiv.getDiv(); - const scale = this.workspace_.getScale(); - - const htmlInput = - /** @type {HTMLTextAreaElement} */ (document.createElement('textarea')); - htmlInput.className = 'blocklyHtmlInput blocklyHtmlTextAreaInput'; - htmlInput.setAttribute('spellcheck', this.spellcheck_); - const fontSize = (this.getConstants().FIELD_TEXT_FONTSIZE * scale) + 'pt'; - div.style.fontSize = fontSize; - htmlInput.style.fontSize = fontSize; - const borderRadius = (FieldTextInput.BORDERRADIUS * scale) + 'px'; - htmlInput.style.borderRadius = borderRadius; - const paddingX = this.getConstants().FIELD_BORDER_RECT_X_PADDING * scale; - const paddingY = this.getConstants().FIELD_BORDER_RECT_Y_PADDING * scale / 2; - htmlInput.style.padding = - paddingY + 'px ' + paddingX + 'px ' + paddingY + 'px ' + paddingX + 'px'; - const lineHeight = this.getConstants().FIELD_TEXT_HEIGHT + - this.getConstants().FIELD_BORDER_RECT_Y_PADDING; - htmlInput.style.lineHeight = (lineHeight * scale) + 'px'; - - div.appendChild(htmlInput); - - htmlInput.value = htmlInput.defaultValue = this.getEditorText_(this.value_); - htmlInput.untypedDefaultValue_ = this.value_; - htmlInput.oldValue_ = null; - if (userAgent.GECKO) { - // In FF, ensure the browser reflows before resizing to avoid issue #2777. - setTimeout(this.resizeEditor_.bind(this), 0); - } else { - this.resizeEditor_(); } - this.bindInputEvents_(htmlInput); - - return htmlInput; -}; - -/** - * Sets the maxLines config for this field. - * @param {number} maxLines Defines the maximum number of lines allowed, - * before scrolling functionality is enabled. - */ -FieldMultilineInput.prototype.setMaxLines = function(maxLines) { - if (typeof maxLines === 'number' && maxLines > 0 && - maxLines !== this.maxLines_) { - this.maxLines_ = maxLines; - this.forceRerender(); + /** + * Construct a FieldMultilineInput from a JSON arg object, + * dereferencing any string table references. + * @param {!Object} options A JSON object with options (text, and spellcheck). + * @return {!FieldMultilineInput} The new field instance. + * @package + * @nocollapse + * @override + */ + static fromJson(options) { + const text = parsing.replaceMessageReferences(options['text']); + // `this` might be a subclass of FieldMultilineInput if that class doesn't + // override the static fromJson method. + return new this(text, undefined, options); } -}; - -/** - * Returns the maxLines config of this field. - * @return {number} The maxLines config value. - */ -FieldMultilineInput.prototype.getMaxLines = function() { - return this.maxLines_; -}; - -/** - * Handle key down to the editor. Override the text input definition of this - * so as to not close the editor when enter is typed in. - * @param {!Event} e Keyboard event. - * @protected - */ -FieldMultilineInput.prototype.onHtmlInputKeyDown_ = function(e) { - if (e.keyCode !== KeyCodes.ENTER) { - FieldMultilineInput.superClass_.onHtmlInputKeyDown_.call(this, e); - } -}; +} /** * CSS for multiline field. See css.js for use. */ Css.register(` - .blocklyHtmlTextAreaInput { - font-family: monospace; - resize: none; - overflow: hidden; - height: 100%; - text-align: left; - } +.blocklyHtmlTextAreaInput { + font-family: monospace; + resize: none; + overflow: hidden; + height: 100%; + text-align: left; +} - .blocklyHtmlTextAreaInputOverflowedY { - overflow-y: scroll; - } +.blocklyHtmlTextAreaInputOverflowedY { + overflow-y: scroll; +} `); fieldRegistry.register('field_multilinetext', FieldMultilineInput); diff --git a/core/field_number.js b/core/field_number.js index afa9ee89b..6e19cfee4 100644 --- a/core/field_number.js +++ b/core/field_number.js @@ -17,67 +17,316 @@ goog.module('Blockly.FieldNumber'); const aria = goog.require('Blockly.utils.aria'); const fieldRegistry = goog.require('Blockly.fieldRegistry'); -const object = goog.require('Blockly.utils.object'); +const {Field} = goog.require('Blockly.Field'); const {FieldTextInput} = goog.require('Blockly.FieldTextInput'); +/* eslint-disable-next-line no-unused-vars */ +const {Sentinel} = goog.requireType('Blockly.utils.Sentinel'); /** * Class for an editable number field. - * @param {string|number=} opt_value The initial value of the field. Should cast - * to a number. Defaults to 0. - * @param {?(string|number)=} opt_min Minimum value. - * @param {?(string|number)=} opt_max Maximum value. - * @param {?(string|number)=} opt_precision Precision for value. - * @param {?Function=} opt_validator A function that is called to validate - * changes to the field's value. Takes in a number & returns a validated - * number, or null to abort the change. - * @param {Object=} opt_config A map of options used to configure the field. - * See the [field creation documentation]{@link - * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/number#creation} - * for a list of properties this parameter supports. * @extends {FieldTextInput} - * @constructor * @alias Blockly.FieldNumber */ -const FieldNumber = function( - opt_value, opt_min, opt_max, opt_precision, opt_validator, opt_config) { +class FieldNumber extends FieldTextInput { /** - * The minimum value this number field can contain. - * @type {number} - * @protected + * @param {(string|number|!Sentinel)=} opt_value The initial value of + * the field. Should cast to a number. Defaults to 0. + * Also accepts Field.SKIP_SETUP if you wish to skip setup (only used by + * subclasses that want to handle configuration and setting the field + * value after their own constructors have run). + * @param {?(string|number)=} opt_min Minimum value. Will only be used if + * opt_config is not provided. + * @param {?(string|number)=} opt_max Maximum value. Will only be used if + * opt_config is not provided. + * @param {?(string|number)=} opt_precision Precision for value. Will only be + * used if opt_config is not provided. + * @param {?Function=} opt_validator A function that is called to validate + * changes to the field's value. Takes in a number & returns a validated + * number, or null to abort the change. + * @param {Object=} opt_config A map of options used to configure the field. + * See the [field creation documentation]{@link + * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/number#creation} + * for a list of properties this parameter supports. */ - this.min_ = -Infinity; + constructor( + opt_value, opt_min, opt_max, opt_precision, opt_validator, opt_config) { + // Pass SENTINEL so that we can define properties before value validation. + super(Field.SKIP_SETUP); + + /** + * The minimum value this number field can contain. + * @type {number} + * @protected + */ + this.min_ = -Infinity; + + /** + * The maximum value this number field can contain. + * @type {number} + * @protected + */ + this.max_ = Infinity; + + /** + * The multiple to which this fields value is rounded. + * @type {number} + * @protected + */ + this.precision_ = 0; + + /** + * The number of decimal places to allow, or null to allow any number of + * decimal digits. + * @type {?number} + * @private + */ + this.decimalPlaces_ = null; + + /** + * Serializable fields are saved by the serializer, non-serializable fields + * are not. Editable fields should also be serializable. + * @type {boolean} + */ + this.SERIALIZABLE = true; + + if (opt_value === Field.SKIP_SETUP) return; + if (opt_config) { + this.configure_(opt_config); + } else { + this.setConstraints(opt_min, opt_max, opt_precision); + } + this.setValue(opt_value); + if (opt_validator) this.setValidator(opt_validator); + } /** - * The maximum value this number field can contain. - * @type {number} + * Configure the field based on the given map of options. + * @param {!Object} config A map of options to configure the field based on. * @protected + * @override */ - this.max_ = Infinity; + configure_(config) { + super.configure_(config); + this.setMinInternal_(config['min']); + this.setMaxInternal_(config['max']); + this.setPrecisionInternal_(config['precision']); + } /** - * The multiple to which this fields value is rounded. - * @type {number} - * @protected + * Set the maximum, minimum and precision constraints on this field. + * Any of these properties may be undefined or NaN to be disabled. + * Setting precision (usually a power of 10) enforces a minimum step between + * values. That is, the user's value will rounded to the closest multiple of + * precision. The least significant digit place is inferred from the + * precision. Integers values can be enforces by choosing an integer + * precision. + * @param {?(number|string|undefined)} min Minimum value. + * @param {?(number|string|undefined)} max Maximum value. + * @param {?(number|string|undefined)} precision Precision for value. */ - this.precision_ = 0; + setConstraints(min, max, precision) { + this.setMinInternal_(min); + this.setMaxInternal_(max); + this.setPrecisionInternal_(precision); + this.setValue(this.getValue()); + } /** - * The number of decimal places to allow, or null to allow any number of - * decimal digits. - * @type {?number} + * Sets the minimum value this field can contain. Updates the value to + * reflect. + * @param {?(number|string|undefined)} min Minimum value. + */ + setMin(min) { + this.setMinInternal_(min); + this.setValue(this.getValue()); + } + + /** + * Sets the minimum value this field can contain. Called internally to avoid + * value updates. + * @param {?(number|string|undefined)} min Minimum value. * @private */ - this.decimalPlaces_ = null; - - FieldNumber.superClass_.constructor.call( - this, opt_value, opt_validator, opt_config); - - if (!opt_config) { // Only do one kind of configuration or the other. - this.setConstraints(opt_min, opt_max, opt_precision); + setMinInternal_(min) { + if (min == null) { + this.min_ = -Infinity; + } else { + min = Number(min); + if (!isNaN(min)) { + this.min_ = min; + } + } } -}; -object.inherits(FieldNumber, FieldTextInput); + + /** + * Returns the current minimum value this field can contain. Default is + * -Infinity. + * @return {number} The current minimum value this field can contain. + */ + getMin() { + return this.min_; + } + + /** + * Sets the maximum value this field can contain. Updates the value to + * reflect. + * @param {?(number|string|undefined)} max Maximum value. + */ + setMax(max) { + this.setMaxInternal_(max); + this.setValue(this.getValue()); + } + + /** + * Sets the maximum value this field can contain. Called internally to avoid + * value updates. + * @param {?(number|string|undefined)} max Maximum value. + * @private + */ + setMaxInternal_(max) { + if (max == null) { + this.max_ = Infinity; + } else { + max = Number(max); + if (!isNaN(max)) { + this.max_ = max; + } + } + } + + /** + * Returns the current maximum value this field can contain. Default is + * Infinity. + * @return {number} The current maximum value this field can contain. + */ + getMax() { + return this.max_; + } + + /** + * Sets the precision of this field's value, i.e. the number to which the + * value is rounded. Updates the field to reflect. + * @param {?(number|string|undefined)} precision The number to which the + * field's value is rounded. + */ + setPrecision(precision) { + this.setPrecisionInternal_(precision); + this.setValue(this.getValue()); + } + + /** + * Sets the precision of this field's value. Called internally to avoid + * value updates. + * @param {?(number|string|undefined)} precision The number to which the + * field's value is rounded. + * @private + */ + setPrecisionInternal_(precision) { + this.precision_ = Number(precision) || 0; + let precisionString = String(this.precision_); + if (precisionString.indexOf('e') !== -1) { + // String() is fast. But it turns .0000001 into '1e-7'. + // Use the much slower toLocaleString to access all the digits. + precisionString = + this.precision_.toLocaleString('en-US', {maximumFractionDigits: 20}); + } + const decimalIndex = precisionString.indexOf('.'); + if (decimalIndex === -1) { + // If the precision is 0 (float) allow any number of decimals, + // otherwise allow none. + this.decimalPlaces_ = precision ? 0 : null; + } else { + this.decimalPlaces_ = precisionString.length - decimalIndex - 1; + } + } + + /** + * Returns the current precision of this field. The precision being the + * number to which the field's value is rounded. A precision of 0 means that + * the value is not rounded. + * @return {number} The number to which this field's value is rounded. + */ + getPrecision() { + return this.precision_; + } + + /** + * Ensure that the input value is a valid number (must fulfill the + * constraints placed on the field). + * @param {*=} opt_newValue The input value. + * @return {?number} A valid number, or null if invalid. + * @protected + * @override + */ + doClassValidation_(opt_newValue) { + if (opt_newValue === null) { + return null; + } + // Clean up text. + let newValue = String(opt_newValue); + // TODO: Handle cases like 'ten', '1.203,14', etc. + // 'O' is sometimes mistaken for '0' by inexperienced users. + newValue = newValue.replace(/O/ig, '0'); + // Strip out thousands separators. + newValue = newValue.replace(/,/g, ''); + // Ignore case of 'Infinity'. + newValue = newValue.replace(/infinity/i, 'Infinity'); + + // Clean up number. + let n = Number(newValue || 0); + if (isNaN(n)) { + // Invalid number. + return null; + } + // Get the value in range. + n = Math.min(Math.max(n, this.min_), this.max_); + // Round to nearest multiple of precision. + if (this.precision_ && isFinite(n)) { + n = Math.round(n / this.precision_) * this.precision_; + } + // Clean up floating point errors. + if (this.decimalPlaces_ !== null) { + n = Number(n.toFixed(this.decimalPlaces_)); + } + return n; + } + + /** + * Create the number input editor widget. + * @return {!HTMLElement} The newly created number input editor. + * @protected + * @override + */ + widgetCreate_() { + const htmlInput = super.widgetCreate_(); + + // Set the accessibility state + if (this.min_ > -Infinity) { + aria.setState(htmlInput, aria.State.VALUEMIN, this.min_); + } + if (this.max_ < Infinity) { + aria.setState(htmlInput, aria.State.VALUEMAX, this.max_); + } + return htmlInput; + } + + /** + * Construct a FieldNumber from a JSON arg object. + * @param {!Object} options A JSON object with options (value, min, max, and + * precision). + * @return {!FieldNumber} The new field instance. + * @package + * @nocollapse + * @override + */ + static fromJson(options) { + // `this` might be a subclass of FieldNumber if that class doesn't override + // the static fromJson method. + return new this( + options['value'], undefined, undefined, undefined, undefined, options); + } +} /** * The default value for this field. @@ -86,236 +335,6 @@ object.inherits(FieldNumber, FieldTextInput); */ FieldNumber.prototype.DEFAULT_VALUE = 0; -/** - * Construct a FieldNumber from a JSON arg object. - * @param {!Object} options A JSON object with options (value, min, max, and - * precision). - * @return {!FieldNumber} The new field instance. - * @package - * @nocollapse - */ -FieldNumber.fromJson = function(options) { - // `this` might be a subclass of FieldNumber if that class doesn't override - // the static fromJson method. - return new this( - options['value'], undefined, undefined, undefined, undefined, options); -}; - -/** - * Serializable fields are saved by the XML renderer, non-serializable fields - * are not. Editable fields should also be serializable. - * @type {boolean} - */ -FieldNumber.prototype.SERIALIZABLE = true; - -/** - * Configure the field based on the given map of options. - * @param {!Object} config A map of options to configure the field based on. - * @protected - * @override - */ -FieldNumber.prototype.configure_ = function(config) { - FieldNumber.superClass_.configure_.call(this, config); - this.setMinInternal_(config['min']); - this.setMaxInternal_(config['max']); - this.setPrecisionInternal_(config['precision']); -}; - -/** - * Set the maximum, minimum and precision constraints on this field. - * Any of these properties may be undefined or NaN to be disabled. - * Setting precision (usually a power of 10) enforces a minimum step between - * values. That is, the user's value will rounded to the closest multiple of - * precision. The least significant digit place is inferred from the precision. - * Integers values can be enforces by choosing an integer precision. - * @param {?(number|string|undefined)} min Minimum value. - * @param {?(number|string|undefined)} max Maximum value. - * @param {?(number|string|undefined)} precision Precision for value. - */ -FieldNumber.prototype.setConstraints = function(min, max, precision) { - this.setMinInternal_(min); - this.setMaxInternal_(max); - this.setPrecisionInternal_(precision); - this.setValue(this.getValue()); -}; - -/** - * Sets the minimum value this field can contain. Updates the value to reflect. - * @param {?(number|string|undefined)} min Minimum value. - */ -FieldNumber.prototype.setMin = function(min) { - this.setMinInternal_(min); - this.setValue(this.getValue()); -}; - -/** - * Sets the minimum value this field can contain. Called internally to avoid - * value updates. - * @param {?(number|string|undefined)} min Minimum value. - * @private - */ -FieldNumber.prototype.setMinInternal_ = function(min) { - if (min == null) { - this.min_ = -Infinity; - } else { - min = Number(min); - if (!isNaN(min)) { - this.min_ = min; - } - } -}; - -/** - * Returns the current minimum value this field can contain. Default is - * -Infinity. - * @return {number} The current minimum value this field can contain. - */ -FieldNumber.prototype.getMin = function() { - return this.min_; -}; - -/** - * Sets the maximum value this field can contain. Updates the value to reflect. - * @param {?(number|string|undefined)} max Maximum value. - */ -FieldNumber.prototype.setMax = function(max) { - this.setMaxInternal_(max); - this.setValue(this.getValue()); -}; - -/** - * Sets the maximum value this field can contain. Called internally to avoid - * value updates. - * @param {?(number|string|undefined)} max Maximum value. - * @private - */ -FieldNumber.prototype.setMaxInternal_ = function(max) { - if (max == null) { - this.max_ = Infinity; - } else { - max = Number(max); - if (!isNaN(max)) { - this.max_ = max; - } - } -}; - -/** - * Returns the current maximum value this field can contain. Default is - * Infinity. - * @return {number} The current maximum value this field can contain. - */ -FieldNumber.prototype.getMax = function() { - return this.max_; -}; - -/** - * Sets the precision of this field's value, i.e. the number to which the - * value is rounded. Updates the field to reflect. - * @param {?(number|string|undefined)} precision The number to which the - * field's value is rounded. - */ -FieldNumber.prototype.setPrecision = function(precision) { - this.setPrecisionInternal_(precision); - this.setValue(this.getValue()); -}; - -/** - * Sets the precision of this field's value. Called internally to avoid - * value updates. - * @param {?(number|string|undefined)} precision The number to which the - * field's value is rounded. - * @private - */ -FieldNumber.prototype.setPrecisionInternal_ = function(precision) { - this.precision_ = Number(precision) || 0; - let precisionString = String(this.precision_); - if (precisionString.indexOf('e') !== -1) { - // String() is fast. But it turns .0000001 into '1e-7'. - // Use the much slower toLocaleString to access all the digits. - precisionString = - this.precision_.toLocaleString('en-US', {maximumFractionDigits: 20}); - } - const decimalIndex = precisionString.indexOf('.'); - if (decimalIndex === -1) { - // If the precision is 0 (float) allow any number of decimals, - // otherwise allow none. - this.decimalPlaces_ = precision ? 0 : null; - } else { - this.decimalPlaces_ = precisionString.length - decimalIndex - 1; - } -}; - -/** - * Returns the current precision of this field. The precision being the - * number to which the field's value is rounded. A precision of 0 means that - * the value is not rounded. - * @return {number} The number to which this field's value is rounded. - */ -FieldNumber.prototype.getPrecision = function() { - return this.precision_; -}; - -/** - * Ensure that the input value is a valid number (must fulfill the - * constraints placed on the field). - * @param {*=} opt_newValue The input value. - * @return {?number} A valid number, or null if invalid. - * @protected - * @override - */ -FieldNumber.prototype.doClassValidation_ = function(opt_newValue) { - if (opt_newValue === null) { - return null; - } - // Clean up text. - let newValue = String(opt_newValue); - // TODO: Handle cases like 'ten', '1.203,14', etc. - // 'O' is sometimes mistaken for '0' by inexperienced users. - newValue = newValue.replace(/O/ig, '0'); - // Strip out thousands separators. - newValue = newValue.replace(/,/g, ''); - // Ignore case of 'Infinity'. - newValue = newValue.replace(/infinity/i, 'Infinity'); - - // Clean up number. - let n = Number(newValue || 0); - if (isNaN(n)) { - // Invalid number. - return null; - } - // Get the value in range. - n = Math.min(Math.max(n, this.min_), this.max_); - // Round to nearest multiple of precision. - if (this.precision_ && isFinite(n)) { - n = Math.round(n / this.precision_) * this.precision_; - } - // Clean up floating point errors. - if (this.decimalPlaces_ !== null) { - n = Number(n.toFixed(this.decimalPlaces_)); - } - return n; -}; - -/** - * Create the number input editor widget. - * @return {!HTMLElement} The newly created number input editor. - * @protected - * @override - */ -FieldNumber.prototype.widgetCreate_ = function() { - const htmlInput = FieldNumber.superClass_.widgetCreate_.call(this); - - // Set the accessibility state - if (this.min_ > -Infinity) { - aria.setState(htmlInput, aria.State.VALUEMIN, this.min_); - } - if (this.max_ < Infinity) { - aria.setState(htmlInput, aria.State.VALUEMAX, this.max_); - } - return htmlInput; -}; - fieldRegistry.register('field_number', FieldNumber); exports.FieldNumber = FieldNumber; diff --git a/core/field_textinput.js b/core/field_textinput.js index dfb6f055f..25a8a15f5 100644 --- a/core/field_textinput.js +++ b/core/field_textinput.js @@ -20,19 +20,20 @@ const aria = goog.require('Blockly.utils.aria'); const browserEvents = goog.require('Blockly.browserEvents'); const dialog = goog.require('Blockly.dialog'); const dom = goog.require('Blockly.utils.dom'); +const dropDownDiv = goog.require('Blockly.dropDownDiv'); const eventUtils = goog.require('Blockly.Events.utils'); const fieldRegistry = goog.require('Blockly.fieldRegistry'); -const object = goog.require('Blockly.utils.object'); const parsing = goog.require('Blockly.utils.parsing'); const userAgent = goog.require('Blockly.utils.userAgent'); /* eslint-disable-next-line no-unused-vars */ const {BlockSvg} = goog.requireType('Blockly.BlockSvg'); const {Coordinate} = goog.require('Blockly.utils.Coordinate'); -const {DropDownDiv} = goog.require('Blockly.DropDownDiv'); const {Field} = goog.require('Blockly.Field'); const {KeyCodes} = goog.require('Blockly.utils.KeyCodes'); const {Msg} = goog.require('Blockly.Msg'); /* eslint-disable-next-line no-unused-vars */ +const {Sentinel} = goog.requireType('Blockly.utils.Sentinel'); +/* eslint-disable-next-line no-unused-vars */ const {WorkspaceSvg} = goog.requireType('Blockly.WorkspaceSvg'); /** @suppress {extraRequire} */ goog.require('Blockly.Events.BlockChange'); @@ -40,65 +41,565 @@ goog.require('Blockly.Events.BlockChange'); /** * Class for an editable text field. - * @param {string=} opt_value The initial value of the field. Should cast to a - * string. Defaults to an empty string if null or undefined. - * @param {?Function=} opt_validator A function that is called to validate - * changes to the field's value. Takes in a string & returns a validated - * string, or null to abort the change. - * @param {Object=} opt_config A map of options used to configure the field. - * See the [field creation documentation]{@link - * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/text-input#creation} - * for a list of properties this parameter supports. - * @extends {Field} - * @constructor * @alias Blockly.FieldTextInput */ -const FieldTextInput = function(opt_value, opt_validator, opt_config) { +class FieldTextInput extends Field { /** - * Allow browser to spellcheck this field. - * @type {boolean} + * @param {(string|!Sentinel)=} opt_value The initial value of the + * field. Should cast to a string. Defaults to an empty string if null or + * undefined. + * Also accepts Field.SKIP_SETUP if you wish to skip setup (only used by + * subclasses that want to handle configuration and setting the field + * value after their own constructors have run). + * @param {?Function=} opt_validator A function that is called to validate + * changes to the field's value. Takes in a string & returns a validated + * string, or null to abort the change. + * @param {Object=} opt_config A map of options used to configure the field. + * See the [field creation documentation]{@link + * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/text-input#creation} + * for a list of properties this parameter supports. + */ + constructor(opt_value, opt_validator, opt_config) { + super(Field.SKIP_SETUP); + + /** + * Allow browser to spellcheck this field. + * @type {boolean} + * @protected + */ + this.spellcheck_ = true; + + /** + * The HTML input element. + * @type {HTMLElement} + * @protected + */ + this.htmlInput_ = null; + + /** + * True if the field's value is currently being edited via the UI. + * @type {boolean} + * @private + */ + this.isBeingEdited_ = false; + + /** + * True if the value currently displayed in the field's editory UI is valid. + * @type {boolean} + * @private + */ + this.isTextValid_ = false; + + /** + * Key down event data. + * @type {?browserEvents.Data} + * @private + */ + this.onKeyDownWrapper_ = null; + + /** + * Key input event data. + * @type {?browserEvents.Data} + * @private + */ + this.onKeyInputWrapper_ = null; + + /** + * Whether the field should consider the whole parent block to be its click + * target. + * @type {?boolean} + */ + this.fullBlockClickTarget_ = false; + + /** + * The workspace that this field belongs to. + * @type {?WorkspaceSvg} + * @protected + */ + this.workspace_ = null; + + /** + * Serializable fields are saved by the serializer, non-serializable fields + * are not. Editable fields should also be serializable. + * @type {boolean} + */ + this.SERIALIZABLE = true; + + /** + * Mouse cursor style when over the hotspot that initiates the editor. + * @type {string} + */ + this.CURSOR = 'text'; + + if (opt_value === Field.SKIP_SETUP) return; + if (opt_config) this.configure_(opt_config); + this.setValue(opt_value); + if (opt_validator) this.setValidator(opt_validator); + } + + /** + * @override + */ + configure_(config) { + super.configure_(config); + if (typeof config['spellcheck'] === 'boolean') { + this.spellcheck_ = config['spellcheck']; + } + } + + /** + * @override + */ + initView() { + if (this.getConstants().FULL_BLOCK_FIELDS) { + // Step one: figure out if this is the only field on this block. + // Rendering is quite different in that case. + let nFields = 0; + let nConnections = 0; + + // Count the number of fields, excluding text fields + for (let i = 0, input; (input = this.sourceBlock_.inputList[i]); i++) { + for (let j = 0; (input.fieldRow[j]); j++) { + nFields++; + } + if (input.connection) { + nConnections++; + } + } + // The special case is when this is the only non-label field on the block + // and it has an output but no inputs. + this.fullBlockClickTarget_ = + nFields <= 1 && this.sourceBlock_.outputConnection && !nConnections; + } else { + this.fullBlockClickTarget_ = false; + } + + if (this.fullBlockClickTarget_) { + this.clickTarget_ = this.sourceBlock_.getSvgRoot(); + } else { + this.createBorderRect_(); + } + this.createTextElement_(); + } + + /** + * Ensure that the input value casts to a valid string. + * @param {*=} opt_newValue The input value. + * @return {*} A valid string, or null if invalid. * @protected */ - this.spellcheck_ = true; - - FieldTextInput.superClass_.constructor.call( - this, opt_value, opt_validator, opt_config); + doClassValidation_(opt_newValue) { + if (opt_newValue === null || opt_newValue === undefined) { + return null; + } + return String(opt_newValue); + } /** - * The HTML input element. - * @type {HTMLElement} - */ - this.htmlInput_ = null; - - /** - * Key down event data. - * @type {?browserEvents.Data} - * @private - */ - this.onKeyDownWrapper_ = null; - - /** - * Key input event data. - * @type {?browserEvents.Data} - * @private - */ - this.onKeyInputWrapper_ = null; - - /** - * Whether the field should consider the whole parent block to be its click - * target. - * @type {?boolean} - */ - this.fullBlockClickTarget_ = false; - - /** - * The workspace that this field belongs to. - * @type {?WorkspaceSvg} + * Called by setValue if the text input is not valid. If the field is + * currently being edited it reverts value of the field to the previous + * value while allowing the display text to be handled by the htmlInput_. + * @param {*} _invalidValue The input value that was determined to be invalid. + * This is not used by the text input because its display value is stored + * on the htmlInput_. * @protected */ - this.workspace_ = null; -}; -object.inherits(FieldTextInput, Field); + doValueInvalid_(_invalidValue) { + if (this.isBeingEdited_) { + this.isTextValid_ = false; + const oldValue = this.value_; + // Revert value when the text becomes invalid. + this.value_ = this.htmlInput_.untypedDefaultValue_; + if (this.sourceBlock_ && eventUtils.isEnabled()) { + eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))( + this.sourceBlock_, 'field', this.name || null, oldValue, + this.value_)); + } + } + } + + /** + * Called by setValue if the text input is valid. Updates the value of the + * field, and updates the text of the field if it is not currently being + * edited (i.e. handled by the htmlInput_). + * @param {*} newValue The value to be saved. The default validator guarantees + * that this is a string. + * @protected + */ + doValueUpdate_(newValue) { + this.isTextValid_ = true; + this.value_ = newValue; + if (!this.isBeingEdited_) { + // This should only occur if setValue is triggered programmatically. + this.isDirty_ = true; + } + } + + /** + * Updates text field to match the colour/style of the block. + * @package + */ + applyColour() { + if (this.sourceBlock_ && this.getConstants().FULL_BLOCK_FIELDS) { + if (this.borderRect_) { + this.borderRect_.setAttribute( + 'stroke', this.sourceBlock_.style.colourTertiary); + } else { + this.sourceBlock_.pathObject.svgPath.setAttribute( + 'fill', this.getConstants().FIELD_BORDER_RECT_COLOUR); + } + } + } + + /** + * Updates the colour of the htmlInput given the current validity of the + * field's value. + * @protected + */ + render_() { + super.render_(); + // This logic is done in render_ rather than doValueInvalid_ or + // doValueUpdate_ so that the code is more centralized. + if (this.isBeingEdited_) { + this.resizeEditor_(); + const htmlInput = /** @type {!HTMLElement} */ (this.htmlInput_); + if (!this.isTextValid_) { + dom.addClass(htmlInput, 'blocklyInvalidInput'); + aria.setState(htmlInput, aria.State.INVALID, true); + } else { + dom.removeClass(htmlInput, 'blocklyInvalidInput'); + aria.setState(htmlInput, aria.State.INVALID, false); + } + } + } + + /** + * Set whether this field is spellchecked by the browser. + * @param {boolean} check True if checked. + */ + setSpellcheck(check) { + if (check === this.spellcheck_) { + return; + } + this.spellcheck_ = check; + if (this.htmlInput_) { + this.htmlInput_.setAttribute('spellcheck', this.spellcheck_); + } + } + + /** + * Show the inline free-text editor on top of the text. + * @param {Event=} _opt_e Optional mouse event that triggered the field to + * open, or undefined if triggered programmatically. + * @param {boolean=} opt_quietInput True if editor should be created without + * focus. Defaults to false. + * @protected + */ + showEditor_(_opt_e, opt_quietInput) { + this.workspace_ = (/** @type {!BlockSvg} */ (this.sourceBlock_)).workspace; + const quietInput = opt_quietInput || false; + if (!quietInput && + (userAgent.MOBILE || userAgent.ANDROID || userAgent.IPAD)) { + this.showPromptEditor_(); + } else { + this.showInlineEditor_(quietInput); + } + } + + /** + * Create and show a text input editor that is a prompt (usually a popup). + * Mobile browsers have issues with in-line textareas (focus and keyboards). + * @private + */ + showPromptEditor_() { + dialog.prompt(Msg['CHANGE_VALUE_TITLE'], this.getText(), function(text) { + // Text is null if user pressed cancel button. + if (text !== null) { + this.setValue(this.getValueFromEditorText_(text)); + } + }.bind(this)); + } + + /** + * Create and show a text input editor that sits directly over the text input. + * @param {boolean} quietInput True if editor should be created without + * focus. + * @private + */ + showInlineEditor_(quietInput) { + WidgetDiv.show(this, this.sourceBlock_.RTL, this.widgetDispose_.bind(this)); + this.htmlInput_ = this.widgetCreate_(); + this.isBeingEdited_ = true; + + if (!quietInput) { + this.htmlInput_.focus({preventScroll: true}); + this.htmlInput_.select(); + } + } + + /** + * Create the text input editor widget. + * @return {!HTMLElement} The newly created text input editor. + * @protected + */ + widgetCreate_() { + eventUtils.setGroup(true); + const div = WidgetDiv.getDiv(); + + dom.addClass(this.getClickTarget_(), 'editing'); + + const htmlInput = + /** @type {HTMLInputElement} */ (document.createElement('input')); + htmlInput.className = 'blocklyHtmlInput'; + htmlInput.setAttribute('spellcheck', this.spellcheck_); + const scale = this.workspace_.getScale(); + const fontSize = (this.getConstants().FIELD_TEXT_FONTSIZE * scale) + 'pt'; + div.style.fontSize = fontSize; + htmlInput.style.fontSize = fontSize; + let borderRadius = (FieldTextInput.BORDERRADIUS * scale) + 'px'; + + if (this.fullBlockClickTarget_) { + const bBox = this.getScaledBBox(); + + // Override border radius. + borderRadius = (bBox.bottom - bBox.top) / 2 + 'px'; + // Pull stroke colour from the existing shadow block + const strokeColour = this.sourceBlock_.getParent() ? + this.sourceBlock_.getParent().style.colourTertiary : + this.sourceBlock_.style.colourTertiary; + htmlInput.style.border = (1 * scale) + 'px solid ' + strokeColour; + div.style.borderRadius = borderRadius; + div.style.transition = 'box-shadow 0.25s ease 0s'; + if (this.getConstants().FIELD_TEXTINPUT_BOX_SHADOW) { + div.style.boxShadow = + 'rgba(255, 255, 255, 0.3) 0 0 0 ' + (4 * scale) + 'px'; + } + } + htmlInput.style.borderRadius = borderRadius; + + div.appendChild(htmlInput); + + htmlInput.value = htmlInput.defaultValue = this.getEditorText_(this.value_); + htmlInput.untypedDefaultValue_ = this.value_; + htmlInput.oldValue_ = null; + + this.resizeEditor_(); + + this.bindInputEvents_(htmlInput); + + return htmlInput; + } + + /** + * Closes the editor, saves the results, and disposes of any events or + * DOM-references belonging to the editor. + * @protected + */ + widgetDispose_() { + // Non-disposal related things that we do when the editor closes. + this.isBeingEdited_ = false; + this.isTextValid_ = true; + // Make sure the field's node matches the field's internal value. + this.forceRerender(); + this.onFinishEditing_(this.value_); + eventUtils.setGroup(false); + + // Actual disposal. + this.unbindInputEvents_(); + const style = WidgetDiv.getDiv().style; + style.width = 'auto'; + style.height = 'auto'; + style.fontSize = ''; + style.transition = ''; + style.boxShadow = ''; + this.htmlInput_ = null; + + dom.removeClass(this.getClickTarget_(), 'editing'); + } + + /** + * A callback triggered when the user is done editing the field via the UI. + * @param {*} _value The new value of the field. + */ + onFinishEditing_(_value) { + // NOP by default. + // TODO(#2496): Support people passing a func into the field. + } + + /** + * Bind handlers for user input on the text input field's editor. + * @param {!HTMLElement} htmlInput The htmlInput to which event + * handlers will be bound. + * @protected + */ + bindInputEvents_(htmlInput) { + // Trap Enter without IME and Esc to hide. + this.onKeyDownWrapper_ = browserEvents.conditionalBind( + htmlInput, 'keydown', this, this.onHtmlInputKeyDown_); + // Resize after every input change. + this.onKeyInputWrapper_ = browserEvents.conditionalBind( + htmlInput, 'input', this, this.onHtmlInputChange_); + } + + /** + * Unbind handlers for user input and workspace size changes. + * @protected + */ + unbindInputEvents_() { + if (this.onKeyDownWrapper_) { + browserEvents.unbind(this.onKeyDownWrapper_); + this.onKeyDownWrapper_ = null; + } + if (this.onKeyInputWrapper_) { + browserEvents.unbind(this.onKeyInputWrapper_); + this.onKeyInputWrapper_ = null; + } + } + + /** + * Handle key down to the editor. + * @param {!Event} e Keyboard event. + * @protected + */ + onHtmlInputKeyDown_(e) { + if (e.keyCode === KeyCodes.ENTER) { + WidgetDiv.hide(); + dropDownDiv.hideWithoutAnimation(); + } else if (e.keyCode === KeyCodes.ESC) { + this.setValue(this.htmlInput_.untypedDefaultValue_); + WidgetDiv.hide(); + dropDownDiv.hideWithoutAnimation(); + } else if (e.keyCode === KeyCodes.TAB) { + WidgetDiv.hide(); + dropDownDiv.hideWithoutAnimation(); + this.sourceBlock_.tab(this, !e.shiftKey); + e.preventDefault(); + } + } + + /** + * Handle a change to the editor. + * @param {!Event} _e Keyboard event. + * @private + */ + onHtmlInputChange_(_e) { + const text = this.htmlInput_.value; + if (text !== this.htmlInput_.oldValue_) { + this.htmlInput_.oldValue_ = text; + + const value = this.getValueFromEditorText_(text); + this.setValue(value); + this.forceRerender(); + this.resizeEditor_(); + } + } + + /** + * Set the HTML input value and the field's internal value. The difference + * between this and ``setValue`` is that this also updates the HTML input + * value whilst editing. + * @param {*} newValue New value. + * @protected + */ + setEditorValue_(newValue) { + this.isDirty_ = true; + if (this.isBeingEdited_) { + // In the case this method is passed an invalid value, we still + // pass it through the transformation method `getEditorText` to deal + // with. Otherwise, the internal field's state will be inconsistent + // with what's shown to the user. + this.htmlInput_.value = this.getEditorText_(newValue); + } + this.setValue(newValue); + } + + /** + * Resize the editor to fit the text. + * @protected + */ + resizeEditor_() { + const div = WidgetDiv.getDiv(); + const bBox = this.getScaledBBox(); + div.style.width = bBox.right - bBox.left + 'px'; + div.style.height = bBox.bottom - bBox.top + 'px'; + + // In RTL mode block fields and LTR input fields the left edge moves, + // whereas the right edge is fixed. Reposition the editor. + const x = this.sourceBlock_.RTL ? bBox.right - div.offsetWidth : bBox.left; + const xy = new Coordinate(x, bBox.top); + + div.style.left = xy.x + 'px'; + div.style.top = xy.y + 'px'; + } + + /** + * Returns whether or not the field is tab navigable. + * @return {boolean} True if the field is tab navigable. + * @override + */ + isTabNavigable() { + return true; + } + + /** + * Use the `getText_` developer hook to override the field's text + * representation. When we're currently editing, return the current HTML value + * instead. Otherwise, return null which tells the field to use the default + * behaviour (which is a string cast of the field's value). + * @return {?string} The HTML value if we're editing, otherwise null. + * @protected + * @override + */ + getText_() { + if (this.isBeingEdited_ && this.htmlInput_) { + // We are currently editing, return the HTML input value instead. + return this.htmlInput_.value; + } + return null; + } + + /** + * Transform the provided value into a text to show in the HTML input. + * Override this method if the field's HTML input representation is different + * than the field's value. This should be coupled with an override of + * `getValueFromEditorText_`. + * @param {*} value The value stored in this field. + * @return {string} The text to show on the HTML input. + * @protected + */ + getEditorText_(value) { + return String(value); + } + + /** + * Transform the text received from the HTML input into a value to store + * in this field. + * Override this method if the field's HTML input representation is different + * than the field's value. This should be coupled with an override of + * `getEditorText_`. + * @param {string} text Text received from the HTML input. + * @return {*} The value to store. + * @protected + */ + getValueFromEditorText_(text) { + return text; + } + + /** + * Construct a FieldTextInput from a JSON arg object, + * dereferencing any string table references. + * @param {!Object} options A JSON object with options (text, and spellcheck). + * @return {!FieldTextInput} The new field instance. + * @package + * @nocollapse + */ + static fromJson(options) { + const text = parsing.replaceMessageReferences(options['text']); + // `this` might be a subclass of FieldTextInput if that class doesn't + // override the static fromJson method. + return new this(text, undefined, options); + } +} /** * The default value for this field. @@ -107,481 +608,12 @@ object.inherits(FieldTextInput, Field); */ FieldTextInput.prototype.DEFAULT_VALUE = ''; -/** - * Construct a FieldTextInput from a JSON arg object, - * dereferencing any string table references. - * @param {!Object} options A JSON object with options (text, and spellcheck). - * @return {!FieldTextInput} The new field instance. - * @package - * @nocollapse - */ -FieldTextInput.fromJson = function(options) { - const text = parsing.replaceMessageReferences(options['text']); - // `this` might be a subclass of FieldTextInput if that class doesn't override - // the static fromJson method. - return new this(text, undefined, options); -}; - -/** - * Serializable fields are saved by the XML renderer, non-serializable fields - * are not. Editable fields should also be serializable. - * @type {boolean} - */ -FieldTextInput.prototype.SERIALIZABLE = true; - /** * Pixel size of input border radius. * Should match blocklyText's border-radius in CSS. */ FieldTextInput.BORDERRADIUS = 4; -/** - * Mouse cursor style when over the hotspot that initiates the editor. - */ -FieldTextInput.prototype.CURSOR = 'text'; - -/** - * @override - */ -FieldTextInput.prototype.configure_ = function(config) { - FieldTextInput.superClass_.configure_.call(this, config); - if (typeof config['spellcheck'] === 'boolean') { - this.spellcheck_ = config['spellcheck']; - } -}; - -/** - * @override - */ -FieldTextInput.prototype.initView = function() { - if (this.getConstants().FULL_BLOCK_FIELDS) { - // Step one: figure out if this is the only field on this block. - // Rendering is quite different in that case. - let nFields = 0; - let nConnections = 0; - - // Count the number of fields, excluding text fields - for (let i = 0, input; (input = this.sourceBlock_.inputList[i]); i++) { - for (let j = 0; (input.fieldRow[j]); j++) { - nFields++; - } - if (input.connection) { - nConnections++; - } - } - // The special case is when this is the only non-label field on the block - // and it has an output but no inputs. - this.fullBlockClickTarget_ = - nFields <= 1 && this.sourceBlock_.outputConnection && !nConnections; - } else { - this.fullBlockClickTarget_ = false; - } - - if (this.fullBlockClickTarget_) { - this.clickTarget_ = this.sourceBlock_.getSvgRoot(); - } else { - this.createBorderRect_(); - } - this.createTextElement_(); -}; - -/** - * Ensure that the input value casts to a valid string. - * @param {*=} opt_newValue The input value. - * @return {*} A valid string, or null if invalid. - * @protected - */ -FieldTextInput.prototype.doClassValidation_ = function(opt_newValue) { - if (opt_newValue === null || opt_newValue === undefined) { - return null; - } - return String(opt_newValue); -}; - -/** - * Called by setValue if the text input is not valid. If the field is - * currently being edited it reverts value of the field to the previous - * value while allowing the display text to be handled by the htmlInput_. - * @param {*} _invalidValue The input value that was determined to be invalid. - * This is not used by the text input because its display value is stored on - * the htmlInput_. - * @protected - */ -FieldTextInput.prototype.doValueInvalid_ = function(_invalidValue) { - if (this.isBeingEdited_) { - this.isTextValid_ = false; - const oldValue = this.value_; - // Revert value when the text becomes invalid. - this.value_ = this.htmlInput_.untypedDefaultValue_; - if (this.sourceBlock_ && eventUtils.isEnabled()) { - eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))( - this.sourceBlock_, 'field', this.name || null, oldValue, - this.value_)); - } - } -}; - -/** - * Called by setValue if the text input is valid. Updates the value of the - * field, and updates the text of the field if it is not currently being - * edited (i.e. handled by the htmlInput_). - * @param {*} newValue The value to be saved. The default validator guarantees - * that this is a string. - * @protected - */ -FieldTextInput.prototype.doValueUpdate_ = function(newValue) { - this.isTextValid_ = true; - this.value_ = newValue; - if (!this.isBeingEdited_) { - // This should only occur if setValue is triggered programmatically. - this.isDirty_ = true; - } -}; - -/** - * Updates text field to match the colour/style of the block. - * @package - */ -FieldTextInput.prototype.applyColour = function() { - if (this.sourceBlock_ && this.getConstants().FULL_BLOCK_FIELDS) { - if (this.borderRect_) { - this.borderRect_.setAttribute( - 'stroke', this.sourceBlock_.style.colourTertiary); - } else { - this.sourceBlock_.pathObject.svgPath.setAttribute( - 'fill', this.getConstants().FIELD_BORDER_RECT_COLOUR); - } - } -}; - -/** - * Updates the colour of the htmlInput given the current validity of the - * field's value. - * @protected - */ -FieldTextInput.prototype.render_ = function() { - FieldTextInput.superClass_.render_.call(this); - // This logic is done in render_ rather than doValueInvalid_ or - // doValueUpdate_ so that the code is more centralized. - if (this.isBeingEdited_) { - this.resizeEditor_(); - const htmlInput = /** @type {!HTMLElement} */ (this.htmlInput_); - if (!this.isTextValid_) { - dom.addClass(htmlInput, 'blocklyInvalidInput'); - aria.setState(htmlInput, aria.State.INVALID, true); - } else { - dom.removeClass(htmlInput, 'blocklyInvalidInput'); - aria.setState(htmlInput, aria.State.INVALID, false); - } - } -}; - -/** - * Set whether this field is spellchecked by the browser. - * @param {boolean} check True if checked. - */ -FieldTextInput.prototype.setSpellcheck = function(check) { - if (check === this.spellcheck_) { - return; - } - this.spellcheck_ = check; - if (this.htmlInput_) { - this.htmlInput_.setAttribute('spellcheck', this.spellcheck_); - } -}; - -/** - * Show the inline free-text editor on top of the text. - * @param {Event=} _opt_e Optional mouse event that triggered the field to open, - * or undefined if triggered programmatically. - * @param {boolean=} opt_quietInput True if editor should be created without - * focus. Defaults to false. - * @protected - */ -FieldTextInput.prototype.showEditor_ = function(_opt_e, opt_quietInput) { - this.workspace_ = (/** @type {!BlockSvg} */ (this.sourceBlock_)).workspace; - const quietInput = opt_quietInput || false; - if (!quietInput && - (userAgent.MOBILE || userAgent.ANDROID || userAgent.IPAD)) { - this.showPromptEditor_(); - } else { - this.showInlineEditor_(quietInput); - } -}; - -/** - * Create and show a text input editor that is a prompt (usually a popup). - * Mobile browsers have issues with in-line textareas (focus and keyboards). - * @private - */ -FieldTextInput.prototype.showPromptEditor_ = function() { - dialog.prompt(Msg['CHANGE_VALUE_TITLE'], this.getText(), function(text) { - // Text is null if user pressed cancel button. - if (text !== null) { - this.setValue(this.getValueFromEditorText_(text)); - } - }.bind(this)); -}; - -/** - * Create and show a text input editor that sits directly over the text input. - * @param {boolean} quietInput True if editor should be created without - * focus. - * @private - */ -FieldTextInput.prototype.showInlineEditor_ = function(quietInput) { - WidgetDiv.show(this, this.sourceBlock_.RTL, this.widgetDispose_.bind(this)); - this.htmlInput_ = this.widgetCreate_(); - this.isBeingEdited_ = true; - - if (!quietInput) { - this.htmlInput_.focus({preventScroll: true}); - this.htmlInput_.select(); - } -}; - -/** - * Create the text input editor widget. - * @return {!HTMLElement} The newly created text input editor. - * @protected - */ -FieldTextInput.prototype.widgetCreate_ = function() { - eventUtils.setGroup(true); - const div = WidgetDiv.getDiv(); - - dom.addClass(this.getClickTarget_(), 'editing'); - - const htmlInput = - /** @type {HTMLInputElement} */ (document.createElement('input')); - htmlInput.className = 'blocklyHtmlInput'; - htmlInput.setAttribute('spellcheck', this.spellcheck_); - const scale = this.workspace_.getScale(); - const fontSize = (this.getConstants().FIELD_TEXT_FONTSIZE * scale) + 'pt'; - div.style.fontSize = fontSize; - htmlInput.style.fontSize = fontSize; - let borderRadius = (FieldTextInput.BORDERRADIUS * scale) + 'px'; - - if (this.fullBlockClickTarget_) { - const bBox = this.getScaledBBox(); - - // Override border radius. - borderRadius = (bBox.bottom - bBox.top) / 2 + 'px'; - // Pull stroke colour from the existing shadow block - const strokeColour = this.sourceBlock_.getParent() ? - this.sourceBlock_.getParent().style.colourTertiary : - this.sourceBlock_.style.colourTertiary; - htmlInput.style.border = (1 * scale) + 'px solid ' + strokeColour; - div.style.borderRadius = borderRadius; - div.style.transition = 'box-shadow 0.25s ease 0s'; - if (this.getConstants().FIELD_TEXTINPUT_BOX_SHADOW) { - div.style.boxShadow = - 'rgba(255, 255, 255, 0.3) 0 0 0 ' + (4 * scale) + 'px'; - } - } - htmlInput.style.borderRadius = borderRadius; - - div.appendChild(htmlInput); - - htmlInput.value = htmlInput.defaultValue = this.getEditorText_(this.value_); - htmlInput.untypedDefaultValue_ = this.value_; - htmlInput.oldValue_ = null; - - this.resizeEditor_(); - - this.bindInputEvents_(htmlInput); - - return htmlInput; -}; - -/** - * Closes the editor, saves the results, and disposes of any events or - * DOM-references belonging to the editor. - * @protected - */ -FieldTextInput.prototype.widgetDispose_ = function() { - // Non-disposal related things that we do when the editor closes. - this.isBeingEdited_ = false; - this.isTextValid_ = true; - // Make sure the field's node matches the field's internal value. - this.forceRerender(); - // TODO(#2496): Make this less of a hack. - if (this.onFinishEditing_) { - this.onFinishEditing_(this.value_); - } - eventUtils.setGroup(false); - - // Actual disposal. - this.unbindInputEvents_(); - const style = WidgetDiv.getDiv().style; - style.width = 'auto'; - style.height = 'auto'; - style.fontSize = ''; - style.transition = ''; - style.boxShadow = ''; - this.htmlInput_ = null; - - dom.removeClass(this.getClickTarget_(), 'editing'); -}; - -/** - * Bind handlers for user input on the text input field's editor. - * @param {!HTMLElement} htmlInput The htmlInput to which event - * handlers will be bound. - * @protected - */ -FieldTextInput.prototype.bindInputEvents_ = function(htmlInput) { - // Trap Enter without IME and Esc to hide. - this.onKeyDownWrapper_ = browserEvents.conditionalBind( - htmlInput, 'keydown', this, this.onHtmlInputKeyDown_); - // Resize after every input change. - this.onKeyInputWrapper_ = browserEvents.conditionalBind( - htmlInput, 'input', this, this.onHtmlInputChange_); -}; - -/** - * Unbind handlers for user input and workspace size changes. - * @protected - */ -FieldTextInput.prototype.unbindInputEvents_ = function() { - if (this.onKeyDownWrapper_) { - browserEvents.unbind(this.onKeyDownWrapper_); - this.onKeyDownWrapper_ = null; - } - if (this.onKeyInputWrapper_) { - browserEvents.unbind(this.onKeyInputWrapper_); - this.onKeyInputWrapper_ = null; - } -}; - -/** - * Handle key down to the editor. - * @param {!Event} e Keyboard event. - * @protected - */ -FieldTextInput.prototype.onHtmlInputKeyDown_ = function(e) { - if (e.keyCode === KeyCodes.ENTER) { - WidgetDiv.hide(); - DropDownDiv.hideWithoutAnimation(); - } else if (e.keyCode === KeyCodes.ESC) { - this.setValue(this.htmlInput_.untypedDefaultValue_); - WidgetDiv.hide(); - DropDownDiv.hideWithoutAnimation(); - } else if (e.keyCode === KeyCodes.TAB) { - WidgetDiv.hide(); - DropDownDiv.hideWithoutAnimation(); - this.sourceBlock_.tab(this, !e.shiftKey); - e.preventDefault(); - } -}; - -/** - * Handle a change to the editor. - * @param {!Event} _e Keyboard event. - * @private - */ -FieldTextInput.prototype.onHtmlInputChange_ = function(_e) { - const text = this.htmlInput_.value; - if (text !== this.htmlInput_.oldValue_) { - this.htmlInput_.oldValue_ = text; - - const value = this.getValueFromEditorText_(text); - this.setValue(value); - this.forceRerender(); - this.resizeEditor_(); - } -}; - -/** - * Set the HTML input value and the field's internal value. The difference - * between this and ``setValue`` is that this also updates the HTML input - * value whilst editing. - * @param {*} newValue New value. - * @protected - */ -FieldTextInput.prototype.setEditorValue_ = function(newValue) { - this.isDirty_ = true; - if (this.isBeingEdited_) { - // In the case this method is passed an invalid value, we still - // pass it through the transformation method `getEditorText` to deal - // with. Otherwise, the internal field's state will be inconsistent - // with what's shown to the user. - this.htmlInput_.value = this.getEditorText_(newValue); - } - this.setValue(newValue); -}; - -/** - * Resize the editor to fit the text. - * @protected - */ -FieldTextInput.prototype.resizeEditor_ = function() { - const div = WidgetDiv.getDiv(); - const bBox = this.getScaledBBox(); - div.style.width = bBox.right - bBox.left + 'px'; - div.style.height = bBox.bottom - bBox.top + 'px'; - - // In RTL mode block fields and LTR input fields the left edge moves, - // whereas the right edge is fixed. Reposition the editor. - const x = this.sourceBlock_.RTL ? bBox.right - div.offsetWidth : bBox.left; - const xy = new Coordinate(x, bBox.top); - - div.style.left = xy.x + 'px'; - div.style.top = xy.y + 'px'; -}; - -/** - * Returns whether or not the field is tab navigable. - * @return {boolean} True if the field is tab navigable. - * @override - */ -FieldTextInput.prototype.isTabNavigable = function() { - return true; -}; - -/** - * Use the `getText_` developer hook to override the field's text - * representation. When we're currently editing, return the current HTML value - * instead. Otherwise, return null which tells the field to use the default - * behaviour (which is a string cast of the field's value). - * @return {?string} The HTML value if we're editing, otherwise null. - * @protected - * @override - */ -FieldTextInput.prototype.getText_ = function() { - if (this.isBeingEdited_ && this.htmlInput_) { - // We are currently editing, return the HTML input value instead. - return this.htmlInput_.value; - } - return null; -}; - -/** - * Transform the provided value into a text to show in the HTML input. - * Override this method if the field's HTML input representation is different - * than the field's value. This should be coupled with an override of - * `getValueFromEditorText_`. - * @param {*} value The value stored in this field. - * @return {string} The text to show on the HTML input. - * @protected - */ -FieldTextInput.prototype.getEditorText_ = function(value) { - return String(value); -}; - -/** - * Transform the text received from the HTML input into a value to store - * in this field. - * Override this method if the field's HTML input representation is different - * than the field's value. This should be coupled with an override of - * `getEditorText_`. - * @param {string} text Text received from the HTML input. - * @return {*} The value to store. - * @protected - */ -FieldTextInput.prototype.getValueFromEditorText_ = function(text) { - return text; -}; - fieldRegistry.register('field_input', FieldTextInput); exports.FieldTextInput = FieldTextInput; diff --git a/core/field_variable.js b/core/field_variable.js index af43f02a5..bca4d5203 100644 --- a/core/field_variable.js +++ b/core/field_variable.js @@ -19,16 +19,18 @@ const Variables = goog.require('Blockly.Variables'); const Xml = goog.require('Blockly.Xml'); const fieldRegistry = goog.require('Blockly.fieldRegistry'); const internalConstants = goog.require('Blockly.internalConstants'); -const object = goog.require('Blockly.utils.object'); const parsing = goog.require('Blockly.utils.parsing'); /* eslint-disable-next-line no-unused-vars */ const {Block} = goog.requireType('Blockly.Block'); +const {Field} = goog.require('Blockly.Field'); const {FieldDropdown} = goog.require('Blockly.FieldDropdown'); /* eslint-disable-next-line no-unused-vars */ const {MenuItem} = goog.requireType('Blockly.MenuItem'); /* eslint-disable-next-line no-unused-vars */ const {Menu} = goog.requireType('Blockly.Menu'); const {Msg} = goog.require('Blockly.Msg'); +/* eslint-disable-next-line no-unused-vars */ +const {Sentinel} = goog.requireType('Blockly.utils.Sentinel'); const {Size} = goog.require('Blockly.utils.Size'); const {VariableModel} = goog.require('Blockly.VariableModel'); /** @suppress {extraRequire} */ @@ -37,483 +39,519 @@ goog.require('Blockly.Events.BlockChange'); /** * Class for a variable's dropdown field. - * @param {?string} varName The default name for the variable. If null, - * a unique variable name will be generated. - * @param {Function=} opt_validator A function that is called to validate - * changes to the field's value. Takes in a variable ID & returns a - * validated variable ID, or null to abort the change. - * @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 ''. - * @param {Object=} opt_config A map of options used to configure the field. - * See the [field creation documentation]{@link - * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/variable#creation} - * for a list of properties this parameter supports. * @extends {FieldDropdown} - * @constructor * @alias Blockly.FieldVariable */ -const FieldVariable = function( - varName, opt_validator, opt_variableTypes, opt_defaultType, opt_config) { - // The FieldDropdown constructor expects the field's initial value to be - // the first entry in the menu generator, which it may or may not be. - // Just do the relevant parts of the constructor. +class FieldVariable extends FieldDropdown { + /** + * @param {?string|!Sentinel} varName The default name for the variable. + * If null, a unique variable name will be generated. + * Also accepts Field.SKIP_SETUP if you wish to skip setup (only used by + * subclasses that want to handle configuration and setting the field + * value after their own constructors have run). + * @param {Function=} opt_validator A function that is called to validate + * changes to the field's value. Takes in a variable ID & returns a + * validated variable ID, or null to abort the change. + * @param {Array=} opt_variableTypes A list of the types of variables + * to include in the dropdown. Will only be used if opt_config is not + * provided. + * @param {string=} opt_defaultType The type of variable to create if this + * field's value is not explicitly set. Defaults to ''. Will only be used + * if opt_config is not provided. + * @param {Object=} opt_config A map of options used to configure the field. + * See the [field creation documentation]{@link + * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/variable#creation} + * for a list of properties this parameter supports. + */ + constructor( + varName, opt_validator, opt_variableTypes, opt_defaultType, opt_config) { + super(Field.SKIP_SETUP); + + /** + * An array of options for a dropdown list, + * or a function which generates these options. + * @type {(!Array| + * !function(this:FieldDropdown): !Array)} + * @protected + */ + this.menuGenerator_ = FieldVariable.dropdownCreate; + + /** + * The initial variable name passed to this field's constructor, or an + * empty string if a name wasn't provided. Used to create the initial + * variable. + * @type {string} + */ + this.defaultVariableName = typeof varName === 'string' ? varName : ''; + + /** + * The type of the default variable for this field. + * @type {string} + * @private + */ + this.defaultType_ = ''; + + /** + * All of the types of variables that will be available in this field's + * dropdown. + * @type {?Array} + */ + this.variableTypes = []; + + /** + * The size of the area rendered by the field. + * @type {Size} + * @protected + * @override + */ + this.size_ = new Size(0, 0); + + /** + * The variable model associated with this field. + * @type {?VariableModel} + * @private + */ + this.variable_ = null; + + /** + * Serializable fields are saved by the serializer, non-serializable fields + * are not. Editable fields should also be serializable. + * @type {boolean} + */ + this.SERIALIZABLE = true; + + if (varName === Field.SKIP_SETUP) return; + + if (opt_config) { + this.configure_(opt_config); + } else { + this.setTypes_(opt_variableTypes, opt_defaultType); + } + if (opt_validator) this.setValidator(opt_validator); + } /** - * An array of options for a dropdown list, - * or a function which generates these options. - * @type {(!Array| - * !function(this:FieldDropdown): !Array)} + * Configure the field based on the given map of options. + * @param {!Object} config A map of options to configure the field based on. * @protected */ - this.menuGenerator_ = FieldVariable.dropdownCreate; + configure_(config) { + super.configure_(config); + this.setTypes_(config['variableTypes'], config['defaultType']); + } /** - * The initial variable name passed to this field's constructor, or an - * empty string if a name wasn't provided. Used to create the initial - * variable. - * @type {string} + * 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 */ - this.defaultVariableName = typeof varName === 'string' ? varName : ''; + initModel() { + if (this.variable_) { + return; // Initialization already happened. + } + const variable = Variables.getOrCreateVariablePackage( + this.sourceBlock_.workspace, null, this.defaultVariableName, + this.defaultType_); + + // Don't call setValue because we don't want to cause a rerender. + this.doValueUpdate_(variable.getId()); + } /** - * The size of the area rendered by the field. - * @type {Size} - * @protected * @override */ - this.size_ = new Size(0, 0); - - opt_config && this.configure_(opt_config); - opt_validator && this.setValidator(opt_validator); - - if (!opt_config) { // Only do one kind of configuration or the other. - this.setTypes_(opt_variableTypes, opt_defaultType); - } -}; -object.inherits(FieldVariable, FieldDropdown); - -/** - * 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). - * @return {!FieldVariable} The new field instance. - * @package - * @nocollapse - */ -FieldVariable.fromJson = function(options) { - const varName = parsing.replaceMessageReferences(options['variable']); - // `this` might be a subclass of FieldVariable if that class doesn't override - // the static fromJson method. - return new this(varName, undefined, undefined, undefined, options); -}; - -/** - * Serializable fields are saved by the XML renderer, non-serializable fields - * are not. Editable fields should also be serializable. - * @type {boolean} - */ -FieldVariable.prototype.SERIALIZABLE = true; - -/** - * Configure the field based on the given map of options. - * @param {!Object} config A map of options to configure the field based on. - * @protected - */ -FieldVariable.prototype.configure_ = function(config) { - FieldVariable.superClass_.configure_.call(this, config); - this.setTypes_(config['variableTypes'], config['defaultType']); -}; - -/** - * 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 - */ -FieldVariable.prototype.initModel = function() { - if (this.variable_) { - return; // Initialization already happened. - } - const variable = Variables.getOrCreateVariablePackage( - this.sourceBlock_.workspace, null, this.defaultVariableName, - this.defaultType_); - - // Don't call setValue because we don't want to cause a rerender. - this.doValueUpdate_(variable.getId()); -}; - -/** - * @override - */ -FieldVariable.prototype.shouldAddBorderRect_ = function() { - return FieldVariable.superClass_.shouldAddBorderRect_.call(this) && - (!this.getConstants().FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW || - this.sourceBlock_.type !== 'variables_get'); -}; - -/** - * Initialize this field based on the given XML. - * @param {!Element} fieldElement The element containing information about the - * variable field's state. - */ -FieldVariable.prototype.fromXml = function(fieldElement) { - const id = fieldElement.getAttribute('id'); - const variableName = fieldElement.textContent; - // 'variabletype' should be lowercase, but until July 2019 it was sometimes - // recorded as 'variableType'. Thus we need to check for both. - const variableType = fieldElement.getAttribute('variabletype') || - fieldElement.getAttribute('variableType') || ''; - - const variable = Variables.getOrCreateVariablePackage( - this.sourceBlock_.workspace, id, variableName, variableType); - - // This should never happen :) - if (variableType !== null && variableType !== variable.type) { - throw Error( - 'Serialized variable type with id \'' + variable.getId() + - '\' had type ' + variable.type + ', and ' + - 'does not match variable field that references it: ' + - Xml.domToText(fieldElement) + '.'); + shouldAddBorderRect_() { + return super.shouldAddBorderRect_() && + (!this.getConstants().FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW || + this.sourceBlock_.type !== 'variables_get'); } - this.setValue(variable.getId()); -}; + /** + * Initialize this field based on the given XML. + * @param {!Element} fieldElement The element containing information about the + * variable field's state. + */ + fromXml(fieldElement) { + const id = fieldElement.getAttribute('id'); + const variableName = fieldElement.textContent; + // 'variabletype' should be lowercase, but until July 2019 it was sometimes + // recorded as 'variableType'. Thus we need to check for both. + const variableType = fieldElement.getAttribute('variabletype') || + fieldElement.getAttribute('variableType') || ''; -/** - * Serialize this field to XML. - * @param {!Element} fieldElement The element to populate with info about the - * field's state. - * @return {!Element} The element containing info about the field's state. - */ -FieldVariable.prototype.toXml = function(fieldElement) { - // Make sure the variable is initialized. - this.initModel(); + const variable = Variables.getOrCreateVariablePackage( + this.sourceBlock_.workspace, id, variableName, variableType); - fieldElement.id = this.variable_.getId(); - fieldElement.textContent = this.variable_.name; - if (this.variable_.type) { - fieldElement.setAttribute('variabletype', this.variable_.type); - } - return fieldElement; -}; - -/** - * Saves this field's value. - * @param {boolean=} doFullSerialization If true, the variable field will - * serialize the full state of the field being referenced (ie ID, name, - * and type) rather than just a reference to it (ie ID). - * @return {*} The state of the variable field. - * @override - * @package - */ -FieldVariable.prototype.saveState = function(doFullSerialization) { - const legacyState = this.saveLegacyState(FieldVariable); - if (legacyState !== null) { - return legacyState; - } - // Make sure the variable is initialized. - this.initModel(); - const state = {'id': this.variable_.getId()}; - if (doFullSerialization) { - state['name'] = this.variable_.name; - state['type'] = this.variable_.type; - } - return state; -}; - -/** - * Sets the field's value based on the given state. - * @param {*} state The state of the variable to assign to this variable field. - * @override - * @package - */ -FieldVariable.prototype.loadState = function(state) { - if (this.loadLegacyState(FieldVariable, state)) { - return; - } - // This is necessary so that blocks in the flyout can have custom var names. - const variable = Variables.getOrCreateVariablePackage( - this.sourceBlock_.workspace, state['id'] || null, state['name'], - state['type'] || ''); - this.setValue(variable.getId()); -}; - -/** - * Attach this field to a block. - * @param {!Block} block The block containing this field. - */ -FieldVariable.prototype.setSourceBlock = function(block) { - if (block.isShadow()) { - throw Error('Variable fields are not allowed to exist on shadow blocks.'); - } - FieldVariable.superClass_.setSourceBlock.call(this, block); -}; - -/** - * Get the variable's ID. - * @return {string} Current variable's ID. - */ -FieldVariable.prototype.getValue = function() { - return this.variable_ ? this.variable_.getId() : null; -}; - -/** - * 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. - */ -FieldVariable.prototype.getText = function() { - return this.variable_ ? this.variable_.name : ''; -}; - -/** - * 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 {?VariableModel} The selected variable, or null if none was - * selected. - * @package - */ -FieldVariable.prototype.getVariable = function() { - return this.variable_; -}; - -/** - * Gets the validation function for this field, or null if not set. - * Returns null if the variable is not set, because validators should not - * run on the initial setValue call, because the field won't be attached to - * a block and workspace at that point. - * @return {?Function} Validation function, or null. - */ -FieldVariable.prototype.getValidator = function() { - // Validators shouldn't operate on the initial setValue call. - // Normally this is achieved by calling setValidator after setValue, but - // this is not a possibility with variable fields. - if (this.variable_) { - return this.validator_; - } - return null; -}; - -/** - * Ensure that the ID belongs to a valid variable of an allowed type. - * @param {*=} opt_newValue The ID of the new variable to set. - * @return {?string} The validated ID, or null if invalid. - * @protected - */ -FieldVariable.prototype.doClassValidation_ = function(opt_newValue) { - if (opt_newValue === null) { - return null; - } - const newId = /** @type {string} */ (opt_newValue); - const variable = Variables.getVariable(this.sourceBlock_.workspace, newId); - if (!variable) { - console.warn( - 'Variable id doesn\'t point to a real variable! ' + - 'ID was ' + newId); - return null; - } - // Type Checks. - const type = variable.type; - if (!this.typeIsAllowed_(type)) { - console.warn('Variable type doesn\'t match this field! Type was ' + type); - return null; - } - return newId; -}; - -/** - * Update the value of this variable field, as well as its variable and text. - * - * The variable ID should be valid at this point, but if a variable field - * validator returns a bad ID, this could break. - * @param {*} newId The value to be saved. - * @protected - */ -FieldVariable.prototype.doValueUpdate_ = function(newId) { - this.variable_ = Variables.getVariable( - this.sourceBlock_.workspace, /** @type {string} */ (newId)); - FieldVariable.superClass_.doValueUpdate_.call(this, newId); -}; - -/** - * 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 - */ -FieldVariable.prototype.typeIsAllowed_ = function(type) { - const typeList = this.getVariableTypes_(); - if (!typeList) { - return true; // If it's null, all types are valid. - } - for (let i = 0; i < typeList.length; i++) { - if (type === typeList[i]) { - return true; + // This should never happen :) + if (variableType !== null && variableType !== variable.type) { + throw Error( + 'Serialized variable type with id \'' + variable.getId() + + '\' had type ' + variable.type + ', and ' + + 'does not match variable field that references it: ' + + Xml.domToText(fieldElement) + '.'); } - } - return false; -}; -/** - * Return a list of variable types to include in the dropdown. - * @return {!Array} Array of variable types. - * @throws {Error} if variableTypes is an empty array. - * @private - */ -FieldVariable.prototype.getVariableTypes_ = function() { - // TODO (#1513): Try to avoid calling this every time the field is edited. - let variableTypes = this.variableTypes; - if (variableTypes === null) { - // If variableTypes is null, return all variable types. - if (this.sourceBlock_ && this.sourceBlock_.workspace) { - return this.sourceBlock_.workspace.getVariableTypes(); + this.setValue(variable.getId()); + } + + /** + * Serialize this field to XML. + * @param {!Element} fieldElement The element to populate with info about the + * field's state. + * @return {!Element} The element containing info about the field's state. + */ + toXml(fieldElement) { + // Make sure the variable is initialized. + this.initModel(); + + fieldElement.id = this.variable_.getId(); + fieldElement.textContent = this.variable_.name; + if (this.variable_.type) { + fieldElement.setAttribute('variabletype', this.variable_.type); } + return fieldElement; } - variableTypes = variableTypes || ['']; - if (variableTypes.length === 0) { - // Throw an error if variableTypes is an empty list. - const name = this.getText(); - throw Error( - '\'variableTypes\' of field variable ' + name + ' was an empty list'); - } - 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 - */ -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. - const defaultType = opt_defaultType || ''; - let variableTypes; - // Set the allowable variable types. Null means all types on the workspace. - if (opt_variableTypes === null || opt_variableTypes === undefined) { - variableTypes = null; - } else if (Array.isArray(opt_variableTypes)) { - variableTypes = opt_variableTypes; - // Make sure the default type is valid. - let isInArray = false; - for (let i = 0; i < variableTypes.length; i++) { - if (variableTypes[i] === defaultType) { - isInArray = true; + /** + * Saves this field's value. + * @param {boolean=} doFullSerialization If true, the variable field will + * serialize the full state of the field being referenced (ie ID, name, + * and type) rather than just a reference to it (ie ID). + * @return {*} The state of the variable field. + * @override + * @package + */ + saveState(doFullSerialization) { + const legacyState = this.saveLegacyState(FieldVariable); + if (legacyState !== null) { + return legacyState; + } + // Make sure the variable is initialized. + this.initModel(); + const state = {'id': this.variable_.getId()}; + if (doFullSerialization) { + state['name'] = this.variable_.name; + state['type'] = this.variable_.type; + } + return state; + } + + /** + * Sets the field's value based on the given state. + * @param {*} state The state of the variable to assign to this variable + * field. + * @override + * @package + */ + loadState(state) { + if (this.loadLegacyState(FieldVariable, state)) { + return; + } + // This is necessary so that blocks in the flyout can have custom var names. + const variable = Variables.getOrCreateVariablePackage( + this.sourceBlock_.workspace, state['id'] || null, state['name'], + state['type'] || ''); + this.setValue(variable.getId()); + } + + /** + * Attach this field to a block. + * @param {!Block} block The block containing this field. + */ + setSourceBlock(block) { + if (block.isShadow()) { + throw Error('Variable fields are not allowed to exist on shadow blocks.'); + } + super.setSourceBlock(block); + } + + /** + * Get the variable's ID. + * @return {?string} Current variable's ID. + */ + getValue() { + return this.variable_ ? this.variable_.getId() : null; + } + + /** + * 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. + */ + getText() { + return this.variable_ ? this.variable_.name : ''; + } + + /** + * 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 {?VariableModel} The selected variable, or null if none was + * selected. + * @package + */ + getVariable() { + return this.variable_; + } + + /** + * Gets the validation function for this field, or null if not set. + * Returns null if the variable is not set, because validators should not + * run on the initial setValue call, because the field won't be attached to + * a block and workspace at that point. + * @return {?Function} Validation function, or null. + */ + getValidator() { + // Validators shouldn't operate on the initial setValue call. + // Normally this is achieved by calling setValidator after setValue, but + // this is not a possibility with variable fields. + if (this.variable_) { + return this.validator_; + } + return null; + } + + /** + * Ensure that the ID belongs to a valid variable of an allowed type. + * @param {*=} opt_newValue The ID of the new variable to set. + * @return {?string} The validated ID, or null if invalid. + * @protected + */ + doClassValidation_(opt_newValue) { + if (opt_newValue === null) { + return null; + } + const newId = /** @type {string} */ (opt_newValue); + const variable = Variables.getVariable(this.sourceBlock_.workspace, newId); + if (!variable) { + console.warn( + 'Variable id doesn\'t point to a real variable! ' + + 'ID was ' + newId); + return null; + } + // Type Checks. + const type = variable.type; + if (!this.typeIsAllowed_(type)) { + console.warn( + 'Variable type doesn\'t match this field! Type was ' + type); + return null; + } + return newId; + } + + /** + * Update the value of this variable field, as well as its variable and text. + * + * The variable ID should be valid at this point, but if a variable field + * validator returns a bad ID, this could break. + * @param {*} newId The value to be saved. + * @protected + */ + doValueUpdate_(newId) { + this.variable_ = Variables.getVariable( + this.sourceBlock_.workspace, /** @type {string} */ (newId)); + super.doValueUpdate_(newId); + } + + /** + * 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 + */ + typeIsAllowed_(type) { + const typeList = this.getVariableTypes_(); + if (!typeList) { + return true; // If it's null, all types are valid. + } + for (let i = 0; i < typeList.length; i++) { + if (type === typeList[i]) { + return true; } } - if (!isInArray) { + return false; + } + + /** + * Return a list of variable types to include in the dropdown. + * @return {!Array} Array of variable types. + * @throws {Error} if variableTypes is an empty array. + * @private + */ + getVariableTypes_() { + // TODO (#1513): Try to avoid calling this every time the field is edited. + let variableTypes = this.variableTypes; + if (variableTypes === null) { + // If variableTypes is null, return all variable types. + if (this.sourceBlock_ && this.sourceBlock_.workspace) { + return this.sourceBlock_.workspace.getVariableTypes(); + } + } + variableTypes = variableTypes || ['']; + if (variableTypes.length === 0) { + // Throw an error if variableTypes is an empty list. + const name = this.getText(); throw Error( - 'Invalid default type \'' + defaultType + '\' in ' + - 'the definition of a FieldVariable'); + '\'variableTypes\' of field variable ' + name + ' was an empty list'); } - } else { - throw Error( - '\'variableTypes\' was not an array in the definition of ' + - 'a FieldVariable'); + return variableTypes; } - // Only update the field once all checks pass. - this.defaultType_ = defaultType; - this.variableTypes = variableTypes; -}; -/** - * Refreshes the name of the variable by grabbing the name of the model. - * Used when a variable gets renamed, but the ID stays the same. Should only - * be called by the block. - * @package - */ -FieldVariable.prototype.refreshVariableName = function() { - this.forceRerender(); -}; - -/** - * Return a sorted list of variable names for variable dropdown menus. - * Include a special option at the end for creating a new variable name. - * @return {!Array} Array of variable names/id tuples. - * @this {FieldVariable} - */ -FieldVariable.dropdownCreate = function() { - if (!this.variable_) { - throw Error( - 'Tried to call dropdownCreate on a variable field with no' + - ' variable selected.'); - } - const name = this.getText(); - let variableModelList = []; - if (this.sourceBlock_ && this.sourceBlock_.workspace) { - const variableTypes = this.getVariableTypes_(); - // Get a copy of the list, so that adding rename and new variable options - // doesn't modify the workspace's list. - for (let i = 0; i < variableTypes.length; i++) { - const variableType = variableTypes[i]; - const variables = - this.sourceBlock_.workspace.getVariablesOfType(variableType); - variableModelList = variableModelList.concat(variables); + /** + * 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 + */ + setTypes_(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. + const defaultType = opt_defaultType || ''; + let variableTypes; + // Set the allowable variable types. Null means all types on the workspace. + if (opt_variableTypes === null || opt_variableTypes === undefined) { + variableTypes = null; + } else if (Array.isArray(opt_variableTypes)) { + variableTypes = opt_variableTypes; + // Make sure the default type is valid. + let isInArray = false; + for (let i = 0; i < variableTypes.length; i++) { + if (variableTypes[i] === defaultType) { + isInArray = true; + } + } + if (!isInArray) { + throw Error( + 'Invalid default type \'' + defaultType + '\' in ' + + 'the definition of a FieldVariable'); + } + } else { + throw Error( + '\'variableTypes\' was not an array in the definition of ' + + 'a FieldVariable'); } - } - variableModelList.sort(VariableModel.compareByName); - - const options = []; - for (let i = 0; i < variableModelList.length; i++) { - // Set the UUID as the internal representation of the variable. - options[i] = [variableModelList[i].name, variableModelList[i].getId()]; - } - options.push([Msg['RENAME_VARIABLE'], internalConstants.RENAME_VARIABLE_ID]); - if (Msg['DELETE_VARIABLE']) { - options.push([ - Msg['DELETE_VARIABLE'].replace('%1', name), - internalConstants.DELETE_VARIABLE_ID, - ]); + // Only update the field once all checks pass. + this.defaultType_ = defaultType; + this.variableTypes = variableTypes; } - return options; -}; + /** + * Refreshes the name of the variable by grabbing the name of the model. + * Used when a variable gets renamed, but the ID stays the same. Should only + * be called by the block. + * @override + * @package + */ + refreshVariableName() { + this.forceRerender(); + } -/** - * Handle the selection of an item in the variable dropdown menu. - * Special case the 'Rename variable...' and 'Delete variable...' options. - * In the rename case, prompt the user for a new name. - * @param {!Menu} menu The Menu component clicked. - * @param {!MenuItem} menuItem The MenuItem selected within menu. - * @protected - */ -FieldVariable.prototype.onItemSelected_ = function(menu, menuItem) { - const id = menuItem.getValue(); - // Handle special cases. - if (this.sourceBlock_ && this.sourceBlock_.workspace) { - if (id === internalConstants.RENAME_VARIABLE_ID) { - // Rename variable. - Variables.renameVariable(this.sourceBlock_.workspace, this.variable_); - return; - } else if (id === internalConstants.DELETE_VARIABLE_ID) { - // Delete variable. - this.sourceBlock_.workspace.deleteVariableById(this.variable_.getId()); - return; + /** + * Handle the selection of an item in the variable dropdown menu. + * Special case the 'Rename variable...' and 'Delete variable...' options. + * In the rename case, prompt the user for a new name. + * @param {!Menu} menu The Menu component clicked. + * @param {!MenuItem} menuItem The MenuItem selected within menu. + * @protected + */ + onItemSelected_(menu, menuItem) { + const id = menuItem.getValue(); + // Handle special cases. + if (this.sourceBlock_ && this.sourceBlock_.workspace) { + if (id === internalConstants.RENAME_VARIABLE_ID) { + // Rename variable. + Variables.renameVariable( + this.sourceBlock_.workspace, + /** @type {!VariableModel} */ (this.variable_)); + return; + } else if (id === internalConstants.DELETE_VARIABLE_ID) { + // Delete variable. + this.sourceBlock_.workspace.deleteVariableById(this.variable_.getId()); + return; + } } + // Handle unspecial case. + this.setValue(id); } - // Handle unspecial case. - this.setValue(id); -}; -/** - * Overrides referencesVariables(), indicating this field refers to a variable. - * @return {boolean} True. - * @package - * @override - */ -FieldVariable.prototype.referencesVariables = function() { - return true; -}; + /** + * Overrides referencesVariables(), indicating this field refers to a + * variable. + * @return {boolean} True. + * @package + * @override + */ + referencesVariables() { + return true; + } + + /** + * 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). + * @return {!FieldVariable} The new field instance. + * @package + * @nocollapse + * @override + */ + static fromJson(options) { + const varName = parsing.replaceMessageReferences(options['variable']); + // `this` might be a subclass of FieldVariable if that class doesn't + // override the static fromJson method. + return new this(varName, undefined, undefined, undefined, options); + } + + /** + * Return a sorted list of variable names for variable dropdown menus. + * Include a special option at the end for creating a new variable name. + * @return {!Array} Array of variable names/id tuples. + * @this {FieldVariable} + */ + static dropdownCreate() { + if (!this.variable_) { + throw Error( + 'Tried to call dropdownCreate on a variable field with no' + + ' variable selected.'); + } + const name = this.getText(); + let variableModelList = []; + if (this.sourceBlock_ && this.sourceBlock_.workspace) { + const variableTypes = this.getVariableTypes_(); + // Get a copy of the list, so that adding rename and new variable options + // doesn't modify the workspace's list. + for (let i = 0; i < variableTypes.length; i++) { + const variableType = variableTypes[i]; + const variables = + this.sourceBlock_.workspace.getVariablesOfType(variableType); + variableModelList = variableModelList.concat(variables); + } + } + variableModelList.sort(VariableModel.compareByName); + + const options = []; + for (let i = 0; i < variableModelList.length; i++) { + // Set the UUID as the internal representation of the variable. + options[i] = [variableModelList[i].name, variableModelList[i].getId()]; + } + options.push( + [Msg['RENAME_VARIABLE'], internalConstants.RENAME_VARIABLE_ID]); + if (Msg['DELETE_VARIABLE']) { + options.push([ + Msg['DELETE_VARIABLE'].replace('%1', name), + internalConstants.DELETE_VARIABLE_ID, + ]); + } + + return options; + } +} fieldRegistry.register('field_variable', FieldVariable); diff --git a/core/flyout_base.js b/core/flyout_base.js index d1484ffa8..ef44c4236 100644 --- a/core/flyout_base.js +++ b/core/flyout_base.js @@ -24,7 +24,6 @@ const common = goog.require('Blockly.common'); const dom = goog.require('Blockly.utils.dom'); const eventUtils = goog.require('Blockly.Events.utils'); const idGenerator = goog.require('Blockly.utils.idGenerator'); -const object = goog.require('Blockly.utils.object'); const toolbox = goog.require('Blockly.utils.toolbox'); /* eslint-disable-next-line no-unused-vars */ const {BlockSvg} = goog.requireType('Blockly.BlockSvg'); @@ -59,1076 +58,1130 @@ goog.require('Blockly.blockRendering'); /** * Class for a flyout. - * @param {!Options} workspaceOptions Dictionary of options for the - * workspace. - * @constructor * @abstract * @implements {IFlyout} * @extends {DeleteArea} * @alias Blockly.Flyout */ -const Flyout = function(workspaceOptions) { - Flyout.superClass_.constructor.call(this); - workspaceOptions.setMetrics = this.setMetrics_.bind(this); - +class Flyout extends DeleteArea { /** - * @type {!WorkspaceSvg} - * @protected + * @param {!Options} workspaceOptions Dictionary of options for the + * workspace. */ - this.workspace_ = new WorkspaceSvg(workspaceOptions); - this.workspace_.setMetricsManager( - new FlyoutMetricsManager(this.workspace_, this)); + constructor(workspaceOptions) { + super(); + workspaceOptions.setMetrics = this.setMetrics_.bind(this); - this.workspace_.isFlyout = true; - // Keep the workspace visibility consistent with the flyout's visibility. - this.workspace_.setVisible(this.isVisible_); + /** + * @type {!WorkspaceSvg} + * @protected + */ + this.workspace_ = new WorkspaceSvg(workspaceOptions); + this.workspace_.setMetricsManager( + new FlyoutMetricsManager(this.workspace_, this)); - /** - * The unique id for this component that is used to register with the - * ComponentManager. - * @type {string} - */ - this.id = idGenerator.genUid(); + this.workspace_.isFlyout = true; + // Keep the workspace visibility consistent with the flyout's visibility. + this.workspace_.setVisible(this.isVisible_); - /** - * Is RTL vs LTR. - * @type {boolean} - */ - this.RTL = !!workspaceOptions.RTL; + /** + * The unique id for this component that is used to register with the + * ComponentManager. + * @type {string} + */ + this.id = idGenerator.genUid(); - /** - * Whether the flyout should be laid out horizontally or not. - * @type {boolean} - * @package - */ - this.horizontalLayout = false; + /** + * Is RTL vs LTR. + * @type {boolean} + */ + this.RTL = !!workspaceOptions.RTL; - /** - * Position of the toolbox and flyout relative to the workspace. - * @type {number} - * @protected - */ - this.toolboxPosition_ = workspaceOptions.toolboxPosition; + /** + * Whether the flyout should be laid out horizontally or not. + * @type {boolean} + * @package + */ + this.horizontalLayout = false; - /** - * Opaque data that can be passed to Blockly.unbindEvent_. - * @type {!Array} - * @private - */ - this.eventWrappers_ = []; + /** + * Position of the toolbox and flyout relative to the workspace. + * @type {number} + * @protected + */ + this.toolboxPosition_ = workspaceOptions.toolboxPosition; - /** - * List of background mats that lurk behind each block to catch clicks - * landing in the blocks' lakes and bays. - * @type {!Array} - * @private - */ - this.mats_ = []; + /** + * Opaque data that can be passed to Blockly.unbindEvent_. + * @type {!Array} + * @private + */ + this.eventWrappers_ = []; - /** - * List of visible buttons. - * @type {!Array} - * @protected - */ - this.buttons_ = []; + /** + * Function that will be registered as a change listener on the workspace + * to reflow when blocks in the flyout workspace change. + * @type {?Function} + * @private + */ + this.reflowWrapper_ = null; - /** - * List of event listeners. - * @type {!Array} - * @private - */ - this.listeners_ = []; - - /** - * List of blocks that should always be disabled. - * @type {!Array} - * @private - */ - this.permanentlyDisabled_ = []; - - /** - * Width of output tab. - * @type {number} - * @protected - * @const - */ - this.tabWidth_ = this.workspace_.getRenderer().getConstants().TAB_WIDTH; - - /** - * The target workspace - * @type {?WorkspaceSvg} - * @package - */ - this.targetWorkspace = null; - - /** - * A list of blocks that can be reused. - * @type {!Array} - * @private - */ - this.recycledBlocks_ = []; -}; -object.inherits(Flyout, DeleteArea); - -/** - * Does the flyout automatically close when a block is created? - * @type {boolean} - */ -Flyout.prototype.autoClose = true; - -/** - * Whether the flyout is visible. - * @type {boolean} - * @private - */ -Flyout.prototype.isVisible_ = false; - -/** - * Whether the workspace containing this flyout is visible. - * @type {boolean} - * @private - */ -Flyout.prototype.containerVisible_ = true; - -/** - * Corner radius of the flyout background. - * @type {number} - * @const - */ -Flyout.prototype.CORNER_RADIUS = 8; - -/** - * Margin around the edges of the blocks in the flyout. - * @type {number} - * @const - */ -Flyout.prototype.MARGIN = Flyout.prototype.CORNER_RADIUS; - -// TODO: Move GAP_X and GAP_Y to their appropriate files. - -/** - * Gap between items in horizontal flyouts. Can be overridden with the "sep" - * element. - * @const {number} - */ -Flyout.prototype.GAP_X = Flyout.prototype.MARGIN * 3; - -/** - * Gap between items in vertical flyouts. Can be overridden with the "sep" - * element. - * @const {number} - */ -Flyout.prototype.GAP_Y = Flyout.prototype.MARGIN * 3; - -/** - * Top/bottom padding between scrollbar and edge of flyout background. - * @type {number} - * @const - */ -Flyout.prototype.SCROLLBAR_MARGIN = 2.5; - -/** - * Width of flyout. - * @type {number} - * @protected - */ -Flyout.prototype.width_ = 0; - -/** - * Height of flyout. - * @type {number} - * @protected - */ -Flyout.prototype.height_ = 0; - -/** - * Range of a drag angle from a flyout considered "dragging toward workspace". - * Drags that are within the bounds of this many degrees from the orthogonal - * line to the flyout edge are considered to be "drags toward the workspace". - * Example: - * Flyout Edge Workspace - * [block] / <-within this angle, drags "toward workspace" | - * [block] ---- orthogonal to flyout boundary ---- | - * [block] \ | - * The angle is given in degrees from the orthogonal. - * - * This is used to know when to create a new block and when to scroll the - * flyout. Setting it to 360 means that all drags create a new block. - * @type {number} - * @protected - */ -Flyout.prototype.dragAngleRange_ = 70; - -/** - * Creates the flyout's DOM. Only needs to be called once. The flyout can - * either exist as its own SVG element or be a g element nested inside a - * separate SVG element. - * @param {string| - * !Svg| - * !Svg} tagName The type of tag to - * put the flyout in. This should be or . - * @return {!SVGElement} The flyout's SVG group. - */ -Flyout.prototype.createDom = function(tagName) { - /* - - - - - */ - // Setting style to display:none to start. The toolbox and flyout - // hide/show code will set up proper visibility and size later. - this.svgGroup_ = dom.createSvgElement( - tagName, {'class': 'blocklyFlyout', 'style': 'display: none'}, null); - this.svgBackground_ = dom.createSvgElement( - Svg.PATH, {'class': 'blocklyFlyoutBackground'}, this.svgGroup_); - this.svgGroup_.appendChild(this.workspace_.createDom()); - this.workspace_.getThemeManager().subscribe( - this.svgBackground_, 'flyoutBackgroundColour', 'fill'); - this.workspace_.getThemeManager().subscribe( - this.svgBackground_, 'flyoutOpacity', 'fill-opacity'); - return this.svgGroup_; -}; - -/** - * Initializes the flyout. - * @param {!WorkspaceSvg} targetWorkspace The workspace in which to - * create new blocks. - */ -Flyout.prototype.init = function(targetWorkspace) { - this.targetWorkspace = targetWorkspace; - this.workspace_.targetWorkspace = targetWorkspace; - - this.workspace_.scrollbar = new ScrollbarPair( - this.workspace_, this.horizontalLayout, !this.horizontalLayout, - 'blocklyFlyoutScrollbar', this.SCROLLBAR_MARGIN); - - this.hide(); - - Array.prototype.push.apply( - this.eventWrappers_, - browserEvents.conditionalBind( - this.svgGroup_, 'wheel', this, this.wheel_)); - if (!this.autoClose) { - this.filterWrapper_ = this.filterForCapacity_.bind(this); - this.targetWorkspace.addChangeListener(this.filterWrapper_); - } - - // Dragging the flyout up and down. - Array.prototype.push.apply( - this.eventWrappers_, - browserEvents.conditionalBind( - 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_.setVariableMap(this.targetWorkspace.getVariableMap()); - - this.workspace_.createPotentialVariableMap(); - - targetWorkspace.getComponentManager().addComponent({ - component: this, - weight: 1, - capabilities: [ - ComponentManager.Capability.DELETE_AREA, - ComponentManager.Capability.DRAG_TARGET, - ], - }); -}; - -/** - * Dispose of this flyout. - * Unlink from all DOM elements to prevent memory leaks. - * @suppress {checkTypes} - */ -Flyout.prototype.dispose = function() { - this.hide(); - this.workspace_.getComponentManager().removeComponent(this.id); - browserEvents.unbind(this.eventWrappers_); - if (this.filterWrapper_) { - this.targetWorkspace.removeChangeListener(this.filterWrapper_); + /** + * Function that disables blocks in the flyout based on max block counts + * allowed in the target workspace. Registered as a change listener on the + * target workspace. + * @type {?Function} + * @private + */ this.filterWrapper_ = null; - } - if (this.workspace_) { - this.workspace_.getThemeManager().unsubscribe(this.svgBackground_); - this.workspace_.targetWorkspace = null; - this.workspace_.dispose(); - this.workspace_ = null; - } - if (this.svgGroup_) { - dom.removeNode(this.svgGroup_); + + /** + * List of background mats that lurk behind each block to catch clicks + * landing in the blocks' lakes and bays. + * @type {!Array} + * @private + */ + this.mats_ = []; + + /** + * List of visible buttons. + * @type {!Array} + * @protected + */ + this.buttons_ = []; + + /** + * List of event listeners. + * @type {!Array} + * @private + */ + this.listeners_ = []; + + /** + * List of blocks that should always be disabled. + * @type {!Array} + * @private + */ + this.permanentlyDisabled_ = []; + + /** + * Width of output tab. + * @type {number} + * @protected + * @const + */ + this.tabWidth_ = this.workspace_.getRenderer().getConstants().TAB_WIDTH; + + /** + * The target workspace + * @type {?WorkspaceSvg} + * @package + */ + this.targetWorkspace = null; + + /** + * A list of blocks that can be reused. + * @type {!Array} + * @private + */ + this.recycledBlocks_ = []; + + /** + * Does the flyout automatically close when a block is created? + * @type {boolean} + */ + this.autoClose = true; + + /** + * Whether the flyout is visible. + * @type {boolean} + * @private + */ + this.isVisible_ = false; + + /** + * Whether the workspace containing this flyout is visible. + * @type {boolean} + * @private + */ + this.containerVisible_ = true; + + /** + * A map from blocks to the rects which are beneath them to act as input + * targets. + * @type {!WeakMap} + * @private + */ + this.rectMap_ = new WeakMap(); + + /** + * Corner radius of the flyout background. + * @type {number} + * @const + */ + this.CORNER_RADIUS = 8; + + /** + * Margin around the edges of the blocks in the flyout. + * @type {number} + * @const + */ + this.MARGIN = this.CORNER_RADIUS; + + // TODO: Move GAP_X and GAP_Y to their appropriate files. + + /** + * Gap between items in horizontal flyouts. Can be overridden with the "sep" + * element. + * @const {number} + */ + this.GAP_X = this.MARGIN * 3; + + /** + * Gap between items in vertical flyouts. Can be overridden with the "sep" + * element. + * @const {number} + */ + this.GAP_Y = this.MARGIN * 3; + + /** + * Top/bottom padding between scrollbar and edge of flyout background. + * @type {number} + * @const + */ + this.SCROLLBAR_MARGIN = 2.5; + + /** + * Width of flyout. + * @type {number} + * @protected + */ + this.width_ = 0; + + /** + * Height of flyout. + * @type {number} + * @protected + */ + this.height_ = 0; + + // clang-format off + /** + * Range of a drag angle from a flyout considered "dragging toward + * workspace". Drags that are within the bounds of this many degrees from + * the orthogonal line to the flyout edge are considered to be "drags toward + * the workspace". + * Example: + * Flyout Edge Workspace + * [block] / <-within this angle, drags "toward workspace" | + * [block] ---- orthogonal to flyout boundary ---- | + * [block] \ | + * The angle is given in degrees from the orthogonal. + * + * This is used to know when to create a new block and when to scroll the + * flyout. Setting it to 360 means that all drags create a new block. + * @type {number} + * @protected + */ + // clang-format on + this.dragAngleRange_ = 70; + + /** + * The path around the background of the flyout, which will be filled with a + * background colour. + * @type {?SVGPathElement} + * @protected + */ + this.svgBackground_ = null; + + /** + * The root SVG group for the button or label. + * @type {?SVGGElement} + * @protected + */ this.svgGroup_ = null; } - this.svgBackground_ = null; - this.targetWorkspace = null; -}; -/** - * Get the width of the flyout. - * @return {number} The width of the flyout. - */ -Flyout.prototype.getWidth = function() { - return this.width_; -}; - -/** - * Get the height of the flyout. - * @return {number} The width of the flyout. - */ -Flyout.prototype.getHeight = function() { - return this.height_; -}; - -/** - * Get the scale (zoom level) of the flyout. By default, - * this matches the target workspace scale, but this can be overridden. - * @return {number} Flyout workspace scale. - */ -Flyout.prototype.getFlyoutScale = function() { - return this.targetWorkspace.scale; -}; - -/** - * Get the workspace inside the flyout. - * @return {!WorkspaceSvg} The workspace inside the flyout. - * @package - */ -Flyout.prototype.getWorkspace = function() { - return this.workspace_; -}; - -/** - * Is the flyout visible? - * @return {boolean} True if visible. - */ -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. - */ -Flyout.prototype.setVisible = function(visible) { - const visibilityChanged = (visible !== this.isVisible()); - - this.isVisible_ = visible; - if (visibilityChanged) { - if (!this.autoClose) { - // Auto-close flyouts are ignored as drag targets, so only non auto-close - // flyouts need to have their drag target updated. - this.workspace_.recordDragTargets(); - } - this.updateDisplay_(); - } -}; - -/** - * Set whether this flyout's container is visible. - * @param {boolean} visible Whether the container is visible. - */ -Flyout.prototype.setContainerVisible = function(visible) { - const visibilityChanged = (visible !== this.containerVisible_); - this.containerVisible_ = visible; - if (visibilityChanged) { - this.updateDisplay_(); - } -}; - -/** - * Update the display property of the flyout based whether it thinks it should - * be visible and whether its containing workspace is visible. - * @private - */ -Flyout.prototype.updateDisplay_ = function() { - let show = true; - if (!this.containerVisible_) { - show = false; - } else { - show = this.isVisible(); - } - this.svgGroup_.style.display = show ? 'block' : 'none'; - // Update the scrollbar's visibility too since it should mimic the - // flyout's visibility. - this.workspace_.scrollbar.setContainerVisible(show); -}; - -/** - * Update the view based on coordinates calculated in position(). - * @param {number} width The computed width of the flyout's SVG group - * @param {number} height The computed height of the flyout's SVG group. - * @param {number} x The computed x origin of the flyout's SVG group. - * @param {number} y The computed y origin of the flyout's SVG group. - * @protected - */ -Flyout.prototype.positionAt_ = function(width, height, x, y) { - this.svgGroup_.setAttribute('width', width); - this.svgGroup_.setAttribute('height', height); - this.workspace_.setCachedParentSvgSize(width, height); - - if (this.svgGroup_.tagName === 'svg') { - const transform = 'translate(' + x + 'px,' + y + 'px)'; - dom.setCssTransform(this.svgGroup_, transform); - } else { - // IE and Edge don't support CSS transforms on SVG elements so - // it's important to set the transform on the SVG element itself - const transform = 'translate(' + x + ',' + y + ')'; - this.svgGroup_.setAttribute('transform', transform); + /** + * Creates the flyout's DOM. Only needs to be called once. The flyout can + * either exist as its own SVG element or be a g element nested inside a + * separate SVG element. + * @param {string| + * !Svg| + * !Svg} tagName The type of tag to + * put the flyout in. This should be or . + * @return {!SVGElement} The flyout's SVG group. + */ + createDom(tagName) { + /* + + + + + */ + // Setting style to display:none to start. The toolbox and flyout + // hide/show code will set up proper visibility and size later. + this.svgGroup_ = dom.createSvgElement( + tagName, {'class': 'blocklyFlyout', 'style': 'display: none'}, null); + this.svgBackground_ = dom.createSvgElement( + Svg.PATH, {'class': 'blocklyFlyoutBackground'}, this.svgGroup_); + this.svgGroup_.appendChild(this.workspace_.createDom()); + this.workspace_.getThemeManager().subscribe( + this.svgBackground_, 'flyoutBackgroundColour', 'fill'); + this.workspace_.getThemeManager().subscribe( + this.svgBackground_, 'flyoutOpacity', 'fill-opacity'); + return this.svgGroup_; } - // Update the scrollbar (if one exists). - const scrollbar = this.workspace_.scrollbar; - if (scrollbar) { - // Set the scrollbars origin to be the top left of the flyout. - scrollbar.setOrigin(x, y); - scrollbar.resize(); - // If origin changed and metrics haven't changed enough to trigger - // reposition in resize, we need to call setPosition. See issue #4692. - if (scrollbar.hScroll) { - scrollbar.hScroll.setPosition( - scrollbar.hScroll.position.x, scrollbar.hScroll.position.y); - } - if (scrollbar.vScroll) { - scrollbar.vScroll.setPosition( - scrollbar.vScroll.position.x, scrollbar.vScroll.position.y); - } - } -}; + /** + * Initializes the flyout. + * @param {!WorkspaceSvg} targetWorkspace The workspace in which to + * create new blocks. + */ + init(targetWorkspace) { + this.targetWorkspace = targetWorkspace; + this.workspace_.targetWorkspace = targetWorkspace; -/** - * Hide and empty the flyout. - */ -Flyout.prototype.hide = function() { - if (!this.isVisible()) { - return; - } - this.setVisible(false); - // Delete all the event listeners. - for (let i = 0, listen; (listen = this.listeners_[i]); i++) { - browserEvents.unbind(listen); - } - this.listeners_.length = 0; - if (this.reflowWrapper_) { - this.workspace_.removeChangeListener(this.reflowWrapper_); - this.reflowWrapper_ = null; - } - // Do NOT delete the blocks here. Wait until Flyout.show. - // https://neil.fraser.name/news/2014/08/09/ -}; + this.workspace_.scrollbar = new ScrollbarPair( + this.workspace_, this.horizontalLayout, !this.horizontalLayout, + 'blocklyFlyoutScrollbar', this.SCROLLBAR_MARGIN); -/** - * Show and populate the flyout. - * @param {!toolbox.FlyoutDefinition|string} flyoutDef Contents to display - * in the flyout. This is either an array of Nodes, a NodeList, a - * toolbox definition, or a string with the name of the dynamic category. - */ -Flyout.prototype.show = function(flyoutDef) { - this.workspace_.setResizesEnabled(false); - this.hide(); - this.clearOldBlocks_(); - - // Handle dynamic categories, represented by a name instead of a list. - if (typeof flyoutDef === 'string') { - flyoutDef = this.getDynamicCategoryContents_(flyoutDef); - } - this.setVisible(true); - - // Parse the Array, Node or NodeList into a a list of flyout items. - const parsedContent = toolbox.convertFlyoutDefToJsonArray(flyoutDef); - const flyoutInfo = - /** @type {{contents:!Array, gaps:!Array}} */ ( - this.createFlyoutInfo_(parsedContent)); - - this.layout_(flyoutInfo.contents, flyoutInfo.gaps); - - // IE 11 is an incompetent browser that fails to fire mouseout events. - // When the mouse is over the background, deselect all blocks. - const deselectAll = - /** @this {Flyout} */ - function() { - const topBlocks = this.workspace_.getTopBlocks(false); - for (let i = 0, block; (block = topBlocks[i]); i++) { - block.removeSelect(); - } - }; - - this.listeners_.push(browserEvents.conditionalBind( - this.svgBackground_, 'mouseover', this, deselectAll)); - - if (this.horizontalLayout) { - this.height_ = 0; - } else { - this.width_ = 0; - } - this.workspace_.setResizesEnabled(true); - this.reflow(); - - this.filterForCapacity_(); - - // Correctly position the flyout's scrollbar when it opens. - this.position(); - - this.reflowWrapper_ = this.reflow.bind(this); - this.workspace_.addChangeListener(this.reflowWrapper_); - this.emptyRecycledBlocks_(); -}; - -/** - * Create the contents array and gaps array necessary to create the layout for - * the flyout. - * @param {!toolbox.FlyoutItemInfoArray} parsedContent The array - * of objects to show in the flyout. - * @return {{contents:Array, gaps:Array}} The list of contents - * and gaps needed to lay out the flyout. - * @private - */ -Flyout.prototype.createFlyoutInfo_ = function(parsedContent) { - const contents = []; - const gaps = []; - this.permanentlyDisabled_.length = 0; - const defaultGap = this.horizontalLayout ? this.GAP_X : this.GAP_Y; - for (let i = 0, contentInfo; (contentInfo = parsedContent[i]); i++) { - if (contentInfo['custom']) { - const customInfo = - /** @type {!toolbox.DynamicCategoryInfo} */ (contentInfo); - const categoryName = customInfo['custom']; - const flyoutDef = this.getDynamicCategoryContents_(categoryName); - const parsedDynamicContent = /** @type {!toolbox.FlyoutItemInfoArray} */ - (toolbox.convertFlyoutDefToJsonArray(flyoutDef)); - // Replace the element at i with the dynamic content it represents. - parsedContent.splice.apply( - parsedContent, [i, 1].concat(parsedDynamicContent)); - contentInfo = parsedContent[i]; - } - - switch (contentInfo['kind'].toUpperCase()) { - case 'BLOCK': { - const blockInfo = /** @type {!toolbox.BlockInfo} */ (contentInfo); - const block = this.createFlyoutBlock_(blockInfo); - contents.push({type: 'block', block: block}); - this.addBlockGap_(blockInfo, gaps, defaultGap); - break; - } - case 'SEP': { - const sepInfo = /** @type {!toolbox.SeparatorInfo} */ (contentInfo); - this.addSeparatorGap_(sepInfo, gaps, defaultGap); - break; - } - case 'LABEL': { - const labelInfo = /** @type {!toolbox.LabelInfo} */ (contentInfo); - // A label is a button with different styling. - const label = this.createButton_(labelInfo, /** isLabel */ true); - contents.push({type: 'button', button: label}); - gaps.push(defaultGap); - break; - } - case 'BUTTON': { - const buttonInfo = /** @type {!toolbox.ButtonInfo} */ (contentInfo); - const button = this.createButton_(buttonInfo, /** isLabel */ false); - contents.push({type: 'button', button: button}); - gaps.push(defaultGap); - break; - } - } - } - return {contents: contents, gaps: gaps}; -}; - -/** - * Gets the flyout definition for the dynamic category. - * @param {string} categoryName The name of the dynamic category. - * @return {!toolbox.FlyoutDefinition} The definition of the - * flyout in one of its many forms. - * @private - */ -Flyout.prototype.getDynamicCategoryContents_ = function(categoryName) { - // Look up the correct category generation function and call that to get a - // valid XML list. - const fnToApply = - this.workspace_.targetWorkspace.getToolboxCategoryCallback(categoryName); - if (typeof fnToApply !== 'function') { - throw TypeError( - 'Couldn\'t find a callback function when opening' + - ' a toolbox category.'); - } - return fnToApply(this.workspace_.targetWorkspace); -}; - -/** - * Creates a flyout button or a flyout label. - * @param {!toolbox.ButtonOrLabelInfo} btnInfo - * The object holding information about a button or a label. - * @param {boolean} isLabel True if the button is a label, false otherwise. - * @return {!FlyoutButton} The object used to display the button in the - * flyout. - * @private - */ -Flyout.prototype.createButton_ = function(btnInfo, isLabel) { - const {FlyoutButton} = goog.module.get('Blockly.FlyoutButton'); - if (!FlyoutButton) { - throw Error('Missing require for Blockly.FlyoutButton'); - } - const curButton = new FlyoutButton( - this.workspace_, - /** @type {!WorkspaceSvg} */ (this.targetWorkspace), btnInfo, isLabel); - return curButton; -}; - -/** - * Create a block from the xml and permanently disable any blocks that were - * defined as disabled. - * @param {!toolbox.BlockInfo} blockInfo The info of the block. - * @return {!BlockSvg} The block created from the blockInfo. - * @private - */ -Flyout.prototype.createFlyoutBlock_ = function(blockInfo) { - let block; - if (blockInfo['blockxml']) { - const xml = typeof blockInfo['blockxml'] === 'string' ? - Xml.textToDom(blockInfo['blockxml']) : - blockInfo['blockxml']; - block = this.getRecycledBlock_(xml.getAttribute('type')); - if (!block) { - block = Xml.domToBlock(xml, this.workspace_); - } - } else { - block = this.getRecycledBlock_(blockInfo['type']); - if (!block) { - if (blockInfo['enabled'] === undefined) { - blockInfo['enabled'] = - blockInfo['disabled'] !== 'true' && blockInfo['disabled'] !== true; - } - block = blocks.append( - /** @type {blocks.State} */ (blockInfo), this.workspace_); - } - } - - if (!block.isEnabled()) { - // Record blocks that were initially disabled. - // Do not enable these blocks as a result of capacity filtering. - this.permanentlyDisabled_.push(block); - } - return /** @type {!BlockSvg} */ (block); -}; - -/** - * Returns a block from the array of recycled blocks with the given type, or - * undefined if one cannot be found. - * @param {string} blockType The type of the block to try to recycle. - * @return {(!BlockSvg|undefined)} The recycled block, or undefined if - * one could not be recycled. - * @private - */ -Flyout.prototype.getRecycledBlock_ = function(blockType) { - let index = -1; - for (let i = 0; i < this.recycledBlocks_.length; i++) { - if (this.recycledBlocks_[i].type === blockType) { - index = i; - break; - } - } - return index === -1 ? undefined : this.recycledBlocks_.splice(index, 1)[0]; -}; - -/** - * Adds a gap in the flyout based on block info. - * @param {!toolbox.BlockInfo} blockInfo Information about a block. - * @param {!Array} gaps The list of gaps between items in the flyout. - * @param {number} defaultGap The default gap between one element and the next. - * @private - */ -Flyout.prototype.addBlockGap_ = function(blockInfo, gaps, defaultGap) { - let gap; - if (blockInfo['gap']) { - gap = parseInt(blockInfo['gap'], 10); - } else if (blockInfo['blockxml']) { - const xml = typeof blockInfo['blockxml'] === 'string' ? - Xml.textToDom(blockInfo['blockxml']) : - blockInfo['blockxml']; - gap = parseInt(xml.getAttribute('gap'), 10); - } - gaps.push(isNaN(gap) ? defaultGap : gap); -}; - -/** - * Add the necessary gap in the flyout for a separator. - * @param {!toolbox.SeparatorInfo} sepInfo The object holding - * information about a separator. - * @param {!Array} gaps The list gaps between items in the flyout. - * @param {number} defaultGap The default gap between the button and next - * element. - * @private - */ -Flyout.prototype.addSeparatorGap_ = function(sepInfo, gaps, defaultGap) { - // Change the gap between two toolbox elements. - // - // The default gap is 24, can be set larger or smaller. - // This overwrites the gap attribute on the previous element. - const newGap = parseInt(sepInfo['gap'], 10); - // Ignore gaps before the first block. - if (!isNaN(newGap) && gaps.length > 0) { - gaps[gaps.length - 1] = newGap; - } else { - gaps.push(defaultGap); - } -}; - -/** - * Delete blocks, mats and buttons from a previous showing of the flyout. - * @private - */ -Flyout.prototype.clearOldBlocks_ = function() { - // Delete any blocks from a previous showing. - const oldBlocks = this.workspace_.getTopBlocks(false); - for (let i = 0, block; (block = oldBlocks[i]); i++) { - if (this.blockIsRecyclable_(block)) { - this.recycleBlock_(block); - } else { - block.dispose(false, false); - } - } - // Delete any mats from a previous showing. - for (let j = 0; j < this.mats_.length; j++) { - const rect = this.mats_[j]; - if (rect) { - Tooltip.unbindMouseEvents(rect); - dom.removeNode(rect); - } - } - this.mats_.length = 0; - // Delete any buttons from a previous showing. - for (let i = 0, button; (button = this.buttons_[i]); i++) { - button.dispose(); - } - this.buttons_.length = 0; - - // Clear potential variables from the previous showing. - this.workspace_.getPotentialVariableMap().clear(); -}; - -/** - * Empties all of the recycled blocks, properly disposing of them. - * @private - */ -Flyout.prototype.emptyRecycledBlocks_ = function() { - for (let i = 0; i < this.recycledBlocks_.length; i++) { - this.recycledBlocks_[i].dispose(); - } - this.recycledBlocks_ = []; -}; - -/** - * Returns whether the given block can be recycled or not. - * @param {!BlockSvg} _block The block to check for recyclability. - * @return {boolean} True if the block can be recycled. False otherwise. - * @protected - */ -Flyout.prototype.blockIsRecyclable_ = function(_block) { - // By default, recycling is disabled. - return false; -}; - -/** - * Puts a previously created block into the recycle bin and moves it to the - * top of the workspace. Used during large workspace swaps to limit the number - * of new DOM elements we need to create. - * @param {!BlockSvg} block The block to recycle. - * @private - */ -Flyout.prototype.recycleBlock_ = function(block) { - const xy = block.getRelativeToSurfaceXY(); - block.moveBy(-xy.x, -xy.y); - this.recycledBlocks_.push(block); -}; - -/** - * Add listeners to a block that has been added to the flyout. - * @param {!SVGElement} root The root node of the SVG group the block is in. - * @param {!BlockSvg} block The block to add listeners for. - * @param {!SVGElement} rect The invisible rectangle under the block that acts - * as a mat for that block. - * @protected - */ -Flyout.prototype.addBlockListeners_ = function(root, block, rect) { - this.listeners_.push(browserEvents.conditionalBind( - root, 'mousedown', null, this.blockMouseDown_(block))); - this.listeners_.push(browserEvents.conditionalBind( - rect, 'mousedown', null, this.blockMouseDown_(block))); - this.listeners_.push( - browserEvents.bind(root, 'mouseenter', block, block.addSelect)); - this.listeners_.push( - browserEvents.bind(root, 'mouseleave', block, block.removeSelect)); - this.listeners_.push( - browserEvents.bind(rect, 'mouseenter', block, block.addSelect)); - this.listeners_.push( - browserEvents.bind(rect, 'mouseleave', block, block.removeSelect)); -}; - -/** - * Handle a mouse-down on an SVG block in a non-closing flyout. - * @param {!BlockSvg} block The flyout block to copy. - * @return {!Function} Function to call when block is clicked. - * @private - */ -Flyout.prototype.blockMouseDown_ = function(block) { - const flyout = this; - return function(e) { - const gesture = flyout.targetWorkspace.getGesture(e); - if (gesture) { - gesture.setStartBlock(block); - gesture.handleFlyoutStart(e, flyout); - } - }; -}; - -/** - * Mouse down on the flyout background. Start a vertical scroll drag. - * @param {!Event} e Mouse down event. - * @private - */ -Flyout.prototype.onMouseDown_ = function(e) { - const gesture = this.targetWorkspace.getGesture(e); - if (gesture) { - gesture.handleFlyoutStart(e, this); - } -}; - -/** - * Does this flyout allow you to create a new instance of the given block? - * Used for deciding if a block can be "dragged out of" the flyout. - * @param {!BlockSvg} block The block to copy from the flyout. - * @return {boolean} True if you can create a new instance of the block, false - * otherwise. - * @package - */ -Flyout.prototype.isBlockCreatable_ = function(block) { - return block.isEnabled(); -}; - -/** - * Create a copy of this block on the workspace. - * @param {!BlockSvg} originalBlock The block to copy from the flyout. - * @return {!BlockSvg} The newly created block. - * @throws {Error} if something went wrong with deserialization. - * @package - */ -Flyout.prototype.createBlock = function(originalBlock) { - let newBlock = null; - eventUtils.disable(); - const variablesBeforeCreation = this.targetWorkspace.getAllVariables(); - this.targetWorkspace.setResizesEnabled(false); - try { - newBlock = this.placeNewBlock_(originalBlock); - } finally { - eventUtils.enable(); - } - - // Close the flyout. - this.targetWorkspace.hideChaff(); - - const newVariables = Variables.getAddedVariables( - this.targetWorkspace, variablesBeforeCreation); - - if (eventUtils.isEnabled()) { - eventUtils.setGroup(true); - // Fire a VarCreate event for each (if any) new variable created. - for (let i = 0; i < newVariables.length; i++) { - const thisVariable = newVariables[i]; - eventUtils.fire( - new (eventUtils.get(eventUtils.VAR_CREATE))(thisVariable)); - } - - // Block events come after var events, in case they refer to newly created - // variables. - eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CREATE))(newBlock)); - } - if (this.autoClose) { this.hide(); - } else { - this.filterForCapacity_(); + + Array.prototype.push.apply( + this.eventWrappers_, + browserEvents.conditionalBind( + /** @type {!SVGGElement} */ (this.svgGroup_), 'wheel', this, + this.wheel_)); + if (!this.autoClose) { + this.filterWrapper_ = this.filterForCapacity_.bind(this); + this.targetWorkspace.addChangeListener(this.filterWrapper_); + } + + // Dragging the flyout up and down. + Array.prototype.push.apply( + this.eventWrappers_, + browserEvents.conditionalBind( + /** @type {!SVGPathElement} */ (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_.setVariableMap(this.targetWorkspace.getVariableMap()); + + this.workspace_.createPotentialVariableMap(); + + targetWorkspace.getComponentManager().addComponent({ + component: this, + weight: 1, + capabilities: [ + ComponentManager.Capability.DELETE_AREA, + ComponentManager.Capability.DRAG_TARGET, + ], + }); } - return newBlock; -}; -/** - * Initialize the given button: move it to the correct location, - * add listeners, etc. - * @param {!FlyoutButton} button The button to initialize and place. - * @param {number} x The x position of the cursor during this layout pass. - * @param {number} y The y position of the cursor during this layout pass. - * @protected - */ -Flyout.prototype.initFlyoutButton_ = function(button, x, y) { - const buttonSvg = button.createDom(); - button.moveTo(x, y); - button.show(); - // Clicking on a flyout button or label is a lot like clicking on the - // flyout background. - this.listeners_.push(browserEvents.conditionalBind( - buttonSvg, 'mousedown', this, this.onMouseDown_)); + /** + * Dispose of this flyout. + * Unlink from all DOM elements to prevent memory leaks. + * @suppress {checkTypes} + */ + dispose() { + this.hide(); + this.workspace_.getComponentManager().removeComponent(this.id); + browserEvents.unbind(this.eventWrappers_); + if (this.filterWrapper_) { + this.targetWorkspace.removeChangeListener(this.filterWrapper_); + this.filterWrapper_ = null; + } + if (this.workspace_) { + this.workspace_.getThemeManager().unsubscribe(this.svgBackground_); + this.workspace_.targetWorkspace = null; + this.workspace_.dispose(); + this.workspace_ = null; + } + if (this.svgGroup_) { + dom.removeNode(this.svgGroup_); + this.svgGroup_ = null; + } + this.svgBackground_ = null; + this.targetWorkspace = null; + } - this.buttons_.push(button); -}; + /** + * Get the width of the flyout. + * @return {number} The width of the flyout. + */ + getWidth() { + return this.width_; + } -/** - * Create and place a rectangle corresponding to the given block. - * @param {!BlockSvg} block The block to associate the rect to. - * @param {number} x The x position of the cursor during this layout pass. - * @param {number} y The y position of the cursor during this layout pass. - * @param {!{height: number, width: number}} blockHW The height and width of the - * block. - * @param {number} index The index into the mats list where this rect should be - * placed. - * @return {!SVGElement} Newly created SVG element for the rectangle behind the - * block. - * @protected - */ -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. - const rect = dom.createSvgElement( - Svg.RECT, { - 'fill-opacity': 0, - 'x': x, - 'y': y, - 'height': blockHW.height, - 'width': blockHW.width, - }, - null); - rect.tooltip = block; - Tooltip.bindMouseEvents(rect); - // Add the rectangles under the blocks, so that the blocks' tooltips work. - this.workspace_.getCanvas().insertBefore(rect, block.getSvgRoot()); + /** + * Get the height of the flyout. + * @return {number} The width of the flyout. + */ + getHeight() { + return this.height_; + } - block.flyoutRect_ = rect; - this.mats_[index] = rect; - return rect; -}; + /** + * Get the scale (zoom level) of the flyout. By default, + * this matches the target workspace scale, but this can be overridden. + * @return {number} Flyout workspace scale. + */ + getFlyoutScale() { + return this.targetWorkspace.scale; + } -/** - * Move a rectangle to sit exactly behind a block, taking into account tabs, - * hats, and any other protrusions we invent. - * @param {!SVGElement} rect The rectangle to move directly behind the block. - * @param {!BlockSvg} block The block the rectangle should be behind. - * @protected - */ -Flyout.prototype.moveRectToBlock_ = function(rect, block) { - const blockHW = block.getHeightWidth(); - rect.setAttribute('width', blockHW.width); - rect.setAttribute('height', blockHW.height); + /** + * Get the workspace inside the flyout. + * @return {!WorkspaceSvg} The workspace inside the flyout. + * @package + */ + getWorkspace() { + return this.workspace_; + } - const blockXY = block.getRelativeToSurfaceXY(); - rect.setAttribute('y', blockXY.y); - rect.setAttribute('x', this.RTL ? blockXY.x - blockHW.width : blockXY.x); -}; + /** + * Is the flyout visible? + * @return {boolean} True if visible. + */ + isVisible() { + return this.isVisible_; + } -/** - * Filter the blocks on the flyout to disable the ones that are above the - * capacity limit. For instance, if the user may only place two more blocks on - * the workspace, an "a + b" block that has two shadow blocks would be disabled. - * @private - */ -Flyout.prototype.filterForCapacity_ = function() { - const blocks = this.workspace_.getTopBlocks(false); - for (let i = 0, block; (block = blocks[i]); i++) { - if (this.permanentlyDisabled_.indexOf(block) === -1) { - const enable = this.targetWorkspace.isCapacityAvailable( - common.getBlockTypeCounts(block)); - while (block) { - block.setEnabled(enable); - block = block.getNextBlock(); + /** + * 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. + */ + setVisible(visible) { + const visibilityChanged = (visible !== this.isVisible()); + + this.isVisible_ = visible; + if (visibilityChanged) { + if (!this.autoClose) { + // Auto-close flyouts are ignored as drag targets, so only non + // auto-close flyouts need to have their drag target updated. + this.workspace_.recordDragTargets(); + } + this.updateDisplay_(); + } + } + + /** + * Set whether this flyout's container is visible. + * @param {boolean} visible Whether the container is visible. + */ + setContainerVisible(visible) { + const visibilityChanged = (visible !== this.containerVisible_); + this.containerVisible_ = visible; + if (visibilityChanged) { + this.updateDisplay_(); + } + } + + /** + * Update the display property of the flyout based whether it thinks it should + * be visible and whether its containing workspace is visible. + * @private + */ + updateDisplay_() { + let show = true; + if (!this.containerVisible_) { + show = false; + } else { + show = this.isVisible(); + } + this.svgGroup_.style.display = show ? 'block' : 'none'; + // Update the scrollbar's visibility too since it should mimic the + // flyout's visibility. + this.workspace_.scrollbar.setContainerVisible(show); + } + + /** + * Update the view based on coordinates calculated in position(). + * @param {number} width The computed width of the flyout's SVG group + * @param {number} height The computed height of the flyout's SVG group. + * @param {number} x The computed x origin of the flyout's SVG group. + * @param {number} y The computed y origin of the flyout's SVG group. + * @protected + */ + positionAt_(width, height, x, y) { + this.svgGroup_.setAttribute('width', width); + this.svgGroup_.setAttribute('height', height); + this.workspace_.setCachedParentSvgSize(width, height); + + if (this.svgGroup_.tagName === 'svg') { + const transform = 'translate(' + x + 'px,' + y + 'px)'; + dom.setCssTransform(this.svgGroup_, transform); + } else { + // IE and Edge don't support CSS transforms on SVG elements so + // it's important to set the transform on the SVG element itself + const transform = 'translate(' + x + ',' + y + ')'; + this.svgGroup_.setAttribute('transform', transform); + } + + // Update the scrollbar (if one exists). + const scrollbar = this.workspace_.scrollbar; + if (scrollbar) { + // Set the scrollbars origin to be the top left of the flyout. + scrollbar.setOrigin(x, y); + scrollbar.resize(); + // If origin changed and metrics haven't changed enough to trigger + // reposition in resize, we need to call setPosition. See issue #4692. + if (scrollbar.hScroll) { + scrollbar.hScroll.setPosition( + scrollbar.hScroll.position.x, scrollbar.hScroll.position.y); + } + if (scrollbar.vScroll) { + scrollbar.vScroll.setPosition( + scrollbar.vScroll.position.x, scrollbar.vScroll.position.y); } } } -}; -/** - * Reflow blocks and their mats. - */ -Flyout.prototype.reflow = function() { - if (this.reflowWrapper_) { - this.workspace_.removeChangeListener(this.reflowWrapper_); + /** + * Hide and empty the flyout. + */ + hide() { + if (!this.isVisible()) { + return; + } + this.setVisible(false); + // Delete all the event listeners. + for (let i = 0, listen; (listen = this.listeners_[i]); i++) { + browserEvents.unbind(listen); + } + this.listeners_.length = 0; + if (this.reflowWrapper_) { + this.workspace_.removeChangeListener(this.reflowWrapper_); + this.reflowWrapper_ = null; + } + // Do NOT delete the blocks here. Wait until Flyout.show. + // https://neil.fraser.name/news/2014/08/09/ } - this.reflowInternal_(); - if (this.reflowWrapper_) { + + /** + * Show and populate the flyout. + * @param {!toolbox.FlyoutDefinition|string} flyoutDef Contents to display + * in the flyout. This is either an array of Nodes, a NodeList, a + * toolbox definition, or a string with the name of the dynamic category. + */ + show(flyoutDef) { + this.workspace_.setResizesEnabled(false); + this.hide(); + this.clearOldBlocks_(); + + // Handle dynamic categories, represented by a name instead of a list. + if (typeof flyoutDef === 'string') { + flyoutDef = this.getDynamicCategoryContents_(flyoutDef); + } + this.setVisible(true); + + // Parse the Array, Node or NodeList into a a list of flyout items. + const parsedContent = toolbox.convertFlyoutDefToJsonArray(flyoutDef); + const flyoutInfo = + /** @type {{contents:!Array, gaps:!Array}} */ ( + this.createFlyoutInfo_(parsedContent)); + + this.layout_(flyoutInfo.contents, flyoutInfo.gaps); + + // IE 11 is an incompetent browser that fails to fire mouseout events. + // When the mouse is over the background, deselect all blocks. + const deselectAll = + /** @this {Flyout} */ + function() { + const topBlocks = this.workspace_.getTopBlocks(false); + for (let i = 0, block; (block = topBlocks[i]); i++) { + block.removeSelect(); + } + }; + + this.listeners_.push(browserEvents.conditionalBind( + /** @type {!SVGPathElement} */ (this.svgBackground_), 'mouseover', this, + deselectAll)); + + if (this.horizontalLayout) { + this.height_ = 0; + } else { + this.width_ = 0; + } + this.workspace_.setResizesEnabled(true); + this.reflow(); + + this.filterForCapacity_(); + + // Correctly position the flyout's scrollbar when it opens. + this.position(); + + this.reflowWrapper_ = this.reflow.bind(this); this.workspace_.addChangeListener(this.reflowWrapper_); - } -}; - -/** - * @return {boolean} True if this flyout may be scrolled with a scrollbar or by - * dragging. - * @package - */ -Flyout.prototype.isScrollable = function() { - return this.workspace_.scrollbar ? this.workspace_.scrollbar.isVisible() : - false; -}; - -/** - * Copy a block from the flyout to the workspace and position it correctly. - * @param {!BlockSvg} oldBlock The flyout block to copy. - * @return {!BlockSvg} The new block in the main workspace. - * @private - */ -Flyout.prototype.placeNewBlock_ = function(oldBlock) { - const targetWorkspace = this.targetWorkspace; - const svgRootOld = oldBlock.getSvgRoot(); - if (!svgRootOld) { - throw Error('oldBlock is not rendered.'); + this.emptyRecycledBlocks_(); } - // Clone the block. - const json = /** @type {!blocks.State} */ (blocks.save(oldBlock)); - // Normallly this resizes leading to weird jumps. Save it for terminateDrag. - targetWorkspace.setResizesEnabled(false); - const block = /** @type {!BlockSvg} */ (blocks.append(json, targetWorkspace)); + /** + * Create the contents array and gaps array necessary to create the layout for + * the flyout. + * @param {!toolbox.FlyoutItemInfoArray} parsedContent The array + * of objects to show in the flyout. + * @return {{contents:Array, gaps:Array}} The list of contents + * and gaps needed to lay out the flyout. + * @private + */ + createFlyoutInfo_(parsedContent) { + const contents = []; + const gaps = []; + this.permanentlyDisabled_.length = 0; + const defaultGap = this.horizontalLayout ? this.GAP_X : this.GAP_Y; + for (let i = 0, contentInfo; (contentInfo = parsedContent[i]); i++) { + if (contentInfo['custom']) { + const customInfo = + /** @type {!toolbox.DynamicCategoryInfo} */ (contentInfo); + const categoryName = customInfo['custom']; + const flyoutDef = this.getDynamicCategoryContents_(categoryName); + const parsedDynamicContent = /** @type {!toolbox.FlyoutItemInfoArray} */ + (toolbox.convertFlyoutDefToJsonArray(flyoutDef)); + // Replace the element at i with the dynamic content it represents. + parsedContent.splice.apply( + parsedContent, [i, 1].concat(parsedDynamicContent)); + contentInfo = parsedContent[i]; + } - this.positionNewBlock_(oldBlock, block); + switch (contentInfo['kind'].toUpperCase()) { + case 'BLOCK': { + const blockInfo = /** @type {!toolbox.BlockInfo} */ (contentInfo); + const block = this.createFlyoutBlock_(blockInfo); + contents.push({type: 'block', block: block}); + this.addBlockGap_(blockInfo, gaps, defaultGap); + break; + } + case 'SEP': { + const sepInfo = /** @type {!toolbox.SeparatorInfo} */ (contentInfo); + this.addSeparatorGap_(sepInfo, gaps, defaultGap); + break; + } + case 'LABEL': { + const labelInfo = /** @type {!toolbox.LabelInfo} */ (contentInfo); + // A label is a button with different styling. + const label = this.createButton_(labelInfo, /** isLabel */ true); + contents.push({type: 'button', button: label}); + gaps.push(defaultGap); + break; + } + case 'BUTTON': { + const buttonInfo = /** @type {!toolbox.ButtonInfo} */ (contentInfo); + const button = this.createButton_(buttonInfo, /** isLabel */ false); + contents.push({type: 'button', button: button}); + gaps.push(defaultGap); + break; + } + } + } + return {contents: contents, gaps: gaps}; + } - return block; -}; + /** + * Gets the flyout definition for the dynamic category. + * @param {string} categoryName The name of the dynamic category. + * @return {!toolbox.FlyoutDefinition} The definition of the + * flyout in one of its many forms. + * @private + */ + getDynamicCategoryContents_(categoryName) { + // Look up the correct category generation function and call that to get a + // valid XML list. + const fnToApply = + this.workspace_.targetWorkspace.getToolboxCategoryCallback( + categoryName); + if (typeof fnToApply !== 'function') { + throw TypeError( + 'Couldn\'t find a callback function when opening' + + ' a toolbox category.'); + } + return fnToApply(this.workspace_.targetWorkspace); + } -/** - * Positions a block on the target workspace. - * @param {!BlockSvg} oldBlock The flyout block being copied. - * @param {!BlockSvg} block The block to posiiton. - * @private - */ -Flyout.prototype.positionNewBlock_ = function(oldBlock, block) { - const targetWorkspace = this.targetWorkspace; + /** + * Creates a flyout button or a flyout label. + * @param {!toolbox.ButtonOrLabelInfo} btnInfo + * The object holding information about a button or a label. + * @param {boolean} isLabel True if the button is a label, false otherwise. + * @return {!FlyoutButton} The object used to display the button in the + * flyout. + * @private + */ + createButton_(btnInfo, isLabel) { + const {FlyoutButton} = goog.module.get('Blockly.FlyoutButton'); + if (!FlyoutButton) { + throw Error('Missing require for Blockly.FlyoutButton'); + } + const curButton = new FlyoutButton( + this.workspace_, + /** @type {!WorkspaceSvg} */ (this.targetWorkspace), btnInfo, isLabel); + return curButton; + } - // The offset in pixels between the main workspace's origin and the upper left - // corner of the injection div. - const mainOffsetPixels = targetWorkspace.getOriginOffsetInPixels(); + /** + * Create a block from the xml and permanently disable any blocks that were + * defined as disabled. + * @param {!toolbox.BlockInfo} blockInfo The info of the block. + * @return {!BlockSvg} The block created from the blockInfo. + * @private + */ + createFlyoutBlock_(blockInfo) { + let block; + if (blockInfo['blockxml']) { + const xml = typeof blockInfo['blockxml'] === 'string' ? + Xml.textToDom(blockInfo['blockxml']) : + blockInfo['blockxml']; + block = this.getRecycledBlock_(xml.getAttribute('type')); + if (!block) { + block = Xml.domToBlock(xml, this.workspace_); + } + } else { + block = this.getRecycledBlock_(blockInfo['type']); + if (!block) { + if (blockInfo['enabled'] === undefined) { + blockInfo['enabled'] = blockInfo['disabled'] !== 'true' && + blockInfo['disabled'] !== true; + } + block = blocks.append( + /** @type {blocks.State} */ (blockInfo), this.workspace_); + } + } - // The offset in pixels between the flyout workspace's origin and the upper - // left corner of the injection div. - const flyoutOffsetPixels = this.workspace_.getOriginOffsetInPixels(); + if (!block.isEnabled()) { + // Record blocks that were initially disabled. + // Do not enable these blocks as a result of capacity filtering. + this.permanentlyDisabled_.push(block); + } + return /** @type {!BlockSvg} */ (block); + } - // The position of the old block in flyout workspace coordinates. - const oldBlockPos = oldBlock.getRelativeToSurfaceXY(); - // The position of the old block in pixels relative to the flyout - // workspace's origin. - oldBlockPos.scale(this.workspace_.scale); + /** + * Returns a block from the array of recycled blocks with the given type, or + * undefined if one cannot be found. + * @param {string} blockType The type of the block to try to recycle. + * @return {(!BlockSvg|undefined)} The recycled block, or undefined if + * one could not be recycled. + * @private + */ + getRecycledBlock_(blockType) { + let index = -1; + for (let i = 0; i < this.recycledBlocks_.length; i++) { + if (this.recycledBlocks_[i].type === blockType) { + index = i; + break; + } + } + return index === -1 ? undefined : this.recycledBlocks_.splice(index, 1)[0]; + } - // The position of the old block in pixels relative to the upper left corner - // of the injection div. - const oldBlockOffsetPixels = Coordinate.sum(flyoutOffsetPixels, oldBlockPos); + /** + * Adds a gap in the flyout based on block info. + * @param {!toolbox.BlockInfo} blockInfo Information about a block. + * @param {!Array} gaps The list of gaps between items in the flyout. + * @param {number} defaultGap The default gap between one element and the + * next. + * @private + */ + addBlockGap_(blockInfo, gaps, defaultGap) { + let gap; + if (blockInfo['gap']) { + gap = parseInt(blockInfo['gap'], 10); + } else if (blockInfo['blockxml']) { + const xml = typeof blockInfo['blockxml'] === 'string' ? + Xml.textToDom(blockInfo['blockxml']) : + blockInfo['blockxml']; + gap = parseInt(xml.getAttribute('gap'), 10); + } + gaps.push(isNaN(gap) ? defaultGap : gap); + } - // The position of the old block in pixels relative to the origin of the - // main workspace. - const finalOffset = - Coordinate.difference(oldBlockOffsetPixels, mainOffsetPixels); - // The position of the old block in main workspace coordinates. - finalOffset.scale(1 / targetWorkspace.scale); + /** + * Add the necessary gap in the flyout for a separator. + * @param {!toolbox.SeparatorInfo} sepInfo The object holding + * information about a separator. + * @param {!Array} gaps The list gaps between items in the flyout. + * @param {number} defaultGap The default gap between the button and next + * element. + * @private + */ + addSeparatorGap_(sepInfo, gaps, defaultGap) { + // Change the gap between two toolbox elements. + // + // The default gap is 24, can be set larger or smaller. + // This overwrites the gap attribute on the previous element. + const newGap = parseInt(sepInfo['gap'], 10); + // Ignore gaps before the first block. + if (!isNaN(newGap) && gaps.length > 0) { + gaps[gaps.length - 1] = newGap; + } else { + gaps.push(defaultGap); + } + } - block.moveTo(new Coordinate(finalOffset.x, finalOffset.y)); -}; + /** + * Delete blocks, mats and buttons from a previous showing of the flyout. + * @private + */ + clearOldBlocks_() { + // Delete any blocks from a previous showing. + const oldBlocks = this.workspace_.getTopBlocks(false); + for (let i = 0, block; (block = oldBlocks[i]); i++) { + if (this.blockIsRecyclable_(block)) { + this.recycleBlock_(block); + } else { + block.dispose(false, false); + } + } + // Delete any mats from a previous showing. + for (let j = 0; j < this.mats_.length; j++) { + const rect = this.mats_[j]; + if (rect) { + Tooltip.unbindMouseEvents(rect); + dom.removeNode(rect); + } + } + this.mats_.length = 0; + // Delete any buttons from a previous showing. + for (let i = 0, button; (button = this.buttons_[i]); i++) { + button.dispose(); + } + this.buttons_.length = 0; + + // Clear potential variables from the previous showing. + this.workspace_.getPotentialVariableMap().clear(); + } + + /** + * Empties all of the recycled blocks, properly disposing of them. + * @private + */ + emptyRecycledBlocks_() { + for (let i = 0; i < this.recycledBlocks_.length; i++) { + this.recycledBlocks_[i].dispose(); + } + this.recycledBlocks_ = []; + } + + /** + * Returns whether the given block can be recycled or not. + * @param {!BlockSvg} _block The block to check for recyclability. + * @return {boolean} True if the block can be recycled. False otherwise. + * @protected + */ + blockIsRecyclable_(_block) { + // By default, recycling is disabled. + return false; + } + + /** + * Puts a previously created block into the recycle bin and moves it to the + * top of the workspace. Used during large workspace swaps to limit the number + * of new DOM elements we need to create. + * @param {!BlockSvg} block The block to recycle. + * @private + */ + recycleBlock_(block) { + const xy = block.getRelativeToSurfaceXY(); + block.moveBy(-xy.x, -xy.y); + this.recycledBlocks_.push(block); + } + + /** + * Add listeners to a block that has been added to the flyout. + * @param {!SVGElement} root The root node of the SVG group the block is in. + * @param {!BlockSvg} block The block to add listeners for. + * @param {!SVGElement} rect The invisible rectangle under the block that acts + * as a mat for that block. + * @protected + */ + addBlockListeners_(root, block, rect) { + this.listeners_.push(browserEvents.conditionalBind( + root, 'mousedown', null, this.blockMouseDown_(block))); + this.listeners_.push(browserEvents.conditionalBind( + rect, 'mousedown', null, this.blockMouseDown_(block))); + this.listeners_.push( + browserEvents.bind(root, 'mouseenter', block, block.addSelect)); + this.listeners_.push( + browserEvents.bind(root, 'mouseleave', block, block.removeSelect)); + this.listeners_.push( + browserEvents.bind(rect, 'mouseenter', block, block.addSelect)); + this.listeners_.push( + browserEvents.bind(rect, 'mouseleave', block, block.removeSelect)); + } + + /** + * Handle a mouse-down on an SVG block in a non-closing flyout. + * @param {!BlockSvg} block The flyout block to copy. + * @return {!Function} Function to call when block is clicked. + * @private + */ + blockMouseDown_(block) { + const flyout = this; + return function(e) { + const gesture = flyout.targetWorkspace.getGesture(e); + if (gesture) { + gesture.setStartBlock(block); + gesture.handleFlyoutStart(e, flyout); + } + }; + } + + /** + * Mouse down on the flyout background. Start a vertical scroll drag. + * @param {!Event} e Mouse down event. + * @private + */ + onMouseDown_(e) { + const gesture = this.targetWorkspace.getGesture(e); + if (gesture) { + gesture.handleFlyoutStart(e, this); + } + } + + /** + * Does this flyout allow you to create a new instance of the given block? + * Used for deciding if a block can be "dragged out of" the flyout. + * @param {!BlockSvg} block The block to copy from the flyout. + * @return {boolean} True if you can create a new instance of the block, false + * otherwise. + * @package + */ + isBlockCreatable_(block) { + return block.isEnabled(); + } + + /** + * Create a copy of this block on the workspace. + * @param {!BlockSvg} originalBlock The block to copy from the flyout. + * @return {!BlockSvg} The newly created block. + * @throws {Error} if something went wrong with deserialization. + * @package + */ + createBlock(originalBlock) { + let newBlock = null; + eventUtils.disable(); + const variablesBeforeCreation = this.targetWorkspace.getAllVariables(); + this.targetWorkspace.setResizesEnabled(false); + try { + newBlock = this.placeNewBlock_(originalBlock); + } finally { + eventUtils.enable(); + } + + // Close the flyout. + this.targetWorkspace.hideChaff(); + + const newVariables = Variables.getAddedVariables( + this.targetWorkspace, variablesBeforeCreation); + + if (eventUtils.isEnabled()) { + eventUtils.setGroup(true); + // Fire a VarCreate event for each (if any) new variable created. + for (let i = 0; i < newVariables.length; i++) { + const thisVariable = newVariables[i]; + eventUtils.fire( + new (eventUtils.get(eventUtils.VAR_CREATE))(thisVariable)); + } + + // Block events come after var events, in case they refer to newly created + // variables. + eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CREATE))(newBlock)); + } + if (this.autoClose) { + this.hide(); + } else { + this.filterForCapacity_(); + } + return newBlock; + } + + /** + * Initialize the given button: move it to the correct location, + * add listeners, etc. + * @param {!FlyoutButton} button The button to initialize and place. + * @param {number} x The x position of the cursor during this layout pass. + * @param {number} y The y position of the cursor during this layout pass. + * @protected + */ + initFlyoutButton_(button, x, y) { + const buttonSvg = button.createDom(); + button.moveTo(x, y); + button.show(); + // Clicking on a flyout button or label is a lot like clicking on the + // flyout background. + this.listeners_.push(browserEvents.conditionalBind( + buttonSvg, 'mousedown', this, this.onMouseDown_)); + + this.buttons_.push(button); + } + + /** + * Create and place a rectangle corresponding to the given block. + * @param {!BlockSvg} block The block to associate the rect to. + * @param {number} x The x position of the cursor during this layout pass. + * @param {number} y The y position of the cursor during this layout pass. + * @param {!{height: number, width: number}} blockHW The height and width of + * the block. + * @param {number} index The index into the mats list where this rect should + * be placed. + * @return {!SVGElement} Newly created SVG element for the rectangle behind + * the block. + * @protected + */ + createRect_(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. + const rect = dom.createSvgElement( + Svg.RECT, { + 'fill-opacity': 0, + 'x': x, + 'y': y, + 'height': blockHW.height, + 'width': blockHW.width, + }, + null); + rect.tooltip = block; + Tooltip.bindMouseEvents(rect); + // Add the rectangles under the blocks, so that the blocks' tooltips work. + this.workspace_.getCanvas().insertBefore(rect, block.getSvgRoot()); + + this.rectMap_.set(block, rect); + this.mats_[index] = rect; + return rect; + } + + /** + * Move a rectangle to sit exactly behind a block, taking into account tabs, + * hats, and any other protrusions we invent. + * @param {!SVGElement} rect The rectangle to move directly behind the block. + * @param {!BlockSvg} block The block the rectangle should be behind. + * @protected + */ + moveRectToBlock_(rect, block) { + const blockHW = block.getHeightWidth(); + rect.setAttribute('width', blockHW.width); + rect.setAttribute('height', blockHW.height); + + const blockXY = block.getRelativeToSurfaceXY(); + rect.setAttribute('y', blockXY.y); + rect.setAttribute('x', this.RTL ? blockXY.x - blockHW.width : blockXY.x); + } + + /** + * Filter the blocks on the flyout to disable the ones that are above the + * capacity limit. For instance, if the user may only place two more blocks + * on the workspace, an "a + b" block that has two shadow blocks would be + * disabled. + * @private + */ + filterForCapacity_() { + const blocks = this.workspace_.getTopBlocks(false); + for (let i = 0, block; (block = blocks[i]); i++) { + if (this.permanentlyDisabled_.indexOf(block) === -1) { + const enable = this.targetWorkspace.isCapacityAvailable( + common.getBlockTypeCounts(block)); + while (block) { + block.setEnabled(enable); + block = block.getNextBlock(); + } + } + } + } + + /** + * Reflow blocks and their mats. + */ + reflow() { + if (this.reflowWrapper_) { + this.workspace_.removeChangeListener(this.reflowWrapper_); + } + this.reflowInternal_(); + if (this.reflowWrapper_) { + this.workspace_.addChangeListener(this.reflowWrapper_); + } + } + + /** + * @return {boolean} True if this flyout may be scrolled with a scrollbar or + * by dragging. + * @package + */ + isScrollable() { + return this.workspace_.scrollbar ? this.workspace_.scrollbar.isVisible() : + false; + } + + /** + * Copy a block from the flyout to the workspace and position it correctly. + * @param {!BlockSvg} oldBlock The flyout block to copy. + * @return {!BlockSvg} The new block in the main workspace. + * @private + */ + placeNewBlock_(oldBlock) { + const targetWorkspace = this.targetWorkspace; + const svgRootOld = oldBlock.getSvgRoot(); + if (!svgRootOld) { + throw Error('oldBlock is not rendered.'); + } + + // Clone the block. + const json = /** @type {!blocks.State} */ (blocks.save(oldBlock)); + // Normallly this resizes leading to weird jumps. Save it for terminateDrag. + targetWorkspace.setResizesEnabled(false); + const block = + /** @type {!BlockSvg} */ (blocks.append(json, targetWorkspace)); + + this.positionNewBlock_(oldBlock, block); + + return block; + } + + /** + * Positions a block on the target workspace. + * @param {!BlockSvg} oldBlock The flyout block being copied. + * @param {!BlockSvg} block The block to posiiton. + * @private + */ + positionNewBlock_(oldBlock, block) { + const targetWorkspace = this.targetWorkspace; + + // The offset in pixels between the main workspace's origin and the upper + // left corner of the injection div. + const mainOffsetPixels = targetWorkspace.getOriginOffsetInPixels(); + + // The offset in pixels between the flyout workspace's origin and the upper + // left corner of the injection div. + const flyoutOffsetPixels = this.workspace_.getOriginOffsetInPixels(); + + // The position of the old block in flyout workspace coordinates. + const oldBlockPos = oldBlock.getRelativeToSurfaceXY(); + // The position of the old block in pixels relative to the flyout + // workspace's origin. + oldBlockPos.scale(this.workspace_.scale); + + // The position of the old block in pixels relative to the upper left corner + // of the injection div. + const oldBlockOffsetPixels = + Coordinate.sum(flyoutOffsetPixels, oldBlockPos); + + // The position of the old block in pixels relative to the origin of the + // main workspace. + const finalOffset = + Coordinate.difference(oldBlockOffsetPixels, mainOffsetPixels); + // The position of the old block in main workspace coordinates. + finalOffset.scale(1 / targetWorkspace.scale); + + block.moveTo(new Coordinate(finalOffset.x, finalOffset.y)); + } +} /** * Returns the bounding rectangle of the drag target area in pixel units diff --git a/core/flyout_button.js b/core/flyout_button.js index 6ade89e2d..3a4602931 100644 --- a/core/flyout_button.js +++ b/core/flyout_button.js @@ -29,311 +29,328 @@ const {WorkspaceSvg} = goog.requireType('Blockly.WorkspaceSvg'); /** - * Class for a button in the flyout. - * @param {!WorkspaceSvg} workspace The workspace in which to place this - * button. - * @param {!WorkspaceSvg} targetWorkspace The flyout's target workspace. - * @param {!toolbox.ButtonOrLabelInfo} json - * The JSON specifying the label/button. - * @param {boolean} isLabel Whether this button should be styled as a label. - * @constructor - * @package + * Class for a button or label in the flyout. * @alias Blockly.FlyoutButton */ -const FlyoutButton = function(workspace, targetWorkspace, json, isLabel) { - // Labels behave the same as buttons, but are styled differently. +class FlyoutButton { + /** + * @param {!WorkspaceSvg} workspace The workspace in which to place this + * button. + * @param {!WorkspaceSvg} targetWorkspace The flyout's target workspace. + * @param {!toolbox.ButtonOrLabelInfo} json + * The JSON specifying the label/button. + * @param {boolean} isLabel Whether this button should be styled as a label. + * @package + */ + constructor(workspace, targetWorkspace, json, isLabel) { + /** + * @type {!WorkspaceSvg} + * @private + */ + this.workspace_ = workspace; + + /** + * @type {!WorkspaceSvg} + * @private + */ + this.targetWorkspace_ = targetWorkspace; + + /** + * @type {string} + * @private + */ + this.text_ = json['text']; + + /** + * @type {!Coordinate} + * @private + */ + this.position_ = new Coordinate(0, 0); + + /** + * Whether this button should be styled as a label. + * Labels behave the same as buttons, but are styled differently. + * @type {boolean} + * @private + */ + this.isLabel_ = isLabel; + + /** + * The key to the function called when this button is clicked. + * @type {string} + * @private + */ + this.callbackKey_ = json['callbackKey'] || + /* Check the lower case version too to satisfy IE */ + json['callbackkey']; + + /** + * If specified, a CSS class to add to this button. + * @type {?string} + * @private + */ + this.cssClass_ = json['web-class'] || null; + + /** + * Mouse up event data. + * @type {?browserEvents.Data} + * @private + */ + this.onMouseUpWrapper_ = null; + + /** + * The JSON specifying the label / button. + * @type {!toolbox.ButtonOrLabelInfo} + */ + this.info = json; + + /** + * The width of the button's rect. + * @type {number} + */ + this.width = 0; + + /** + * The height of the button's rect. + * @type {number} + */ + this.height = 0; + + /** + * The root SVG group for the button or label. + * @type {?SVGGElement} + * @private + */ + this.svgGroup_ = null; + + /** + * The SVG element with the text of the label or button. + * @type {?SVGTextElement} + * @private + */ + this.svgText_ = null; + } /** - * @type {!WorkspaceSvg} + * Create the button elements. + * @return {!SVGElement} The button's SVG group. + */ + createDom() { + let cssClass = this.isLabel_ ? 'blocklyFlyoutLabel' : 'blocklyFlyoutButton'; + if (this.cssClass_) { + cssClass += ' ' + this.cssClass_; + } + + this.svgGroup_ = dom.createSvgElement( + Svg.G, {'class': cssClass}, this.workspace_.getCanvas()); + + let shadow; + if (!this.isLabel_) { + // Shadow rectangle (light source does not mirror in RTL). + shadow = dom.createSvgElement( + Svg.RECT, { + 'class': 'blocklyFlyoutButtonShadow', + 'rx': 4, + 'ry': 4, + 'x': 1, + 'y': 1, + }, + this.svgGroup_); + } + // Background rectangle. + const rect = dom.createSvgElement( + Svg.RECT, { + 'class': this.isLabel_ ? 'blocklyFlyoutLabelBackground' : + 'blocklyFlyoutButtonBackground', + 'rx': 4, + 'ry': 4, + }, + this.svgGroup_); + + const svgText = dom.createSvgElement( + Svg.TEXT, { + 'class': this.isLabel_ ? 'blocklyFlyoutLabelText' : 'blocklyText', + 'x': 0, + 'y': 0, + 'text-anchor': 'middle', + }, + this.svgGroup_); + let text = parsing.replaceMessageReferences(this.text_); + if (this.workspace_.RTL) { + // Force text to be RTL by adding an RLM. + text += '\u200F'; + } + svgText.textContent = text; + if (this.isLabel_) { + this.svgText_ = svgText; + this.workspace_.getThemeManager().subscribe( + this.svgText_, 'flyoutForegroundColour', 'fill'); + } + + const fontSize = style.getComputedStyle(svgText, 'fontSize'); + const fontWeight = style.getComputedStyle(svgText, 'fontWeight'); + const fontFamily = style.getComputedStyle(svgText, 'fontFamily'); + this.width = dom.getFastTextWidthWithSizeString( + svgText, fontSize, fontWeight, fontFamily); + const fontMetrics = + dom.measureFontMetrics(text, fontSize, fontWeight, fontFamily); + this.height = fontMetrics.height; + + if (!this.isLabel_) { + this.width += 2 * FlyoutButton.TEXT_MARGIN_X; + this.height += 2 * FlyoutButton.TEXT_MARGIN_Y; + shadow.setAttribute('width', this.width); + shadow.setAttribute('height', this.height); + } + rect.setAttribute('width', this.width); + rect.setAttribute('height', this.height); + + svgText.setAttribute('x', this.width / 2); + svgText.setAttribute( + 'y', this.height / 2 - fontMetrics.height / 2 + fontMetrics.baseline); + + this.updateTransform_(); + + this.onMouseUpWrapper_ = browserEvents.conditionalBind( + this.svgGroup_, 'mouseup', this, this.onMouseUp_); + return this.svgGroup_; + } + + /** + * Correctly position the flyout button and make it visible. + */ + show() { + this.updateTransform_(); + this.svgGroup_.setAttribute('display', 'block'); + } + + /** + * Update SVG attributes to match internal state. * @private */ - this.workspace_ = workspace; + updateTransform_() { + this.svgGroup_.setAttribute( + 'transform', + 'translate(' + this.position_.x + ',' + this.position_.y + ')'); + } /** - * @type {!WorkspaceSvg} + * Move the button to the given x, y coordinates. + * @param {number} x The new x coordinate. + * @param {number} y The new y coordinate. + */ + moveTo(x, y) { + this.position_.x = x; + this.position_.y = y; + this.updateTransform_(); + } + + /** + * @return {boolean} Whether or not the button is a label. + */ + isLabel() { + return this.isLabel_; + } + + /** + * Location of the button. + * @return {!Coordinate} x, y coordinates. + * @package + */ + getPosition() { + return this.position_; + } + + /** + * @return {string} Text of the button. + */ + getButtonText() { + return this.text_; + } + + /** + * Get the button's target workspace. + * @return {!WorkspaceSvg} The target workspace of the flyout where this + * button resides. + */ + getTargetWorkspace() { + return this.targetWorkspace_; + } + + /** + * Dispose of this button. + */ + dispose() { + if (this.onMouseUpWrapper_) { + browserEvents.unbind(this.onMouseUpWrapper_); + } + if (this.svgGroup_) { + dom.removeNode(this.svgGroup_); + } + if (this.svgText_) { + this.workspace_.getThemeManager().unsubscribe(this.svgText_); + } + } + + /** + * Do something when the button is clicked. + * @param {!Event} e Mouse up event. * @private */ - this.targetWorkspace_ = targetWorkspace; + onMouseUp_(e) { + const gesture = this.targetWorkspace_.getGesture(e); + if (gesture) { + gesture.cancel(); + } - /** - * @type {string} - * @private - */ - this.text_ = json['text']; - - /** - * @type {!Coordinate} - * @private - */ - this.position_ = new Coordinate(0, 0); - - /** - * Whether this button should be styled as a label. - * @type {boolean} - * @private - */ - this.isLabel_ = isLabel; - - /** - * The key to the function called when this button is clicked. - * @type {string} - * @private - */ - this.callbackKey_ = json['callbackKey'] || - /* Check the lower case version too to satisfy IE */ - json['callbackkey']; - - /** - * If specified, a CSS class to add to this button. - * @type {?string} - * @private - */ - this.cssClass_ = json['web-class'] || null; - - /** - * Mouse up event data. - * @type {?browserEvents.Data} - * @private - */ - this.onMouseUpWrapper_ = null; - - /** - * The JSON specifying the label / button. - * @type {!toolbox.ButtonOrLabelInfo} - */ - this.info = json; -}; + if (this.isLabel_ && this.callbackKey_) { + console.warn( + 'Labels should not have callbacks. Label text: ' + this.text_); + } else if ( + !this.isLabel_ && + !(this.callbackKey_ && + this.targetWorkspace_.getButtonCallback(this.callbackKey_))) { + console.warn('Buttons should have callbacks. Button text: ' + this.text_); + } else if (!this.isLabel_) { + this.targetWorkspace_.getButtonCallback(this.callbackKey_)(this); + } + } +} /** * The horizontal margin around the text in the button. */ -FlyoutButton.MARGIN_X = 5; +FlyoutButton.TEXT_MARGIN_X = 5; /** * The vertical margin around the text in the button. */ -FlyoutButton.MARGIN_Y = 2; - -/** - * The width of the button's rect. - * @type {number} - */ -FlyoutButton.prototype.width = 0; - -/** - * The height of the button's rect. - * @type {number} - */ -FlyoutButton.prototype.height = 0; - -/** - * Create the button elements. - * @return {!SVGElement} The button's SVG group. - */ -FlyoutButton.prototype.createDom = function() { - let cssClass = this.isLabel_ ? 'blocklyFlyoutLabel' : 'blocklyFlyoutButton'; - if (this.cssClass_) { - cssClass += ' ' + this.cssClass_; - } - - this.svgGroup_ = dom.createSvgElement( - Svg.G, {'class': cssClass}, this.workspace_.getCanvas()); - - let shadow; - if (!this.isLabel_) { - // Shadow rectangle (light source does not mirror in RTL). - shadow = dom.createSvgElement( - Svg.RECT, { - 'class': 'blocklyFlyoutButtonShadow', - 'rx': 4, - 'ry': 4, - 'x': 1, - 'y': 1, - }, - this.svgGroup_); - } - // Background rectangle. - const rect = dom.createSvgElement( - Svg.RECT, { - 'class': this.isLabel_ ? 'blocklyFlyoutLabelBackground' : - 'blocklyFlyoutButtonBackground', - 'rx': 4, - 'ry': 4, - }, - this.svgGroup_); - - const svgText = dom.createSvgElement( - Svg.TEXT, { - 'class': this.isLabel_ ? 'blocklyFlyoutLabelText' : 'blocklyText', - 'x': 0, - 'y': 0, - 'text-anchor': 'middle', - }, - this.svgGroup_); - let text = parsing.replaceMessageReferences(this.text_); - if (this.workspace_.RTL) { - // Force text to be RTL by adding an RLM. - text += '\u200F'; - } - svgText.textContent = text; - if (this.isLabel_) { - this.svgText_ = svgText; - this.workspace_.getThemeManager().subscribe( - this.svgText_, 'flyoutForegroundColour', 'fill'); - } - - const fontSize = style.getComputedStyle(svgText, 'fontSize'); - const fontWeight = style.getComputedStyle(svgText, 'fontWeight'); - const fontFamily = style.getComputedStyle(svgText, 'fontFamily'); - this.width = dom.getFastTextWidthWithSizeString( - svgText, fontSize, fontWeight, fontFamily); - const fontMetrics = - dom.measureFontMetrics(text, fontSize, fontWeight, fontFamily); - this.height = fontMetrics.height; - - if (!this.isLabel_) { - this.width += 2 * FlyoutButton.MARGIN_X; - this.height += 2 * FlyoutButton.MARGIN_Y; - shadow.setAttribute('width', this.width); - shadow.setAttribute('height', this.height); - } - rect.setAttribute('width', this.width); - rect.setAttribute('height', this.height); - - svgText.setAttribute('x', this.width / 2); - svgText.setAttribute( - 'y', this.height / 2 - fontMetrics.height / 2 + fontMetrics.baseline); - - this.updateTransform_(); - - this.onMouseUpWrapper_ = browserEvents.conditionalBind( - this.svgGroup_, 'mouseup', this, this.onMouseUp_); - return this.svgGroup_; -}; - -/** - * Correctly position the flyout button and make it visible. - */ -FlyoutButton.prototype.show = function() { - this.updateTransform_(); - this.svgGroup_.setAttribute('display', 'block'); -}; - -/** - * Update SVG attributes to match internal state. - * @private - */ -FlyoutButton.prototype.updateTransform_ = function() { - this.svgGroup_.setAttribute( - 'transform', - 'translate(' + this.position_.x + ',' + this.position_.y + ')'); -}; - -/** - * Move the button to the given x, y coordinates. - * @param {number} x The new x coordinate. - * @param {number} y The new y coordinate. - */ -FlyoutButton.prototype.moveTo = function(x, y) { - this.position_.x = x; - this.position_.y = y; - this.updateTransform_(); -}; - -/** - * @return {boolean} Whether or not the button is a label. - */ -FlyoutButton.prototype.isLabel = function() { - return this.isLabel_; -}; - -/** - * Location of the button. - * @return {!Coordinate} x, y coordinates. - * @package - */ -FlyoutButton.prototype.getPosition = function() { - return this.position_; -}; - -/** - * @return {string} Text of the button. - */ -FlyoutButton.prototype.getButtonText = function() { - return this.text_; -}; - -/** - * Get the button's target workspace. - * @return {!WorkspaceSvg} The target workspace of the flyout where this - * button resides. - */ -FlyoutButton.prototype.getTargetWorkspace = function() { - return this.targetWorkspace_; -}; - -/** - * Dispose of this button. - */ -FlyoutButton.prototype.dispose = function() { - if (this.onMouseUpWrapper_) { - browserEvents.unbind(this.onMouseUpWrapper_); - } - if (this.svgGroup_) { - dom.removeNode(this.svgGroup_); - } - if (this.svgText_) { - this.workspace_.getThemeManager().unsubscribe(this.svgText_); - } -}; - -/** - * Do something when the button is clicked. - * @param {!Event} e Mouse up event. - * @private - */ -FlyoutButton.prototype.onMouseUp_ = function(e) { - const gesture = this.targetWorkspace_.getGesture(e); - if (gesture) { - gesture.cancel(); - } - - if (this.isLabel_ && this.callbackKey_) { - console.warn('Labels should not have callbacks. Label text: ' + this.text_); - } else if ( - !this.isLabel_ && - !(this.callbackKey_ && - this.targetWorkspace_.getButtonCallback(this.callbackKey_))) { - console.warn('Buttons should have callbacks. Button text: ' + this.text_); - } else if (!this.isLabel_) { - this.targetWorkspace_.getButtonCallback(this.callbackKey_)(this); - } -}; +FlyoutButton.TEXT_MARGIN_Y = 2; /** * CSS for buttons and labels. See css.js for use. */ Css.register(` - .blocklyFlyoutButton { - fill: #888; - cursor: default; - } +.blocklyFlyoutButton { + fill: #888; + cursor: default; +} - .blocklyFlyoutButtonShadow { - fill: #666; - } +.blocklyFlyoutButtonShadow { + fill: #666; +} - .blocklyFlyoutButton:hover { - fill: #aaa; - } +.blocklyFlyoutButton:hover { + fill: #aaa; +} - .blocklyFlyoutLabel { - cursor: default; - } +.blocklyFlyoutLabel { + cursor: default; +} - .blocklyFlyoutLabelBackground { - opacity: 0; - } +.blocklyFlyoutLabelBackground { + opacity: 0; +} `); exports.FlyoutButton = FlyoutButton; diff --git a/core/flyout_horizontal.js b/core/flyout_horizontal.js index 1ff150c4f..d405da96d 100644 --- a/core/flyout_horizontal.js +++ b/core/flyout_horizontal.js @@ -17,12 +17,11 @@ goog.module('Blockly.HorizontalFlyout'); const WidgetDiv = goog.require('Blockly.WidgetDiv'); const browserEvents = goog.require('Blockly.browserEvents'); -const object = goog.require('Blockly.utils.object'); +const dropDownDiv = goog.require('Blockly.dropDownDiv'); const registry = goog.require('Blockly.registry'); const toolbox = goog.require('Blockly.utils.toolbox'); /* eslint-disable-next-line no-unused-vars */ const {Coordinate} = goog.requireType('Blockly.utils.Coordinate'); -const {DropDownDiv} = goog.require('Blockly.DropDownDiv'); const {Flyout} = goog.require('Blockly.Flyout'); /* eslint-disable-next-line no-unused-vars */ const {Options} = goog.requireType('Blockly.Options'); @@ -32,355 +31,358 @@ const {Scrollbar} = goog.require('Blockly.Scrollbar'); /** * Class for a flyout. - * @param {!Options} workspaceOptions Dictionary of options for the - * workspace. * @extends {Flyout} - * @constructor * @alias Blockly.HorizontalFlyout */ -const HorizontalFlyout = function(workspaceOptions) { - HorizontalFlyout.superClass_.constructor.call(this, workspaceOptions); - this.horizontalLayout = true; -}; -object.inherits(HorizontalFlyout, Flyout); - -/** - * Sets the translation of the flyout to match the scrollbars. - * @param {!{x:number,y:number}} xyRatio Contains a y property which is a float - * between 0 and 1 specifying the degree of scrolling and a - * similar x property. - * @protected - */ -HorizontalFlyout.prototype.setMetrics_ = function(xyRatio) { - if (!this.isVisible()) { - return; +class HorizontalFlyout extends Flyout { + /** + * @param {!Options} workspaceOptions Dictionary of options for the + * workspace. + */ + constructor(workspaceOptions) { + super(workspaceOptions); + this.horizontalLayout = true; } - const metricsManager = this.workspace_.getMetricsManager(); - const scrollMetrics = metricsManager.getScrollMetrics(); - const viewMetrics = metricsManager.getViewMetrics(); - const absoluteMetrics = metricsManager.getAbsoluteMetrics(); + /** + * Sets the translation of the flyout to match the scrollbars. + * @param {!{x:number,y:number}} xyRatio Contains a y property which is a + * float between 0 and 1 specifying the degree of scrolling and a similar + * x property. + * @protected + */ + setMetrics_(xyRatio) { + if (!this.isVisible()) { + return; + } - if (typeof xyRatio.x === 'number') { - this.workspace_.scrollX = - -(scrollMetrics.left + - (scrollMetrics.width - viewMetrics.width) * xyRatio.x); + const metricsManager = this.workspace_.getMetricsManager(); + const scrollMetrics = metricsManager.getScrollMetrics(); + const viewMetrics = metricsManager.getViewMetrics(); + const absoluteMetrics = metricsManager.getAbsoluteMetrics(); + + if (typeof xyRatio.x === 'number') { + this.workspace_.scrollX = + -(scrollMetrics.left + + (scrollMetrics.width - viewMetrics.width) * xyRatio.x); + } + + this.workspace_.translate( + this.workspace_.scrollX + absoluteMetrics.left, + this.workspace_.scrollY + absoluteMetrics.top); } - this.workspace_.translate( - this.workspace_.scrollX + absoluteMetrics.left, - this.workspace_.scrollY + absoluteMetrics.top); -}; - -/** - * Calculates the x coordinate for the flyout position. - * @return {number} X coordinate. - */ -HorizontalFlyout.prototype.getX = function() { - // X is always 0 since this is a horizontal flyout. - return 0; -}; - -/** - * Calculates the y coordinate for the flyout position. - * @return {number} Y coordinate. - */ -HorizontalFlyout.prototype.getY = function() { - if (!this.isVisible()) { + /** + * Calculates the x coordinate for the flyout position. + * @return {number} X coordinate. + */ + getX() { + // X is always 0 since this is a horizontal flyout. return 0; } - const metricsManager = this.targetWorkspace.getMetricsManager(); - const absoluteMetrics = metricsManager.getAbsoluteMetrics(); - const viewMetrics = metricsManager.getViewMetrics(); - const toolboxMetrics = metricsManager.getToolboxMetrics(); - let y = 0; - const atTop = this.toolboxPosition_ === toolbox.Position.TOP; - // If this flyout is not the trashcan flyout (e.g. toolbox or mutator). - if (this.targetWorkspace.toolboxPosition === this.toolboxPosition_) { - // If there is a category toolbox. - if (this.targetWorkspace.getToolbox()) { - if (atTop) { - y = toolboxMetrics.height; + /** + * Calculates the y coordinate for the flyout position. + * @return {number} Y coordinate. + */ + getY() { + if (!this.isVisible()) { + return 0; + } + const metricsManager = this.targetWorkspace.getMetricsManager(); + const absoluteMetrics = metricsManager.getAbsoluteMetrics(); + const viewMetrics = metricsManager.getViewMetrics(); + const toolboxMetrics = metricsManager.getToolboxMetrics(); + + let y = 0; + const atTop = this.toolboxPosition_ === toolbox.Position.TOP; + // If this flyout is not the trashcan flyout (e.g. toolbox or mutator). + if (this.targetWorkspace.toolboxPosition === this.toolboxPosition_) { + // If there is a category toolbox. + if (this.targetWorkspace.getToolbox()) { + if (atTop) { + y = toolboxMetrics.height; + } else { + y = viewMetrics.height - this.height_; + } + // Simple (flyout-only) toolbox. } else { - y = viewMetrics.height - this.height_; + if (atTop) { + y = 0; + } else { + // The simple flyout does not cover the workspace. + y = viewMetrics.height; + } } - // Simple (flyout-only) toolbox. + // Trashcan flyout is opposite the main flyout. } else { if (atTop) { y = 0; } else { - // The simple flyout does not cover the workspace. - y = viewMetrics.height; + // Because the anchor point of the flyout is on the top, but we want + // to align the bottom edge of the flyout with the bottom edge of the + // blocklyDiv, we calculate the full height of the div minus the height + // of the flyout. + y = viewMetrics.height + absoluteMetrics.top - this.height_; } } - // Trashcan flyout is opposite the main flyout. - } else { + + return y; + } + + /** + * Move the flyout to the edge of the workspace. + */ + position() { + if (!this.isVisible() || !this.targetWorkspace.isVisible()) { + return; + } + const metricsManager = this.targetWorkspace.getMetricsManager(); + const targetWorkspaceViewMetrics = metricsManager.getViewMetrics(); + + // Record the width for workspace metrics. + this.width_ = targetWorkspaceViewMetrics.width; + + const edgeWidth = targetWorkspaceViewMetrics.width - 2 * this.CORNER_RADIUS; + const edgeHeight = this.height_ - this.CORNER_RADIUS; + this.setBackgroundPath_(edgeWidth, edgeHeight); + + const x = this.getX(); + const y = this.getY(); + + this.positionAt_(this.width_, this.height_, x, y); + } + + /** + * Create and set the path for the visible boundaries of the flyout. + * @param {number} width The width of the flyout, not including the + * rounded corners. + * @param {number} height The height of the flyout, not including + * rounded corners. + * @private + */ + setBackgroundPath_(width, height) { + const atTop = this.toolboxPosition_ === toolbox.Position.TOP; + // Start at top left. + const path = ['M 0,' + (atTop ? 0 : this.CORNER_RADIUS)]; + if (atTop) { - y = 0; + // Top. + path.push('h', width + 2 * this.CORNER_RADIUS); + // Right. + path.push('v', height); + // Bottom. + path.push( + 'a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1, + -this.CORNER_RADIUS, this.CORNER_RADIUS); + path.push('h', -width); + // Left. + path.push( + 'a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1, + -this.CORNER_RADIUS, -this.CORNER_RADIUS); + path.push('z'); } else { - // Because the anchor point of the flyout is on the top, but we want - // to align the bottom edge of the flyout with the bottom edge of the - // blocklyDiv, we calculate the full height of the div minus the height - // of the flyout. - y = viewMetrics.height + absoluteMetrics.top - this.height_; + // Top. + path.push( + 'a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1, + this.CORNER_RADIUS, -this.CORNER_RADIUS); + path.push('h', width); + // Right. + path.push( + 'a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1, + this.CORNER_RADIUS, this.CORNER_RADIUS); + path.push('v', height); + // Bottom. + path.push('h', -width - 2 * this.CORNER_RADIUS); + // Left. + path.push('z'); + } + this.svgBackground_.setAttribute('d', path.join(' ')); + } + + /** + * Scroll the flyout to the top. + */ + scrollToStart() { + this.workspace_.scrollbar.setX(this.RTL ? Infinity : 0); + } + + /** + * Scroll the flyout. + * @param {!Event} e Mouse wheel scroll event. + * @protected + */ + wheel_(e) { + const scrollDelta = browserEvents.getScrollDeltaPixels(e); + const delta = scrollDelta.x || scrollDelta.y; + + if (delta) { + const metricsManager = this.workspace_.getMetricsManager(); + const scrollMetrics = metricsManager.getScrollMetrics(); + const viewMetrics = metricsManager.getViewMetrics(); + + const pos = (viewMetrics.left - scrollMetrics.left) + delta; + this.workspace_.scrollbar.setX(pos); + // When the flyout moves from a wheel event, hide WidgetDiv and + // dropDownDiv. + WidgetDiv.hide(); + dropDownDiv.hideWithoutAnimation(); + } + + // Don't scroll the page. + e.preventDefault(); + // Don't propagate mousewheel event (zooming). + e.stopPropagation(); + } + + /** + * Lay out the blocks in the flyout. + * @param {!Array} contents The blocks and buttons to lay out. + * @param {!Array} gaps The visible gaps between blocks. + * @protected + */ + layout_(contents, gaps) { + this.workspace_.scale = this.targetWorkspace.scale; + const margin = this.MARGIN; + let cursorX = margin + this.tabWidth_; + const cursorY = margin; + if (this.RTL) { + contents = contents.reverse(); + } + + for (let i = 0, item; (item = contents[i]); i++) { + if (item.type === 'block') { + const block = item.block; + const allBlocks = block.getDescendants(false); + for (let j = 0, child; (child = allBlocks[j]); j++) { + // Mark blocks as being inside a flyout. This is used to detect and + // prevent the closure of the flyout if the user right-clicks on such + // a block. + child.isInFlyout = true; + } + block.render(); + const root = block.getSvgRoot(); + const blockHW = block.getHeightWidth(); + + // Figure out where to place the block. + const tab = block.outputConnection ? this.tabWidth_ : 0; + let moveX; + if (this.RTL) { + moveX = cursorX + blockHW.width; + } else { + moveX = cursorX - tab; + } + block.moveBy(moveX, cursorY); + + const rect = this.createRect_(block, moveX, cursorY, blockHW, i); + cursorX += (blockHW.width + gaps[i]); + + this.addBlockListeners_(root, block, rect); + } else if (item.type === 'button') { + this.initFlyoutButton_(item.button, cursorX, cursorY); + cursorX += (item.button.width + gaps[i]); + } } } - return y; -}; + /** + * Determine if a drag delta is toward the workspace, based on the position + * and orientation of the flyout. This is used in determineDragIntention_ to + * determine if a new block should be created or if the flyout should scroll. + * @param {!Coordinate} currentDragDeltaXY How far the pointer has + * moved from the position at mouse down, in pixel units. + * @return {boolean} True if the drag is toward the workspace. + * @package + */ + isDragTowardWorkspace(currentDragDeltaXY) { + const dx = currentDragDeltaXY.x; + const dy = currentDragDeltaXY.y; + // Direction goes from -180 to 180, with 0 toward the right and 90 on top. + const dragDirection = Math.atan2(dy, dx) / Math.PI * 180; -/** - * Move the flyout to the edge of the workspace. - */ -HorizontalFlyout.prototype.position = function() { - if (!this.isVisible() || !this.targetWorkspace.isVisible()) { - return; - } - const metricsManager = this.targetWorkspace.getMetricsManager(); - const targetWorkspaceViewMetrics = metricsManager.getViewMetrics(); - - // Record the width for workspace metrics. - this.width_ = targetWorkspaceViewMetrics.width; - - const edgeWidth = targetWorkspaceViewMetrics.width - 2 * this.CORNER_RADIUS; - const edgeHeight = this.height_ - this.CORNER_RADIUS; - this.setBackgroundPath_(edgeWidth, edgeHeight); - - const x = this.getX(); - const y = this.getY(); - - this.positionAt_(this.width_, this.height_, x, y); -}; - -/** - * Create and set the path for the visible boundaries of the flyout. - * @param {number} width The width of the flyout, not including the - * rounded corners. - * @param {number} height The height of the flyout, not including - * rounded corners. - * @private - */ -HorizontalFlyout.prototype.setBackgroundPath_ = function(width, height) { - const atTop = this.toolboxPosition_ === toolbox.Position.TOP; - // Start at top left. - const path = ['M 0,' + (atTop ? 0 : this.CORNER_RADIUS)]; - - if (atTop) { - // Top. - path.push('h', width + 2 * this.CORNER_RADIUS); - // Right. - path.push('v', height); - // Bottom. - path.push( - 'a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1, - -this.CORNER_RADIUS, this.CORNER_RADIUS); - path.push('h', -width); - // Left. - path.push( - 'a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1, - -this.CORNER_RADIUS, -this.CORNER_RADIUS); - path.push('z'); - } else { - // Top. - path.push( - 'a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1, - this.CORNER_RADIUS, -this.CORNER_RADIUS); - path.push('h', width); - // Right. - path.push( - 'a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1, - this.CORNER_RADIUS, this.CORNER_RADIUS); - path.push('v', height); - // Bottom. - path.push('h', -width - 2 * this.CORNER_RADIUS); - // Left. - path.push('z'); - } - this.svgBackground_.setAttribute('d', path.join(' ')); -}; - -/** - * Scroll the flyout to the top. - */ -HorizontalFlyout.prototype.scrollToStart = function() { - this.workspace_.scrollbar.setX(this.RTL ? Infinity : 0); -}; - -/** - * Scroll the flyout. - * @param {!Event} e Mouse wheel scroll event. - * @protected - */ -HorizontalFlyout.prototype.wheel_ = function(e) { - const scrollDelta = browserEvents.getScrollDeltaPixels(e); - const delta = scrollDelta.x || scrollDelta.y; - - if (delta) { - const metricsManager = this.workspace_.getMetricsManager(); - const scrollMetrics = metricsManager.getScrollMetrics(); - const viewMetrics = metricsManager.getViewMetrics(); - - const pos = (viewMetrics.left - scrollMetrics.left) + delta; - this.workspace_.scrollbar.setX(pos); - // When the flyout moves from a wheel event, hide WidgetDiv and DropDownDiv. - WidgetDiv.hide(); - DropDownDiv.hideWithoutAnimation(); + const range = this.dragAngleRange_; + // Check for up or down dragging. + if ((dragDirection < 90 + range && dragDirection > 90 - range) || + (dragDirection > -90 - range && dragDirection < -90 + range)) { + return true; + } + return false; } - // Don't scroll the page. - e.preventDefault(); - // Don't propagate mousewheel event (zooming). - e.stopPropagation(); -}; + /** + * Returns the bounding rectangle of the drag target area in pixel units + * relative to viewport. + * @return {?Rect} The component's bounding box. Null if drag + * target area should be ignored. + */ + getClientRect() { + if (!this.svgGroup_ || this.autoClose || !this.isVisible()) { + // The bounding rectangle won't compute correctly if the flyout is closed + // and auto-close flyouts aren't valid drag targets (or delete areas). + return null; + } -/** - * Lay out the blocks in the flyout. - * @param {!Array} contents The blocks and buttons to lay out. - * @param {!Array} gaps The visible gaps between blocks. - * @protected - */ -HorizontalFlyout.prototype.layout_ = function(contents, gaps) { - this.workspace_.scale = this.targetWorkspace.scale; - const margin = this.MARGIN; - let cursorX = margin + this.tabWidth_; - const cursorY = margin; - if (this.RTL) { - contents = contents.reverse(); - } + const flyoutRect = this.svgGroup_.getBoundingClientRect(); + // BIG_NUM is offscreen padding so that blocks dragged beyond the shown + // flyout area are still deleted. Must be larger than the largest screen + // size, but be smaller than half Number.MAX_SAFE_INTEGER (not available on + // IE). + const BIG_NUM = 1000000000; + const top = flyoutRect.top; - for (let i = 0, item; (item = contents[i]); i++) { - if (item.type === 'block') { - const block = item.block; - const allBlocks = block.getDescendants(false); - for (let j = 0, child; (child = allBlocks[j]); j++) { - // Mark blocks as being inside a flyout. This is used to detect and - // prevent the closure of the flyout if the user right-clicks on such a - // block. - child.isInFlyout = true; - } - block.render(); - const root = block.getSvgRoot(); - const blockHW = block.getHeightWidth(); - - // Figure out where to place the block. - const tab = block.outputConnection ? this.tabWidth_ : 0; - let moveX; - if (this.RTL) { - moveX = cursorX + blockHW.width; - } else { - moveX = cursorX - tab; - } - block.moveBy(moveX, cursorY); - - const rect = this.createRect_(block, moveX, cursorY, blockHW, i); - cursorX += (blockHW.width + gaps[i]); - - this.addBlockListeners_(root, block, rect); - } else if (item.type === 'button') { - this.initFlyoutButton_(item.button, cursorX, cursorY); - cursorX += (item.button.width + gaps[i]); + if (this.toolboxPosition_ === toolbox.Position.TOP) { + const height = flyoutRect.height; + return new Rect(-BIG_NUM, top + height, -BIG_NUM, BIG_NUM); + } else { // Bottom. + return new Rect(top, BIG_NUM, -BIG_NUM, BIG_NUM); } } -}; -/** - * Determine if a drag delta is toward the workspace, based on the position - * and orientation of the flyout. This is used in determineDragIntention_ to - * determine if a new block should be created or if the flyout should scroll. - * @param {!Coordinate} currentDragDeltaXY How far the pointer has - * moved from the position at mouse down, in pixel units. - * @return {boolean} True if the drag is toward the workspace. - * @package - */ -HorizontalFlyout.prototype.isDragTowardWorkspace = function( - currentDragDeltaXY) { - const dx = currentDragDeltaXY.x; - const dy = currentDragDeltaXY.y; - // Direction goes from -180 to 180, with 0 toward the right and 90 on top. - const dragDirection = Math.atan2(dy, dx) / Math.PI * 180; - - const range = this.dragAngleRange_; - // Check for up or down dragging. - if ((dragDirection < 90 + range && dragDirection > 90 - range) || - (dragDirection > -90 - range && dragDirection < -90 + range)) { - return true; - } - return false; -}; - -/** - * Returns the bounding rectangle of the drag target area in pixel units - * relative to viewport. - * @return {?Rect} The component's bounding box. Null if drag - * target area should be ignored. - */ -HorizontalFlyout.prototype.getClientRect = function() { - if (!this.svgGroup_ || this.autoClose || !this.isVisible()) { - // The bounding rectangle won't compute correctly if the flyout is closed - // and auto-close flyouts aren't valid drag targets (or delete areas). - return null; - } - - const flyoutRect = this.svgGroup_.getBoundingClientRect(); - // BIG_NUM is offscreen padding so that blocks dragged beyond the shown flyout - // area are still deleted. Must be larger than the largest screen size, - // but be smaller than half Number.MAX_SAFE_INTEGER (not available on IE). - const BIG_NUM = 1000000000; - const top = flyoutRect.top; - - if (this.toolboxPosition_ === toolbox.Position.TOP) { - const height = flyoutRect.height; - return new Rect(-BIG_NUM, top + height, -BIG_NUM, BIG_NUM); - } else { // Bottom. - return new Rect(top, BIG_NUM, -BIG_NUM, BIG_NUM); - } -}; - -/** - * Compute height of flyout. toolbox.Position mat under each block. - * For RTL: Lay out the blocks right-aligned. - * @protected - */ -HorizontalFlyout.prototype.reflowInternal_ = function() { - this.workspace_.scale = this.getFlyoutScale(); - let flyoutHeight = 0; - const blocks = this.workspace_.getTopBlocks(false); - for (let i = 0, block; (block = blocks[i]); i++) { - flyoutHeight = Math.max(flyoutHeight, block.getHeightWidth().height); - } - const buttons = this.buttons_; - for (let i = 0, button; (button = buttons[i]); i++) { - flyoutHeight = Math.max(flyoutHeight, button.height); - } - flyoutHeight += this.MARGIN * 1.5; - flyoutHeight *= this.workspace_.scale; - flyoutHeight += Scrollbar.scrollbarThickness; - - if (this.height_ !== flyoutHeight) { + /** + * Compute height of flyout. toolbox.Position mat under each block. + * For RTL: Lay out the blocks right-aligned. + * @protected + */ + reflowInternal_() { + this.workspace_.scale = this.getFlyoutScale(); + let flyoutHeight = 0; + const blocks = this.workspace_.getTopBlocks(false); for (let i = 0, block; (block = blocks[i]); i++) { - if (block.flyoutRect_) { - this.moveRectToBlock_(block.flyoutRect_, block); + flyoutHeight = Math.max(flyoutHeight, block.getHeightWidth().height); + } + const buttons = this.buttons_; + for (let i = 0, button; (button = buttons[i]); i++) { + flyoutHeight = Math.max(flyoutHeight, button.height); + } + flyoutHeight += this.MARGIN * 1.5; + flyoutHeight *= this.workspace_.scale; + flyoutHeight += Scrollbar.scrollbarThickness; + + if (this.height_ !== flyoutHeight) { + for (let i = 0, block; (block = blocks[i]); i++) { + if (this.rectMap_.has(block)) { + this.moveRectToBlock_(this.rectMap_.get(block), block); + } } - } - if (this.targetWorkspace.toolboxPosition === this.toolboxPosition_ && - this.toolboxPosition_ === toolbox.Position.TOP && - !this.targetWorkspace.getToolbox()) { - // This flyout is a simple toolbox. Reposition the workspace so that (0,0) - // is in the correct position relative to the new absolute edge (ie - // toolbox edge). - this.targetWorkspace.translate( - this.targetWorkspace.scrollX, - this.targetWorkspace.scrollY + flyoutHeight); - } + if (this.targetWorkspace.toolboxPosition === this.toolboxPosition_ && + this.toolboxPosition_ === toolbox.Position.TOP && + !this.targetWorkspace.getToolbox()) { + // This flyout is a simple toolbox. Reposition the workspace so that + // (0,0) is in the correct position relative to the new absolute edge + // (ie toolbox edge). + this.targetWorkspace.translate( + this.targetWorkspace.scrollX, + this.targetWorkspace.scrollY + flyoutHeight); + } - // Record the height for workspace metrics and .position. - this.height_ = flyoutHeight; - this.position(); - this.targetWorkspace.recordDragTargets(); + // Record the height for workspace metrics and .position. + this.height_ = flyoutHeight; + this.position(); + this.targetWorkspace.recordDragTargets(); + } } -}; +} registry.register( registry.Type.FLYOUTS_HORIZONTAL_TOOLBOX, registry.DEFAULT, diff --git a/core/flyout_metrics_manager.js b/core/flyout_metrics_manager.js index ddbabd2a6..e8b4581cb 100644 --- a/core/flyout_metrics_manager.js +++ b/core/flyout_metrics_manager.js @@ -15,7 +15,6 @@ */ goog.module('Blockly.FlyoutMetricsManager'); -const object = goog.require('Blockly.utils.object'); /* eslint-disable-next-line no-unused-vars */ const {IFlyout} = goog.requireType('Blockly.IFlyout'); const {MetricsManager} = goog.require('Blockly.MetricsManager'); @@ -26,81 +25,82 @@ const {WorkspaceSvg} = goog.requireType('Blockly.WorkspaceSvg'); /** * Calculates metrics for a flyout's workspace. * The metrics are mainly used to size scrollbars for the flyout. - * @param {!WorkspaceSvg} workspace The flyout's workspace. - * @param {!IFlyout} flyout The flyout. * @extends {MetricsManager} - * @constructor * @alias Blockly.FlyoutMetricsManager */ -const FlyoutMetricsManager = function(workspace, flyout) { +class FlyoutMetricsManager extends MetricsManager { /** - * The flyout that owns the workspace to calculate metrics for. - * @type {!IFlyout} - * @protected + * @param {!WorkspaceSvg} workspace The flyout's workspace. + * @param {!IFlyout} flyout The flyout. */ - this.flyout_ = flyout; + constructor(workspace, flyout) { + super(workspace); - FlyoutMetricsManager.superClass_.constructor.call(this, workspace); -}; -object.inherits(FlyoutMetricsManager, MetricsManager); - -/** - * Gets the bounding box of the blocks on the flyout's workspace. - * This is in workspace coordinates. - * @return {!SVGRect|{height: number, y: number, width: number, x: number}} The - * bounding box of the blocks on the workspace. - * @private - */ -FlyoutMetricsManager.prototype.getBoundingBox_ = function() { - let blockBoundingBox; - try { - blockBoundingBox = this.workspace_.getCanvas().getBBox(); - } catch (e) { - // Firefox has trouble with hidden elements (Bug 528969). - // 2021 Update: It looks like this was fixed around Firefox 77 released in - // 2020. - blockBoundingBox = {height: 0, y: 0, width: 0, x: 0}; + /** + * The flyout that owns the workspace to calculate metrics for. + * @type {!IFlyout} + * @protected + */ + this.flyout_ = flyout; } - return blockBoundingBox; -}; -/** - * @override - */ -FlyoutMetricsManager.prototype.getContentMetrics = function( - opt_getWorkspaceCoordinates) { - // The bounding box is in workspace coordinates. - const blockBoundingBox = this.getBoundingBox_(); - const scale = opt_getWorkspaceCoordinates ? 1 : this.workspace_.scale; + /** + * Gets the bounding box of the blocks on the flyout's workspace. + * This is in workspace coordinates. + * @return {!SVGRect|{height: number, y: number, width: number, x: number}} + * The bounding box of the blocks on the workspace. + * @private + */ + getBoundingBox_() { + let blockBoundingBox; + try { + blockBoundingBox = this.workspace_.getCanvas().getBBox(); + } catch (e) { + // Firefox has trouble with hidden elements (Bug 528969). + // 2021 Update: It looks like this was fixed around Firefox 77 released in + // 2020. + blockBoundingBox = {height: 0, y: 0, width: 0, x: 0}; + } + return blockBoundingBox; + } - return { - height: blockBoundingBox.height * scale, - width: blockBoundingBox.width * scale, - top: blockBoundingBox.y * scale, - left: blockBoundingBox.x * scale, - }; -}; + /** + * @override + */ + getContentMetrics(opt_getWorkspaceCoordinates) { + // The bounding box is in workspace coordinates. + const blockBoundingBox = this.getBoundingBox_(); + const scale = opt_getWorkspaceCoordinates ? 1 : this.workspace_.scale; -/** - * @override - */ -FlyoutMetricsManager.prototype.getScrollMetrics = function( - opt_getWorkspaceCoordinates, opt_viewMetrics, opt_contentMetrics) { - const contentMetrics = opt_contentMetrics || this.getContentMetrics(); - const margin = this.flyout_.MARGIN * this.workspace_.scale; - const scale = opt_getWorkspaceCoordinates ? this.workspace_.scale : 1; + return { + height: blockBoundingBox.height * scale, + width: blockBoundingBox.width * scale, + top: blockBoundingBox.y * scale, + left: blockBoundingBox.x * scale, + }; + } - // The left padding isn't just the margin. Some blocks are also offset by - // tabWidth so that value and statement blocks line up. - // The contentMetrics.left value is equivalent to the variable left padding. - const leftPadding = contentMetrics.left; + /** + * @override + */ + getScrollMetrics( + opt_getWorkspaceCoordinates, opt_viewMetrics, opt_contentMetrics) { + const contentMetrics = opt_contentMetrics || this.getContentMetrics(); + const margin = this.flyout_.MARGIN * this.workspace_.scale; + const scale = opt_getWorkspaceCoordinates ? this.workspace_.scale : 1; - return { - height: (contentMetrics.height + 2 * margin) / scale, - width: (contentMetrics.width + leftPadding + margin) / scale, - top: 0, - left: 0, - }; -}; + // The left padding isn't just the margin. Some blocks are also offset by + // tabWidth so that value and statement blocks line up. + // The contentMetrics.left value is equivalent to the variable left padding. + const leftPadding = contentMetrics.left; + + return { + height: (contentMetrics.height + 2 * margin) / scale, + width: (contentMetrics.width + leftPadding + margin) / scale, + top: 0, + left: 0, + }; + } +} exports.FlyoutMetricsManager = FlyoutMetricsManager; diff --git a/core/flyout_vertical.js b/core/flyout_vertical.js index b6e985f99..631cd705d 100644 --- a/core/flyout_vertical.js +++ b/core/flyout_vertical.js @@ -17,12 +17,11 @@ goog.module('Blockly.VerticalFlyout'); const WidgetDiv = goog.require('Blockly.WidgetDiv'); const browserEvents = goog.require('Blockly.browserEvents'); -const object = goog.require('Blockly.utils.object'); +const dropDownDiv = goog.require('Blockly.dropDownDiv'); const registry = goog.require('Blockly.registry'); const toolbox = goog.require('Blockly.utils.toolbox'); /* eslint-disable-next-line no-unused-vars */ const {Coordinate} = goog.requireType('Blockly.utils.Coordinate'); -const {DropDownDiv} = goog.require('Blockly.DropDownDiv'); const {Flyout} = goog.require('Blockly.Flyout'); /* eslint-disable-next-line no-unused-vars */ const {Options} = goog.requireType('Blockly.Options'); @@ -36,16 +35,353 @@ goog.require('Blockly.constants'); /** * Class for a flyout. - * @param {!Options} workspaceOptions Dictionary of options for the - * workspace. * @extends {Flyout} - * @constructor * @alias Blockly.VerticalFlyout */ -const VerticalFlyout = function(workspaceOptions) { - VerticalFlyout.superClass_.constructor.call(this, workspaceOptions); -}; -object.inherits(VerticalFlyout, Flyout); +class VerticalFlyout extends Flyout { + /** + * @param {!Options} workspaceOptions Dictionary of options for the + * workspace. + */ + constructor(workspaceOptions) { + super(workspaceOptions); + } + + /** + * Sets the translation of the flyout to match the scrollbars. + * @param {!{x:number,y:number}} xyRatio Contains a y property which is a + * float between 0 and 1 specifying the degree of scrolling and a similar + * x property. + * @protected + */ + setMetrics_(xyRatio) { + if (!this.isVisible()) { + return; + } + const metricsManager = this.workspace_.getMetricsManager(); + const scrollMetrics = metricsManager.getScrollMetrics(); + const viewMetrics = metricsManager.getViewMetrics(); + const absoluteMetrics = metricsManager.getAbsoluteMetrics(); + + if (typeof xyRatio.y === 'number') { + this.workspace_.scrollY = + -(scrollMetrics.top + + (scrollMetrics.height - viewMetrics.height) * xyRatio.y); + } + this.workspace_.translate( + this.workspace_.scrollX + absoluteMetrics.left, + this.workspace_.scrollY + absoluteMetrics.top); + } + + /** + * Calculates the x coordinate for the flyout position. + * @return {number} X coordinate. + */ + getX() { + if (!this.isVisible()) { + return 0; + } + const metricsManager = this.targetWorkspace.getMetricsManager(); + const absoluteMetrics = metricsManager.getAbsoluteMetrics(); + const viewMetrics = metricsManager.getViewMetrics(); + const toolboxMetrics = metricsManager.getToolboxMetrics(); + let x = 0; + + // If this flyout is not the trashcan flyout (e.g. toolbox or mutator). + if (this.targetWorkspace.toolboxPosition === this.toolboxPosition_) { + // If there is a category toolbox. + if (this.targetWorkspace.getToolbox()) { + if (this.toolboxPosition_ === toolbox.Position.LEFT) { + x = toolboxMetrics.width; + } else { + x = viewMetrics.width - this.width_; + } + // Simple (flyout-only) toolbox. + } else { + if (this.toolboxPosition_ === toolbox.Position.LEFT) { + x = 0; + } else { + // The simple flyout does not cover the workspace. + x = viewMetrics.width; + } + } + // Trashcan flyout is opposite the main flyout. + } else { + if (this.toolboxPosition_ === toolbox.Position.LEFT) { + x = 0; + } else { + // Because the anchor point of the flyout is on the left, but we want + // to align the right edge of the flyout with the right edge of the + // blocklyDiv, we calculate the full width of the div minus the width + // of the flyout. + x = viewMetrics.width + absoluteMetrics.left - this.width_; + } + } + + return x; + } + + /** + * Calculates the y coordinate for the flyout position. + * @return {number} Y coordinate. + */ + getY() { + // Y is always 0 since this is a vertical flyout. + return 0; + } + + /** + * Move the flyout to the edge of the workspace. + */ + position() { + if (!this.isVisible() || !this.targetWorkspace.isVisible()) { + return; + } + const metricsManager = this.targetWorkspace.getMetricsManager(); + const targetWorkspaceViewMetrics = metricsManager.getViewMetrics(); + + // Record the height for workspace metrics. + this.height_ = targetWorkspaceViewMetrics.height; + + const edgeWidth = this.width_ - this.CORNER_RADIUS; + const edgeHeight = + targetWorkspaceViewMetrics.height - 2 * this.CORNER_RADIUS; + this.setBackgroundPath_(edgeWidth, edgeHeight); + + const x = this.getX(); + const y = this.getY(); + + this.positionAt_(this.width_, this.height_, x, y); + } + + /** + * Create and set the path for the visible boundaries of the flyout. + * @param {number} width The width of the flyout, not including the + * rounded corners. + * @param {number} height The height of the flyout, not including + * rounded corners. + * @private + */ + setBackgroundPath_(width, height) { + const atRight = this.toolboxPosition_ === toolbox.Position.RIGHT; + const totalWidth = width + this.CORNER_RADIUS; + + // Decide whether to start on the left or right. + const path = ['M ' + (atRight ? totalWidth : 0) + ',0']; + // Top. + path.push('h', atRight ? -width : width); + // Rounded corner. + path.push( + 'a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, atRight ? 0 : 1, + atRight ? -this.CORNER_RADIUS : this.CORNER_RADIUS, this.CORNER_RADIUS); + // Side closest to workspace. + path.push('v', Math.max(0, height)); + // Rounded corner. + path.push( + 'a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, atRight ? 0 : 1, + atRight ? this.CORNER_RADIUS : -this.CORNER_RADIUS, this.CORNER_RADIUS); + // Bottom. + path.push('h', atRight ? width : -width); + path.push('z'); + this.svgBackground_.setAttribute('d', path.join(' ')); + } + + /** + * Scroll the flyout to the top. + */ + scrollToStart() { + this.workspace_.scrollbar.setY(0); + } + + /** + * Scroll the flyout. + * @param {!Event} e Mouse wheel scroll event. + * @protected + */ + wheel_(e) { + const scrollDelta = browserEvents.getScrollDeltaPixels(e); + + if (scrollDelta.y) { + const metricsManager = this.workspace_.getMetricsManager(); + const scrollMetrics = metricsManager.getScrollMetrics(); + const viewMetrics = metricsManager.getViewMetrics(); + const pos = (viewMetrics.top - scrollMetrics.top) + scrollDelta.y; + + this.workspace_.scrollbar.setY(pos); + // When the flyout moves from a wheel event, hide WidgetDiv and + // dropDownDiv. + WidgetDiv.hide(); + dropDownDiv.hideWithoutAnimation(); + } + + // Don't scroll the page. + e.preventDefault(); + // Don't propagate mousewheel event (zooming). + e.stopPropagation(); + } + + /** + * Lay out the blocks in the flyout. + * @param {!Array} contents The blocks and buttons to lay out. + * @param {!Array} gaps The visible gaps between blocks. + * @protected + */ + layout_(contents, gaps) { + this.workspace_.scale = this.targetWorkspace.scale; + const margin = this.MARGIN; + const cursorX = this.RTL ? margin : margin + this.tabWidth_; + let cursorY = margin; + + for (let i = 0, item; (item = contents[i]); i++) { + if (item.type === 'block') { + const block = item.block; + const allBlocks = block.getDescendants(false); + for (let j = 0, child; (child = allBlocks[j]); j++) { + // Mark blocks as being inside a flyout. This is used to detect and + // prevent the closure of the flyout if the user right-clicks on such + // a block. + child.isInFlyout = true; + } + block.render(); + const root = block.getSvgRoot(); + const blockHW = block.getHeightWidth(); + const moveX = + block.outputConnection ? cursorX - this.tabWidth_ : cursorX; + block.moveBy(moveX, cursorY); + + const rect = this.createRect_( + block, this.RTL ? moveX - blockHW.width : moveX, cursorY, blockHW, + i); + + this.addBlockListeners_(root, block, rect); + + cursorY += blockHW.height + gaps[i]; + } else if (item.type === 'button') { + this.initFlyoutButton_(item.button, cursorX, cursorY); + cursorY += item.button.height + gaps[i]; + } + } + } + + /** + * Determine if a drag delta is toward the workspace, based on the position + * and orientation of the flyout. This is used in determineDragIntention_ to + * determine if a new block should be created or if the flyout should scroll. + * @param {!Coordinate} currentDragDeltaXY How far the pointer has + * moved from the position at mouse down, in pixel units. + * @return {boolean} True if the drag is toward the workspace. + * @package + */ + isDragTowardWorkspace(currentDragDeltaXY) { + const dx = currentDragDeltaXY.x; + const dy = currentDragDeltaXY.y; + // Direction goes from -180 to 180, with 0 toward the right and 90 on top. + const dragDirection = Math.atan2(dy, dx) / Math.PI * 180; + + const range = this.dragAngleRange_; + // Check for left or right dragging. + if ((dragDirection < range && dragDirection > -range) || + (dragDirection < -180 + range || dragDirection > 180 - range)) { + return true; + } + return false; + } + + /** + * Returns the bounding rectangle of the drag target area in pixel units + * relative to viewport. + * @return {?Rect} The component's bounding box. Null if drag + * target area should be ignored. + */ + getClientRect() { + if (!this.svgGroup_ || this.autoClose || !this.isVisible()) { + // The bounding rectangle won't compute correctly if the flyout is closed + // and auto-close flyouts aren't valid drag targets (or delete areas). + return null; + } + + const flyoutRect = this.svgGroup_.getBoundingClientRect(); + // BIG_NUM is offscreen padding so that blocks dragged beyond the shown + // flyout area are still deleted. Must be larger than the largest screen + // size, but be smaller than half Number.MAX_SAFE_INTEGER (not available on + // IE). + const BIG_NUM = 1000000000; + const left = flyoutRect.left; + + if (this.toolboxPosition_ === toolbox.Position.LEFT) { + const width = flyoutRect.width; + return new Rect(-BIG_NUM, BIG_NUM, -BIG_NUM, left + width); + } else { // Right + return new Rect(-BIG_NUM, BIG_NUM, left, BIG_NUM); + } + } + + /** + * Compute width of flyout. toolbox.Position mat under each block. + * For RTL: Lay out the blocks and buttons to be right-aligned. + * @protected + */ + reflowInternal_() { + this.workspace_.scale = this.getFlyoutScale(); + let flyoutWidth = 0; + const blocks = this.workspace_.getTopBlocks(false); + for (let i = 0, block; (block = blocks[i]); i++) { + let width = block.getHeightWidth().width; + if (block.outputConnection) { + width -= this.tabWidth_; + } + flyoutWidth = Math.max(flyoutWidth, width); + } + for (let i = 0, button; (button = this.buttons_[i]); i++) { + flyoutWidth = Math.max(flyoutWidth, button.width); + } + flyoutWidth += this.MARGIN * 1.5 + this.tabWidth_; + flyoutWidth *= this.workspace_.scale; + flyoutWidth += Scrollbar.scrollbarThickness; + + if (this.width_ !== flyoutWidth) { + for (let i = 0, block; (block = blocks[i]); i++) { + if (this.RTL) { + // With the flyoutWidth known, right-align the blocks. + const oldX = block.getRelativeToSurfaceXY().x; + let newX = flyoutWidth / this.workspace_.scale - this.MARGIN; + if (!block.outputConnection) { + newX -= this.tabWidth_; + } + block.moveBy(newX - oldX, 0); + } + if (this.rectMap_.has(block)) { + this.moveRectToBlock_(this.rectMap_.get(block), block); + } + } + if (this.RTL) { + // With the flyoutWidth known, right-align the buttons. + for (let i = 0, button; (button = this.buttons_[i]); i++) { + const y = button.getPosition().y; + const x = flyoutWidth / this.workspace_.scale - button.width - + this.MARGIN - this.tabWidth_; + button.moveTo(x, y); + } + } + + if (this.targetWorkspace.toolboxPosition === this.toolboxPosition_ && + this.toolboxPosition_ === toolbox.Position.LEFT && + !this.targetWorkspace.getToolbox()) { + // This flyout is a simple toolbox. Reposition the workspace so that + // (0,0) is in the correct position relative to the new absolute edge + // (ie toolbox edge). + this.targetWorkspace.translate( + this.targetWorkspace.scrollX + flyoutWidth, + this.targetWorkspace.scrollY); + } + + // Record the width for workspace metrics and .position. + this.width_ = flyoutWidth; + this.position(); + this.targetWorkspace.recordDragTargets(); + } + } +} /** * The name of the vertical flyout in the registry. @@ -53,336 +389,6 @@ object.inherits(VerticalFlyout, Flyout); */ VerticalFlyout.registryName = 'verticalFlyout'; -/** - * Sets the translation of the flyout to match the scrollbars. - * @param {!{x:number,y:number}} xyRatio Contains a y property which is a float - * between 0 and 1 specifying the degree of scrolling and a - * similar x property. - * @protected - */ -VerticalFlyout.prototype.setMetrics_ = function(xyRatio) { - if (!this.isVisible()) { - return; - } - const metricsManager = this.workspace_.getMetricsManager(); - const scrollMetrics = metricsManager.getScrollMetrics(); - const viewMetrics = metricsManager.getViewMetrics(); - const absoluteMetrics = metricsManager.getAbsoluteMetrics(); - - if (typeof xyRatio.y === 'number') { - this.workspace_.scrollY = - -(scrollMetrics.top + - (scrollMetrics.height - viewMetrics.height) * xyRatio.y); - } - this.workspace_.translate( - this.workspace_.scrollX + absoluteMetrics.left, - this.workspace_.scrollY + absoluteMetrics.top); -}; - -/** - * Calculates the x coordinate for the flyout position. - * @return {number} X coordinate. - */ -VerticalFlyout.prototype.getX = function() { - if (!this.isVisible()) { - return 0; - } - const metricsManager = this.targetWorkspace.getMetricsManager(); - const absoluteMetrics = metricsManager.getAbsoluteMetrics(); - const viewMetrics = metricsManager.getViewMetrics(); - const toolboxMetrics = metricsManager.getToolboxMetrics(); - let x = 0; - - // If this flyout is not the trashcan flyout (e.g. toolbox or mutator). - if (this.targetWorkspace.toolboxPosition === this.toolboxPosition_) { - // If there is a category toolbox. - if (this.targetWorkspace.getToolbox()) { - if (this.toolboxPosition_ === toolbox.Position.LEFT) { - x = toolboxMetrics.width; - } else { - x = viewMetrics.width - this.width_; - } - // Simple (flyout-only) toolbox. - } else { - if (this.toolboxPosition_ === toolbox.Position.LEFT) { - x = 0; - } else { - // The simple flyout does not cover the workspace. - x = viewMetrics.width; - } - } - // Trashcan flyout is opposite the main flyout. - } else { - if (this.toolboxPosition_ === toolbox.Position.LEFT) { - x = 0; - } else { - // Because the anchor point of the flyout is on the left, but we want - // to align the right edge of the flyout with the right edge of the - // blocklyDiv, we calculate the full width of the div minus the width - // of the flyout. - x = viewMetrics.width + absoluteMetrics.left - this.width_; - } - } - - return x; -}; - -/** - * Calculates the y coordinate for the flyout position. - * @return {number} Y coordinate. - */ -VerticalFlyout.prototype.getY = function() { - // Y is always 0 since this is a vertical flyout. - return 0; -}; - -/** - * Move the flyout to the edge of the workspace. - */ -VerticalFlyout.prototype.position = function() { - if (!this.isVisible() || !this.targetWorkspace.isVisible()) { - return; - } - const metricsManager = this.targetWorkspace.getMetricsManager(); - const targetWorkspaceViewMetrics = metricsManager.getViewMetrics(); - - // Record the height for workspace metrics. - this.height_ = targetWorkspaceViewMetrics.height; - - const edgeWidth = this.width_ - this.CORNER_RADIUS; - const edgeHeight = targetWorkspaceViewMetrics.height - 2 * this.CORNER_RADIUS; - this.setBackgroundPath_(edgeWidth, edgeHeight); - - const x = this.getX(); - const y = this.getY(); - - this.positionAt_(this.width_, this.height_, x, y); -}; - -/** - * Create and set the path for the visible boundaries of the flyout. - * @param {number} width The width of the flyout, not including the - * rounded corners. - * @param {number} height The height of the flyout, not including - * rounded corners. - * @private - */ -VerticalFlyout.prototype.setBackgroundPath_ = function(width, height) { - const atRight = this.toolboxPosition_ === toolbox.Position.RIGHT; - const totalWidth = width + this.CORNER_RADIUS; - - // Decide whether to start on the left or right. - const path = ['M ' + (atRight ? totalWidth : 0) + ',0']; - // Top. - path.push('h', atRight ? -width : width); - // Rounded corner. - path.push( - 'a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, atRight ? 0 : 1, - atRight ? -this.CORNER_RADIUS : this.CORNER_RADIUS, this.CORNER_RADIUS); - // Side closest to workspace. - path.push('v', Math.max(0, height)); - // Rounded corner. - path.push( - 'a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, atRight ? 0 : 1, - atRight ? this.CORNER_RADIUS : -this.CORNER_RADIUS, this.CORNER_RADIUS); - // Bottom. - path.push('h', atRight ? width : -width); - path.push('z'); - this.svgBackground_.setAttribute('d', path.join(' ')); -}; - -/** - * Scroll the flyout to the top. - */ -VerticalFlyout.prototype.scrollToStart = function() { - this.workspace_.scrollbar.setY(0); -}; - -/** - * Scroll the flyout. - * @param {!Event} e Mouse wheel scroll event. - * @protected - */ -VerticalFlyout.prototype.wheel_ = function(e) { - const scrollDelta = browserEvents.getScrollDeltaPixels(e); - - if (scrollDelta.y) { - const metricsManager = this.workspace_.getMetricsManager(); - const scrollMetrics = metricsManager.getScrollMetrics(); - const viewMetrics = metricsManager.getViewMetrics(); - const pos = (viewMetrics.top - scrollMetrics.top) + scrollDelta.y; - - this.workspace_.scrollbar.setY(pos); - // When the flyout moves from a wheel event, hide WidgetDiv and DropDownDiv. - WidgetDiv.hide(); - DropDownDiv.hideWithoutAnimation(); - } - - // Don't scroll the page. - e.preventDefault(); - // Don't propagate mousewheel event (zooming). - e.stopPropagation(); -}; - -/** - * Lay out the blocks in the flyout. - * @param {!Array} contents The blocks and buttons to lay out. - * @param {!Array} gaps The visible gaps between blocks. - * @protected - */ -VerticalFlyout.prototype.layout_ = function(contents, gaps) { - this.workspace_.scale = this.targetWorkspace.scale; - const margin = this.MARGIN; - const cursorX = this.RTL ? margin : margin + this.tabWidth_; - let cursorY = margin; - - for (let i = 0, item; (item = contents[i]); i++) { - if (item.type === 'block') { - const block = item.block; - const allBlocks = block.getDescendants(false); - for (let j = 0, child; (child = allBlocks[j]); j++) { - // Mark blocks as being inside a flyout. This is used to detect and - // prevent the closure of the flyout if the user right-clicks on such a - // block. - child.isInFlyout = true; - } - block.render(); - const root = block.getSvgRoot(); - const blockHW = block.getHeightWidth(); - const moveX = block.outputConnection ? cursorX - this.tabWidth_ : cursorX; - block.moveBy(moveX, cursorY); - - const rect = this.createRect_( - block, this.RTL ? moveX - blockHW.width : moveX, cursorY, blockHW, i); - - this.addBlockListeners_(root, block, rect); - - cursorY += blockHW.height + gaps[i]; - } else if (item.type === 'button') { - this.initFlyoutButton_(item.button, cursorX, cursorY); - cursorY += item.button.height + gaps[i]; - } - } -}; - -/** - * Determine if a drag delta is toward the workspace, based on the position - * and orientation of the flyout. This is used in determineDragIntention_ to - * determine if a new block should be created or if the flyout should scroll. - * @param {!Coordinate} currentDragDeltaXY How far the pointer has - * moved from the position at mouse down, in pixel units. - * @return {boolean} True if the drag is toward the workspace. - * @package - */ -VerticalFlyout.prototype.isDragTowardWorkspace = function(currentDragDeltaXY) { - const dx = currentDragDeltaXY.x; - const dy = currentDragDeltaXY.y; - // Direction goes from -180 to 180, with 0 toward the right and 90 on top. - const dragDirection = Math.atan2(dy, dx) / Math.PI * 180; - - const range = this.dragAngleRange_; - // Check for left or right dragging. - if ((dragDirection < range && dragDirection > -range) || - (dragDirection < -180 + range || dragDirection > 180 - range)) { - return true; - } - return false; -}; - -/** - * Returns the bounding rectangle of the drag target area in pixel units - * relative to viewport. - * @return {?Rect} The component's bounding box. Null if drag - * target area should be ignored. - */ -VerticalFlyout.prototype.getClientRect = function() { - if (!this.svgGroup_ || this.autoClose || !this.isVisible()) { - // The bounding rectangle won't compute correctly if the flyout is closed - // and auto-close flyouts aren't valid drag targets (or delete areas). - return null; - } - - const flyoutRect = this.svgGroup_.getBoundingClientRect(); - // BIG_NUM is offscreen padding so that blocks dragged beyond the shown flyout - // area are still deleted. Must be larger than the largest screen size, - // but be smaller than half Number.MAX_SAFE_INTEGER (not available on IE). - const BIG_NUM = 1000000000; - const left = flyoutRect.left; - - if (this.toolboxPosition_ === toolbox.Position.LEFT) { - const width = flyoutRect.width; - return new Rect(-BIG_NUM, BIG_NUM, -BIG_NUM, left + width); - } else { // Right - return new Rect(-BIG_NUM, BIG_NUM, left, BIG_NUM); - } -}; - -/** - * Compute width of flyout. toolbox.Position mat under each block. - * For RTL: Lay out the blocks and buttons to be right-aligned. - * @protected - */ -VerticalFlyout.prototype.reflowInternal_ = function() { - this.workspace_.scale = this.getFlyoutScale(); - let flyoutWidth = 0; - const blocks = this.workspace_.getTopBlocks(false); - for (let i = 0, block; (block = blocks[i]); i++) { - let width = block.getHeightWidth().width; - if (block.outputConnection) { - width -= this.tabWidth_; - } - flyoutWidth = Math.max(flyoutWidth, width); - } - for (let i = 0, button; (button = this.buttons_[i]); i++) { - flyoutWidth = Math.max(flyoutWidth, button.width); - } - flyoutWidth += this.MARGIN * 1.5 + this.tabWidth_; - flyoutWidth *= this.workspace_.scale; - flyoutWidth += Scrollbar.scrollbarThickness; - - if (this.width_ !== flyoutWidth) { - for (let i = 0, block; (block = blocks[i]); i++) { - if (this.RTL) { - // With the flyoutWidth known, right-align the blocks. - const oldX = block.getRelativeToSurfaceXY().x; - let newX = flyoutWidth / this.workspace_.scale - this.MARGIN; - if (!block.outputConnection) { - newX -= this.tabWidth_; - } - block.moveBy(newX - oldX, 0); - } - if (block.flyoutRect_) { - this.moveRectToBlock_(block.flyoutRect_, block); - } - } - if (this.RTL) { - // With the flyoutWidth known, right-align the buttons. - for (let i = 0, button; (button = this.buttons_[i]); i++) { - const y = button.getPosition().y; - const x = flyoutWidth / this.workspace_.scale - button.width - - this.MARGIN - this.tabWidth_; - button.moveTo(x, y); - } - } - - if (this.targetWorkspace.toolboxPosition === this.toolboxPosition_ && - this.toolboxPosition_ === toolbox.Position.LEFT && - !this.targetWorkspace.getToolbox()) { - // This flyout is a simple toolbox. Reposition the workspace so that (0,0) - // is in the correct position relative to the new absolute edge (ie - // toolbox edge). - this.targetWorkspace.translate( - this.targetWorkspace.scrollX + flyoutWidth, - this.targetWorkspace.scrollY); - } - - // Record the width for workspace metrics and .position. - this.width_ = flyoutWidth; - this.position(); - this.targetWorkspace.recordDragTargets(); - } -}; - registry.register( registry.Type.FLYOUTS_VERTICAL_TOOLBOX, registry.DEFAULT, VerticalFlyout); diff --git a/core/generator.js b/core/generator.js index 6e8a7bfa7..42cbaf483 100644 --- a/core/generator.js +++ b/core/generator.js @@ -29,390 +29,508 @@ const {Workspace} = goog.requireType('Blockly.Workspace'); /** * Class for a code generator that translates the blocks into a language. - * @param {string} name Language name of this generator. - * @constructor + * @unrestricted * @alias Blockly.Generator */ -const Generator = function(name) { - this.name_ = name; - this.FUNCTION_NAME_PLACEHOLDER_REGEXP_ = - new RegExp(this.FUNCTION_NAME_PLACEHOLDER_, 'g'); -}; +class Generator { + /** + * @param {string} name Language name of this generator. + */ + constructor(name) { + this.name_ = name; -/** - * Arbitrary code to inject into locations that risk causing infinite loops. - * Any instances of '%1' will be replaced by the block ID that failed. - * E.g. ' checkTimeout(%1);\n' - * @type {?string} - */ -Generator.prototype.INFINITE_LOOP_TRAP = null; + /** + * This is used as a placeholder in functions defined using + * Generator.provideFunction_. It must not be legal code that could + * legitimately appear in a function definition (or comment), and it must + * not confuse the regular expression parser. + * @type {string} + * @protected + */ + this.FUNCTION_NAME_PLACEHOLDER_ = '{leCUI8hutHZI4480Dc}'; -/** - * Arbitrary code to inject before every statement. - * Any instances of '%1' will be replaced by the block ID of the statement. - * E.g. 'highlight(%1);\n' - * @type {?string} - */ -Generator.prototype.STATEMENT_PREFIX = null; + this.FUNCTION_NAME_PLACEHOLDER_REGEXP_ = + new RegExp(this.FUNCTION_NAME_PLACEHOLDER_, 'g'); -/** - * Arbitrary code to inject after every statement. - * Any instances of '%1' will be replaced by the block ID of the statement. - * E.g. 'highlight(%1);\n' - * @type {?string} - */ -Generator.prototype.STATEMENT_SUFFIX = null; + /** + * Arbitrary code to inject into locations that risk causing infinite loops. + * Any instances of '%1' will be replaced by the block ID that failed. + * E.g. ' checkTimeout(%1);\n' + * @type {?string} + */ + this.INFINITE_LOOP_TRAP = null; -/** - * The method of indenting. Defaults to two spaces, but language generators - * may override this to increase indent or change to tabs. - * @type {string} - */ -Generator.prototype.INDENT = ' '; + /** + * Arbitrary code to inject before every statement. + * Any instances of '%1' will be replaced by the block ID of the statement. + * E.g. 'highlight(%1);\n' + * @type {?string} + */ + this.STATEMENT_PREFIX = null; -/** - * Maximum length for a comment before wrapping. Does not account for - * indenting level. - * @type {number} - */ -Generator.prototype.COMMENT_WRAP = 60; + /** + * Arbitrary code to inject after every statement. + * Any instances of '%1' will be replaced by the block ID of the statement. + * E.g. 'highlight(%1);\n' + * @type {?string} + */ + this.STATEMENT_SUFFIX = null; -/** - * List of outer-inner pairings that do NOT require parentheses. - * @type {!Array>} - */ -Generator.prototype.ORDER_OVERRIDES = []; + /** + * The method of indenting. Defaults to two spaces, but language generators + * may override this to increase indent or change to tabs. + * @type {string} + */ + this.INDENT = ' '; -/** - * Whether the init method has been called. - * Generators that set this flag to false after creation and true in init - * will cause blockToCode to emit a warning if the generator has not been - * initialized. If this flag is untouched, it will have no effect. - * @type {?boolean} - */ -Generator.prototype.isInitialized = null; + /** + * Maximum length for a comment before wrapping. Does not account for + * indenting level. + * @type {number} + */ + this.COMMENT_WRAP = 60; -/** - * Generate code for all blocks in the workspace to the specified language. - * @param {!Workspace=} workspace Workspace to generate code from. - * @return {string} Generated code. - */ -Generator.prototype.workspaceToCode = function(workspace) { - if (!workspace) { - // Backwards compatibility from before there could be multiple workspaces. - console.warn('No workspace specified in workspaceToCode call. Guessing.'); - workspace = common.getMainWorkspace(); + /** + * List of outer-inner pairings that do NOT require parentheses. + * @type {!Array>} + */ + this.ORDER_OVERRIDES = []; + + /** + * Whether the init method has been called. + * Generators that set this flag to false after creation and true in init + * will cause blockToCode to emit a warning if the generator has not been + * initialized. If this flag is untouched, it will have no effect. + * @type {?boolean} + */ + this.isInitialized = null; + + /** + * Comma-separated list of reserved words. + * @type {string} + * @protected + */ + this.RESERVED_WORDS_ = ''; + + /** + * A dictionary of definitions to be printed before the code. + * @type {!Object|undefined} + * @protected + */ + this.definitions_ = undefined; + + /** + * A dictionary mapping desired function names in definitions_ to actual + * function names (to avoid collisions with user functions). + * @type {!Object|undefined} + * @protected + */ + this.functionNames_ = undefined; + + /** + * A database of variable and procedure names. + * @type {!Names|undefined} + * @protected + */ + this.nameDB_ = undefined; } - let code = []; - this.init(workspace); - const blocks = workspace.getTopBlocks(true); - for (let i = 0, block; (block = blocks[i]); i++) { - let line = this.blockToCode(block); - if (Array.isArray(line)) { - // Value blocks return tuples of code and operator order. - // Top-level blocks don't care about operator order. - line = line[0]; + + /** + * Generate code for all blocks in the workspace to the specified language. + * @param {!Workspace=} workspace Workspace to generate code from. + * @return {string} Generated code. + */ + workspaceToCode(workspace) { + if (!workspace) { + // Backwards compatibility from before there could be multiple workspaces. + console.warn( + 'No workspace specified in workspaceToCode call. Guessing.'); + workspace = common.getMainWorkspace(); } - if (line) { - 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); - if (this.STATEMENT_PREFIX && !block.suppressPrefixSuffix) { - line = this.injectId(this.STATEMENT_PREFIX, block) + line; + let code = []; + this.init(workspace); + const blocks = workspace.getTopBlocks(true); + for (let i = 0, block; (block = blocks[i]); i++) { + let line = this.blockToCode(block); + if (Array.isArray(line)) { + // Value blocks return tuples of code and operator order. + // Top-level blocks don't care about operator order. + line = line[0]; + } + if (line) { + 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); + if (this.STATEMENT_PREFIX && !block.suppressPrefixSuffix) { + line = this.injectId(this.STATEMENT_PREFIX, block) + line; + } + if (this.STATEMENT_SUFFIX && !block.suppressPrefixSuffix) { + line = line + this.injectId(this.STATEMENT_SUFFIX, block); + } } - if (this.STATEMENT_SUFFIX && !block.suppressPrefixSuffix) { - line = line + this.injectId(this.STATEMENT_SUFFIX, block); + code.push(line); + } + } + code = code.join('\n'); // Blank line between each section. + code = this.finish(code); + // Final scrubbing of whitespace. + code = code.replace(/^\s+\n/, ''); + code = code.replace(/\n\s+$/, '\n'); + code = code.replace(/[ \t]+\n/g, '\n'); + return code; + } + + // The following are some helpful functions which can be used by multiple + + // languages. + + /** + * Prepend a common prefix onto each line of code. + * Intended for indenting code or adding comment markers. + * @param {string} text The lines of code. + * @param {string} prefix The common prefix. + * @return {string} The prefixed lines of code. + */ + prefixLines(text, prefix) { + return prefix + text.replace(/(?!\n$)\n/g, '\n' + prefix); + } + + /** + * Recursively spider a tree of blocks, returning all their comments. + * @param {!Block} block The block from which to start spidering. + * @return {string} Concatenated list of comments. + */ + allNestedComments(block) { + const comments = []; + const blocks = block.getDescendants(true); + for (let i = 0; i < blocks.length; i++) { + const comment = blocks[i].getCommentText(); + if (comment) { + comments.push(comment); + } + } + // Append an empty string to create a trailing line break when joined. + if (comments.length) { + comments.push(''); + } + return comments.join('\n'); + } + + /** + * Generate code for the specified block (and attached blocks). + * The generator must be initialized before calling this function. + * @param {?Block} block The block to generate code for. + * @param {boolean=} opt_thisOnly True to generate code for only this + * statement. + * @return {string|!Array} For statement blocks, the generated code. + * For value blocks, an array containing the generated code and an + * operator order value. Returns '' if block is null. + */ + blockToCode(block, opt_thisOnly) { + if (this.isInitialized === false) { + console.warn( + 'Generator init was not called before blockToCode was called.'); + } + if (!block) { + return ''; + } + if (!block.isEnabled()) { + // Skip past this block if it is disabled. + return opt_thisOnly ? '' : this.blockToCode(block.getNextBlock()); + } + if (block.isInsertionMarker()) { + // Skip past insertion markers. + return opt_thisOnly ? '' : this.blockToCode(block.getChildren(false)[0]); + } + + const func = this[block.type]; + if (typeof func !== 'function') { + throw Error( + 'Language "' + this.name_ + '" does not know how to generate ' + + 'code for block type "' + block.type + '".'); + } + // First argument to func.call is the value of 'this' in the generator. + // Prior to 24 September 2013 'this' was the only way to access the block. + // The current preferred method of accessing the block is through the second + // argument to func.call, which becomes the first parameter to the + // generator. + let code = func.call(block, block); + if (Array.isArray(code)) { + // Value blocks return tuples of code and operator order. + if (!block.outputConnection) { + throw TypeError('Expecting string from statement block: ' + block.type); + } + return [this.scrub_(block, code[0], opt_thisOnly), code[1]]; + } else if (typeof code === 'string') { + if (this.STATEMENT_PREFIX && !block.suppressPrefixSuffix) { + code = this.injectId(this.STATEMENT_PREFIX, block) + code; + } + if (this.STATEMENT_SUFFIX && !block.suppressPrefixSuffix) { + code = code + this.injectId(this.STATEMENT_SUFFIX, block); + } + return this.scrub_(block, code, opt_thisOnly); + } else if (code === null) { + // Block has handled code generation itself. + return ''; + } + throw SyntaxError('Invalid code generated: ' + code); + } + + /** + * Generate code representing the specified value input. + * @param {!Block} block The block containing the input. + * @param {string} name The name of the input. + * @param {number} outerOrder The maximum binding strength (minimum order + * value) of any operators adjacent to "block". + * @return {string} Generated code or '' if no blocks are connected or the + * specified input does not exist. + */ + valueToCode(block, name, outerOrder) { + if (isNaN(outerOrder)) { + throw TypeError('Expecting valid order from block: ' + block.type); + } + const targetBlock = block.getInputTargetBlock(name); + if (!targetBlock) { + return ''; + } + const tuple = this.blockToCode(targetBlock); + if (tuple === '') { + // Disabled block. + return ''; + } + // Value blocks must return code and order of operations info. + // Statement blocks must only return code. + if (!Array.isArray(tuple)) { + throw TypeError('Expecting tuple from value block: ' + targetBlock.type); + } + let code = tuple[0]; + const innerOrder = tuple[1]; + if (isNaN(innerOrder)) { + throw TypeError( + 'Expecting valid order from value block: ' + targetBlock.type); + } + if (!code) { + return ''; + } + + // Add parentheses if needed. + let parensNeeded = false; + const outerOrderClass = Math.floor(outerOrder); + const innerOrderClass = Math.floor(innerOrder); + if (outerOrderClass <= innerOrderClass) { + if (outerOrderClass === innerOrderClass && + (outerOrderClass === 0 || outerOrderClass === 99)) { + // Don't generate parens around NONE-NONE and ATOMIC-ATOMIC pairs. + // 0 is the atomic order, 99 is the none order. No parentheses needed. + // In all known languages multiple such code blocks are not order + // sensitive. In fact in Python ('a' 'b') 'c' would fail. + } else { + // The operators outside this code are stronger than the operators + // inside this code. To prevent the code from being pulled apart, + // wrap the code in parentheses. + parensNeeded = true; + // Check for special exceptions. + for (let i = 0; i < this.ORDER_OVERRIDES.length; i++) { + if (this.ORDER_OVERRIDES[i][0] === outerOrder && + this.ORDER_OVERRIDES[i][1] === innerOrder) { + parensNeeded = false; + break; + } } } - code.push(line); } - } - code = code.join('\n'); // Blank line between each section. - code = this.finish(code); - // Final scrubbing of whitespace. - code = code.replace(/^\s+\n/, ''); - code = code.replace(/\n\s+$/, '\n'); - code = code.replace(/[ \t]+\n/g, '\n'); - return code; -}; - -// The following are some helpful functions which can be used by multiple -// languages. - -/** - * Prepend a common prefix onto each line of code. - * Intended for indenting code or adding comment markers. - * @param {string} text The lines of code. - * @param {string} prefix The common prefix. - * @return {string} The prefixed lines of code. - */ -Generator.prototype.prefixLines = function(text, prefix) { - return prefix + text.replace(/(?!\n$)\n/g, '\n' + prefix); -}; - -/** - * Recursively spider a tree of blocks, returning all their comments. - * @param {!Block} block The block from which to start spidering. - * @return {string} Concatenated list of comments. - */ -Generator.prototype.allNestedComments = function(block) { - const comments = []; - const blocks = block.getDescendants(true); - for (let i = 0; i < blocks.length; i++) { - const comment = blocks[i].getCommentText(); - if (comment) { - comments.push(comment); + if (parensNeeded) { + // Technically, this should be handled on a language-by-language basis. + // However all known (sane) languages use parentheses for grouping. + code = '(' + code + ')'; } - } - // Append an empty string to create a trailing line break when joined. - if (comments.length) { - comments.push(''); - } - return comments.join('\n'); -}; - -/** - * Generate code for the specified block (and attached blocks). - * The generator must be initialized before calling this function. - * @param {Block} block The block to generate code for. - * @param {boolean=} opt_thisOnly True to generate code for only this statement. - * @return {string|!Array} For statement blocks, the generated code. - * For value blocks, an array containing the generated code and an - * operator order value. Returns '' if block is null. - */ -Generator.prototype.blockToCode = function(block, opt_thisOnly) { - if (this.isInitialized === false) { - console.warn( - 'Generator init was not called before blockToCode was called.'); - } - if (!block) { - return ''; - } - if (!block.isEnabled()) { - // Skip past this block if it is disabled. - return opt_thisOnly ? '' : this.blockToCode(block.getNextBlock()); - } - if (block.isInsertionMarker()) { - // Skip past insertion markers. - return opt_thisOnly ? '' : this.blockToCode(block.getChildren(false)[0]); + return code; } - const func = this[block.type]; - if (typeof func !== 'function') { - throw Error( - 'Language "' + this.name_ + '" does not know how to generate ' + - 'code for block type "' + block.type + '".'); - } - // First argument to func.call is the value of 'this' in the generator. - // Prior to 24 September 2013 'this' was the only way to access the block. - // The current preferred method of accessing the block is through the second - // argument to func.call, which becomes the first parameter to the generator. - let code = func.call(block, block); - if (Array.isArray(code)) { - // Value blocks return tuples of code and operator order. - if (!block.outputConnection) { - throw TypeError('Expecting string from statement block: ' + block.type); + /** + * Generate a code string representing the blocks attached to the named + * statement input. Indent the code. + * This is mainly used in generators. When trying to generate code to evaluate + * look at using workspaceToCode or blockToCode. + * @param {!Block} block The block containing the input. + * @param {string} name The name of the input. + * @return {string} Generated code or '' if no blocks are connected. + */ + statementToCode(block, name) { + const targetBlock = block.getInputTargetBlock(name); + let code = this.blockToCode(targetBlock); + // Value blocks must return code and order of operations info. + // Statement blocks must only return code. + if (typeof code !== 'string') { + throw TypeError( + 'Expecting code from statement block: ' + + (targetBlock && targetBlock.type)); } - return [this.scrub_(block, code[0], opt_thisOnly), code[1]]; - } else if (typeof code === 'string') { - if (this.STATEMENT_PREFIX && !block.suppressPrefixSuffix) { - code = this.injectId(this.STATEMENT_PREFIX, block) + code; + if (code) { + code = this.prefixLines(/** @type {string} */ (code), this.INDENT); + } + return code; + } + + /** + * Add an infinite loop trap to the contents of a loop. + * Add statement suffix at the start of the loop block (right after the loop + * statement executes), and a statement prefix to the end of the loop block + * (right before the loop statement executes). + * @param {string} branch Code for loop contents. + * @param {!Block} block Enclosing block. + * @return {string} Loop contents, with infinite loop trap added. + */ + addLoopTrap(branch, block) { + if (this.INFINITE_LOOP_TRAP) { + branch = this.prefixLines( + this.injectId(this.INFINITE_LOOP_TRAP, block), this.INDENT) + + branch; } if (this.STATEMENT_SUFFIX && !block.suppressPrefixSuffix) { - code = code + this.injectId(this.STATEMENT_SUFFIX, block); + branch = this.prefixLines( + this.injectId(this.STATEMENT_SUFFIX, block), this.INDENT) + + branch; } - return this.scrub_(block, code, opt_thisOnly); - } else if (code === null) { - // Block has handled code generation itself. - return ''; - } - throw SyntaxError('Invalid code generated: ' + code); -}; - -/** - * Generate code representing the specified value input. - * @param {!Block} block The block containing the input. - * @param {string} name The name of the input. - * @param {number} outerOrder The maximum binding strength (minimum order value) - * of any operators adjacent to "block". - * @return {string} Generated code or '' if no blocks are connected or the - * specified input does not exist. - */ -Generator.prototype.valueToCode = function(block, name, outerOrder) { - if (isNaN(outerOrder)) { - throw TypeError('Expecting valid order from block: ' + block.type); - } - const targetBlock = block.getInputTargetBlock(name); - if (!targetBlock) { - return ''; - } - const tuple = this.blockToCode(targetBlock); - if (tuple === '') { - // Disabled block. - return ''; - } - // Value blocks must return code and order of operations info. - // Statement blocks must only return code. - if (!Array.isArray(tuple)) { - throw TypeError('Expecting tuple from value block: ' + targetBlock.type); - } - let code = tuple[0]; - const innerOrder = tuple[1]; - if (isNaN(innerOrder)) { - throw TypeError( - 'Expecting valid order from value block: ' + targetBlock.type); - } - if (!code) { - return ''; + if (this.STATEMENT_PREFIX && !block.suppressPrefixSuffix) { + branch = branch + + this.prefixLines( + this.injectId(this.STATEMENT_PREFIX, block), this.INDENT); + } + return branch; } - // Add parentheses if needed. - let parensNeeded = false; - const outerOrderClass = Math.floor(outerOrder); - const innerOrderClass = Math.floor(innerOrder); - if (outerOrderClass <= innerOrderClass) { - if (outerOrderClass === innerOrderClass && - (outerOrderClass === 0 || outerOrderClass === 99)) { - // Don't generate parens around NONE-NONE and ATOMIC-ATOMIC pairs. - // 0 is the atomic order, 99 is the none order. No parentheses needed. - // In all known languages multiple such code blocks are not order - // sensitive. In fact in Python ('a' 'b') 'c' would fail. - } else { - // The operators outside this code are stronger than the operators - // inside this code. To prevent the code from being pulled apart, - // wrap the code in parentheses. - parensNeeded = true; - // Check for special exceptions. - for (let i = 0; i < this.ORDER_OVERRIDES.length; i++) { - if (this.ORDER_OVERRIDES[i][0] === outerOrder && - this.ORDER_OVERRIDES[i][1] === innerOrder) { - parensNeeded = false; - break; - } + /** + * Inject a block ID into a message to replace '%1'. + * Used for STATEMENT_PREFIX, STATEMENT_SUFFIX, and INFINITE_LOOP_TRAP. + * @param {string} msg Code snippet with '%1'. + * @param {!Block} block Block which has an ID. + * @return {string} Code snippet with ID. + */ + injectId(msg, block) { + const id = block.id.replace(/\$/g, '$$$$'); // Issue 251. + return msg.replace(/%1/g, '\'' + id + '\''); + } + + /** + * Add one or more words to the list of reserved words for this language. + * @param {string} words Comma-separated list of words to add to the list. + * No spaces. Duplicates are ok. + */ + addReservedWords(words) { + this.RESERVED_WORDS_ += words + ','; + } + + /** + * Define a developer-defined function (not a user-defined procedure) to be + * included in the generated code. Used for creating private helper + * functions. The first time this is called with a given desiredName, the code + * is saved and an actual name is generated. Subsequent calls with the same + * desiredName have no effect but have the same return value. + * + * It is up to the caller to make sure the same desiredName is not + * used for different helper functions (e.g. use "colourRandom" and + * "listRandom", not "random"). There is no danger of colliding with reserved + * words, or user-defined variable or procedure names. + * + * The code gets output when Generator.finish() is called. + * + * @param {string} desiredName The desired name of the function + * (e.g. mathIsPrime). + * @param {!Array|string} code A list of statements or one multi-line + * code string. Use ' ' for indents (they will be replaced). + * @return {string} The actual name of the new function. This may differ + * from desiredName if the former has already been taken by the user. + * @protected + */ + provideFunction_(desiredName, code) { + if (!this.definitions_[desiredName]) { + const functionName = + this.nameDB_.getDistinctName(desiredName, NameType.PROCEDURE); + this.functionNames_[desiredName] = functionName; + if (Array.isArray(code)) { + code = code.join('\n'); } + let codeText = code.trim().replace( + this.FUNCTION_NAME_PLACEHOLDER_REGEXP_, functionName); + // Change all ' ' indents into the desired indent. + // To avoid an infinite loop of replacements, change all indents to '\0' + // character first, then replace them all with the indent. + // We are assuming that no provided functions contain a literal null char. + let oldCodeText; + while (oldCodeText !== codeText) { + oldCodeText = codeText; + codeText = codeText.replace(/^(( {2})*) {2}/gm, '$1\0'); + } + codeText = codeText.replace(/\0/g, this.INDENT); + this.definitions_[desiredName] = codeText; } + return this.functionNames_[desiredName]; } - if (parensNeeded) { - // Technically, this should be handled on a language-by-language basis. - // However all known (sane) languages use parentheses for grouping. - code = '(' + code + ')'; + + /** + * Hook for code to run before code generation starts. + * Subclasses may override this, e.g. to initialise the database of variable + * names. + * @param {!Workspace} _workspace Workspace to generate code from. + */ + init(_workspace) { + // Optionally override + // Create a dictionary of definitions to be printed before the code. + this.definitions_ = Object.create(null); + // Create a dictionary mapping desired developer-defined function names in + // definitions_ to actual function names (to avoid collisions with + // user-defined procedures). + this.functionNames_ = Object.create(null); } - return code; -}; -/** - * Generate a code string representing the blocks attached to the named - * statement input. Indent the code. - * This is mainly used in generators. When trying to generate code to evaluate - * look at using workspaceToCode or blockToCode. - * @param {!Block} block The block containing the input. - * @param {string} name The name of the input. - * @return {string} Generated code or '' if no blocks are connected. - */ -Generator.prototype.statementToCode = function(block, name) { - const targetBlock = block.getInputTargetBlock(name); - let code = this.blockToCode(targetBlock); - // Value blocks must return code and order of operations info. - // Statement blocks must only return code. - if (typeof code !== 'string') { - throw TypeError( - 'Expecting code from statement block: ' + - (targetBlock && targetBlock.type)); + /** + * Common tasks for generating code from blocks. This is called from + * blockToCode and is called on every block, not just top level blocks. + * Subclasses may override this, e.g. to generate code for statements + * following the block, or to handle comments for the specified block and any + * connected value blocks. + * @param {!Block} _block The current block. + * @param {string} code The code created for this block. + * @param {boolean=} _opt_thisOnly True to generate code for only this + * statement. + * @return {string} Code with comments and subsequent blocks added. + * @protected + */ + scrub_(_block, code, _opt_thisOnly) { + // Optionally override + return code; } - if (code) { - code = this.prefixLines(/** @type {string} */ (code), this.INDENT); + + /** + * Hook for code to run at end of code generation. + * Subclasses may override this, e.g. to prepend the generated code with + * import statements or variable definitions. + * @param {string} code Generated code. + * @return {string} Completed code. + */ + finish(code) { + // Optionally override + // Clean up temporary data. + delete this.definitions_; + delete this.functionNames_; + return code; } - return code; -}; -/** - * Add an infinite loop trap to the contents of a loop. - * Add statement suffix at the start of the loop block (right after the loop - * statement executes), and a statement prefix to the end of the loop block - * (right before the loop statement executes). - * @param {string} branch Code for loop contents. - * @param {!Block} block Enclosing block. - * @return {string} Loop contents, with infinite loop trap added. - */ -Generator.prototype.addLoopTrap = function(branch, block) { - if (this.INFINITE_LOOP_TRAP) { - branch = this.prefixLines( - this.injectId(this.INFINITE_LOOP_TRAP, block), this.INDENT) + - branch; + /** + * Naked values are top-level blocks with outputs that aren't plugged into + * anything. + * Subclasses may override this, e.g. if their language does not allow + * naked values. + * @param {string} line Line of generated code. + * @return {string} Legal line of code. + */ + scrubNakedValue(line) { + // Optionally override + return line; } - if (this.STATEMENT_SUFFIX && !block.suppressPrefixSuffix) { - branch = this.prefixLines( - this.injectId(this.STATEMENT_SUFFIX, block), this.INDENT) + - branch; - } - if (this.STATEMENT_PREFIX && !block.suppressPrefixSuffix) { - branch = branch + - this.prefixLines( - this.injectId(this.STATEMENT_PREFIX, block), this.INDENT); - } - return branch; -}; - -/** - * Inject a block ID into a message to replace '%1'. - * Used for STATEMENT_PREFIX, STATEMENT_SUFFIX, and INFINITE_LOOP_TRAP. - * @param {string} msg Code snippet with '%1'. - * @param {!Block} block Block which has an ID. - * @return {string} Code snippet with ID. - */ -Generator.prototype.injectId = function(msg, block) { - const id = block.id.replace(/\$/g, '$$$$'); // Issue 251. - return msg.replace(/%1/g, '\'' + id + '\''); -}; - -/** - * Comma-separated list of reserved words. - * @type {string} - * @protected - */ -Generator.prototype.RESERVED_WORDS_ = ''; - -/** - * Add one or more words to the list of reserved words for this language. - * @param {string} words Comma-separated list of words to add to the list. - * No spaces. Duplicates are ok. - */ -Generator.prototype.addReservedWords = function(words) { - this.RESERVED_WORDS_ += words + ','; -}; - -/** - * This is used as a placeholder in functions defined using - * Generator.provideFunction_. It must not be legal code that could - * legitimately appear in a function definition (or comment), and it must - * not confuse the regular expression parser. - * @type {string} - * @protected - */ -Generator.prototype.FUNCTION_NAME_PLACEHOLDER_ = '{leCUI8hutHZI4480Dc}'; - -/** - * A dictionary of definitions to be printed before the code. - * @type {!Object|undefined} - * @protected - */ -Generator.prototype.definitions_; - -/** - * A dictionary mapping desired function names in definitions_ to actual - * function names (to avoid collisions with user functions). - * @type {!Object|undefined} - * @protected - */ -Generator.prototype.functionNames_; - -/** - * A database of variable and procedure names. - * @type {!Names|undefined} - * @protected - */ -Generator.prototype.nameDB_; +} Object.defineProperties(Generator.prototype, { /** @@ -443,109 +561,4 @@ Object.defineProperties(Generator.prototype, { }, }); -/** - * Define a developer-defined function (not a user-defined procedure) to be - * included in the generated code. Used for creating private helper functions. - * The first time this is called with a given desiredName, the code is - * saved and an actual name is generated. Subsequent calls with the - * same desiredName have no effect but have the same return value. - * - * It is up to the caller to make sure the same desiredName is not - * used for different helper functions (e.g. use "colourRandom" and - * "listRandom", not "random"). There is no danger of colliding with reserved - * words, or user-defined variable or procedure names. - * - * The code gets output when Generator.finish() is called. - * - * @param {string} desiredName The desired name of the function - * (e.g. mathIsPrime). - * @param {!Array} code A list of statements. Use ' ' for indents. - * @return {string} The actual name of the new function. This may differ - * from desiredName if the former has already been taken by the user. - * @protected - */ -Generator.prototype.provideFunction_ = function(desiredName, code) { - if (!this.definitions_[desiredName]) { - const functionName = - this.nameDB_.getDistinctName(desiredName, NameType.PROCEDURE); - this.functionNames_[desiredName] = functionName; - let codeText = code.join('\n').replace( - this.FUNCTION_NAME_PLACEHOLDER_REGEXP_, functionName); - // Change all ' ' indents into the desired indent. - // To avoid an infinite loop of replacements, change all indents to '\0' - // character first, then replace them all with the indent. - // We are assuming that no provided functions contain a literal null char. - let oldCodeText; - while (oldCodeText !== codeText) { - oldCodeText = codeText; - codeText = codeText.replace(/^(( {2})*) {2}/gm, '$1\0'); - } - codeText = codeText.replace(/\0/g, this.INDENT); - this.definitions_[desiredName] = codeText; - } - return this.functionNames_[desiredName]; -}; - -/** - * Hook for code to run before code generation starts. - * Subclasses may override this, e.g. to initialise the database of variable - * names. - * @param {!Workspace} _workspace Workspace to generate code from. - */ -Generator.prototype.init = function(_workspace) { - // Optionally override - // Create a dictionary of definitions to be printed before the code. - this.definitions_ = Object.create(null); - // Create a dictionary mapping desired developer-defined function names in - // definitions_ to actual function names (to avoid collisions with - // user-defined procedures). - this.functionNames_ = Object.create(null); -}; - -/** - * Common tasks for generating code from blocks. This is called from - * blockToCode and is called on every block, not just top level blocks. - * Subclasses may override this, e.g. to generate code for statements following - * the block, or to handle comments for the specified block and any connected - * value blocks. - * @param {!Block} _block The current block. - * @param {string} code The code created for this block. - * @param {boolean=} _opt_thisOnly True to generate code for only this - * statement. - * @return {string} Code with comments and subsequent blocks added. - * @protected - */ -Generator.prototype.scrub_ = function(_block, code, _opt_thisOnly) { - // Optionally override - return code; -}; - -/** - * Hook for code to run at end of code generation. - * Subclasses may override this, e.g. to prepend the generated code with import - * statements or variable definitions. - * @param {string} code Generated code. - * @return {string} Completed code. - */ -Generator.prototype.finish = function(code) { - // Optionally override - // Clean up temporary data. - delete this.definitions_; - delete this.functionNames_; - return code; -}; - -/** - * Naked values are top-level blocks with outputs that aren't plugged into - * anything. - * Subclasses may override this, e.g. if their language does not allow - * naked values. - * @param {string} line Line of generated code. - * @return {string} Legal line of code. - */ -Generator.prototype.scrubNakedValue = function(line) { - // Optionally override - return line; -}; - exports.Generator = Generator; diff --git a/core/gesture.js b/core/gesture.js index faa178076..0fe034a06 100644 --- a/core/gesture.js +++ b/core/gesture.js @@ -28,6 +28,7 @@ const registry = goog.require('Blockly.registry'); /* eslint-disable-next-line no-unused-vars */ const {BlockSvg} = goog.requireType('Blockly.BlockSvg'); const {BubbleDragger} = goog.require('Blockly.BubbleDragger'); +const {config} = goog.require('Blockly.config'); const {Coordinate} = goog.require('Blockly.utils.Coordinate'); /* eslint-disable-next-line no-unused-vars */ const {Field} = goog.requireType('Blockly.Field'); @@ -55,956 +56,971 @@ goog.require('Blockly.Events.Click'); /** * Class for one gesture. - * @param {!Event} e The event that kicked off this gesture. - * @param {!WorkspaceSvg} creatorWorkspace The workspace that created - * this gesture and has a reference to it. - * @constructor * @alias Blockly.Gesture */ -const Gesture = function(e, creatorWorkspace) { +class Gesture { /** - * The position of the mouse when the gesture started. Units are CSS pixels, - * with (0, 0) at the top left of the browser window (mouseEvent clientX/Y). - * @type {Coordinate} - * @private + * @param {!Event} e The event that kicked off this gesture. + * @param {!WorkspaceSvg} creatorWorkspace The workspace that created + * this gesture and has a reference to it. */ - this.mouseDownXY_ = null; + constructor(e, creatorWorkspace) { + /** + * The position of the mouse when the gesture started. Units are CSS + * pixels, with (0, 0) at the top left of the browser window (mouseEvent + * clientX/Y). + * @type {Coordinate} + * @private + */ + this.mouseDownXY_ = null; - /** - * How far the mouse has moved during this drag, in pixel units. - * (0, 0) is at this.mouseDownXY_. - * @type {!Coordinate} - * @private - */ - this.currentDragDeltaXY_ = new Coordinate(0, 0); + /** + * How far the mouse has moved during this drag, in pixel units. + * (0, 0) is at this.mouseDownXY_. + * @type {!Coordinate} + * @private + */ + this.currentDragDeltaXY_ = new Coordinate(0, 0); - /** - * The bubble that the gesture started on, or null if it did not start on a - * bubble. - * @type {IBubble} - * @private - */ - this.startBubble_ = null; + /** + * The bubble that the gesture started on, or null if it did not start on a + * bubble. + * @type {IBubble} + * @private + */ + this.startBubble_ = null; - /** - * The field that the gesture started on, or null if it did not start on a - * field. - * @type {Field} - * @private - */ - this.startField_ = null; + /** + * The field that the gesture started on, or null if it did not start on a + * field. + * @type {Field} + * @private + */ + this.startField_ = null; - /** - * The block that the gesture started on, or null if it did not start on a - * block. - * @type {BlockSvg} - * @private - */ - this.startBlock_ = null; - - /** - * The block that this gesture targets. If the gesture started on a - * shadow block, this is the first non-shadow parent of the block. If the - * gesture started in the flyout, this is the root block of the block group - * that was clicked or dragged. - * @type {BlockSvg} - * @private - */ - this.targetBlock_ = null; - - /** - * The workspace that the gesture started on. There may be multiple - * workspaces on a page; this is more accurate than using - * Blockly.common.getMainWorkspace(). - * @type {WorkspaceSvg} - * @protected - */ - this.startWorkspace_ = null; - - /** - * The workspace that created this gesture. This workspace keeps a reference - * to the gesture, which will need to be cleared at deletion. - * This may be different from the start workspace. For instance, a flyout is - * a workspace, but its parent workspace manages gestures for it. - * @type {!WorkspaceSvg} - * @private - */ - this.creatorWorkspace_ = creatorWorkspace; - - /** - * Whether the pointer has at any point moved out of the drag radius. - * A gesture that exceeds the drag radius is a drag even if it ends exactly - * at its start point. - * @type {boolean} - * @private - */ - this.hasExceededDragRadius_ = false; - - /** - * Whether the workspace is currently being dragged. - * @type {boolean} - * @private - */ - this.isDraggingWorkspace_ = false; - - /** - * Whether the block is currently being dragged. - * @type {boolean} - * @private - */ - this.isDraggingBlock_ = false; - - /** - * Whether the bubble is currently being dragged. - * @type {boolean} - * @private - */ - this.isDraggingBubble_ = false; - - /** - * The event that most recently updated this gesture. - * @type {!Event} - * @private - */ - this.mostRecentEvent_ = e; - - /** - * A handle to use to unbind a mouse move listener at the end of a drag. - * Opaque data returned from Blockly.bindEventWithChecks_. - * @type {?browserEvents.Data} - * @protected - */ - this.onMoveWrapper_ = null; - - /** - * A handle to use to unbind a mouse up listener at the end of a drag. - * Opaque data returned from Blockly.bindEventWithChecks_. - * @type {?browserEvents.Data} - * @protected - */ - this.onUpWrapper_ = null; - - /** - * The object tracking a bubble drag, or null if none is in progress. - * @type {BubbleDragger} - * @private - */ - this.bubbleDragger_ = null; - - /** - * The object tracking a block drag, or null if none is in progress. - * @type {?IBlockDragger} - * @private - */ - this.blockDragger_ = null; - - /** - * The object tracking a workspace or flyout workspace drag, or null if none - * is in progress. - * @type {WorkspaceDragger} - * @private - */ - this.workspaceDragger_ = null; - - /** - * The flyout a gesture started in, if any. - * @type {IFlyout} - * @private - */ - this.flyout_ = null; - - /** - * Boolean for sanity-checking that some code is only called once. - * @type {boolean} - * @private - */ - this.calledUpdateIsDragging_ = false; - - /** - * Boolean for sanity-checking that some code is only called once. - * @type {boolean} - * @private - */ - this.hasStarted_ = false; - - /** - * Boolean used internally to break a cycle in disposal. - * @type {boolean} - * @protected - */ - this.isEnding_ = false; - - /** - * Boolean used to indicate whether or not to heal the stack after - * disconnecting a block. - * @type {boolean} - * @private - */ - this.healStack_ = !internalConstants.DRAG_STACK; -}; - -/** - * Sever all links from this object. - * @package - */ -Gesture.prototype.dispose = function() { - Touch.clearTouchIdentifier(); - Tooltip.unblock(); - // Clear the owner's reference to this gesture. - this.creatorWorkspace_.clearGesture(); - - if (this.onMoveWrapper_) { - browserEvents.unbind(this.onMoveWrapper_); - } - if (this.onUpWrapper_) { - browserEvents.unbind(this.onUpWrapper_); - } - - if (this.blockDragger_) { - this.blockDragger_.dispose(); - } - if (this.workspaceDragger_) { - this.workspaceDragger_.dispose(); - } - if (this.bubbleDragger_) { - this.bubbleDragger_.dispose(); - } -}; - -/** - * Update internal state based on an event. - * @param {!Event} e The most recent mouse or touch event. - * @private - */ -Gesture.prototype.updateFromEvent_ = function(e) { - const currentXY = new Coordinate(e.clientX, e.clientY); - const changed = this.updateDragDelta_(currentXY); - // Exceeded the drag radius for the first time. - if (changed) { - this.updateIsDragging_(); - Touch.longStop(); - } - this.mostRecentEvent_ = e; -}; - -/** - * DO MATH to set currentDragDeltaXY_ based on the most recent mouse position. - * @param {!Coordinate} currentXY The most recent mouse/pointer - * position, in pixel units, with (0, 0) at the window's top left corner. - * @return {boolean} True if the drag just exceeded the drag radius for the - * first time. - * @private - */ -Gesture.prototype.updateDragDelta_ = function(currentXY) { - this.currentDragDeltaXY_ = Coordinate.difference( - currentXY, - /** @type {!Coordinate} */ (this.mouseDownXY_)); - - if (!this.hasExceededDragRadius_) { - const currentDragDelta = Coordinate.magnitude(this.currentDragDeltaXY_); - - // The flyout has a different drag radius from the rest of Blockly. - const limitRadius = this.flyout_ ? internalConstants.FLYOUT_DRAG_RADIUS : - internalConstants.DRAG_RADIUS; - - this.hasExceededDragRadius_ = currentDragDelta > limitRadius; - return this.hasExceededDragRadius_; - } - return false; -}; - -/** - * Update this gesture to record whether a block is being dragged from the - * flyout. - * This function should be called on a mouse/touch move event the first time the - * drag radius is exceeded. It should be called no more than once per gesture. - * If a block should be dragged from the flyout this function creates the new - * block on the main workspace and updates targetBlock_ and startWorkspace_. - * @return {boolean} True if a block is being dragged from the flyout. - * @private - */ -Gesture.prototype.updateIsDraggingFromFlyout_ = function() { - if (!this.targetBlock_) { - return false; - } - if (!this.flyout_.isBlockCreatable_(this.targetBlock_)) { - return false; - } - if (!this.flyout_.isScrollable() || - this.flyout_.isDragTowardWorkspace(this.currentDragDeltaXY_)) { - this.startWorkspace_ = this.flyout_.targetWorkspace; - this.startWorkspace_.updateScreenCalculationsIfScrolled(); - // Start the event group now, so that the same event group is used for block - // creation and block dragging. - if (!eventUtils.getGroup()) { - eventUtils.setGroup(true); - } - // The start block is no longer relevant, because this is a drag. + /** + * The block that the gesture started on, or null if it did not start on a + * block. + * @type {BlockSvg} + * @private + */ this.startBlock_ = null; - this.targetBlock_ = this.flyout_.createBlock(this.targetBlock_); - this.targetBlock_.select(); - return true; - } - 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 - */ -Gesture.prototype.updateIsDraggingBubble_ = function() { - if (!this.startBubble_) { + /** + * The block that this gesture targets. If the gesture started on a + * shadow block, this is the first non-shadow parent of the block. If the + * gesture started in the flyout, this is the root block of the block group + * that was clicked or dragged. + * @type {BlockSvg} + * @private + */ + this.targetBlock_ = null; + + /** + * The workspace that the gesture started on. There may be multiple + * workspaces on a page; this is more accurate than using + * Blockly.common.getMainWorkspace(). + * @type {WorkspaceSvg} + * @protected + */ + this.startWorkspace_ = null; + + /** + * The workspace that created this gesture. This workspace keeps a + * reference to the gesture, which will need to be cleared at deletion. This + * may be different from the start workspace. For instance, a flyout is a + * workspace, but its parent workspace manages gestures for it. + * @type {!WorkspaceSvg} + * @private + */ + this.creatorWorkspace_ = creatorWorkspace; + + /** + * Whether the pointer has at any point moved out of the drag radius. + * A gesture that exceeds the drag radius is a drag even if it ends exactly + * at its start point. + * @type {boolean} + * @private + */ + this.hasExceededDragRadius_ = false; + + /** + * Whether the workspace is currently being dragged. + * @type {boolean} + * @private + */ + this.isDraggingWorkspace_ = false; + + /** + * Whether the block is currently being dragged. + * @type {boolean} + * @private + */ + this.isDraggingBlock_ = false; + + /** + * Whether the bubble is currently being dragged. + * @type {boolean} + * @private + */ + this.isDraggingBubble_ = false; + + /** + * The event that most recently updated this gesture. + * @type {!Event} + * @private + */ + this.mostRecentEvent_ = e; + + /** + * A handle to use to unbind a mouse move listener at the end of a drag. + * Opaque data returned from Blockly.bindEventWithChecks_. + * @type {?browserEvents.Data} + * @protected + */ + this.onMoveWrapper_ = null; + + /** + * A handle to use to unbind a mouse up listener at the end of a drag. + * Opaque data returned from Blockly.bindEventWithChecks_. + * @type {?browserEvents.Data} + * @protected + */ + this.onUpWrapper_ = null; + + /** + * The object tracking a bubble drag, or null if none is in progress. + * @type {BubbleDragger} + * @private + */ + this.bubbleDragger_ = null; + + /** + * The object tracking a block drag, or null if none is in progress. + * @type {?IBlockDragger} + * @private + */ + this.blockDragger_ = null; + + /** + * The object tracking a workspace or flyout workspace drag, or null if none + * is in progress. + * @type {WorkspaceDragger} + * @private + */ + this.workspaceDragger_ = null; + + /** + * The flyout a gesture started in, if any. + * @type {IFlyout} + * @private + */ + this.flyout_ = null; + + /** + * Boolean for sanity-checking that some code is only called once. + * @type {boolean} + * @private + */ + this.calledUpdateIsDragging_ = false; + + /** + * Boolean for sanity-checking that some code is only called once. + * @type {boolean} + * @private + */ + this.hasStarted_ = false; + + /** + * Boolean used internally to break a cycle in disposal. + * @type {boolean} + * @protected + */ + this.isEnding_ = false; + + /** + * Boolean used to indicate whether or not to heal the stack after + * disconnecting a block. + * @type {boolean} + * @private + */ + this.healStack_ = !internalConstants.DRAG_STACK; + } + + /** + * Sever all links from this object. + * @package + */ + dispose() { + Touch.clearTouchIdentifier(); + Tooltip.unblock(); + // Clear the owner's reference to this gesture. + this.creatorWorkspace_.clearGesture(); + + if (this.onMoveWrapper_) { + browserEvents.unbind(this.onMoveWrapper_); + } + if (this.onUpWrapper_) { + browserEvents.unbind(this.onUpWrapper_); + } + + if (this.blockDragger_) { + this.blockDragger_.dispose(); + } + if (this.workspaceDragger_) { + this.workspaceDragger_.dispose(); + } + if (this.bubbleDragger_) { + this.bubbleDragger_.dispose(); + } + } + + /** + * Update internal state based on an event. + * @param {!Event} e The most recent mouse or touch event. + * @private + */ + updateFromEvent_(e) { + const currentXY = new Coordinate(e.clientX, e.clientY); + const changed = this.updateDragDelta_(currentXY); + // Exceeded the drag radius for the first time. + if (changed) { + this.updateIsDragging_(); + Touch.longStop(); + } + this.mostRecentEvent_ = e; + } + + /** + * DO MATH to set currentDragDeltaXY_ based on the most recent mouse position. + * @param {!Coordinate} currentXY The most recent mouse/pointer + * position, in pixel units, with (0, 0) at the window's top left corner. + * @return {boolean} True if the drag just exceeded the drag radius for the + * first time. + * @private + */ + updateDragDelta_(currentXY) { + this.currentDragDeltaXY_ = Coordinate.difference( + currentXY, + /** @type {!Coordinate} */ (this.mouseDownXY_)); + + if (!this.hasExceededDragRadius_) { + const currentDragDelta = Coordinate.magnitude(this.currentDragDeltaXY_); + + // The flyout has a different drag radius from the rest of Blockly. + const limitRadius = + this.flyout_ ? config.flyoutDragRadius : config.dragRadius; + + this.hasExceededDragRadius_ = currentDragDelta > limitRadius; + return this.hasExceededDragRadius_; + } 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 - * drag radius is exceeded. It should be called no more than once per gesture. - * If a block should be dragged, either from the flyout or in the workspace, - * this function creates the necessary BlockDragger and starts the drag. - * @return {boolean} True if a block is being dragged. - * @private - */ -Gesture.prototype.updateIsDraggingBlock_ = function() { - if (!this.targetBlock_) { - return false; - } - - if (this.flyout_) { - this.isDraggingBlock_ = this.updateIsDraggingFromFlyout_(); - } else if (this.targetBlock_.isMovable()) { - this.isDraggingBlock_ = true; - } - - if (this.isDraggingBlock_) { - this.startDraggingBlock_(); - return true; - } - return false; -}; - -/** - * Update this gesture to record whether a workspace is being dragged. - * This function should be called on a mouse/touch move event the first time the - * drag radius is exceeded. It should be called no more than once per gesture. - * If a workspace is being dragged this function creates the necessary - * WorkspaceDragger and starts the drag. - * @private - */ -Gesture.prototype.updateIsDraggingWorkspace_ = function() { - const wsMovable = this.flyout_ ? - this.flyout_.isScrollable() : - this.startWorkspace_ && this.startWorkspace_.isDraggable(); - - if (!wsMovable) { - return; - } - - this.workspaceDragger_ = new WorkspaceDragger( - /** @type {!WorkspaceSvg} */ (this.startWorkspace_)); - - this.isDraggingWorkspace_ = true; - this.workspaceDragger_.startDrag(); -}; - -/** - * Update this gesture to record whether anything is being dragged. - * This function should be called on a mouse/touch move event the first time the - * drag radius is exceeded. It should be called no more than once per gesture. - * @private - */ -Gesture.prototype.updateIsDragging_ = function() { - // Sanity check. - if (this.calledUpdateIsDragging_) { - throw Error('updateIsDragging_ should only be called once per gesture.'); - } - this.calledUpdateIsDragging_ = true; - - // 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; - } - // Then check if it's a workspace drag. - this.updateIsDraggingWorkspace_(); -}; - -/** - * Create a block dragger and start dragging the selected block. - * @private - */ -Gesture.prototype.startDraggingBlock_ = function() { - const BlockDraggerClass = registry.getClassFromOptions( - registry.Type.BLOCK_DRAGGER, this.creatorWorkspace_.options, true); - - this.blockDragger_ = new BlockDraggerClass( - /** @type {!BlockSvg} */ (this.targetBlock_), - /** @type {!WorkspaceSvg} */ (this.startWorkspace_)); - this.blockDragger_.startDrag(this.currentDragDeltaXY_, this.healStack_); - this.blockDragger_.drag(this.mostRecentEvent_, this.currentDragDeltaXY_); -}; - -/** - * Create a bubble dragger and start dragging the selected bubble. - * @private - */ -// TODO (fenichel): Possibly combine this and startDraggingBlock_. -Gesture.prototype.startDraggingBubble_ = function() { - this.bubbleDragger_ = new BubbleDragger( - /** @type {!IBubble} */ (this.startBubble_), - /** @type {!WorkspaceSvg} */ (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. - * @param {!Event} e A mouse down or touch start event. - * @package - */ -Gesture.prototype.doStart = function(e) { - if (browserEvents.isTargetInput(e)) { - this.cancel(); - return; - } - this.hasStarted_ = true; - - blockAnimations.disconnectUiStop(); - this.startWorkspace_.updateScreenCalculationsIfScrolled(); - if (this.startWorkspace_.isMutator) { - // Mutator's coordinate system could be out of date because the bubble was - // dragged, the block was moved, the parent workspace zoomed, etc. - this.startWorkspace_.resize(); - } - - // Hide chaff also hides the flyout, so don't do it if the click is in a - // flyout. - this.startWorkspace_.hideChaff(!!this.flyout_); - - this.startWorkspace_.markFocused(); - this.mostRecentEvent_ = e; - - Tooltip.block(); - - if (this.targetBlock_) { - this.targetBlock_.select(); - } - - if (browserEvents.isRightButton(e)) { - this.handleRightClick(e); - return; - } - - if ((e.type.toLowerCase() === 'touchstart' || - e.type.toLowerCase() === 'pointerdown') && - e.pointerType !== 'mouse') { - Touch.longStart(e, this); - } - - this.mouseDownXY_ = new Coordinate(e.clientX, e.clientY); - this.healStack_ = e.altKey || e.ctrlKey || e.metaKey; - - this.bindMouseEvents(e); -}; - -/** - * Bind gesture events. - * @param {!Event} e A mouse down or touch start event. - * @package - */ -Gesture.prototype.bindMouseEvents = function(e) { - this.onMoveWrapper_ = browserEvents.conditionalBind( - document, 'mousemove', null, this.handleMove.bind(this)); - this.onUpWrapper_ = browserEvents.conditionalBind( - document, 'mouseup', null, this.handleUp.bind(this)); - - e.preventDefault(); - e.stopPropagation(); -}; - -/** - * Handle a mouse move or touch move event. - * @param {!Event} e A mouse move or touch move event. - * @package - */ -Gesture.prototype.handleMove = function(e) { - this.updateFromEvent_(e); - if (this.isDraggingWorkspace_) { - this.workspaceDragger_.drag(this.currentDragDeltaXY_); - } else if (this.isDraggingBlock_) { - this.blockDragger_.drag(this.mostRecentEvent_, this.currentDragDeltaXY_); - } else if (this.isDraggingBubble_) { - this.bubbleDragger_.dragBubble( - this.mostRecentEvent_, this.currentDragDeltaXY_); - } - e.preventDefault(); - e.stopPropagation(); -}; - -/** - * Handle a mouse up or touch end event. - * @param {!Event} e A mouse up or touch end event. - * @package - */ -Gesture.prototype.handleUp = function(e) { - this.updateFromEvent_(e); - Touch.longStop(); - - if (this.isEnding_) { - console.log('Trying to end a gesture recursively.'); - return; - } - this.isEnding_ = true; - // The ordering of these checks is important: drags have higher priority than - // clicks. Fields have higher priority than blocks; blocks have higher - // priority than workspaces. - // 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_.endDrag(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_()) { - this.doBlockClick_(); - } else if (this.isWorkspaceClick_()) { - this.doWorkspaceClick_(e); - } - - e.preventDefault(); - e.stopPropagation(); - - this.dispose(); -}; - -/** - * Cancel an in-progress gesture. If a workspace or block drag is in progress, - * end the drag at the most recent location. - * @package - */ -Gesture.prototype.cancel = function() { - // Disposing of a block cancels in-progress drags, but dragging to a delete - // area disposes of a block and leads to recursive disposal. Break that cycle. - if (this.isEnding_) { - return; - } - Touch.longStop(); - if (this.isDraggingBubble_) { - this.bubbleDragger_.endBubbleDrag( - this.mostRecentEvent_, this.currentDragDeltaXY_); - } else if (this.isDraggingBlock_) { - this.blockDragger_.endDrag(this.mostRecentEvent_, this.currentDragDeltaXY_); - } else if (this.isDraggingWorkspace_) { - this.workspaceDragger_.endDrag(this.currentDragDeltaXY_); - } - this.dispose(); -}; - -/** - * Handle a real or faked right-click event by showing a context menu. - * @param {!Event} e A mouse move or touch move event. - * @package - */ -Gesture.prototype.handleRightClick = function(e) { - if (this.targetBlock_) { - this.bringBlockToFront_(); - this.targetBlock_.workspace.hideChaff(!!this.flyout_); - this.targetBlock_.showContextMenu(e); - } else if (this.startBubble_) { - this.startBubble_.showContextMenu(e); - } else if (this.startWorkspace_ && !this.flyout_) { - this.startWorkspace_.hideChaff(); - this.startWorkspace_.showContextMenu(e); - } - - // TODO: Handle right-click on a bubble. - e.preventDefault(); - e.stopPropagation(); - - this.dispose(); -}; - -/** - * Handle a mousedown/touchstart event on a workspace. - * @param {!Event} e A mouse down or touch start event. - * @param {!WorkspaceSvg} ws The workspace the event hit. - * @package - */ -Gesture.prototype.handleWsStart = function(e, ws) { - if (this.hasStarted_) { - throw Error( - 'Tried to call gesture.handleWsStart, ' + - 'but the gesture had already been started.'); - } - this.setStartWorkspace_(ws); - this.mostRecentEvent_ = e; - this.doStart(e); -}; - -/** - * Fires a workspace click event. - * @param {!WorkspaceSvg} ws The workspace that a user clicks on. - * @private - */ -Gesture.prototype.fireWorkspaceClick_ = function(ws) { - eventUtils.fire( - new (eventUtils.get(eventUtils.CLICK))(null, ws.id, 'workspace')); -}; - -/** - * Handle a mousedown/touchstart event on a flyout. - * @param {!Event} e A mouse down or touch start event. - * @param {!IFlyout} flyout The flyout the event hit. - * @package - */ -Gesture.prototype.handleFlyoutStart = function(e, flyout) { - if (this.hasStarted_) { - throw Error( - 'Tried to call gesture.handleFlyoutStart, ' + - 'but the gesture had already been started.'); - } - this.setStartFlyout_(flyout); - this.handleWsStart(e, flyout.getWorkspace()); -}; - -/** - * Handle a mousedown/touchstart event on a block. - * @param {!Event} e A mouse down or touch start event. - * @param {!BlockSvg} block The block the event hit. - * @package - */ -Gesture.prototype.handleBlockStart = function(e, block) { - if (this.hasStarted_) { - throw Error( - '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 {!IBubble} bubble The bubble the event hit. - * @package - */ -Gesture.prototype.handleBubbleStart = function(e, bubble) { - if (this.hasStarted_) { - throw Error( - '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 - */ -Gesture.prototype.doBubbleClick_ = function() { - // TODO (#1673): Consistent handling of single clicks. - this.startBubble_.setFocus && this.startBubble_.setFocus(); - this.startBubble_.select && this.startBubble_.select(); -}; - -/** - * Execute a field click. - * @private - */ -Gesture.prototype.doFieldClick_ = function() { - this.startField_.showEditor(this.mostRecentEvent_); - this.bringBlockToFront_(); -}; - -/** - * Execute a block click. - * @private - */ -Gesture.prototype.doBlockClick_ = function() { - // Block click in an autoclosing flyout. - if (this.flyout_ && this.flyout_.autoClose) { - if (this.targetBlock_.isEnabled()) { + /** + * Update this gesture to record whether a block is being dragged from the + * flyout. + * This function should be called on a mouse/touch move event the first time + * the drag radius is exceeded. It should be called no more than once per + * gesture. If a block should be dragged from the flyout this function creates + * the new block on the main workspace and updates targetBlock_ and + * startWorkspace_. + * @return {boolean} True if a block is being dragged from the flyout. + * @private + */ + updateIsDraggingFromFlyout_() { + if (!this.targetBlock_) { + return false; + } + if (!this.flyout_.isBlockCreatable_(this.targetBlock_)) { + return false; + } + if (!this.flyout_.isScrollable() || + this.flyout_.isDragTowardWorkspace(this.currentDragDeltaXY_)) { + this.startWorkspace_ = this.flyout_.targetWorkspace; + this.startWorkspace_.updateScreenCalculationsIfScrolled(); + // Start the event group now, so that the same event group is used for + // block creation and block dragging. if (!eventUtils.getGroup()) { eventUtils.setGroup(true); } - const newBlock = this.flyout_.createBlock(this.targetBlock_); - newBlock.scheduleSnapAndBump(); - } - } else { - // Clicks events are on the start block, even if it was a shadow. - const event = new (eventUtils.get(eventUtils.CLICK))( - this.startBlock_, this.startWorkspace_.id, 'block'); - eventUtils.fire(event); - } - this.bringBlockToFront_(); - eventUtils.setGroup(false); -}; - -/** - * Execute a workspace click. When in accessibility mode shift clicking will - * move the cursor. - * @param {!Event} _e A mouse up or touch end event. - * @private - */ -Gesture.prototype.doWorkspaceClick_ = function(_e) { - const ws = this.creatorWorkspace_; - if (common.getSelected()) { - common.getSelected().unselect(); - } - this.fireWorkspaceClick_(this.startWorkspace_ || ws); -}; - -/* 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. - * @private - */ -Gesture.prototype.bringBlockToFront_ = function() { - // Blocks in the flyout don't overlap, so skip the work. - if (this.targetBlock_ && !this.flyout_) { - this.targetBlock_.bringToFront(); - } -}; - -/* Begin functions for populating a gesture at mouse down. */ - -/** - * Record the field that a gesture started on. - * @param {Field} field The field the gesture started on. - * @package - */ -Gesture.prototype.setStartField = function(field) { - if (this.hasStarted_) { - throw Error( - '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 {IBubble} bubble The bubble the gesture started on. - * @package - */ -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. - * @param {BlockSvg} block The block the gesture started on. - * @package - */ -Gesture.prototype.setStartBlock = function(block) { - // If the gesture already went through a bubble, don't set the start block. - if (!this.startBlock_ && !this.startBubble_) { - this.startBlock_ = block; - if (block.isInFlyout && block !== block.getRootBlock()) { - this.setTargetBlock_(block.getRootBlock()); - } else { - this.setTargetBlock_(block); - } - } -}; - -/** - * Record the block that a gesture targets, meaning the block that will be - * dragged if this turns into a drag. If this block is a shadow, that will be - * its first non-shadow parent. - * @param {BlockSvg} block The block the gesture targets. - * @private - */ -Gesture.prototype.setTargetBlock_ = function(block) { - if (block.isShadow()) { - this.setTargetBlock_(block.getParent()); - } else { - this.targetBlock_ = block; - } -}; - -/** - * Record the workspace that a gesture started on. - * @param {WorkspaceSvg} ws The workspace the gesture started on. - * @private - */ -Gesture.prototype.setStartWorkspace_ = function(ws) { - if (!this.startWorkspace_) { - this.startWorkspace_ = ws; - } -}; - -/** - * Record the flyout that a gesture started on. - * @param {IFlyout} flyout The flyout the gesture started on. - * @private - */ -Gesture.prototype.setStartFlyout_ = function(flyout) { - if (!this.flyout_) { - this.flyout_ = flyout; - } -}; - - -/* End functions for populating a gesture at mouse down. */ - -/* Begin helper functions defining types of clicks. Any developer wanting - * to change the definition of a click should modify only this code. */ - -/** - * Whether this gesture is a click on a 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 - */ -Gesture.prototype.isBubbleClick_ = function() { - // A bubble click starts on a bubble and never escapes the drag radius. - const 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). - * @return {boolean} Whether this gesture was a click on a block. - * @private - */ -Gesture.prototype.isBlockClick_ = function() { - // A block click starts on a block, never escapes the drag radius, and is not - // a field click. - const hasStartBlock = !!this.startBlock_; - return hasStartBlock && !this.hasExceededDragRadius_ && !this.isFieldClick_(); -}; - -/** - * Whether this gesture is a click on a field. This should only be called when - * ending a gesture (mouse up, touch end). - * @return {boolean} Whether this gesture was a click on a field. - * @private - */ -Gesture.prototype.isFieldClick_ = function() { - const fieldClickable = - this.startField_ ? this.startField_.isClickable() : false; - return fieldClickable && !this.hasExceededDragRadius_ && - (!this.flyout_ || !this.flyout_.autoClose); -}; - -/** - * Whether this gesture is a click on a workspace. This should only be called - * when ending a gesture (mouse up, touch end). - * @return {boolean} Whether this gesture was a click on a workspace. - * @private - */ -Gesture.prototype.isWorkspaceClick_ = function() { - const onlyTouchedWorkspace = - !this.startBlock_ && !this.startBubble_ && !this.startField_; - return onlyTouchedWorkspace && !this.hasExceededDragRadius_; -}; - -/* End helper functions defining types of clicks. */ - -/** - * Whether this gesture is a drag of either a workspace or block. - * This function is called externally to block actions that cannot be taken - * mid-drag (e.g. using the keyboard to delete the selected blocks). - * @return {boolean} True if this gesture is a drag of a workspace or block. - * @package - */ -Gesture.prototype.isDragging = function() { - return this.isDraggingWorkspace_ || this.isDraggingBlock_ || - this.isDraggingBubble_; -}; - -/** - * Whether this gesture has already been started. In theory every mouse down - * has a corresponding mouse up, but in reality it is possible to lose a - * mouse up, leaving an in-process gesture hanging. - * @return {boolean} Whether this gesture was a click on a workspace. - * @package - */ -Gesture.prototype.hasStarted = function() { - return this.hasStarted_; -}; - -/** - * Get a list of the insertion markers that currently exist. Block drags have - * 0, 1, or 2 insertion markers. - * @return {!Array} A possibly empty list of insertion - * marker blocks. - * @package - */ -Gesture.prototype.getInsertionMarkers = function() { - if (this.blockDragger_) { - return this.blockDragger_.getInsertionMarkers(); - } - return []; -}; - -/** - * Gets the current dragger if an item is being dragged. Null if nothing is - * being dragged. - * @return {!WorkspaceDragger|!BubbleDragger|!IBlockDragger|null} - * The dragger that is currently in use or null if no drag is in progress. - */ -Gesture.prototype.getCurrentDragger = function() { - if (this.isDraggingBlock_) { - return this.blockDragger_; - } else if (this.isDraggingWorkspace_) { - return this.workspaceDragger_; - } else if (this.isDraggingBubble_) { - return this.bubbleDragger_; - } - return null; -}; - -/** - * Is a drag or other gesture currently in progress on any workspace? - * @return {boolean} True if gesture is occurring. - */ -Gesture.inProgress = function() { - const workspaces = Workspace.getAll(); - for (let i = 0, workspace; (workspace = workspaces[i]); i++) { - if (workspace.currentGesture_) { + // The start block is no longer relevant, because this is a drag. + this.startBlock_ = null; + this.targetBlock_ = this.flyout_.createBlock(this.targetBlock_); + this.targetBlock_.select(); return true; } + return false; } - 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 + */ + updateIsDraggingBubble_() { + 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 drag radius is exceeded. It should be called no more than once per + * gesture. If a block should be dragged, either from the flyout or in the + * workspace, this function creates the necessary BlockDragger and starts the + * drag. + * @return {boolean} True if a block is being dragged. + * @private + */ + updateIsDraggingBlock_() { + if (!this.targetBlock_) { + return false; + } + + if (this.flyout_) { + this.isDraggingBlock_ = this.updateIsDraggingFromFlyout_(); + } else if (this.targetBlock_.isMovable()) { + this.isDraggingBlock_ = true; + } + + if (this.isDraggingBlock_) { + this.startDraggingBlock_(); + return true; + } + return false; + } + + /** + * Update this gesture to record whether a workspace is being dragged. + * This function should be called on a mouse/touch move event the first time + * the drag radius is exceeded. It should be called no more than once per + * gesture. If a workspace is being dragged this function creates the + * necessary WorkspaceDragger and starts the drag. + * @private + */ + updateIsDraggingWorkspace_() { + const wsMovable = this.flyout_ ? + this.flyout_.isScrollable() : + this.startWorkspace_ && this.startWorkspace_.isDraggable(); + + if (!wsMovable) { + return; + } + + this.workspaceDragger_ = new WorkspaceDragger( + /** @type {!WorkspaceSvg} */ (this.startWorkspace_)); + + this.isDraggingWorkspace_ = true; + this.workspaceDragger_.startDrag(); + } + + /** + * Update this gesture to record whether anything is being dragged. + * This function should be called on a mouse/touch move event the first time + * the drag radius is exceeded. It should be called no more than once per + * gesture. + * @private + */ + updateIsDragging_() { + // Sanity check. + if (this.calledUpdateIsDragging_) { + throw Error('updateIsDragging_ should only be called once per gesture.'); + } + this.calledUpdateIsDragging_ = true; + + // 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; + } + // Then check if it's a workspace drag. + this.updateIsDraggingWorkspace_(); + } + + /** + * Create a block dragger and start dragging the selected block. + * @private + */ + startDraggingBlock_() { + const BlockDraggerClass = registry.getClassFromOptions( + registry.Type.BLOCK_DRAGGER, this.creatorWorkspace_.options, true); + + this.blockDragger_ = new BlockDraggerClass( + /** @type {!BlockSvg} */ (this.targetBlock_), + /** @type {!WorkspaceSvg} */ (this.startWorkspace_)); + this.blockDragger_.startDrag(this.currentDragDeltaXY_, this.healStack_); + this.blockDragger_.drag(this.mostRecentEvent_, this.currentDragDeltaXY_); + } + + // TODO (fenichel): Possibly combine this and startDraggingBlock_. + /** + * Create a bubble dragger and start dragging the selected bubble. + * @private + */ + startDraggingBubble_() { + this.bubbleDragger_ = new BubbleDragger( + /** @type {!IBubble} */ (this.startBubble_), + /** @type {!WorkspaceSvg} */ (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. + * @param {!Event} e A mouse down or touch start event. + * @package + */ + doStart(e) { + if (browserEvents.isTargetInput(e)) { + this.cancel(); + return; + } + this.hasStarted_ = true; + + blockAnimations.disconnectUiStop(); + this.startWorkspace_.updateScreenCalculationsIfScrolled(); + if (this.startWorkspace_.isMutator) { + // Mutator's coordinate system could be out of date because the bubble was + // dragged, the block was moved, the parent workspace zoomed, etc. + this.startWorkspace_.resize(); + } + + // Hide chaff also hides the flyout, so don't do it if the click is in a + // flyout. + this.startWorkspace_.hideChaff(!!this.flyout_); + + this.startWorkspace_.markFocused(); + this.mostRecentEvent_ = e; + + Tooltip.block(); + + if (this.targetBlock_) { + this.targetBlock_.select(); + } + + if (browserEvents.isRightButton(e)) { + this.handleRightClick(e); + return; + } + + if ((e.type.toLowerCase() === 'touchstart' || + e.type.toLowerCase() === 'pointerdown') && + e.pointerType !== 'mouse') { + Touch.longStart(e, this); + } + + this.mouseDownXY_ = new Coordinate(e.clientX, e.clientY); + this.healStack_ = e.altKey || e.ctrlKey || e.metaKey; + + this.bindMouseEvents(e); + } + + /** + * Bind gesture events. + * @param {!Event} e A mouse down or touch start event. + * @package + */ + bindMouseEvents(e) { + this.onMoveWrapper_ = browserEvents.conditionalBind( + document, 'mousemove', null, this.handleMove.bind(this)); + this.onUpWrapper_ = browserEvents.conditionalBind( + document, 'mouseup', null, this.handleUp.bind(this)); + + e.preventDefault(); + e.stopPropagation(); + } + + /** + * Handle a mouse move or touch move event. + * @param {!Event} e A mouse move or touch move event. + * @package + */ + handleMove(e) { + this.updateFromEvent_(e); + if (this.isDraggingWorkspace_) { + this.workspaceDragger_.drag(this.currentDragDeltaXY_); + } else if (this.isDraggingBlock_) { + this.blockDragger_.drag(this.mostRecentEvent_, this.currentDragDeltaXY_); + } else if (this.isDraggingBubble_) { + this.bubbleDragger_.dragBubble( + this.mostRecentEvent_, this.currentDragDeltaXY_); + } + e.preventDefault(); + e.stopPropagation(); + } + + /** + * Handle a mouse up or touch end event. + * @param {!Event} e A mouse up or touch end event. + * @package + */ + handleUp(e) { + this.updateFromEvent_(e); + Touch.longStop(); + + if (this.isEnding_) { + console.log('Trying to end a gesture recursively.'); + return; + } + this.isEnding_ = true; + // The ordering of these checks is important: drags have higher priority + // than clicks. Fields have higher priority than blocks; blocks have higher + // priority than workspaces. + // 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_.endDrag(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_()) { + this.doBlockClick_(); + } else if (this.isWorkspaceClick_()) { + this.doWorkspaceClick_(e); + } + + e.preventDefault(); + e.stopPropagation(); + + this.dispose(); + } + + /** + * Cancel an in-progress gesture. If a workspace or block drag is in + * progress, end the drag at the most recent location. + * @package + */ + cancel() { + // Disposing of a block cancels in-progress drags, but dragging to a delete + // area disposes of a block and leads to recursive disposal. Break that + // cycle. + if (this.isEnding_) { + return; + } + Touch.longStop(); + if (this.isDraggingBubble_) { + this.bubbleDragger_.endBubbleDrag( + this.mostRecentEvent_, this.currentDragDeltaXY_); + } else if (this.isDraggingBlock_) { + this.blockDragger_.endDrag( + this.mostRecentEvent_, this.currentDragDeltaXY_); + } else if (this.isDraggingWorkspace_) { + this.workspaceDragger_.endDrag(this.currentDragDeltaXY_); + } + this.dispose(); + } + + /** + * Handle a real or faked right-click event by showing a context menu. + * @param {!Event} e A mouse move or touch move event. + * @package + */ + handleRightClick(e) { + if (this.targetBlock_) { + this.bringBlockToFront_(); + this.targetBlock_.workspace.hideChaff(!!this.flyout_); + this.targetBlock_.showContextMenu(e); + } else if (this.startBubble_) { + this.startBubble_.showContextMenu(e); + } else if (this.startWorkspace_ && !this.flyout_) { + this.startWorkspace_.hideChaff(); + this.startWorkspace_.showContextMenu(e); + } + + // TODO: Handle right-click on a bubble. + e.preventDefault(); + e.stopPropagation(); + + this.dispose(); + } + + /** + * Handle a mousedown/touchstart event on a workspace. + * @param {!Event} e A mouse down or touch start event. + * @param {!WorkspaceSvg} ws The workspace the event hit. + * @package + */ + handleWsStart(e, ws) { + if (this.hasStarted_) { + throw Error( + 'Tried to call gesture.handleWsStart, ' + + 'but the gesture had already been started.'); + } + this.setStartWorkspace_(ws); + this.mostRecentEvent_ = e; + this.doStart(e); + } + + /** + * Fires a workspace click event. + * @param {!WorkspaceSvg} ws The workspace that a user clicks on. + * @private + */ + fireWorkspaceClick_(ws) { + eventUtils.fire( + new (eventUtils.get(eventUtils.CLICK))(null, ws.id, 'workspace')); + } + + /** + * Handle a mousedown/touchstart event on a flyout. + * @param {!Event} e A mouse down or touch start event. + * @param {!IFlyout} flyout The flyout the event hit. + * @package + */ + handleFlyoutStart(e, flyout) { + if (this.hasStarted_) { + throw Error( + 'Tried to call gesture.handleFlyoutStart, ' + + 'but the gesture had already been started.'); + } + this.setStartFlyout_(flyout); + this.handleWsStart(e, flyout.getWorkspace()); + } + + /** + * Handle a mousedown/touchstart event on a block. + * @param {!Event} e A mouse down or touch start event. + * @param {!BlockSvg} block The block the event hit. + * @package + */ + handleBlockStart(e, block) { + if (this.hasStarted_) { + throw Error( + '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 {!IBubble} bubble The bubble the event hit. + * @package + */ + handleBubbleStart(e, bubble) { + if (this.hasStarted_) { + throw Error( + '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 + */ + doBubbleClick_() { + // TODO (#1673): Consistent handling of single clicks. + this.startBubble_.setFocus && this.startBubble_.setFocus(); + this.startBubble_.select && this.startBubble_.select(); + } + + /** + * Execute a field click. + * @private + */ + doFieldClick_() { + this.startField_.showEditor(this.mostRecentEvent_); + this.bringBlockToFront_(); + } + + /** + * Execute a block click. + * @private + */ + doBlockClick_() { + // Block click in an autoclosing flyout. + if (this.flyout_ && this.flyout_.autoClose) { + if (this.targetBlock_.isEnabled()) { + if (!eventUtils.getGroup()) { + eventUtils.setGroup(true); + } + const newBlock = this.flyout_.createBlock(this.targetBlock_); + newBlock.scheduleSnapAndBump(); + } + } else { + // Clicks events are on the start block, even if it was a shadow. + const event = new (eventUtils.get(eventUtils.CLICK))( + this.startBlock_, this.startWorkspace_.id, 'block'); + eventUtils.fire(event); + } + this.bringBlockToFront_(); + eventUtils.setGroup(false); + } + + /** + * Execute a workspace click. When in accessibility mode shift clicking will + * move the cursor. + * @param {!Event} _e A mouse up or touch end event. + * @private + */ + doWorkspaceClick_(_e) { + const ws = this.creatorWorkspace_; + if (common.getSelected()) { + common.getSelected().unselect(); + } + this.fireWorkspaceClick_(this.startWorkspace_ || ws); + } + + /* 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. + * @private + */ + bringBlockToFront_() { + // Blocks in the flyout don't overlap, so skip the work. + if (this.targetBlock_ && !this.flyout_) { + this.targetBlock_.bringToFront(); + } + } + + /* Begin functions for populating a gesture at mouse down. */ + + /** + * Record the field that a gesture started on. + * @param {Field} field The field the gesture started on. + * @package + */ + setStartField(field) { + if (this.hasStarted_) { + throw Error( + '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 {IBubble} bubble The bubble the gesture started on. + * @package + */ + setStartBubble(bubble) { + if (!this.startBubble_) { + this.startBubble_ = bubble; + } + } + + /** + * Record the block that a gesture started on, and set the target block + * appropriately. + * @param {BlockSvg} block The block the gesture started on. + * @package + */ + setStartBlock(block) { + // If the gesture already went through a bubble, don't set the start block. + if (!this.startBlock_ && !this.startBubble_) { + this.startBlock_ = block; + if (block.isInFlyout && block !== block.getRootBlock()) { + this.setTargetBlock_(block.getRootBlock()); + } else { + this.setTargetBlock_(block); + } + } + } + + /** + * Record the block that a gesture targets, meaning the block that will be + * dragged if this turns into a drag. If this block is a shadow, that will be + * its first non-shadow parent. + * @param {BlockSvg} block The block the gesture targets. + * @private + */ + setTargetBlock_(block) { + if (block.isShadow()) { + this.setTargetBlock_(block.getParent()); + } else { + this.targetBlock_ = block; + } + } + + /** + * Record the workspace that a gesture started on. + * @param {WorkspaceSvg} ws The workspace the gesture started on. + * @private + */ + setStartWorkspace_(ws) { + if (!this.startWorkspace_) { + this.startWorkspace_ = ws; + } + } + + /** + * Record the flyout that a gesture started on. + * @param {IFlyout} flyout The flyout the gesture started on. + * @private + */ + setStartFlyout_(flyout) { + if (!this.flyout_) { + this.flyout_ = flyout; + } + } + + /* End functions for populating a gesture at mouse down. */ + + /* Begin helper functions defining types of clicks. Any developer wanting + * to change the definition of a click should modify only this code. */ + + /** + * Whether this gesture is a click on a 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 + */ + isBubbleClick_() { + // A bubble click starts on a bubble and never escapes the drag radius. + const 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). + * @return {boolean} Whether this gesture was a click on a block. + * @private + */ + isBlockClick_() { + // A block click starts on a block, never escapes the drag radius, and is + // not a field click. + const hasStartBlock = !!this.startBlock_; + return hasStartBlock && !this.hasExceededDragRadius_ && + !this.isFieldClick_(); + } + + /** + * Whether this gesture is a click on a field. This should only be called + * when ending a gesture (mouse up, touch end). + * @return {boolean} Whether this gesture was a click on a field. + * @private + */ + isFieldClick_() { + const fieldClickable = + this.startField_ ? this.startField_.isClickable() : false; + return fieldClickable && !this.hasExceededDragRadius_ && + (!this.flyout_ || !this.flyout_.autoClose); + } + + /** + * Whether this gesture is a click on a workspace. This should only be called + * when ending a gesture (mouse up, touch end). + * @return {boolean} Whether this gesture was a click on a workspace. + * @private + */ + isWorkspaceClick_() { + const onlyTouchedWorkspace = + !this.startBlock_ && !this.startBubble_ && !this.startField_; + return onlyTouchedWorkspace && !this.hasExceededDragRadius_; + } + + /* End helper functions defining types of clicks. */ + + /** + * Whether this gesture is a drag of either a workspace or block. + * This function is called externally to block actions that cannot be taken + * mid-drag (e.g. using the keyboard to delete the selected blocks). + * @return {boolean} True if this gesture is a drag of a workspace or block. + * @package + */ + isDragging() { + return this.isDraggingWorkspace_ || this.isDraggingBlock_ || + this.isDraggingBubble_; + } + + /** + * Whether this gesture has already been started. In theory every mouse down + * has a corresponding mouse up, but in reality it is possible to lose a + * mouse up, leaving an in-process gesture hanging. + * @return {boolean} Whether this gesture was a click on a workspace. + * @package + */ + hasStarted() { + return this.hasStarted_; + } + + /** + * Get a list of the insertion markers that currently exist. Block drags have + * 0, 1, or 2 insertion markers. + * @return {!Array} A possibly empty list of insertion + * marker blocks. + * @package + */ + getInsertionMarkers() { + if (this.blockDragger_) { + return this.blockDragger_.getInsertionMarkers(); + } + return []; + } + + /** + * Gets the current dragger if an item is being dragged. Null if nothing is + * being dragged. + * @return {!WorkspaceDragger|!BubbleDragger|!IBlockDragger|null} + * The dragger that is currently in use or null if no drag is in progress. + */ + getCurrentDragger() { + if (this.isDraggingBlock_) { + return this.blockDragger_; + } else if (this.isDraggingWorkspace_) { + return this.workspaceDragger_; + } else if (this.isDraggingBubble_) { + return this.bubbleDragger_; + } + return null; + } + + /** + * Is a drag or other gesture currently in progress on any workspace? + * @return {boolean} True if gesture is occurring. + */ + static inProgress() { + const workspaces = Workspace.getAll(); + for (let i = 0, workspace; (workspace = workspaces[i]); i++) { + // Not actually necessarily a WorkspaceSvg, but it doesn't matter b/c + // we're just checking if the property exists. Theoretically we would + // want to use instanceof, but that causes a circular dependency. + if (/** @type {!WorkspaceSvg} */ (workspace).currentGesture_) { + return true; + } + } + return false; + } +} exports.Gesture = Gesture; diff --git a/core/grid.js b/core/grid.js index a16d3f083..e29191931 100644 --- a/core/grid.js +++ b/core/grid.js @@ -24,200 +24,203 @@ const {Svg} = goog.require('Blockly.utils.Svg'); /** * Class for a workspace's grid. - * @param {!SVGElement} pattern The grid's SVG pattern, created during - * injection. - * @param {!Object} options A dictionary of normalized options for the grid. - * See grid documentation: - * https://developers.google.com/blockly/guides/configure/web/grid - * @constructor * @alias Blockly.Grid */ -const Grid = function(pattern, options) { +class Grid { /** - * The grid's SVG pattern, created during injection. - * @type {!SVGElement} - * @private + * @param {!SVGElement} pattern The grid's SVG pattern, created during + * injection. + * @param {!Object} options A dictionary of normalized options for the grid. + * See grid documentation: + * https://developers.google.com/blockly/guides/configure/web/grid */ - this.gridPattern_ = pattern; + constructor(pattern, options) { + /** + * The scale of the grid, used to set stroke width on grid lines. + * This should always be the same as the workspace scale. + * @type {number} + * @private + */ + this.scale_ = 1; - /** - * The spacing of the grid lines (in px). - * @type {number} - * @private - */ - this.spacing_ = options['spacing']; + /** + * The grid's SVG pattern, created during injection. + * @type {!SVGElement} + * @private + */ + this.gridPattern_ = pattern; - /** - * How long the grid lines should be (in px). - * @type {number} - * @private - */ - this.length_ = options['length']; + /** + * The spacing of the grid lines (in px). + * @type {number} + * @private + */ + this.spacing_ = options['spacing']; - /** - * The horizontal grid line, if it exists. - * @type {SVGElement} - * @private - */ - this.line1_ = /** @type {SVGElement} */ (pattern.firstChild); + /** + * How long the grid lines should be (in px). + * @type {number} + * @private + */ + this.length_ = options['length']; - /** - * The vertical grid line, if it exists. - * @type {SVGElement} - * @private - */ - this.line2_ = - this.line1_ && (/** @type {SVGElement} */ (this.line1_.nextSibling)); + /** + * The horizontal grid line, if it exists. + * @type {SVGElement} + * @private + */ + this.line1_ = /** @type {SVGElement} */ (pattern.firstChild); - /** - * Whether blocks should snap to the grid. - * @type {boolean} - * @private - */ - this.snapToGrid_ = options['snap']; -}; + /** + * The vertical grid line, if it exists. + * @type {SVGElement} + * @private + */ + this.line2_ = + this.line1_ && (/** @type {SVGElement} */ (this.line1_.nextSibling)); -/** - * The scale of the grid, used to set stroke width on grid lines. - * This should always be the same as the workspace scale. - * @type {number} - * @private - */ -Grid.prototype.scale_ = 1; - -/** - * Dispose of this grid and unlink from the DOM. - * @package - * @suppress {checkTypes} - */ -Grid.prototype.dispose = function() { - this.gridPattern_ = null; -}; - -/** - * Whether blocks should snap to the grid, based on the initial configuration. - * @return {boolean} True if blocks should snap, false otherwise. - * @package - */ -Grid.prototype.shouldSnap = function() { - return this.snapToGrid_; -}; - -/** - * Get the spacing of the grid points (in px). - * @return {number} The spacing of the grid points. - * @package - */ -Grid.prototype.getSpacing = function() { - return this.spacing_; -}; - -/** - * Get the ID of the pattern element, which should be randomized to avoid - * conflicts with other Blockly instances on the page. - * @return {string} The pattern ID. - * @package - */ -Grid.prototype.getPatternId = function() { - return this.gridPattern_.id; -}; - -/** - * Update the grid with a new scale. - * @param {number} scale The new workspace scale. - * @package - */ -Grid.prototype.update = function(scale) { - this.scale_ = scale; - // MSIE freaks if it sees a 0x0 pattern, so set empty patterns to 100x100. - const safeSpacing = (this.spacing_ * scale) || 100; - - this.gridPattern_.setAttribute('width', safeSpacing); - this.gridPattern_.setAttribute('height', safeSpacing); - - let half = Math.floor(this.spacing_ / 2) + 0.5; - let start = half - this.length_ / 2; - let end = half + this.length_ / 2; - - half *= scale; - start *= scale; - end *= scale; - - this.setLineAttributes_(this.line1_, scale, start, end, half, half); - this.setLineAttributes_(this.line2_, scale, half, half, start, end); -}; - -/** - * Set the attributes on one of the lines in the grid. Use this to update the - * length and stroke width of the grid lines. - * @param {SVGElement} line Which line to update. - * @param {number} width The new stroke size (in px). - * @param {number} x1 The new x start position of the line (in px). - * @param {number} x2 The new x end position of the line (in px). - * @param {number} y1 The new y start position of the line (in px). - * @param {number} y2 The new y end position of the line (in px). - * @private - */ -Grid.prototype.setLineAttributes_ = function(line, width, x1, x2, y1, y2) { - if (line) { - line.setAttribute('stroke-width', width); - line.setAttribute('x1', x1); - line.setAttribute('y1', y1); - line.setAttribute('x2', x2); - line.setAttribute('y2', y2); + /** + * Whether blocks should snap to the grid. + * @type {boolean} + * @private + */ + this.snapToGrid_ = options['snap']; } -}; -/** - * Move the grid to a new x and y position, and make sure that change is - * visible. - * @param {number} x The new x position of the grid (in px). - * @param {number} y The new y position of the grid (in px). - * @package - */ -Grid.prototype.moveTo = function(x, y) { - this.gridPattern_.setAttribute('x', x); - this.gridPattern_.setAttribute('y', y); - - if (userAgent.IE || userAgent.EDGE) { - // IE/Edge doesn't notice that the x/y offsets have changed. - // Force an update. - this.update(this.scale_); + /** + * Dispose of this grid and unlink from the DOM. + * @package + * @suppress {checkTypes} + */ + dispose() { + this.gridPattern_ = null; } -}; -/** - * Create the DOM for the grid described by options. - * @param {string} rnd A random ID to append to the pattern's ID. - * @param {!Object} gridOptions The object containing grid configuration. - * @param {!SVGElement} defs The root SVG element for this workspace's defs. - * @return {!SVGElement} The SVG element for the grid pattern. - * @package - */ -Grid.createDom = function(rnd, gridOptions, defs) { - /* - - - - - */ - const gridPattern = dom.createSvgElement( - Svg.PATTERN, - {'id': 'blocklyGridPattern' + rnd, 'patternUnits': 'userSpaceOnUse'}, - defs); - if (gridOptions['length'] > 0 && gridOptions['spacing'] > 0) { - dom.createSvgElement( - Svg.LINE, {'stroke': gridOptions['colour']}, gridPattern); - if (gridOptions['length'] > 1) { + /** + * Whether blocks should snap to the grid, based on the initial configuration. + * @return {boolean} True if blocks should snap, false otherwise. + * @package + */ + shouldSnap() { + return this.snapToGrid_; + } + + /** + * Get the spacing of the grid points (in px). + * @return {number} The spacing of the grid points. + * @package + */ + getSpacing() { + return this.spacing_; + } + + /** + * Get the ID of the pattern element, which should be randomized to avoid + * conflicts with other Blockly instances on the page. + * @return {string} The pattern ID. + * @package + */ + getPatternId() { + return this.gridPattern_.id; + } + + /** + * Update the grid with a new scale. + * @param {number} scale The new workspace scale. + * @package + */ + update(scale) { + this.scale_ = scale; + // MSIE freaks if it sees a 0x0 pattern, so set empty patterns to 100x100. + const safeSpacing = (this.spacing_ * scale) || 100; + + this.gridPattern_.setAttribute('width', safeSpacing); + this.gridPattern_.setAttribute('height', safeSpacing); + + let half = Math.floor(this.spacing_ / 2) + 0.5; + let start = half - this.length_ / 2; + let end = half + this.length_ / 2; + + half *= scale; + start *= scale; + end *= scale; + + this.setLineAttributes_(this.line1_, scale, start, end, half, half); + this.setLineAttributes_(this.line2_, scale, half, half, start, end); + } + + /** + * Set the attributes on one of the lines in the grid. Use this to update the + * length and stroke width of the grid lines. + * @param {SVGElement} line Which line to update. + * @param {number} width The new stroke size (in px). + * @param {number} x1 The new x start position of the line (in px). + * @param {number} x2 The new x end position of the line (in px). + * @param {number} y1 The new y start position of the line (in px). + * @param {number} y2 The new y end position of the line (in px). + * @private + */ + setLineAttributes_(line, width, x1, x2, y1, y2) { + if (line) { + line.setAttribute('stroke-width', width); + line.setAttribute('x1', x1); + line.setAttribute('y1', y1); + line.setAttribute('x2', x2); + line.setAttribute('y2', y2); + } + } + + /** + * Move the grid to a new x and y position, and make sure that change is + * visible. + * @param {number} x The new x position of the grid (in px). + * @param {number} y The new y position of the grid (in px). + * @package + */ + moveTo(x, y) { + this.gridPattern_.setAttribute('x', x); + this.gridPattern_.setAttribute('y', y); + + if (userAgent.IE || userAgent.EDGE) { + // IE/Edge doesn't notice that the x/y offsets have changed. + // Force an update. + this.update(this.scale_); + } + } + + /** + * Create the DOM for the grid described by options. + * @param {string} rnd A random ID to append to the pattern's ID. + * @param {!Object} gridOptions The object containing grid configuration. + * @param {!SVGElement} defs The root SVG element for this workspace's defs. + * @return {!SVGElement} The SVG element for the grid pattern. + * @package + */ + static createDom(rnd, gridOptions, defs) { + /* + + + + + */ + const gridPattern = dom.createSvgElement( + Svg.PATTERN, + {'id': 'blocklyGridPattern' + rnd, 'patternUnits': 'userSpaceOnUse'}, + defs); + if (gridOptions['length'] > 0 && gridOptions['spacing'] > 0) { dom.createSvgElement( Svg.LINE, {'stroke': gridOptions['colour']}, gridPattern); + if (gridOptions['length'] > 1) { + dom.createSvgElement( + Svg.LINE, {'stroke': gridOptions['colour']}, gridPattern); + } + // x1, y1, x1, x2 properties will be set later in update. + } else { + // Edge 16 doesn't handle empty patterns + dom.createSvgElement(Svg.LINE, {}, gridPattern); } - // x1, y1, x1, x2 properties will be set later in update. - } else { - // Edge 16 doesn't handle empty patterns - dom.createSvgElement(Svg.LINE, {}, gridPattern); + return gridPattern; } - return gridPattern; -}; +} exports.Grid = Grid; diff --git a/core/icon.js b/core/icon.js index aa7195749..d6dfdc978 100644 --- a/core/icon.js +++ b/core/icon.js @@ -29,188 +29,197 @@ const {Svg} = goog.require('Blockly.utils.Svg'); /** * Class for an icon. - * @param {BlockSvg} block The block associated with this icon. - * @constructor * @abstract * @alias Blockly.Icon */ -const Icon = function(block) { +class Icon { /** - * The block this icon is attached to. - * @type {BlockSvg} + * @param {BlockSvg} block The block associated with this icon. + */ + constructor(block) { + /** + * The block this icon is attached to. + * @type {BlockSvg} + * @protected + */ + this.block_ = block; + + /** + * The icon SVG group. + * @type {?SVGGElement} + */ + this.iconGroup_ = null; + + /** + * Whether this icon gets hidden when the block is collapsed. + * @type {boolean} + */ + this.collapseHidden = true; + + /** + * Height and width of icons. + * @const + */ + this.SIZE = 17; + + /** + * Bubble UI (if visible). + * @type {?Bubble} + * @protected + */ + this.bubble_ = null; + + /** + * Absolute coordinate of icon's center. + * @type {?Coordinate} + * @protected + */ + this.iconXY_ = null; + } + + /** + * Create the icon on the block. + */ + createIcon() { + if (this.iconGroup_) { + // Icon already exists. + return; + } + /* Here's the markup that will be generated: + + ... + + */ + this.iconGroup_ = + dom.createSvgElement(Svg.G, {'class': 'blocklyIconGroup'}, null); + if (this.block_.isInFlyout) { + dom.addClass( + /** @type {!Element} */ (this.iconGroup_), + 'blocklyIconGroupReadonly'); + } + this.drawIcon_(this.iconGroup_); + + this.block_.getSvgRoot().appendChild(this.iconGroup_); + browserEvents.conditionalBind( + this.iconGroup_, 'mouseup', this, this.iconClick_); + this.updateEditable(); + } + + /** + * Dispose of this icon. + */ + dispose() { + // Dispose of and unlink the icon. + dom.removeNode(this.iconGroup_); + this.iconGroup_ = null; + // Dispose of and unlink the bubble. + this.setVisible(false); + this.block_ = null; + } + + /** + * Add or remove the UI indicating if this icon may be clicked or not. + */ + updateEditable() { + // No-op on the base class. + } + + /** + * Is the associated bubble visible? + * @return {boolean} True if the bubble is visible. + */ + isVisible() { + return !!this.bubble_; + } + + /** + * Clicking on the icon toggles if the bubble is visible. + * @param {!Event} e Mouse click event. * @protected */ - this.block_ = block; + iconClick_(e) { + if (this.block_.workspace.isDragging()) { + // Drag operation is concluding. Don't open the editor. + return; + } + if (!this.block_.isInFlyout && !browserEvents.isRightButton(e)) { + this.setVisible(!this.isVisible()); + } + } /** - * The icon SVG group. - * @type {?SVGGElement} + * Change the colour of the associated bubble to match its block. */ - this.iconGroup_ = null; -}; - -/** - * Does this icon get hidden when the block is collapsed. - */ -Icon.prototype.collapseHidden = true; - -/** - * Height and width of icons. - * @const - */ -Icon.prototype.SIZE = 17; - -/** - * Bubble UI (if visible). - * @type {?Bubble} - * @protected - */ -Icon.prototype.bubble_ = null; - -/** - * Absolute coordinate of icon's center. - * @type {?Coordinate} - * @protected - */ -Icon.prototype.iconXY_ = null; - -/** - * Create the icon on the block. - */ -Icon.prototype.createIcon = function() { - if (this.iconGroup_) { - // Icon already exists. - return; + applyColour() { + if (this.isVisible()) { + this.bubble_.setColour(this.block_.style.colourPrimary); + } } - /* Here's the markup that will be generated: - - ... - - */ - this.iconGroup_ = - dom.createSvgElement(Svg.G, {'class': 'blocklyIconGroup'}, null); - if (this.block_.isInFlyout) { - dom.addClass( - /** @type {!Element} */ (this.iconGroup_), 'blocklyIconGroupReadonly'); + + /** + * Notification that the icon has moved. Update the arrow accordingly. + * @param {!Coordinate} xy Absolute location in workspace coordinates. + */ + setIconLocation(xy) { + this.iconXY_ = xy; + if (this.isVisible()) { + this.bubble_.setAnchorLocation(xy); + } } - this.drawIcon_(this.iconGroup_); - this.block_.getSvgRoot().appendChild(this.iconGroup_); - browserEvents.conditionalBind( - this.iconGroup_, 'mouseup', this, this.iconClick_); - this.updateEditable(); -}; - -/** - * Dispose of this icon. - */ -Icon.prototype.dispose = function() { - // Dispose of and unlink the icon. - dom.removeNode(this.iconGroup_); - this.iconGroup_ = null; - // Dispose of and unlink the bubble. - this.setVisible(false); - this.block_ = null; -}; - -/** - * Add or remove the UI indicating if this icon may be clicked or not. - */ -Icon.prototype.updateEditable = function() { - // No-op on the base class. -}; - -/** - * Is the associated bubble visible? - * @return {boolean} True if the bubble is visible. - */ -Icon.prototype.isVisible = function() { - return !!this.bubble_; -}; - -/** - * Clicking on the icon toggles if the bubble is visible. - * @param {!Event} e Mouse click event. - * @protected - */ -Icon.prototype.iconClick_ = function(e) { - if (this.block_.workspace.isDragging()) { - // Drag operation is concluding. Don't open the editor. - return; + /** + * Notification that the icon has moved, but we don't really know where. + * Recompute the icon's location from scratch. + */ + computeIconLocation() { + // Find coordinates for the centre of the icon and update the arrow. + const blockXY = this.block_.getRelativeToSurfaceXY(); + const iconXY = svgMath.getRelativeXY( + /** @type {!SVGElement} */ (this.iconGroup_)); + const newXY = new Coordinate( + blockXY.x + iconXY.x + this.SIZE / 2, + blockXY.y + iconXY.y + this.SIZE / 2); + if (!Coordinate.equals(this.getIconLocation(), newXY)) { + this.setIconLocation(newXY); + } } - if (!this.block_.isInFlyout && !browserEvents.isRightButton(e)) { - this.setVisible(!this.isVisible()); + + /** + * Returns the center of the block's icon relative to the surface. + * @return {?Coordinate} Object with x and y properties in + * workspace coordinates. + */ + getIconLocation() { + return this.iconXY_; } -}; -/** - * Change the colour of the associated bubble to match its block. - */ -Icon.prototype.applyColour = function() { - if (this.isVisible()) { - this.bubble_.setColour(this.block_.style.colourPrimary); + /** + * Get the size of the icon as used for rendering. + * This differs from the actual size of the icon, because it bulges slightly + * out of its row rather than increasing the height of its row. + * @return {!Size} Height and width. + */ + getCorrectedSize() { + // TODO (#2562): Remove getCorrectedSize. + return new Size(this.SIZE, this.SIZE - 2); } -}; -/** - * Notification that the icon has moved. Update the arrow accordingly. - * @param {!Coordinate} xy Absolute location in workspace coordinates. - */ -Icon.prototype.setIconLocation = function(xy) { - this.iconXY_ = xy; - if (this.isVisible()) { - this.bubble_.setAnchorLocation(xy); + /** + * Draw the icon. + * @param {!Element} _group The icon group. + * @protected + */ + drawIcon_(_group) { + // No-op on base class. } -}; -/** - * Notification that the icon has moved, but we don't really know where. - * Recompute the icon's location from scratch. - */ -Icon.prototype.computeIconLocation = function() { - // Find coordinates for the centre of the icon and update the arrow. - const blockXY = this.block_.getRelativeToSurfaceXY(); - const iconXY = svgMath.getRelativeXY( - /** @type {!SVGElement} */ (this.iconGroup_)); - const newXY = new Coordinate( - blockXY.x + iconXY.x + this.SIZE / 2, - blockXY.y + iconXY.y + this.SIZE / 2); - if (!Coordinate.equals(this.getIconLocation(), newXY)) { - this.setIconLocation(newXY); + /** + * Show or hide the icon. + * @param {boolean} _visible True if the icon should be visible. + */ + setVisible(_visible) { + // No-op on base class } -}; - -/** - * Returns the center of the block's icon relative to the surface. - * @return {?Coordinate} Object with x and y properties in - * workspace coordinates. - */ -Icon.prototype.getIconLocation = function() { - return this.iconXY_; -}; - -/** - * Get the size of the icon as used for rendering. - * This differs from the actual size of the icon, because it bulges slightly - * out of its row rather than increasing the height of its row. - * @return {!Size} Height and width. - */ -// TODO (#2562): Remove getCorrectedSize. -Icon.prototype.getCorrectedSize = function() { - return new Size(Icon.prototype.SIZE, Icon.prototype.SIZE - 2); -}; - -/** - * Draw the icon. - * @param {!Element} group The icon group. - * @protected - */ -Icon.prototype.drawIcon_; - -/** - * Show or hide the icon. - * @param {boolean} visible True if the icon should be visible. - */ -Icon.prototype.setVisible; +} exports.Icon = Icon; diff --git a/core/inject.js b/core/inject.js index ef6c0ed88..3493f9f2c 100644 --- a/core/inject.js +++ b/core/inject.js @@ -24,11 +24,11 @@ const browserEvents = goog.require('Blockly.browserEvents'); const bumpObjects = goog.require('Blockly.bumpObjects'); const common = goog.require('Blockly.common'); const dom = goog.require('Blockly.utils.dom'); +const dropDownDiv = goog.require('Blockly.dropDownDiv'); const userAgent = goog.require('Blockly.utils.userAgent'); const {BlockDragSurfaceSvg} = goog.require('Blockly.BlockDragSurfaceSvg'); /* eslint-disable-next-line no-unused-vars */ const {BlocklyOptions} = goog.requireType('Blockly.BlocklyOptions'); -const {DropDownDiv} = goog.require('Blockly.DropDownDiv'); const {Grid} = goog.require('Blockly.Grid'); const {Msg} = goog.require('Blockly.Msg'); const {Options} = goog.require('Blockly.Options'); @@ -59,7 +59,8 @@ const inject = function(container, opt_options) { } const options = new Options(opt_options || (/** @type {!BlocklyOptions} */ ({}))); - const subContainer = document.createElement('div'); + const subContainer = + /** @type {!HTMLDivElement} */ (document.createElement('div')); subContainer.className = 'injectionDiv'; subContainer.tabIndex = 0; aria.setState(subContainer, aria.State.LABEL, Msg['WORKSPACE_ARIA_LABEL']); @@ -192,7 +193,7 @@ const createMainWorkspace = function( // The SVG is now fully assembled. common.svgResize(mainWorkspace); WidgetDiv.createDom(); - DropDownDiv.createDom(); + dropDownDiv.createDom(); Tooltip.createDom(); return mainWorkspace; }; @@ -274,7 +275,8 @@ const init = function(mainWorkspace) { // TODO (https://github.com/google/blockly/issues/1998) handle cases where there // are multiple workspaces and non-main workspaces are able to accept input. const onKeyDown = function(e) { - const mainWorkspace = common.getMainWorkspace(); + const mainWorkspace = + /** @type {!WorkspaceSvg} */ (common.getMainWorkspace()); if (!mainWorkspace) { return; } @@ -337,7 +339,7 @@ const bindDocumentEvents = function() { /** * Load sounds for the given workspace. * @param {string} pathToMedia The path to the media directory. - * @param {!Workspace} workspace The workspace to load sounds for. + * @param {!WorkspaceSvg} workspace The workspace to load sounds for. */ const loadSounds = function(pathToMedia, workspace) { const audioMgr = workspace.getAudioManager(); diff --git a/core/input.js b/core/input.js index edc0c5c62..4427a5204 100644 --- a/core/input.js +++ b/core/input.js @@ -15,18 +15,6 @@ */ goog.module('Blockly.Input'); -/** - * Enum for alignment of inputs. - * @enum {number} - * @alias Blockly.Input.Align - */ -const Align = { - LEFT: -1, - CENTRE: 0, - RIGHT: 1, -}; -exports.Align = Align; - const fieldRegistry = goog.require('Blockly.fieldRegistry'); /* eslint-disable-next-line no-unused-vars */ const {BlockSvg} = goog.requireType('Blockly.BlockSvg'); @@ -42,285 +30,306 @@ const {inputTypes} = goog.require('Blockly.inputTypes'); /** @suppress {extraRequire} */ goog.require('Blockly.FieldLabel'); + /** * Class for an input with an optional field. - * @param {number} type The type of the input. - * @param {string} name Language-neutral identifier which may used to find this - * input again. - * @param {!Block} block The block containing this input. - * @param {Connection} connection Optional connection for this input. - * @constructor * @alias Blockly.Input */ -const Input = function(type, name, block, connection) { - if (type !== inputTypes.DUMMY && !name) { - throw Error('Value inputs and statement inputs must have non-empty name.'); - } - /** @type {number} */ - this.type = type; - /** @type {string} */ - this.name = name; +class Input { /** - * @type {!Block} - * @private + * @param {number} type The type of the input. + * @param {string} name Language-neutral identifier which may used to find + * this input again. + * @param {!Block} block The block containing this input. + * @param {Connection} connection Optional connection for this input. */ - this.sourceBlock_ = block; - /** @type {Connection} */ - this.connection = connection; - /** @type {!Array} */ - this.fieldRow = []; -}; + constructor(type, name, block, connection) { + if (type !== inputTypes.DUMMY && !name) { + throw Error( + 'Value inputs and statement inputs must have non-empty name.'); + } + /** @type {number} */ + this.type = type; + /** @type {string} */ + this.name = name; + /** + * @type {!Block} + * @private + */ + this.sourceBlock_ = block; + /** @type {Connection} */ + this.connection = connection; + /** @type {!Array} */ + this.fieldRow = []; -/** - * Alignment of input's fields (left, right or centre). - * @type {number} - */ -Input.prototype.align = Align.LEFT; + /** + * Alignment of input's fields (left, right or centre). + * @type {number} + */ + this.align = Align.LEFT; -/** - * Is the input visible? - * @type {boolean} - * @private - */ -Input.prototype.visible_ = true; - -/** - * Get the source block for this input. - * @return {?Block} The source block, or null if there is none. - */ -Input.prototype.getSourceBlock = function() { - return this.sourceBlock_; -}; - -/** - * Add a field (or label from string), and all prefix and suffix fields, to the - * end of the input's field row. - * @param {string|!Field} field Something to add as a field. - * @param {string=} opt_name Language-neutral identifier which may used to find - * this field again. Should be unique to the host block. - * @return {!Input} The input being append to (to allow chaining). - */ -Input.prototype.appendField = function(field, opt_name) { - this.insertFieldAt(this.fieldRow.length, field, opt_name); - return this; -}; - -/** - * Inserts a field (or label from string), and all prefix and suffix fields, at - * the location of the input's field row. - * @param {number} index The index at which to insert field. - * @param {string|!Field} field Something to add as a field. - * @param {string=} opt_name Language-neutral identifier which may used to find - * this field again. Should be unique to the host block. - * @return {number} The index following the last inserted field. - */ -Input.prototype.insertFieldAt = function(index, field, opt_name) { - if (index < 0 || index > this.fieldRow.length) { - throw Error('index ' + index + ' out of bounds.'); + /** + * Is the input visible? + * @type {boolean} + * @private + */ + this.visible_ = true; } - // Falsy field values don't generate a field, unless the field is an empty - // string and named. - if (!field && !(field === '' && opt_name)) { + + /** + * Get the source block for this input. + * @return {?Block} The source block, or null if there is none. + */ + getSourceBlock() { + return this.sourceBlock_; + } + + /** + * Add a field (or label from string), and all prefix and suffix fields, to + * the end of the input's field row. + * @param {string|!Field} field Something to add as a field. + * @param {string=} opt_name Language-neutral identifier which may used to + * find this field again. Should be unique to the host block. + * @return {!Input} The input being append to (to allow chaining). + */ + appendField(field, opt_name) { + this.insertFieldAt(this.fieldRow.length, field, opt_name); + return this; + } + + /** + * Inserts a field (or label from string), and all prefix and suffix fields, + * at the location of the input's field row. + * @param {number} index The index at which to insert field. + * @param {string|!Field} field Something to add as a field. + * @param {string=} opt_name Language-neutral identifier which may used to + * find this field again. Should be unique to the host block. + * @return {number} The index following the last inserted field. + */ + insertFieldAt(index, field, opt_name) { + if (index < 0 || index > this.fieldRow.length) { + throw Error('index ' + index + ' out of bounds.'); + } + // Falsy field values don't generate a field, unless the field is an empty + // string and named. + if (!field && !(field === '' && opt_name)) { + return index; + } + + // Generate a FieldLabel when given a plain text field. + if (typeof field === 'string') { + field = /** @type {!Field} **/ (fieldRegistry.fromJson({ + 'type': 'field_label', + 'text': field, + })); + } + + field.setSourceBlock(this.sourceBlock_); + if (this.sourceBlock_.rendered) { + field.init(); + field.applyColour(); + } + field.name = opt_name; + field.setVisible(this.isVisible()); + + if (field.prefixField) { + // Add any prefix. + index = this.insertFieldAt(index, field.prefixField); + } + // Add the field to the field row. + this.fieldRow.splice(index, 0, field); + index++; + if (field.suffixField) { + // Add any suffix. + index = this.insertFieldAt(index, field.suffixField); + } + + if (this.sourceBlock_.rendered) { + this.sourceBlock_ = /** @type {!BlockSvg} */ (this.sourceBlock_); + this.sourceBlock_.render(); + // Adding a field will cause the block to change shape. + this.sourceBlock_.bumpNeighbours(); + } return index; } - // Generate a FieldLabel when given a plain text field. - if (typeof field === 'string') { - field = /** @type {!Field} **/ (fieldRegistry.fromJson({ - 'type': 'field_label', - 'text': field, - })); - } - - field.setSourceBlock(this.sourceBlock_); - if (this.sourceBlock_.rendered) { - field.init(); - field.applyColour(); - } - field.name = opt_name; - field.setVisible(this.isVisible()); - - if (field.prefixField) { - // Add any prefix. - index = this.insertFieldAt(index, field.prefixField); - } - // Add the field to the field row. - this.fieldRow.splice(index, 0, field); - index++; - if (field.suffixField) { - // Add any suffix. - index = this.insertFieldAt(index, field.suffixField); - } - - if (this.sourceBlock_.rendered) { - this.sourceBlock_ = /** @type {!BlockSvg} */ (this.sourceBlock_); - this.sourceBlock_.render(); - // Adding a field will cause the block to change shape. - this.sourceBlock_.bumpNeighbours(); - } - return index; -}; - -/** - * Remove a field from this input. - * @param {string} name The name of the field. - * @param {boolean=} opt_quiet True to prevent an error if field is not present. - * @return {boolean} True if operation succeeds, false if field is not present - * and opt_quiet is true. - * @throws {Error} if the field is not present and opt_quiet is false. - */ -Input.prototype.removeField = function(name, opt_quiet) { - for (let i = 0, field; (field = this.fieldRow[i]); i++) { - if (field.name === name) { - field.dispose(); - this.fieldRow.splice(i, 1); - if (this.sourceBlock_.rendered) { - this.sourceBlock_ = /** @type {!BlockSvg} */ (this.sourceBlock_); - this.sourceBlock_.render(); - // Removing a field will cause the block to change shape. - this.sourceBlock_.bumpNeighbours(); + /** + * Remove a field from this input. + * @param {string} name The name of the field. + * @param {boolean=} opt_quiet True to prevent an error if field is not + * present. + * @return {boolean} True if operation succeeds, false if field is not present + * and opt_quiet is true. + * @throws {Error} if the field is not present and opt_quiet is false. + */ + removeField(name, opt_quiet) { + for (let i = 0, field; (field = this.fieldRow[i]); i++) { + if (field.name === name) { + field.dispose(); + this.fieldRow.splice(i, 1); + if (this.sourceBlock_.rendered) { + this.sourceBlock_ = /** @type {!BlockSvg} */ (this.sourceBlock_); + this.sourceBlock_.render(); + // Removing a field will cause the block to change shape. + this.sourceBlock_.bumpNeighbours(); + } + return true; } - return true; } + if (opt_quiet) { + return false; + } + throw Error('Field "' + name + '" not found.'); } - if (opt_quiet) { - return false; + + /** + * Gets whether this input is visible or not. + * @return {boolean} True if visible. + */ + isVisible() { + return this.visible_; } - throw Error('Field "' + name + '" not found.'); -}; -/** - * Gets whether this input is visible or not. - * @return {boolean} True if visible. - */ -Input.prototype.isVisible = function() { - return this.visible_; -}; + /** + * Sets whether this input is visible or not. + * Should only be used to collapse/uncollapse a block. + * @param {boolean} visible True if visible. + * @return {!Array} List of blocks to render. + * @package + */ + setVisible(visible) { + // Note: Currently there are only unit tests for block.setCollapsed() + // because this function is package. If this function goes back to being a + // public API tests (lots of tests) should be added. + let renderList = []; + if (this.visible_ === visible) { + return renderList; + } + this.visible_ = visible; -/** - * Sets whether this input is visible or not. - * Should only be used to collapse/uncollapse a block. - * @param {boolean} visible True if visible. - * @return {!Array} List of blocks to render. - * @package - */ -Input.prototype.setVisible = function(visible) { - // Note: Currently there are only unit tests for block.setCollapsed() - // because this function is package. If this function goes back to being a - // public API tests (lots of tests) should be added. - let renderList = []; - if (this.visible_ === visible) { + for (let y = 0, field; (field = this.fieldRow[y]); y++) { + field.setVisible(visible); + } + if (this.connection) { + this.connection = + /** @type {!RenderedConnection} */ (this.connection); + // Has a connection. + if (visible) { + renderList = this.connection.startTrackingAll(); + } else { + this.connection.stopTrackingAll(); + } + const child = this.connection.targetBlock(); + if (child) { + child.getSvgRoot().style.display = visible ? 'block' : 'none'; + } + } return renderList; } - this.visible_ = visible; - for (let y = 0, field; (field = this.fieldRow[y]); y++) { - field.setVisible(visible); - } - if (this.connection) { - this.connection = - /** @type {!RenderedConnection} */ (this.connection); - // Has a connection. - if (visible) { - renderList = this.connection.startTrackingAll(); - } else { - this.connection.stopTrackingAll(); - } - const child = this.connection.targetBlock(); - if (child) { - child.getSvgRoot().style.display = visible ? 'block' : 'none'; + /** + * Mark all fields on this input as dirty. + * @package + */ + markDirty() { + for (let y = 0, field; (field = this.fieldRow[y]); y++) { + field.markDirty(); } } - return renderList; -}; + + /** + * Change a connection's compatibility. + * @param {string|Array|null} check Compatible value type or + * list of value types. Null if all types are compatible. + * @return {!Input} The input being modified (to allow chaining). + */ + setCheck(check) { + if (!this.connection) { + throw Error('This input does not have a connection.'); + } + this.connection.setCheck(check); + return this; + } + + /** + * Change the alignment of the connection's field(s). + * @param {number} align One of the values of Align + * In RTL mode directions are reversed, and Align.RIGHT aligns to the left. + * @return {!Input} The input being modified (to allow chaining). + */ + setAlign(align) { + this.align = align; + if (this.sourceBlock_.rendered) { + this.sourceBlock_ = /** @type {!BlockSvg} */ (this.sourceBlock_); + this.sourceBlock_.render(); + } + return this; + } + + /** + * Changes the connection's shadow block. + * @param {?Element} shadow DOM representation of a block or null. + * @return {!Input} The input being modified (to allow chaining). + */ + setShadowDom(shadow) { + if (!this.connection) { + throw Error('This input does not have a connection.'); + } + this.connection.setShadowDom(shadow); + return this; + } + + /** + * Returns the XML representation of the connection's shadow block. + * @return {?Element} Shadow DOM representation of a block or null. + */ + getShadowDom() { + if (!this.connection) { + throw Error('This input does not have a connection.'); + } + return this.connection.getShadowDom(); + } + + /** + * Initialize the fields on this input. + */ + init() { + if (!this.sourceBlock_.workspace.rendered) { + return; // Headless blocks don't need fields initialized. + } + for (let i = 0; i < this.fieldRow.length; i++) { + this.fieldRow[i].init(); + } + } + + /** + * Sever all links to this input. + * @suppress {checkTypes} + */ + dispose() { + for (let i = 0, field; (field = this.fieldRow[i]); i++) { + field.dispose(); + } + if (this.connection) { + this.connection.dispose(); + } + this.sourceBlock_ = null; + } +} /** - * Mark all fields on this input as dirty. - * @package + * Enum for alignment of inputs. + * @enum {number} + * @alias Blockly.Input.Align */ -Input.prototype.markDirty = function() { - for (let y = 0, field; (field = this.fieldRow[y]); y++) { - field.markDirty(); - } +const Align = { + LEFT: -1, + CENTRE: 0, + RIGHT: 1, }; +exports.Align = Align; -/** - * Change a connection's compatibility. - * @param {string|Array|null} check Compatible value type or - * list of value types. Null if all types are compatible. - * @return {!Input} The input being modified (to allow chaining). - */ -Input.prototype.setCheck = function(check) { - if (!this.connection) { - throw Error('This input does not have a connection.'); - } - this.connection.setCheck(check); - return this; -}; - -/** - * Change the alignment of the connection's field(s). - * @param {number} align One of the values of Align - * In RTL mode directions are reversed, and Align.RIGHT aligns to the left. - * @return {!Input} The input being modified (to allow chaining). - */ -Input.prototype.setAlign = function(align) { - this.align = align; - if (this.sourceBlock_.rendered) { - this.sourceBlock_ = /** @type {!BlockSvg} */ (this.sourceBlock_); - this.sourceBlock_.render(); - } - return this; -}; - -/** - * Changes the connection's shadow block. - * @param {?Element} shadow DOM representation of a block or null. - * @return {!Input} The input being modified (to allow chaining). - */ -Input.prototype.setShadowDom = function(shadow) { - if (!this.connection) { - throw Error('This input does not have a connection.'); - } - this.connection.setShadowDom(shadow); - return this; -}; - -/** - * Returns the XML representation of the connection's shadow block. - * @return {?Element} Shadow DOM representation of a block or null. - */ -Input.prototype.getShadowDom = function() { - if (!this.connection) { - throw Error('This input does not have a connection.'); - } - return this.connection.getShadowDom(); -}; - -/** - * Initialize the fields on this input. - */ -Input.prototype.init = function() { - if (!this.sourceBlock_.workspace.rendered) { - return; // Headless blocks don't need fields initialized. - } - for (let i = 0; i < this.fieldRow.length; i++) { - this.fieldRow[i].init(); - } -}; - -/** - * Sever all links to this input. - * @suppress {checkTypes} - */ -Input.prototype.dispose = function() { - for (let i = 0, field; (field = this.fieldRow[i]); i++) { - field.dispose(); - } - if (this.connection) { - this.connection.dispose(); - } - this.sourceBlock_ = null; -}; +// Add Align to Input so that `Blockly.Input.Align` is publicly accessible. +Input.Align = Align; exports.Input = Input; diff --git a/core/insertion_marker_manager.js b/core/insertion_marker_manager.js index df86ecc8c..20155bc84 100644 --- a/core/insertion_marker_manager.js +++ b/core/insertion_marker_manager.js @@ -19,10 +19,10 @@ const blockAnimations = goog.require('Blockly.blockAnimations'); const common = goog.require('Blockly.common'); const constants = goog.require('Blockly.constants'); const eventUtils = goog.require('Blockly.Events.utils'); -const internalConstants = goog.require('Blockly.internalConstants'); /* eslint-disable-next-line no-unused-vars */ const {BlockSvg} = goog.requireType('Blockly.BlockSvg'); const {ComponentManager} = goog.require('Blockly.ComponentManager'); +const {config} = goog.require('Blockly.config'); const {ConnectionType} = goog.require('Blockly.ConnectionType'); /* eslint-disable-next-line no-unused-vars */ const {Coordinate} = goog.requireType('Blockly.utils.Coordinate'); @@ -36,119 +36,745 @@ const {RenderedConnection} = goog.requireType('Blockly.RenderedConnection'); const {WorkspaceSvg} = goog.requireType('Blockly.WorkspaceSvg'); +/** + * An error message to throw if the block created by createMarkerBlock_ is + * missing any components. + * @type {string} + * @const + */ +const DUPLICATE_BLOCK_ERROR = 'The insertion marker ' + + 'manager tried to create a marker but the result is missing %1. If ' + + 'you are using a mutator, make sure your domToMutation method is ' + + 'properly defined.'; + + /** * Class that controls updates to connections during drags. It is primarily * responsible for finding the closest eligible connection and highlighting or * unhighlighting it as needed during a drag. - * @param {!BlockSvg} block The top block in the stack being dragged. - * @constructor * @alias Blockly.InsertionMarkerManager */ -const InsertionMarkerManager = function(block) { - common.setSelected(block); +class InsertionMarkerManager { + /** + * @param {!BlockSvg} block The top block in the stack being dragged. + */ + constructor(block) { + common.setSelected(block); + + /** + * The top block in the stack being dragged. + * Does not change during a drag. + * @type {!BlockSvg} + * @private + */ + this.topBlock_ = block; + + /** + * The workspace on which these connections are being dragged. + * Does not change during a drag. + * @type {!WorkspaceSvg} + * @private + */ + this.workspace_ = block.workspace; + + /** + * The last connection on the stack, if it's not the last connection on the + * first block. + * Set in initAvailableConnections, if at all. + * @type {RenderedConnection} + * @private + */ + this.lastOnStack_ = null; + + /** + * The insertion marker corresponding to the last block in the stack, if + * that's not the same as the first block in the stack. + * Set in initAvailableConnections, if at all + * @type {BlockSvg} + * @private + */ + this.lastMarker_ = null; + + /** + * The insertion marker that shows up between blocks to show where a block + * would go if dropped immediately. + * @type {BlockSvg} + * @private + */ + this.firstMarker_ = this.createMarkerBlock_(this.topBlock_); + + /** + * The connection that this block would connect to if released immediately. + * Updated on every mouse move. + * This is not on any of the blocks that are being dragged. + * @type {RenderedConnection} + * @private + */ + this.closestConnection_ = null; + + /** + * The connection that would connect to this.closestConnection_ if this + * block were released immediately. Updated on every mouse move. This is on + * the top block that is being dragged or the last block in the dragging + * stack. + * @type {RenderedConnection} + * @private + */ + this.localConnection_ = null; + + /** + * Whether the block would be deleted if it were dropped immediately. + * Updated on every mouse move. + * @type {boolean} + * @private + */ + this.wouldDeleteBlock_ = false; + + /** + * Connection on the insertion marker block that corresponds to + * this.localConnection_ on the currently dragged block. + * @type {RenderedConnection} + * @private + */ + this.markerConnection_ = null; + + /** + * The block that currently has an input being highlighted, or null. + * @type {BlockSvg} + * @private + */ + this.highlightedBlock_ = null; + + /** + * The block being faded to indicate replacement, or null. + * @type {BlockSvg} + * @private + */ + this.fadedBlock_ = null; + + /** + * The connections on the dragging blocks that are available to connect to + * other blocks. This includes all open connections on the top block, as + * well as the last connection on the block stack. Does not change during a + * drag. + * @type {!Array} + * @private + */ + this.availableConnections_ = this.initAvailableConnections_(); + } /** - * The top block in the stack being dragged. - * Does not change during a drag. - * @type {!BlockSvg} - * @private + * Sever all links from this object. + * @package */ - this.topBlock_ = block; + dispose() { + this.availableConnections_.length = 0; + + eventUtils.disable(); + try { + if (this.firstMarker_) { + this.firstMarker_.dispose(); + } + if (this.lastMarker_) { + this.lastMarker_.dispose(); + } + } finally { + eventUtils.enable(); + } + } /** - * The workspace on which these connections are being dragged. - * Does not change during a drag. - * @type {!WorkspaceSvg} - * @private + * Update the available connections for the top block. These connections can + * change if a block is unplugged and the stack is healed. + * @package */ - this.workspace_ = block.workspace; + updateAvailableConnections() { + this.availableConnections_ = this.initAvailableConnections_(); + } /** - * The last connection on the stack, if it's not the last connection on the - * first block. - * Set in initAvailableConnections, if at all. - * @type {RenderedConnection} - * @private + * Return whether the block would be deleted if dropped immediately, based on + * information from the most recent move event. + * @return {boolean} True if the block would be deleted if dropped + * immediately. + * @package */ - this.lastOnStack_ = null; + wouldDeleteBlock() { + return this.wouldDeleteBlock_; + } /** - * The insertion marker corresponding to the last block in the stack, if - * that's not the same as the first block in the stack. - * Set in initAvailableConnections, if at all - * @type {BlockSvg} - * @private + * Return whether the block would be connected if dropped immediately, based + * on information from the most recent move event. + * @return {boolean} True if the block would be connected if dropped + * immediately. + * @package */ - this.lastMarker_ = null; + wouldConnectBlock() { + return !!this.closestConnection_; + } /** - * The insertion marker that shows up between blocks to show where a block - * would go if dropped immediately. - * @type {BlockSvg} - * @private + * Connect to the closest connection and render the results. + * This should be called at the end of a drag. + * @package */ - this.firstMarker_ = this.createMarkerBlock_(this.topBlock_); + applyConnections() { + if (this.closestConnection_) { + // Don't fire events for insertion markers. + eventUtils.disable(); + this.hidePreview_(); + eventUtils.enable(); + // Connect two blocks together. + this.localConnection_.connect(this.closestConnection_); + if (this.topBlock_.rendered) { + // Trigger a connection animation. + // Determine which connection is inferior (lower in the source stack). + const inferiorConnection = this.localConnection_.isSuperior() ? + this.closestConnection_ : + this.localConnection_; + blockAnimations.connectionUiEffect(inferiorConnection.getSourceBlock()); + // Bring the just-edited stack to the front. + const rootBlock = this.topBlock_.getRootBlock(); + rootBlock.bringToFront(); + } + } + } /** - * The connection that this block would connect to if released immediately. - * Updated on every mouse move. - * This is not on any of the blocks that are being dragged. - * @type {RenderedConnection} - * @private + * Update connections based on the most recent move location. + * @param {!Coordinate} dxy Position relative to drag start, + * in workspace units. + * @param {?IDragTarget} dragTarget The drag target that the block is + * currently over. + * @package */ - this.closestConnection_ = null; + update(dxy, dragTarget) { + const candidate = this.getCandidate_(dxy); + + this.wouldDeleteBlock_ = this.shouldDelete_(candidate, dragTarget); + + const shouldUpdate = + this.wouldDeleteBlock_ || this.shouldUpdatePreviews_(candidate, dxy); + + if (shouldUpdate) { + // Don't fire events for insertion marker creation or movement. + eventUtils.disable(); + this.maybeHidePreview_(candidate); + this.maybeShowPreview_(candidate); + eventUtils.enable(); + } + } /** - * The connection that would connect to this.closestConnection_ if this block - * were released immediately. - * Updated on every mouse move. - * This is on the top block that is being dragged or the last block in the - * dragging stack. - * @type {RenderedConnection} + * Create an insertion marker that represents the given block. + * @param {!BlockSvg} sourceBlock The block that the insertion marker + * will represent. + * @return {!BlockSvg} The insertion marker that represents the given + * block. * @private */ - this.localConnection_ = null; + createMarkerBlock_(sourceBlock) { + const imType = sourceBlock.type; + + eventUtils.disable(); + let result; + try { + result = this.workspace_.newBlock(imType); + result.setInsertionMarker(true); + if (sourceBlock.saveExtraState) { + const state = sourceBlock.saveExtraState(); + if (state) { + result.loadExtraState(state); + } + } else if (sourceBlock.mutationToDom) { + const oldMutationDom = sourceBlock.mutationToDom(); + if (oldMutationDom) { + result.domToMutation(oldMutationDom); + } + } + // Copy field values from the other block. These values may impact the + // rendered size of the insertion marker. Note that we do not care about + // child blocks here. + for (let i = 0; i < sourceBlock.inputList.length; i++) { + const sourceInput = sourceBlock.inputList[i]; + if (sourceInput.name === constants.COLLAPSED_INPUT_NAME) { + continue; // Ignore the collapsed input. + } + const resultInput = result.inputList[i]; + if (!resultInput) { + throw new Error(DUPLICATE_BLOCK_ERROR.replace('%1', 'an input')); + } + for (let j = 0; j < sourceInput.fieldRow.length; j++) { + const sourceField = sourceInput.fieldRow[j]; + const resultField = resultInput.fieldRow[j]; + if (!resultField) { + throw new Error(DUPLICATE_BLOCK_ERROR.replace('%1', 'a field')); + } + resultField.setValue(sourceField.getValue()); + } + } + + result.setCollapsed(sourceBlock.isCollapsed()); + result.setInputsInline(sourceBlock.getInputsInline()); + + result.initSvg(); + result.getSvgRoot().setAttribute('visibility', 'hidden'); + } finally { + eventUtils.enable(); + } + + return result; + } /** - * Whether the block would be deleted if it were dropped immediately. - * Updated on every mouse move. - * @type {boolean} + * Populate the list of available connections on this block stack. This + * should only be called once, at the beginning of a drag. If the stack has + * more than one block, this function will populate lastOnStack_ and create + * the corresponding insertion marker. + * @return {!Array} A list of available + * connections. * @private */ - this.wouldDeleteBlock_ = false; + initAvailableConnections_() { + const available = this.topBlock_.getConnections_(false); + // Also check the last connection on this stack + const lastOnStack = this.topBlock_.lastConnectionInStack(true); + if (lastOnStack && lastOnStack !== this.topBlock_.nextConnection) { + available.push(lastOnStack); + this.lastOnStack_ = lastOnStack; + if (this.lastMarker_) { + eventUtils.disable(); + try { + this.lastMarker_.dispose(); + } finally { + eventUtils.enable(); + } + } + this.lastMarker_ = this.createMarkerBlock_(lastOnStack.getSourceBlock()); + } + return available; + } /** - * Connection on the insertion marker block that corresponds to - * this.localConnection_ on the currently dragged block. - * @type {RenderedConnection} + * Whether the previews (insertion marker and replacement marker) should be + * updated based on the closest candidate and the current drag distance. + * @param {!Object} candidate An object containing a local connection, a + * closest connection, and a radius. Returned by getCandidate_. + * @param {!Coordinate} dxy Position relative to drag start, + * in workspace units. + * @return {boolean} Whether the preview should be updated. * @private */ - this.markerConnection_ = null; + shouldUpdatePreviews_(candidate, dxy) { + const candidateLocal = candidate.local; + const candidateClosest = candidate.closest; + const radius = candidate.radius; + + // Found a connection! + if (candidateLocal && candidateClosest) { + // We're already showing an insertion marker. + // Decide whether the new connection has higher priority. + if (this.localConnection_ && this.closestConnection_) { + // The connection was the same as the current connection. + if (this.closestConnection_ === candidateClosest && + this.localConnection_ === candidateLocal) { + return false; + } + const xDiff = + this.localConnection_.x + dxy.x - this.closestConnection_.x; + const yDiff = + this.localConnection_.y + dxy.y - this.closestConnection_.y; + const curDistance = Math.sqrt(xDiff * xDiff + yDiff * yDiff); + // Slightly prefer the existing preview over a new preview. + return !( + candidateClosest && + radius > curDistance - config.currentConnectionPreference); + } else if (!this.localConnection_ && !this.closestConnection_) { + // We weren't showing a preview before, but we should now. + return true; + } else { + console.error( + 'Only one of localConnection_ and closestConnection_ was set.'); + } + } else { // No connection found. + // Only need to update if we were showing a preview before. + return !!(this.localConnection_ && this.closestConnection_); + } + + console.error( + 'Returning true from shouldUpdatePreviews, but it\'s not clear why.'); + return true; + } /** - * The block that currently has an input being highlighted, or null. - * @type {BlockSvg} + * Find the nearest valid connection, which may be the same as the current + * closest connection. + * @param {!Coordinate} dxy Position relative to drag start, + * in workspace units. + * @return {!Object} An object containing a local connection, a closest + * connection, and a radius. * @private */ - this.highlightedBlock_ = null; + getCandidate_(dxy) { + let radius = this.getStartRadius_(); + let candidateClosest = null; + let candidateLocal = null; + + // It's possible that a block has added or removed connections during a + // drag, (e.g. in a drag/move event handler), so let's update the available + // connections. Note that this will be called on every move while dragging, + // so it might cause slowness, especially if the block stack is large. If + // so, maybe it could be made more efficient. Also note that we won't update + // the connections if we've already connected the insertion marker to a + // block. + if (!this.markerConnection_ || !this.markerConnection_.isConnected()) { + this.updateAvailableConnections(); + } + + for (let i = 0; i < this.availableConnections_.length; i++) { + const myConnection = this.availableConnections_[i]; + const neighbour = myConnection.closest(radius, dxy); + if (neighbour.connection) { + candidateClosest = neighbour.connection; + candidateLocal = myConnection; + radius = neighbour.radius; + } + } + return {closest: candidateClosest, local: candidateLocal, radius: radius}; + } /** - * The block being faded to indicate replacement, or null. - * @type {BlockSvg} + * Decide the radius at which to start searching for the closest connection. + * @return {number} The radius at which to start the search for the closest + * connection. * @private */ - this.fadedBlock_ = null; + getStartRadius_() { + // If there is already a connection highlighted, + // increase the radius we check for making new connections. + // Why? When a connection is highlighted, blocks move around when the + // insertion marker is created, which could cause the connection became out + // of range. By increasing radiusConnection when a connection already + // exists, we never "lose" the connection from the offset. + if (this.closestConnection_ && this.localConnection_) { + return config.connectingSnapRadius; + } + return config.snapRadius; + } /** - * The connections on the dragging blocks that are available to connect to - * other blocks. This includes all open connections on the top block, as well - * as the last connection on the block stack. - * Does not change during a drag. - * @type {!Array} + * Whether ending the drag would delete the block. + * @param {!Object} candidate An object containing a local connection, a + * closest + * connection, and a radius. + * @param {?IDragTarget} dragTarget The drag target that the block is + * currently over. + * @return {boolean} Whether dropping the block immediately would delete the + * block. * @private */ - this.availableConnections_ = this.initAvailableConnections_(); -}; + shouldDelete_(candidate, dragTarget) { + if (dragTarget) { + const componentManager = this.workspace_.getComponentManager(); + const isDeleteArea = componentManager.hasCapability( + dragTarget.id, ComponentManager.Capability.DELETE_AREA); + if (isDeleteArea) { + return ( + /** @type {!IDeleteArea} */ (dragTarget)) + .wouldDelete(this.topBlock_, candidate && !!candidate.closest); + } + } + return false; + } + + /** + * Show an insertion marker or replacement highlighting during a drag, if + * needed. + * At the beginning of this function, this.localConnection_ and + * this.closestConnection_ should both be null. + * @param {!Object} candidate An object containing a local connection, a + * closest connection, and a radius. + * @private + */ + maybeShowPreview_(candidate) { + // Nope, don't add a marker. + if (this.wouldDeleteBlock_) { + return; + } + const closest = candidate.closest; + const local = candidate.local; + + // Nothing to connect to. + if (!closest) { + return; + } + + // Something went wrong and we're trying to connect to an invalid + // connection. + if (closest === this.closestConnection_ || + closest.getSourceBlock().isInsertionMarker()) { + console.log('Trying to connect to an insertion marker'); + return; + } + // Add an insertion marker or replacement marker. + this.closestConnection_ = closest; + this.localConnection_ = local; + this.showPreview_(); + } + + /** + * A preview should be shown. This function figures out if it should be a + * block highlight or an insertion marker, and shows the appropriate one. + * @private + */ + showPreview_() { + const closest = this.closestConnection_; + const renderer = this.workspace_.getRenderer(); + const method = renderer.getConnectionPreviewMethod( + /** @type {!RenderedConnection} */ (closest), + /** @type {!RenderedConnection} */ (this.localConnection_), + this.topBlock_); + + switch (method) { + case InsertionMarkerManager.PREVIEW_TYPE.INPUT_OUTLINE: + this.showInsertionInputOutline_(); + break; + case InsertionMarkerManager.PREVIEW_TYPE.INSERTION_MARKER: + this.showInsertionMarker_(); + break; + case InsertionMarkerManager.PREVIEW_TYPE.REPLACEMENT_FADE: + this.showReplacementFade_(); + break; + } + + // Optionally highlight the actual connection, as a nod to previous + // behaviour. + if (closest && renderer.shouldHighlightConnection(closest)) { + closest.highlight(); + } + } + + /** + * Show an insertion marker or replacement highlighting during a drag, if + * needed. + * At the end of this function, this.localConnection_ and + * this.closestConnection_ should both be null. + * @param {!Object} candidate An object containing a local connection, a + * closest connection, and a radius. + * @private + */ + maybeHidePreview_(candidate) { + // If there's no new preview, remove the old one but don't bother deleting + // it. We might need it later, and this saves disposing of it and recreating + // it. + if (!candidate.closest) { + this.hidePreview_(); + } else { + // If there's a new preview and there was an preview before, and either + // connection has changed, remove the old preview. + const hadPreview = this.closestConnection_ && this.localConnection_; + const closestChanged = this.closestConnection_ !== candidate.closest; + const localChanged = this.localConnection_ !== candidate.local; + + // Also hide if we had a preview before but now we're going to delete + // instead. + if (hadPreview && + (closestChanged || localChanged || this.wouldDeleteBlock_)) { + this.hidePreview_(); + } + } + + // Either way, clear out old state. + this.markerConnection_ = null; + this.closestConnection_ = null; + this.localConnection_ = null; + } + + /** + * A preview should be hidden. This function figures out if it is a block + * highlight or an insertion marker, and hides the appropriate one. + * @private + */ + hidePreview_() { + if (this.closestConnection_ && this.closestConnection_.targetBlock() && + this.workspace_.getRenderer().shouldHighlightConnection( + this.closestConnection_)) { + this.closestConnection_.unhighlight(); + } + if (this.fadedBlock_) { + this.hideReplacementFade_(); + } else if (this.highlightedBlock_) { + this.hideInsertionInputOutline_(); + } else if (this.markerConnection_) { + this.hideInsertionMarker_(); + } + } + + /** + * Shows an insertion marker connected to the appropriate blocks (based on + * manager state). + * @private + */ + showInsertionMarker_() { + const local = this.localConnection_; + const closest = this.closestConnection_; + + const isLastInStack = this.lastOnStack_ && local === this.lastOnStack_; + let imBlock = isLastInStack ? this.lastMarker_ : this.firstMarker_; + let imConn; + try { + imConn = imBlock.getMatchingConnection(local.getSourceBlock(), local); + } catch (e) { + // It's possible that the number of connections on the local block has + // changed since the insertion marker was originally created. Let's + // recreate the insertion marker and try again. In theory we could + // probably recreate the marker block (e.g. in getCandidate_), which is + // called more often during the drag, but creating a block that often + // might be too slow, so we only do it if necessary. + this.firstMarker_ = this.createMarkerBlock_(this.topBlock_); + imBlock = isLastInStack ? this.lastMarker_ : this.firstMarker_; + imConn = imBlock.getMatchingConnection(local.getSourceBlock(), local); + } + + if (imConn === this.markerConnection_) { + throw Error( + 'Made it to showInsertionMarker_ even though the marker isn\'t ' + + 'changing'); + } + + // Render disconnected from everything else so that we have a valid + // connection location. + imBlock.render(); + imBlock.rendered = true; + imBlock.getSvgRoot().setAttribute('visibility', 'visible'); + + if (imConn && closest) { + // Position so that the existing block doesn't move. + imBlock.positionNearConnection(imConn, closest); + } + if (closest) { + // Connect() also renders the insertion marker. + imConn.connect(closest); + } + + this.markerConnection_ = imConn; + } + + /** + * Disconnects and hides the current insertion marker. Should return the + * blocks to their original state. + * @private + */ + hideInsertionMarker_() { + if (!this.markerConnection_) { + console.log('No insertion marker connection to disconnect'); + return; + } + + const imConn = this.markerConnection_; + const imBlock = imConn.getSourceBlock(); + const markerNext = imBlock.nextConnection; + const markerPrev = imBlock.previousConnection; + const markerOutput = imBlock.outputConnection; + + const isFirstInStatementStack = + (imConn === markerNext && !(markerPrev && markerPrev.targetConnection)); + + const isFirstInOutputStack = imConn.type === ConnectionType.INPUT_VALUE && + !(markerOutput && markerOutput.targetConnection); + // The insertion marker is the first block in a stack. Unplug won't do + // anything in that case. Instead, unplug the following block. + if (isFirstInStatementStack || isFirstInOutputStack) { + imConn.targetBlock().unplug(false); + } else if ( + imConn.type === ConnectionType.NEXT_STATEMENT && + imConn !== markerNext) { + // Inside of a C-block, first statement connection. + const innerConnection = imConn.targetConnection; + innerConnection.getSourceBlock().unplug(false); + + const previousBlockNextConnection = + markerPrev ? markerPrev.targetConnection : null; + + imBlock.unplug(true); + if (previousBlockNextConnection) { + previousBlockNextConnection.connect(innerConnection); + } + } else { + imBlock.unplug(true /* healStack */); + } + + if (imConn.targetConnection) { + throw Error( + 'markerConnection_ still connected at the end of ' + + 'disconnectInsertionMarker'); + } + + this.markerConnection_ = null; + const svg = imBlock.getSvgRoot(); + if (svg) { + svg.setAttribute('visibility', 'hidden'); + } + } + + /** + * Shows an outline around the input the closest connection belongs to. + * @private + */ + showInsertionInputOutline_() { + const closest = this.closestConnection_; + this.highlightedBlock_ = closest.getSourceBlock(); + this.highlightedBlock_.highlightShapeForInput(closest, true); + } + + /** + * Hides any visible input outlines. + * @private + */ + hideInsertionInputOutline_() { + this.highlightedBlock_.highlightShapeForInput( + this.closestConnection_, false); + this.highlightedBlock_ = null; + } + + /** + * Shows a replacement fade affect on the closest connection's target block + * (the block that is currently connected to it). + * @private + */ + showReplacementFade_() { + this.fadedBlock_ = this.closestConnection_.targetBlock(); + this.fadedBlock_.fadeForReplacement(true); + } + + /** + * Hides/Removes any visible fade affects. + * @private + */ + hideReplacementFade_() { + this.fadedBlock_.fadeForReplacement(false); + this.fadedBlock_ = null; + } + + /** + * Get a list of the insertion markers that currently exist. Drags have 0, 1, + * or 2 insertion markers. + * @return {!Array} A possibly empty list of insertion + * marker blocks. + * @package + */ + getInsertionMarkers() { + const result = []; + if (this.firstMarker_) { + result.push(this.firstMarker_); + } + if (this.lastMarker_) { + result.push(this.lastMarker_); + } + return result; + } +} /** * An enum describing different kinds of previews the InsertionMarkerManager @@ -161,599 +787,4 @@ InsertionMarkerManager.PREVIEW_TYPE = { REPLACEMENT_FADE: 2, }; -/** - * An error message to throw if the block created by createMarkerBlock_ is - * missing any components. - * @type {string} - * @const - */ -InsertionMarkerManager.DUPLICATE_BLOCK_ERROR = 'The insertion marker ' + - 'manager tried to create a marker but the result is missing %1. If ' + - 'you are using a mutator, make sure your domToMutation method is ' + - 'properly defined.'; - -/** - * Sever all links from this object. - * @package - */ -InsertionMarkerManager.prototype.dispose = function() { - this.availableConnections_.length = 0; - - eventUtils.disable(); - try { - if (this.firstMarker_) { - this.firstMarker_.dispose(); - } - if (this.lastMarker_) { - this.lastMarker_.dispose(); - } - } finally { - eventUtils.enable(); - } -}; - -/** - * Update the available connections for the top block. These connections can - * change if a block is unplugged and the stack is healed. - * @package - */ -InsertionMarkerManager.prototype.updateAvailableConnections = function() { - this.availableConnections_ = this.initAvailableConnections_(); -}; - -/** - * Return whether the block would be deleted if dropped immediately, based on - * information from the most recent move event. - * @return {boolean} True if the block would be deleted if dropped immediately. - * @package - */ -InsertionMarkerManager.prototype.wouldDeleteBlock = function() { - return this.wouldDeleteBlock_; -}; - -/** - * Return whether the block would be connected if dropped immediately, based on - * information from the most recent move event. - * @return {boolean} True if the block would be connected if dropped - * immediately. - * @package - */ -InsertionMarkerManager.prototype.wouldConnectBlock = function() { - return !!this.closestConnection_; -}; - -/** - * Connect to the closest connection and render the results. - * This should be called at the end of a drag. - * @package - */ -InsertionMarkerManager.prototype.applyConnections = function() { - if (this.closestConnection_) { - // Don't fire events for insertion markers. - eventUtils.disable(); - this.hidePreview_(); - eventUtils.enable(); - // Connect two blocks together. - this.localConnection_.connect(this.closestConnection_); - if (this.topBlock_.rendered) { - // Trigger a connection animation. - // Determine which connection is inferior (lower in the source stack). - const inferiorConnection = this.localConnection_.isSuperior() ? - this.closestConnection_ : - this.localConnection_; - blockAnimations.connectionUiEffect(inferiorConnection.getSourceBlock()); - // Bring the just-edited stack to the front. - const rootBlock = this.topBlock_.getRootBlock(); - rootBlock.bringToFront(); - } - } -}; - -/** - * Update connections based on the most recent move location. - * @param {!Coordinate} dxy Position relative to drag start, - * in workspace units. - * @param {?IDragTarget} dragTarget The drag target that the block is - * currently over. - * @package - */ -InsertionMarkerManager.prototype.update = function(dxy, dragTarget) { - const candidate = this.getCandidate_(dxy); - - this.wouldDeleteBlock_ = this.shouldDelete_(candidate, dragTarget); - - const shouldUpdate = - this.wouldDeleteBlock_ || this.shouldUpdatePreviews_(candidate, dxy); - - if (shouldUpdate) { - // Don't fire events for insertion marker creation or movement. - eventUtils.disable(); - this.maybeHidePreview_(candidate); - this.maybeShowPreview_(candidate); - eventUtils.enable(); - } -}; - -/** - * Create an insertion marker that represents the given block. - * @param {!BlockSvg} sourceBlock The block that the insertion marker - * will represent. - * @return {!BlockSvg} The insertion marker that represents the given - * block. - * @private - */ -InsertionMarkerManager.prototype.createMarkerBlock_ = function(sourceBlock) { - const imType = sourceBlock.type; - - eventUtils.disable(); - let result; - try { - result = this.workspace_.newBlock(imType); - result.setInsertionMarker(true); - if (sourceBlock.saveExtraState) { - const state = sourceBlock.saveExtraState(); - if (state) { - result.loadExtraState(state); - } - } else if (sourceBlock.mutationToDom) { - const oldMutationDom = sourceBlock.mutationToDom(); - if (oldMutationDom) { - result.domToMutation(oldMutationDom); - } - } - // Copy field values from the other block. These values may impact the - // rendered size of the insertion marker. Note that we do not care about - // child blocks here. - for (let i = 0; i < sourceBlock.inputList.length; i++) { - const sourceInput = sourceBlock.inputList[i]; - if (sourceInput.name === constants.COLLAPSED_INPUT_NAME) { - continue; // Ignore the collapsed input. - } - const resultInput = result.inputList[i]; - if (!resultInput) { - throw new Error(InsertionMarkerManager.DUPLICATE_BLOCK_ERROR.replace( - '%1', 'an input')); - } - for (let j = 0; j < sourceInput.fieldRow.length; j++) { - const sourceField = sourceInput.fieldRow[j]; - const resultField = resultInput.fieldRow[j]; - if (!resultField) { - throw new Error(InsertionMarkerManager.DUPLICATE_BLOCK_ERROR.replace( - '%1', 'a field')); - } - resultField.setValue(sourceField.getValue()); - } - } - - result.setCollapsed(sourceBlock.isCollapsed()); - result.setInputsInline(sourceBlock.getInputsInline()); - - result.initSvg(); - result.getSvgRoot().setAttribute('visibility', 'hidden'); - } finally { - eventUtils.enable(); - } - - return result; -}; - -/** - * Populate the list of available connections on this block stack. This should - * only be called once, at the beginning of a drag. - * If the stack has more than one block, this function will populate - * lastOnStack_ and create the corresponding insertion marker. - * @return {!Array} A list of available - * connections. - * @private - */ -InsertionMarkerManager.prototype.initAvailableConnections_ = function() { - const available = this.topBlock_.getConnections_(false); - // Also check the last connection on this stack - const lastOnStack = this.topBlock_.lastConnectionInStack(true); - if (lastOnStack && lastOnStack !== this.topBlock_.nextConnection) { - available.push(lastOnStack); - this.lastOnStack_ = lastOnStack; - if (this.lastMarker_) { - eventUtils.disable(); - try { - this.lastMarker_.dispose(); - } finally { - eventUtils.enable(); - } - } - this.lastMarker_ = this.createMarkerBlock_(lastOnStack.getSourceBlock()); - } - return available; -}; - -/** - * Whether the previews (insertion marker and replacement marker) should be - * updated based on the closest candidate and the current drag distance. - * @param {!Object} candidate An object containing a local connection, a closest - * connection, and a radius. Returned by getCandidate_. - * @param {!Coordinate} dxy Position relative to drag start, - * in workspace units. - * @return {boolean} Whether the preview should be updated. - * @private - */ -InsertionMarkerManager.prototype.shouldUpdatePreviews_ = function( - candidate, dxy) { - const candidateLocal = candidate.local; - const candidateClosest = candidate.closest; - const radius = candidate.radius; - - // Found a connection! - if (candidateLocal && candidateClosest) { - // We're already showing an insertion marker. - // Decide whether the new connection has higher priority. - if (this.localConnection_ && this.closestConnection_) { - // The connection was the same as the current connection. - if (this.closestConnection_ === candidateClosest && - this.localConnection_ === candidateLocal) { - return false; - } - const xDiff = this.localConnection_.x + dxy.x - this.closestConnection_.x; - const yDiff = this.localConnection_.y + dxy.y - this.closestConnection_.y; - const curDistance = Math.sqrt(xDiff * xDiff + yDiff * yDiff); - // Slightly prefer the existing preview over a new preview. - return !( - candidateClosest && - radius > - curDistance - internalConstants.CURRENT_CONNECTION_PREFERENCE); - } else if (!this.localConnection_ && !this.closestConnection_) { - // We weren't showing a preview before, but we should now. - return true; - } else { - console.error( - 'Only one of localConnection_ and closestConnection_ was set.'); - } - } else { // No connection found. - // Only need to update if we were showing a preview before. - return !!(this.localConnection_ && this.closestConnection_); - } - - console.error( - 'Returning true from shouldUpdatePreviews, but it\'s not clear why.'); - return true; -}; - -/** - * Find the nearest valid connection, which may be the same as the current - * closest connection. - * @param {!Coordinate} dxy Position relative to drag start, - * in workspace units. - * @return {!Object} An object containing a local connection, a closest - * connection, and a radius. - * @private - */ -InsertionMarkerManager.prototype.getCandidate_ = function(dxy) { - let radius = this.getStartRadius_(); - let candidateClosest = null; - let candidateLocal = null; - - for (let i = 0; i < this.availableConnections_.length; i++) { - const myConnection = this.availableConnections_[i]; - const neighbour = myConnection.closest(radius, dxy); - if (neighbour.connection) { - candidateClosest = neighbour.connection; - candidateLocal = myConnection; - radius = neighbour.radius; - } - } - return {closest: candidateClosest, local: candidateLocal, radius: radius}; -}; - -/** - * Decide the radius at which to start searching for the closest connection. - * @return {number} The radius at which to start the search for the closest - * connection. - * @private - */ -InsertionMarkerManager.prototype.getStartRadius_ = function() { - // If there is already a connection highlighted, - // increase the radius we check for making new connections. - // Why? When a connection is highlighted, blocks move around when the - // insertion marker is created, which could cause the connection became out of - // range. By increasing radiusConnection when a connection already exists, we - // never "lose" the connection from the offset. - if (this.closestConnection_ && this.localConnection_) { - return internalConstants.CONNECTING_SNAP_RADIUS; - } - return internalConstants.SNAP_RADIUS; -}; - -/** - * Whether ending the drag would delete the block. - * @param {!Object} candidate An object containing a local connection, a closest - * connection, and a radius. - * @param {?IDragTarget} dragTarget The drag target that the block is - * currently over. - * @return {boolean} Whether dropping the block immediately would delete the - * block. - * @private - */ -InsertionMarkerManager.prototype.shouldDelete_ = function( - candidate, dragTarget) { - if (dragTarget) { - const componentManager = this.workspace_.getComponentManager(); - const isDeleteArea = componentManager.hasCapability( - dragTarget.id, ComponentManager.Capability.DELETE_AREA); - if (isDeleteArea) { - return ( - /** @type {!IDeleteArea} */ (dragTarget)) - .wouldDelete(this.topBlock_, candidate && !!candidate.closest); - } - } - return false; -}; - -/** - * Show an insertion marker or replacement highlighting during a drag, if - * needed. - * At the beginning of this function, this.localConnection_ and - * this.closestConnection_ should both be null. - * @param {!Object} candidate An object containing a local connection, a closest - * connection, and a radius. - * @private - */ -InsertionMarkerManager.prototype.maybeShowPreview_ = function(candidate) { - // Nope, don't add a marker. - if (this.wouldDeleteBlock_) { - return; - } - const closest = candidate.closest; - const local = candidate.local; - - // Nothing to connect to. - if (!closest) { - return; - } - - // Something went wrong and we're trying to connect to an invalid connection. - if (closest === this.closestConnection_ || - closest.getSourceBlock().isInsertionMarker()) { - console.log('Trying to connect to an insertion marker'); - return; - } - // Add an insertion marker or replacement marker. - this.closestConnection_ = closest; - this.localConnection_ = local; - this.showPreview_(); -}; - -/** - * A preview should be shown. This function figures out if it should be a block - * highlight or an insertion marker, and shows the appropriate one. - * @private - */ -InsertionMarkerManager.prototype.showPreview_ = function() { - const closest = this.closestConnection_; - const renderer = this.workspace_.getRenderer(); - const method = renderer.getConnectionPreviewMethod( - /** @type {!RenderedConnection} */ (closest), - /** @type {!RenderedConnection} */ (this.localConnection_), - this.topBlock_); - - switch (method) { - case InsertionMarkerManager.PREVIEW_TYPE.INPUT_OUTLINE: - this.showInsertionInputOutline_(); - break; - case InsertionMarkerManager.PREVIEW_TYPE.INSERTION_MARKER: - this.showInsertionMarker_(); - break; - case InsertionMarkerManager.PREVIEW_TYPE.REPLACEMENT_FADE: - this.showReplacementFade_(); - break; - } - - // Optionally highlight the actual connection, as a nod to previous behaviour. - if (closest && renderer.shouldHighlightConnection(closest)) { - closest.highlight(); - } -}; - -/** - * Show an insertion marker or replacement highlighting during a drag, if - * needed. - * At the end of this function, this.localConnection_ and - * this.closestConnection_ should both be null. - * @param {!Object} candidate An object containing a local connection, a closest - * connection, and a radius. - * @private - */ -InsertionMarkerManager.prototype.maybeHidePreview_ = function(candidate) { - // If there's no new preview, remove the old one but don't bother deleting it. - // We might need it later, and this saves disposing of it and recreating it. - if (!candidate.closest) { - this.hidePreview_(); - } else { - // If there's a new preview and there was an preview before, and either - // connection has changed, remove the old preview. - const hadPreview = this.closestConnection_ && this.localConnection_; - const closestChanged = this.closestConnection_ !== candidate.closest; - const localChanged = this.localConnection_ !== candidate.local; - - // Also hide if we had a preview before but now we're going to delete - // instead. - if (hadPreview && - (closestChanged || localChanged || this.wouldDeleteBlock_)) { - this.hidePreview_(); - } - } - - // Either way, clear out old state. - this.markerConnection_ = null; - this.closestConnection_ = null; - this.localConnection_ = null; -}; - -/** - * A preview should be hidden. This function figures out if it is a block - * highlight or an insertion marker, and hides the appropriate one. - * @private - */ -InsertionMarkerManager.prototype.hidePreview_ = function() { - if (this.closestConnection_ && this.closestConnection_.targetBlock() && - this.workspace_.getRenderer().shouldHighlightConnection( - this.closestConnection_)) { - this.closestConnection_.unhighlight(); - } - if (this.fadedBlock_) { - this.hideReplacementFade_(); - } else if (this.highlightedBlock_) { - this.hideInsertionInputOutline_(); - } else if (this.markerConnection_) { - this.hideInsertionMarker_(); - } -}; - -/** - * Shows an insertion marker connected to the appropriate blocks (based on - * manager state). - * @private - */ -InsertionMarkerManager.prototype.showInsertionMarker_ = function() { - const local = this.localConnection_; - const closest = this.closestConnection_; - - const isLastInStack = this.lastOnStack_ && local === this.lastOnStack_; - const imBlock = isLastInStack ? this.lastMarker_ : this.firstMarker_; - const imConn = imBlock.getMatchingConnection(local.getSourceBlock(), local); - - if (imConn === this.markerConnection_) { - throw Error( - 'Made it to showInsertionMarker_ even though the marker isn\'t ' + - 'changing'); - } - - // Render disconnected from everything else so that we have a valid - // connection location. - imBlock.render(); - imBlock.rendered = true; - imBlock.getSvgRoot().setAttribute('visibility', 'visible'); - - if (imConn && closest) { - // Position so that the existing block doesn't move. - imBlock.positionNearConnection(imConn, closest); - } - if (closest) { - // Connect() also renders the insertion marker. - imConn.connect(closest); - } - - this.markerConnection_ = imConn; -}; - -/** - * Disconnects and hides the current insertion marker. Should return the blocks - * to their original state. - * @private - */ -InsertionMarkerManager.prototype.hideInsertionMarker_ = function() { - if (!this.markerConnection_) { - console.log('No insertion marker connection to disconnect'); - return; - } - - const imConn = this.markerConnection_; - const imBlock = imConn.getSourceBlock(); - const markerNext = imBlock.nextConnection; - const markerPrev = imBlock.previousConnection; - const markerOutput = imBlock.outputConnection; - - const isFirstInStatementStack = - (imConn === markerNext && !(markerPrev && markerPrev.targetConnection)); - - const isFirstInOutputStack = imConn.type === ConnectionType.INPUT_VALUE && - !(markerOutput && markerOutput.targetConnection); - // The insertion marker is the first block in a stack. Unplug won't do - // anything in that case. Instead, unplug the following block. - if (isFirstInStatementStack || isFirstInOutputStack) { - imConn.targetBlock().unplug(false); - } else if ( - imConn.type === ConnectionType.NEXT_STATEMENT && imConn !== markerNext) { - // Inside of a C-block, first statement connection. - const innerConnection = imConn.targetConnection; - innerConnection.getSourceBlock().unplug(false); - - const previousBlockNextConnection = - markerPrev ? markerPrev.targetConnection : null; - - imBlock.unplug(true); - if (previousBlockNextConnection) { - previousBlockNextConnection.connect(innerConnection); - } - } else { - imBlock.unplug(true /* healStack */); - } - - if (imConn.targetConnection) { - throw Error( - 'markerConnection_ still connected at the end of ' + - 'disconnectInsertionMarker'); - } - - this.markerConnection_ = null; - const svg = imBlock.getSvgRoot(); - if (svg) { - svg.setAttribute('visibility', 'hidden'); - } -}; - -/** - * Shows an outline around the input the closest connection belongs to. - * @private - */ -InsertionMarkerManager.prototype.showInsertionInputOutline_ = function() { - const closest = this.closestConnection_; - this.highlightedBlock_ = closest.getSourceBlock(); - this.highlightedBlock_.highlightShapeForInput(closest, true); -}; - -/** - * Hides any visible input outlines. - * @private - */ -InsertionMarkerManager.prototype.hideInsertionInputOutline_ = function() { - this.highlightedBlock_.highlightShapeForInput(this.closestConnection_, false); - this.highlightedBlock_ = null; -}; - -/** - * Shows a replacement fade affect on the closest connection's target block - * (the block that is currently connected to it). - * @private - */ -InsertionMarkerManager.prototype.showReplacementFade_ = function() { - this.fadedBlock_ = this.closestConnection_.targetBlock(); - this.fadedBlock_.fadeForReplacement(true); -}; - -/** - * Hides/Removes any visible fade affects. - * @private - */ -InsertionMarkerManager.prototype.hideReplacementFade_ = function() { - this.fadedBlock_.fadeForReplacement(false); - this.fadedBlock_ = null; -}; - -/** - * Get a list of the insertion markers that currently exist. Drags have 0, 1, - * or 2 insertion markers. - * @return {!Array} A possibly empty list of insertion - * marker blocks. - * @package - */ -InsertionMarkerManager.prototype.getInsertionMarkers = function() { - const result = []; - if (this.firstMarker_) { - result.push(this.firstMarker_); - } - if (this.lastMarker_) { - result.push(this.lastMarker_); - } - return result; -}; - exports.InsertionMarkerManager = InsertionMarkerManager; diff --git a/core/internal_constants.js b/core/internal_constants.js index d5952eb05..630806ff6 100644 --- a/core/internal_constants.js +++ b/core/internal_constants.js @@ -21,76 +21,6 @@ goog.module('Blockly.internalConstants'); const {ConnectionType} = goog.require('Blockly.ConnectionType'); -/** - * The multiplier for scroll wheel deltas using the line delta mode. - * @type {number} - * @alias Blockly.internalConstants.LINE_MODE_MULTIPLIER - */ -const LINE_MODE_MULTIPLIER = 40; -exports.LINE_MODE_MULTIPLIER = LINE_MODE_MULTIPLIER; - -/** - * The multiplier for scroll wheel deltas using the page delta mode. - * @type {number} - * @alias Blockly.internalConstants.PAGE_MODE_MULTIPLIER - */ -const PAGE_MODE_MULTIPLIER = 125; -exports.PAGE_MODE_MULTIPLIER = PAGE_MODE_MULTIPLIER; - -/** - * Number of pixels the mouse must move before a drag starts. - * @alias Blockly.internalConstants.DRAG_RADIUS - */ -const DRAG_RADIUS = 5; -exports.DRAG_RADIUS = DRAG_RADIUS; - -/** - * Number of pixels the mouse must move before a drag/scroll starts from the - * flyout. Because the drag-intention is determined when this is reached, it is - * larger than DRAG_RADIUS so that the drag-direction is clearer. - * @alias Blockly.internalConstants.FLYOUT_DRAG_RADIUS - */ -const FLYOUT_DRAG_RADIUS = 10; -exports.FLYOUT_DRAG_RADIUS = FLYOUT_DRAG_RADIUS; - -/** - * Maximum misalignment between connections for them to snap together. - * @alias Blockly.internalConstants.SNAP_RADIUS - */ -const SNAP_RADIUS = 28; -exports.SNAP_RADIUS = SNAP_RADIUS; - -/** - * Maximum misalignment between connections for them to snap together, - * when a connection is already highlighted. - * @alias Blockly.internalConstants.CONNECTING_SNAP_RADIUS - */ -const CONNECTING_SNAP_RADIUS = SNAP_RADIUS; -exports.CONNECTING_SNAP_RADIUS = CONNECTING_SNAP_RADIUS; - -/** - * How much to prefer staying connected to the current connection over moving to - * a new connection. The current previewed connection is considered to be this - * much closer to the matching connection on the block than it actually is. - * @alias Blockly.internalConstants.CURRENT_CONNECTION_PREFERENCE - */ -const CURRENT_CONNECTION_PREFERENCE = 8; -exports.CURRENT_CONNECTION_PREFERENCE = CURRENT_CONNECTION_PREFERENCE; - -/** - * Delay in ms between trigger and bumping unconnected block out of alignment. - * @alias Blockly.internalConstants.BUMP_DELAY - */ -const BUMP_DELAY = 250; -exports.BUMP_DELAY = BUMP_DELAY; - -/** - * Maximum randomness in workspace units for bumping a block. - * @alias Blockly.internalConstants.BUMP_RANDOMNESS - */ -const BUMP_RANDOMNESS = 10; -exports.BUMP_RANDOMNESS = BUMP_RANDOMNESS; - /** * Number of characters to truncate a collapsed block to. * @alias Blockly.internalConstants.COLLAPSE_CHARS @@ -98,21 +28,6 @@ exports.BUMP_RANDOMNESS = BUMP_RANDOMNESS; const COLLAPSE_CHARS = 30; exports.COLLAPSE_CHARS = COLLAPSE_CHARS; -/** - * Length in ms for a touch to become a long press. - * @alias Blockly.internalConstants.LONGPRESS - */ -const LONGPRESS = 750; -exports.LONGPRESS = LONGPRESS; - -/** - * Prevent a sound from playing if another sound preceded it within this many - * milliseconds. - * @alias Blockly.internalConstants.SOUND_LIMIT - */ -const SOUND_LIMIT = 100; -exports.SOUND_LIMIT = SOUND_LIMIT; - /** * When dragging a block out of a stack, split the stack in two (true), or drag * out the block healing the stack (false). @@ -121,50 +36,6 @@ exports.SOUND_LIMIT = SOUND_LIMIT; const DRAG_STACK = true; exports.DRAG_STACK = DRAG_STACK; -/** - * Sprited icons and images. - * @alias Blockly.internalConstants.SPRITE - */ -const SPRITE = { - width: 96, - height: 124, - url: 'sprites.png', -}; -exports.SPRITE = SPRITE; - -/** - * ENUM for no drag operation. - * @const - * @alias Blockly.internalConstants.DRAG_NONE - */ -const DRAG_NONE = 0; -exports.DRAG_NONE = DRAG_NONE; - -/** - * ENUM for inside the sticky DRAG_RADIUS. - * @const - * @alias Blockly.internalConstants.DRAG_STICKY - */ -const DRAG_STICKY = 1; -exports.DRAG_STICKY = DRAG_STICKY; - -/** - * ENUM for inside the non-sticky DRAG_RADIUS, for differentiating between - * clicks and drags. - * @const - * @alias Blockly.internalConstants.DRAG_BEGIN - */ -const DRAG_BEGIN = 1; -exports.DRAG_BEGIN = DRAG_BEGIN; - -/** - * ENUM for freely draggable (outside the DRAG_RADIUS, if one applies). - * @const - * @alias Blockly.internalConstants.DRAG_FREE - */ -const DRAG_FREE = 2; -exports.DRAG_FREE = DRAG_FREE; - /** * Lookup table for determining the opposite type of a connection. * @const diff --git a/core/keyboard_nav/ast_node.js b/core/keyboard_nav/ast_node.js index 7e0d045fe..44b9f65dc 100644 --- a/core/keyboard_nav/ast_node.js +++ b/core/keyboard_nav/ast_node.js @@ -39,49 +39,649 @@ const {Workspace} = goog.requireType('Blockly.Workspace'); * Class for an AST node. * It is recommended that you use one of the createNode methods instead of * creating a node directly. - * @param {string} type The type of the location. - * Must be in ASTNode.types. - * @param {!IASTNodeLocation} location The position in the AST. - * @param {!ASTNode.Params=} opt_params Optional dictionary of options. - * @constructor * @alias Blockly.ASTNode */ -const ASTNode = function(type, location, opt_params) { - if (!location) { - throw Error('Cannot create a node without a location.'); +class ASTNode { + /** + * @param {string} type The type of the location. + * Must be in ASTNode.types. + * @param {!IASTNodeLocation} location The position in the AST. + * @param {!ASTNode.Params=} opt_params Optional dictionary of options. + * @alias Blockly.ASTNode + */ + constructor(type, location, opt_params) { + if (!location) { + throw Error('Cannot create a node without a location.'); + } + + /** + * The type of the location. + * One of ASTNode.types + * @type {string} + * @private + */ + this.type_ = type; + + /** + * Whether the location points to a connection. + * @type {boolean} + * @private + */ + this.isConnection_ = ASTNode.isConnectionType_(type); + + /** + * The location of the AST node. + * @type {!IASTNodeLocation} + * @private + */ + this.location_ = location; + + /** + * The coordinate on the workspace. + * @type {Coordinate} + * @private + */ + this.wsCoordinate_ = null; + + this.processParams_(opt_params || null); } /** - * The type of the location. + * Parse the optional parameters. + * @param {?ASTNode.Params} params The user specified parameters. + * @private + */ + processParams_(params) { + if (!params) { + return; + } + if (params.wsCoordinate) { + this.wsCoordinate_ = params.wsCoordinate; + } + } + + /** + * Gets the value pointed to by this node. + * It is the callers responsibility to check the node type to figure out what + * type of object they get back from this. + * @return {!IASTNodeLocation} The current field, connection, workspace, or + * block the cursor is on. + */ + getLocation() { + return this.location_; + } + + /** + * The type of the current location. * One of ASTNode.types - * @type {string} - * @private + * @return {string} The type of the location. */ - this.type_ = type; - - /** - * Whether the location points to a connection. - * @type {boolean} - * @private - */ - this.isConnection_ = ASTNode.isConnectionType_(type); - - /** - * The location of the AST node. - * @type {!IASTNodeLocation} - * @private - */ - this.location_ = location; + getType() { + return this.type_; + } /** * The coordinate on the workspace. - * @type {Coordinate} + * @return {Coordinate} The workspace coordinate or null if the + * location is not a workspace. + */ + getWsCoordinate() { + return this.wsCoordinate_; + } + + /** + * Whether the node points to a connection. + * @return {boolean} [description] + * @package + */ + isConnection() { + return this.isConnection_; + } + + /** + * Given an input find the next editable field or an input with a non null + * connection in the same block. The current location must be an input + * connection. + * @return {ASTNode} The AST node holding the next field or connection + * or null if there is no editable field or input connection after the + * given input. * @private */ - this.wsCoordinate_ = null; + findNextForInput_() { + const location = /** @type {!Connection} */ (this.location_); + const parentInput = location.getParentInput(); + const block = parentInput.getSourceBlock(); + const curIdx = block.inputList.indexOf(parentInput); + for (let i = curIdx + 1; i < block.inputList.length; i++) { + const input = block.inputList[i]; + const fieldRow = input.fieldRow; + for (let j = 0; j < fieldRow.length; j++) { + const field = fieldRow[j]; + if (field.isClickable() || ASTNode.NAVIGATE_ALL_FIELDS) { + return ASTNode.createFieldNode(field); + } + } + if (input.connection) { + return ASTNode.createInputNode(input); + } + } + return null; + } - this.processParams_(opt_params || null); -}; + /** + * Given a field find the next editable field or an input with a non null + * connection in the same block. The current location must be a field. + * @return {ASTNode} The AST node pointing to the next field or + * connection or null if there is no editable field or input connection + * after the given input. + * @private + */ + findNextForField_() { + const location = /** @type {!Field} */ (this.location_); + const input = location.getParentInput(); + const block = location.getSourceBlock(); + const curIdx = block.inputList.indexOf(/** @type {!Input} */ (input)); + let fieldIdx = input.fieldRow.indexOf(location) + 1; + for (let i = curIdx; i < block.inputList.length; i++) { + const newInput = block.inputList[i]; + const fieldRow = newInput.fieldRow; + while (fieldIdx < fieldRow.length) { + if (fieldRow[fieldIdx].isClickable() || ASTNode.NAVIGATE_ALL_FIELDS) { + return ASTNode.createFieldNode(fieldRow[fieldIdx]); + } + fieldIdx++; + } + fieldIdx = 0; + if (newInput.connection) { + return ASTNode.createInputNode(newInput); + } + } + return null; + } + + /** + * Given an input find the previous editable field or an input with a non null + * connection in the same block. The current location must be an input + * connection. + * @return {ASTNode} The AST node holding the previous field or + * connection. + * @private + */ + findPrevForInput_() { + const location = /** @type {!Connection} */ (this.location_); + const parentInput = location.getParentInput(); + const block = parentInput.getSourceBlock(); + const curIdx = block.inputList.indexOf(parentInput); + for (let i = curIdx; i >= 0; i--) { + const input = block.inputList[i]; + if (input.connection && input !== parentInput) { + return ASTNode.createInputNode(input); + } + const fieldRow = input.fieldRow; + for (let j = fieldRow.length - 1; j >= 0; j--) { + const field = fieldRow[j]; + if (field.isClickable() || ASTNode.NAVIGATE_ALL_FIELDS) { + return ASTNode.createFieldNode(field); + } + } + } + return null; + } + + /** + * Given a field find the previous editable field or an input with a non null + * connection in the same block. The current location must be a field. + * @return {ASTNode} The AST node holding the previous input or field. + * @private + */ + findPrevForField_() { + const location = /** @type {!Field} */ (this.location_); + const parentInput = location.getParentInput(); + const block = location.getSourceBlock(); + const curIdx = block.inputList.indexOf( + /** @type {!Input} */ (parentInput)); + let fieldIdx = parentInput.fieldRow.indexOf(location) - 1; + for (let i = curIdx; i >= 0; i--) { + const input = block.inputList[i]; + if (input.connection && input !== parentInput) { + return ASTNode.createInputNode(input); + } + const fieldRow = input.fieldRow; + while (fieldIdx > -1) { + if (fieldRow[fieldIdx].isClickable() || ASTNode.NAVIGATE_ALL_FIELDS) { + return ASTNode.createFieldNode(fieldRow[fieldIdx]); + } + fieldIdx--; + } + // Reset the fieldIdx to the length of the field row of the previous + // input. + if (i - 1 >= 0) { + fieldIdx = block.inputList[i - 1].fieldRow.length - 1; + } + } + return null; + } + + /** + * Navigate between stacks of blocks on the workspace. + * @param {boolean} forward True to go forward. False to go backwards. + * @return {ASTNode} The first block of the next stack or null if there + * are no blocks on the workspace. + * @private + */ + navigateBetweenStacks_(forward) { + let curLocation = this.getLocation(); + if (curLocation.getSourceBlock) { + curLocation = /** @type {!IASTNodeLocationWithBlock} */ (curLocation) + .getSourceBlock(); + } + if (!curLocation || !curLocation.workspace) { + return null; + } + const curRoot = curLocation.getRootBlock(); + const topBlocks = curRoot.workspace.getTopBlocks(true); + for (let i = 0; i < topBlocks.length; i++) { + const topBlock = topBlocks[i]; + if (curRoot.id === topBlock.id) { + const offset = forward ? 1 : -1; + const resultIndex = i + offset; + if (resultIndex === -1 || resultIndex === topBlocks.length) { + return null; + } + return ASTNode.createStackNode(topBlocks[resultIndex]); + } + } + throw Error( + 'Couldn\'t find ' + (forward ? 'next' : 'previous') + ' stack?!'); + } + + /** + * Finds the top most AST node for a given block. + * This is either the previous connection, output connection or block + * depending on what kind of connections the block has. + * @param {!Block} block The block that we want to find the top + * connection on. + * @return {!ASTNode} The AST node containing the top connection. + * @private + */ + findTopASTNodeForBlock_(block) { + const topConnection = getParentConnection(block); + if (topConnection) { + return /** @type {!ASTNode} */ ( + ASTNode.createConnectionNode(topConnection)); + } else { + return /** @type {!ASTNode} */ (ASTNode.createBlockNode(block)); + } + } + + /** + * Get the AST node pointing to the input that the block is nested under or if + * the block is not nested then get the stack AST node. + * @param {Block} block The source block of the current location. + * @return {ASTNode} The AST node pointing to the input connection or + * the top block of the stack this block is in. + * @private + */ + getOutAstNodeForBlock_(block) { + if (!block) { + return null; + } + // If the block doesn't have a previous connection then it is the top of the + // substack. + const topBlock = block.getTopStackBlock(); + const topConnection = getParentConnection(topBlock); + // If the top connection has a parentInput, create an AST node pointing to + // that input. + if (topConnection && topConnection.targetConnection && + topConnection.targetConnection.getParentInput()) { + return ASTNode.createInputNode( + topConnection.targetConnection.getParentInput()); + } else { + // Go to stack level if you are not underneath an input. + return ASTNode.createStackNode(topBlock); + } + } + + /** + * Find the first editable field or input with a connection on a given block. + * @param {!Block} block The source block of the current location. + * @return {ASTNode} An AST node pointing to the first field or input. + * Null if there are no editable fields or inputs with connections on the + * block. + * @private + */ + findFirstFieldOrInput_(block) { + const inputs = block.inputList; + for (let i = 0; i < inputs.length; i++) { + const input = inputs[i]; + const fieldRow = input.fieldRow; + for (let j = 0; j < fieldRow.length; j++) { + const field = fieldRow[j]; + if (field.isClickable() || ASTNode.NAVIGATE_ALL_FIELDS) { + return ASTNode.createFieldNode(field); + } + } + if (input.connection) { + return ASTNode.createInputNode(input); + } + } + return null; + } + + /** + * Finds the source block of the location of this node. + * @return {Block} The source block of the location, or null if the node + * is of type workspace. + */ + getSourceBlock() { + if (this.getType() === ASTNode.types.BLOCK) { + return /** @type {Block} */ (this.getLocation()); + } else if (this.getType() === ASTNode.types.STACK) { + return /** @type {Block} */ (this.getLocation()); + } else if (this.getType() === ASTNode.types.WORKSPACE) { + return null; + } else { + return /** @type {IASTNodeLocationWithBlock} */ (this.getLocation()) + .getSourceBlock(); + } + } + + /** + * Find the element to the right of the current element in the AST. + * @return {ASTNode} An AST node that wraps the next field, connection, + * block, or workspace. Or null if there is no node to the right. + */ + next() { + switch (this.type_) { + case ASTNode.types.STACK: + return this.navigateBetweenStacks_(true); + + case ASTNode.types.OUTPUT: { + const connection = /** @type {!Connection} */ (this.location_); + return ASTNode.createBlockNode(connection.getSourceBlock()); + } + case ASTNode.types.FIELD: + return this.findNextForField_(); + + case ASTNode.types.INPUT: + return this.findNextForInput_(); + + case ASTNode.types.BLOCK: { + const block = /** @type {!Block} */ (this.location_); + const nextConnection = block.nextConnection; + return ASTNode.createConnectionNode(nextConnection); + } + case ASTNode.types.PREVIOUS: { + const connection = /** @type {!Connection} */ (this.location_); + return ASTNode.createBlockNode(connection.getSourceBlock()); + } + case ASTNode.types.NEXT: { + const connection = /** @type {!Connection} */ (this.location_); + const targetConnection = connection.targetConnection; + return ASTNode.createConnectionNode(targetConnection); + } + } + + return null; + } + + /** + * Find the element one level below and all the way to the left of the current + * location. + * @return {ASTNode} An AST node that wraps the next field, connection, + * workspace, or block. Or null if there is nothing below this node. + */ + in() { + switch (this.type_) { + case ASTNode.types.WORKSPACE: { + const workspace = /** @type {!Workspace} */ (this.location_); + const topBlocks = workspace.getTopBlocks(true); + if (topBlocks.length > 0) { + return ASTNode.createStackNode(topBlocks[0]); + } + break; + } + case ASTNode.types.STACK: { + const block = /** @type {!Block} */ (this.location_); + return this.findTopASTNodeForBlock_(block); + } + case ASTNode.types.BLOCK: { + const block = /** @type {!Block} */ (this.location_); + return this.findFirstFieldOrInput_(block); + } + case ASTNode.types.INPUT: { + const connection = /** @type {!Connection} */ (this.location_); + const targetConnection = connection.targetConnection; + return ASTNode.createConnectionNode(targetConnection); + } + } + + return null; + } + + /** + * Find the element to the left of the current element in the AST. + * @return {ASTNode} An AST node that wraps the previous field, + * connection, workspace or block. Or null if no node exists to the left. + * null. + */ + prev() { + switch (this.type_) { + case ASTNode.types.STACK: + return this.navigateBetweenStacks_(false); + + case ASTNode.types.OUTPUT: + return null; + + case ASTNode.types.FIELD: + return this.findPrevForField_(); + + case ASTNode.types.INPUT: + return this.findPrevForInput_(); + + case ASTNode.types.BLOCK: { + const block = /** @type {!Block} */ (this.location_); + const topConnection = getParentConnection(block); + return ASTNode.createConnectionNode(topConnection); + } + case ASTNode.types.PREVIOUS: { + const connection = /** @type {!Connection} */ (this.location_); + const targetConnection = connection.targetConnection; + if (targetConnection && !targetConnection.getParentInput()) { + return ASTNode.createConnectionNode(targetConnection); + } + break; + } + case ASTNode.types.NEXT: { + const connection = /** @type {!Connection} */ (this.location_); + return ASTNode.createBlockNode(connection.getSourceBlock()); + } + } + + return null; + } + + /** + * Find the next element that is one position above and all the way to the + * left of the current location. + * @return {ASTNode} An AST node that wraps the next field, connection, + * workspace or block. Or null if we are at the workspace level. + */ + out() { + switch (this.type_) { + case ASTNode.types.STACK: { + const block = /** @type {!Block} */ (this.location_); + const blockPos = block.getRelativeToSurfaceXY(); + // TODO: Make sure this is in the bounds of the workspace. + const wsCoordinate = + new Coordinate(blockPos.x, blockPos.y + ASTNode.DEFAULT_OFFSET_Y); + return ASTNode.createWorkspaceNode(block.workspace, wsCoordinate); + } + case ASTNode.types.OUTPUT: { + const connection = /** @type {!Connection} */ (this.location_); + const target = connection.targetConnection; + if (target) { + return ASTNode.createConnectionNode(target); + } + return ASTNode.createStackNode(connection.getSourceBlock()); + } + case ASTNode.types.FIELD: { + const field = /** @type {!Field} */ (this.location_); + return ASTNode.createBlockNode(field.getSourceBlock()); + } + case ASTNode.types.INPUT: { + const connection = /** @type {!Connection} */ (this.location_); + return ASTNode.createBlockNode(connection.getSourceBlock()); + } + case ASTNode.types.BLOCK: { + const block = /** @type {!Block} */ (this.location_); + return this.getOutAstNodeForBlock_(block); + } + case ASTNode.types.PREVIOUS: { + const connection = /** @type {!Connection} */ (this.location_); + return this.getOutAstNodeForBlock_(connection.getSourceBlock()); + } + case ASTNode.types.NEXT: { + const connection = /** @type {!Connection} */ (this.location_); + return this.getOutAstNodeForBlock_(connection.getSourceBlock()); + } + } + + return null; + } + + /** + * Whether an AST node of the given type points to a connection. + * @param {string} type The type to check. One of ASTNode.types. + * @return {boolean} True if a node of the given type points to a connection. + * @private + */ + static isConnectionType_(type) { + switch (type) { + case ASTNode.types.PREVIOUS: + case ASTNode.types.NEXT: + case ASTNode.types.INPUT: + case ASTNode.types.OUTPUT: + return true; + } + return false; + } + + /** + * Create an AST node pointing to a field. + * @param {Field} field The location of the AST node. + * @return {ASTNode} An AST node pointing to a field. + */ + static createFieldNode(field) { + if (!field) { + return null; + } + return new ASTNode(ASTNode.types.FIELD, field); + } + + /** + * Creates an AST node pointing to a connection. If the connection has a + * parent input then create an AST node of type input that will hold the + * connection. + * @param {Connection} connection This is the connection the node will + * point to. + * @return {ASTNode} An AST node pointing to a connection. + */ + static createConnectionNode(connection) { + if (!connection) { + return null; + } + const type = connection.type; + if (type === ConnectionType.INPUT_VALUE) { + return ASTNode.createInputNode(connection.getParentInput()); + } else if ( + type === ConnectionType.NEXT_STATEMENT && connection.getParentInput()) { + return ASTNode.createInputNode(connection.getParentInput()); + } else if (type === ConnectionType.NEXT_STATEMENT) { + return new ASTNode(ASTNode.types.NEXT, connection); + } else if (type === ConnectionType.OUTPUT_VALUE) { + return new ASTNode(ASTNode.types.OUTPUT, connection); + } else if (type === ConnectionType.PREVIOUS_STATEMENT) { + return new ASTNode(ASTNode.types.PREVIOUS, connection); + } + return null; + } + + /** + * Creates an AST node pointing to an input. Stores the input connection as + * the location. + * @param {Input} input The input used to create an AST node. + * @return {ASTNode} An AST node pointing to a input. + */ + static createInputNode(input) { + if (!input || !input.connection) { + return null; + } + return new ASTNode(ASTNode.types.INPUT, input.connection); + } + + /** + * Creates an AST node pointing to a block. + * @param {Block} block The block used to create an AST node. + * @return {ASTNode} An AST node pointing to a block. + */ + static createBlockNode(block) { + if (!block) { + return null; + } + return new ASTNode(ASTNode.types.BLOCK, block); + } + + /** + * Create an AST node of type stack. A stack, represented by its top block, is + * the set of all blocks connected to a top block, including the top + * block. + * @param {Block} topBlock A top block has no parent and can be found + * in the list returned by workspace.getTopBlocks(). + * @return {ASTNode} An AST node of type stack that points to the top + * block on the stack. + */ + static createStackNode(topBlock) { + if (!topBlock) { + return null; + } + return new ASTNode(ASTNode.types.STACK, topBlock); + } + + /** + * Creates an AST node pointing to a workspace. + * @param {!Workspace} workspace The workspace that we are on. + * @param {Coordinate} wsCoordinate The position on the workspace + * for this node. + * @return {ASTNode} An AST node pointing to a workspace and a position + * on the workspace. + */ + static createWorkspaceNode(workspace, wsCoordinate) { + if (!wsCoordinate || !workspace) { + return null; + } + const params = {wsCoordinate: wsCoordinate}; + return new ASTNode(ASTNode.types.WORKSPACE, workspace, params); + } + + /** + * Creates an AST node for the top position on a block. + * This is either an output connection, previous connection, or block. + * @param {!Block} block The block to find the top most AST node on. + * @return {ASTNode} The AST node holding the top most position on the + * block. + */ + static createTopNode(block) { + let astNode; + const topConnection = getParentConnection(block); + if (topConnection) { + astNode = ASTNode.createConnectionNode(topConnection); + } else { + astNode = ASTNode.createBlockNode(block); + } + return astNode; + } +} /** * @typedef {{ @@ -119,118 +719,6 @@ ASTNode.NAVIGATE_ALL_FIELDS = false; */ ASTNode.DEFAULT_OFFSET_Y = -20; -/** - * Whether an AST node of the given type points to a connection. - * @param {string} type The type to check. One of ASTNode.types. - * @return {boolean} True if a node of the given type points to a connection. - * @private - */ -ASTNode.isConnectionType_ = function(type) { - switch (type) { - case ASTNode.types.PREVIOUS: - case ASTNode.types.NEXT: - case ASTNode.types.INPUT: - case ASTNode.types.OUTPUT: - return true; - } - return false; -}; - -/** - * Create an AST node pointing to a field. - * @param {Field} field The location of the AST node. - * @return {ASTNode} An AST node pointing to a field. - */ -ASTNode.createFieldNode = function(field) { - if (!field) { - return null; - } - return new ASTNode(ASTNode.types.FIELD, field); -}; - -/** - * Creates an AST node pointing to a connection. If the connection has a parent - * input then create an AST node of type input that will hold the connection. - * @param {Connection} connection This is the connection the node will - * point to. - * @return {ASTNode} An AST node pointing to a connection. - */ -ASTNode.createConnectionNode = function(connection) { - if (!connection) { - return null; - } - const type = connection.type; - if (type === ConnectionType.INPUT_VALUE) { - return ASTNode.createInputNode(connection.getParentInput()); - } else if ( - type === ConnectionType.NEXT_STATEMENT && connection.getParentInput()) { - return ASTNode.createInputNode(connection.getParentInput()); - } else if (type === ConnectionType.NEXT_STATEMENT) { - return new ASTNode(ASTNode.types.NEXT, connection); - } else if (type === ConnectionType.OUTPUT_VALUE) { - return new ASTNode(ASTNode.types.OUTPUT, connection); - } else if (type === ConnectionType.PREVIOUS_STATEMENT) { - return new ASTNode(ASTNode.types.PREVIOUS, connection); - } - return null; -}; - -/** - * Creates an AST node pointing to an input. Stores the input connection as the - * location. - * @param {Input} input The input used to create an AST node. - * @return {ASTNode} An AST node pointing to a input. - */ -ASTNode.createInputNode = function(input) { - if (!input || !input.connection) { - return null; - } - return new ASTNode(ASTNode.types.INPUT, input.connection); -}; - -/** - * Creates an AST node pointing to a block. - * @param {Block} block The block used to create an AST node. - * @return {ASTNode} An AST node pointing to a block. - */ -ASTNode.createBlockNode = function(block) { - if (!block) { - return null; - } - return new ASTNode(ASTNode.types.BLOCK, block); -}; - -/** - * Create an AST node of type stack. A stack, represented by its top block, is - * the set of all blocks connected to a top block, including the top block. - * @param {Block} topBlock A top block has no parent and can be found - * in the list returned by workspace.getTopBlocks(). - * @return {ASTNode} An AST node of type stack that points to the top - * block on the stack. - */ -ASTNode.createStackNode = function(topBlock) { - if (!topBlock) { - return null; - } - return new ASTNode(ASTNode.types.STACK, topBlock); -}; - -/** - * Creates an AST node pointing to a workspace. - * @param {!Workspace} workspace The workspace that we are on. - * @param {Coordinate} wsCoordinate The position on the workspace - * for this node. - * @return {ASTNode} An AST node pointing to a workspace and a position - * on the workspace. - */ -ASTNode.createWorkspaceNode = function(workspace, wsCoordinate) { - if (!wsCoordinate || !workspace) { - return null; - } - const params = {wsCoordinate: wsCoordinate}; - return new ASTNode(ASTNode.types.WORKSPACE, workspace, params); -}; - /** * Gets the parent connection on a block. * This is either an output connection, previous connection or undefined. @@ -250,483 +738,4 @@ const getParentConnection = function(block) { return topConnection; }; -/** - * Creates an AST node for the top position on a block. - * This is either an output connection, previous connection, or block. - * @param {!Block} block The block to find the top most AST node on. - * @return {ASTNode} The AST node holding the top most position on the - * block. - */ -ASTNode.createTopNode = function(block) { - let astNode; - const topConnection = getParentConnection(block); - if (topConnection) { - astNode = ASTNode.createConnectionNode(topConnection); - } else { - astNode = ASTNode.createBlockNode(block); - } - return astNode; -}; - -/** - * Parse the optional parameters. - * @param {?ASTNode.Params} params The user specified parameters. - * @private - */ -ASTNode.prototype.processParams_ = function(params) { - if (!params) { - return; - } - if (params.wsCoordinate) { - this.wsCoordinate_ = params.wsCoordinate; - } -}; - -/** - * Gets the value pointed to by this node. - * It is the callers responsibility to check the node type to figure out what - * type of object they get back from this. - * @return {!IASTNodeLocation} The current field, connection, workspace, or - * block the cursor is on. - */ -ASTNode.prototype.getLocation = function() { - return this.location_; -}; - -/** - * The type of the current location. - * One of ASTNode.types - * @return {string} The type of the location. - */ -ASTNode.prototype.getType = function() { - return this.type_; -}; - -/** - * The coordinate on the workspace. - * @return {Coordinate} The workspace coordinate or null if the - * location is not a workspace. - */ -ASTNode.prototype.getWsCoordinate = function() { - return this.wsCoordinate_; -}; - -/** - * Whether the node points to a connection. - * @return {boolean} [description] - * @package - */ -ASTNode.prototype.isConnection = function() { - return this.isConnection_; -}; - -/** - * Given an input find the next editable field or an input with a non null - * connection in the same block. The current location must be an input - * connection. - * @return {ASTNode} The AST node holding the next field or connection - * or null if there is no editable field or input connection after the given - * input. - * @private - */ -ASTNode.prototype.findNextForInput_ = function() { - const location = /** @type {!Connection} */ (this.location_); - const parentInput = location.getParentInput(); - const block = parentInput.getSourceBlock(); - const curIdx = block.inputList.indexOf(parentInput); - for (let i = curIdx + 1; i < block.inputList.length; i++) { - const input = block.inputList[i]; - const fieldRow = input.fieldRow; - for (let j = 0; j < fieldRow.length; j++) { - const field = fieldRow[j]; - if (field.isClickable() || ASTNode.NAVIGATE_ALL_FIELDS) { - return ASTNode.createFieldNode(field); - } - } - if (input.connection) { - return ASTNode.createInputNode(input); - } - } - return null; -}; - -/** - * Given a field find the next editable field or an input with a non null - * connection in the same block. The current location must be a field. - * @return {ASTNode} The AST node pointing to the next field or - * connection or null if there is no editable field or input connection - * after the given input. - * @private - */ -ASTNode.prototype.findNextForField_ = function() { - const location = /** @type {!Field} */ (this.location_); - const input = location.getParentInput(); - const block = location.getSourceBlock(); - const curIdx = block.inputList.indexOf(/** @type {!Input} */ (input)); - let fieldIdx = input.fieldRow.indexOf(location) + 1; - for (let i = curIdx; i < block.inputList.length; i++) { - const newInput = block.inputList[i]; - const fieldRow = newInput.fieldRow; - while (fieldIdx < fieldRow.length) { - if (fieldRow[fieldIdx].isClickable() || ASTNode.NAVIGATE_ALL_FIELDS) { - return ASTNode.createFieldNode(fieldRow[fieldIdx]); - } - fieldIdx++; - } - fieldIdx = 0; - if (newInput.connection) { - return ASTNode.createInputNode(newInput); - } - } - return null; -}; - -/** - * Given an input find the previous editable field or an input with a non null - * connection in the same block. The current location must be an input - * connection. - * @return {ASTNode} The AST node holding the previous field or - * connection. - * @private - */ -ASTNode.prototype.findPrevForInput_ = function() { - const location = /** @type {!Connection} */ (this.location_); - const parentInput = location.getParentInput(); - const block = parentInput.getSourceBlock(); - const curIdx = block.inputList.indexOf(parentInput); - for (let i = curIdx; i >= 0; i--) { - const input = block.inputList[i]; - if (input.connection && input !== parentInput) { - return ASTNode.createInputNode(input); - } - const fieldRow = input.fieldRow; - for (let j = fieldRow.length - 1; j >= 0; j--) { - const field = fieldRow[j]; - if (field.isClickable() || ASTNode.NAVIGATE_ALL_FIELDS) { - return ASTNode.createFieldNode(field); - } - } - } - return null; -}; - -/** - * Given a field find the previous editable field or an input with a non null - * connection in the same block. The current location must be a field. - * @return {ASTNode} The AST node holding the previous input or field. - * @private - */ -ASTNode.prototype.findPrevForField_ = function() { - const location = /** @type {!Field} */ (this.location_); - const parentInput = location.getParentInput(); - const block = location.getSourceBlock(); - const curIdx = block.inputList.indexOf( - /** @type {!Input} */ (parentInput)); - let fieldIdx = parentInput.fieldRow.indexOf(location) - 1; - for (let i = curIdx; i >= 0; i--) { - const input = block.inputList[i]; - if (input.connection && input !== parentInput) { - return ASTNode.createInputNode(input); - } - const fieldRow = input.fieldRow; - while (fieldIdx > -1) { - if (fieldRow[fieldIdx].isClickable() || ASTNode.NAVIGATE_ALL_FIELDS) { - return ASTNode.createFieldNode(fieldRow[fieldIdx]); - } - fieldIdx--; - } - // Reset the fieldIdx to the length of the field row of the previous input. - if (i - 1 >= 0) { - fieldIdx = block.inputList[i - 1].fieldRow.length - 1; - } - } - return null; -}; - -/** - * Navigate between stacks of blocks on the workspace. - * @param {boolean} forward True to go forward. False to go backwards. - * @return {ASTNode} The first block of the next stack or null if there - * are no blocks on the workspace. - * @private - */ -ASTNode.prototype.navigateBetweenStacks_ = function(forward) { - let curLocation = this.getLocation(); - if (curLocation.getSourceBlock) { - curLocation = /** @type {!IASTNodeLocationWithBlock} */ (curLocation) - .getSourceBlock(); - } - if (!curLocation || !curLocation.workspace) { - return null; - } - const curRoot = curLocation.getRootBlock(); - const topBlocks = curRoot.workspace.getTopBlocks(true); - for (let i = 0; i < topBlocks.length; i++) { - const topBlock = topBlocks[i]; - if (curRoot.id === topBlock.id) { - const offset = forward ? 1 : -1; - const resultIndex = i + offset; - if (resultIndex === -1 || resultIndex === topBlocks.length) { - return null; - } - return ASTNode.createStackNode(topBlocks[resultIndex]); - } - } - throw Error('Couldn\'t find ' + (forward ? 'next' : 'previous') + ' stack?!'); -}; - -/** - * Finds the top most AST node for a given block. - * This is either the previous connection, output connection or block depending - * on what kind of connections the block has. - * @param {!Block} block The block that we want to find the top - * connection on. - * @return {!ASTNode} The AST node containing the top connection. - * @private - */ -ASTNode.prototype.findTopASTNodeForBlock_ = function(block) { - const topConnection = getParentConnection(block); - if (topConnection) { - return /** @type {!ASTNode} */ ( - ASTNode.createConnectionNode(topConnection)); - } else { - return /** @type {!ASTNode} */ (ASTNode.createBlockNode(block)); - } -}; - -/** - * Get the AST node pointing to the input that the block is nested under or if - * the block is not nested then get the stack AST node. - * @param {Block} block The source block of the current location. - * @return {ASTNode} The AST node pointing to the input connection or - * the top block of the stack this block is in. - * @private - */ -ASTNode.prototype.getOutAstNodeForBlock_ = function(block) { - if (!block) { - return null; - } - // If the block doesn't have a previous connection then it is the top of the - // substack. - const topBlock = block.getTopStackBlock(); - const topConnection = getParentConnection(topBlock); - // If the top connection has a parentInput, create an AST node pointing to - // that input. - if (topConnection && topConnection.targetConnection && - topConnection.targetConnection.getParentInput()) { - return ASTNode.createInputNode( - topConnection.targetConnection.getParentInput()); - } else { - // Go to stack level if you are not underneath an input. - return ASTNode.createStackNode(topBlock); - } -}; - -/** - * Find the first editable field or input with a connection on a given block. - * @param {!Block} block The source block of the current location. - * @return {ASTNode} An AST node pointing to the first field or input. - * Null if there are no editable fields or inputs with connections on the block. - * @private - */ -ASTNode.prototype.findFirstFieldOrInput_ = function(block) { - const inputs = block.inputList; - for (let i = 0; i < inputs.length; i++) { - const input = inputs[i]; - const fieldRow = input.fieldRow; - for (let j = 0; j < fieldRow.length; j++) { - const field = fieldRow[j]; - if (field.isClickable() || ASTNode.NAVIGATE_ALL_FIELDS) { - return ASTNode.createFieldNode(field); - } - } - if (input.connection) { - return ASTNode.createInputNode(input); - } - } - return null; -}; - -/** - * Finds the source block of the location of this node. - * @return {Block} The source block of the location, or null if the node - * is of type workspace. - */ -ASTNode.prototype.getSourceBlock = function() { - if (this.getType() === ASTNode.types.BLOCK) { - return /** @type {Block} */ (this.getLocation()); - } else if (this.getType() === ASTNode.types.STACK) { - return /** @type {Block} */ (this.getLocation()); - } else if (this.getType() === ASTNode.types.WORKSPACE) { - return null; - } else { - return /** @type {IASTNodeLocationWithBlock} */ (this.getLocation()) - .getSourceBlock(); - } -}; - -/** - * Find the element to the right of the current element in the AST. - * @return {ASTNode} An AST node that wraps the next field, connection, - * block, or workspace. Or null if there is no node to the right. - */ -ASTNode.prototype.next = function() { - switch (this.type_) { - case ASTNode.types.STACK: - return this.navigateBetweenStacks_(true); - - case ASTNode.types.OUTPUT: { - const connection = /** @type {!Connection} */ (this.location_); - return ASTNode.createBlockNode(connection.getSourceBlock()); - } - case ASTNode.types.FIELD: - return this.findNextForField_(); - - case ASTNode.types.INPUT: - return this.findNextForInput_(); - - case ASTNode.types.BLOCK: { - const block = /** @type {!Block} */ (this.location_); - const nextConnection = block.nextConnection; - return ASTNode.createConnectionNode(nextConnection); - } - case ASTNode.types.PREVIOUS: { - const connection = /** @type {!Connection} */ (this.location_); - return ASTNode.createBlockNode(connection.getSourceBlock()); - } - case ASTNode.types.NEXT: { - const connection = /** @type {!Connection} */ (this.location_); - const targetConnection = connection.targetConnection; - return ASTNode.createConnectionNode(targetConnection); - } - } - - return null; -}; - -/** - * Find the element one level below and all the way to the left of the current - * location. - * @return {ASTNode} An AST node that wraps the next field, connection, - * workspace, or block. Or null if there is nothing below this node. - */ -ASTNode.prototype.in = function() { - switch (this.type_) { - case ASTNode.types.WORKSPACE: { - const workspace = /** @type {!Workspace} */ (this.location_); - const topBlocks = workspace.getTopBlocks(true); - if (topBlocks.length > 0) { - return ASTNode.createStackNode(topBlocks[0]); - } - break; - } - case ASTNode.types.STACK: { - const block = /** @type {!Block} */ (this.location_); - return this.findTopASTNodeForBlock_(block); - } - case ASTNode.types.BLOCK: { - const block = /** @type {!Block} */ (this.location_); - return this.findFirstFieldOrInput_(block); - } - case ASTNode.types.INPUT: { - const connection = /** @type {!Connection} */ (this.location_); - const targetConnection = connection.targetConnection; - return ASTNode.createConnectionNode(targetConnection); - } - } - - return null; -}; - -/** - * Find the element to the left of the current element in the AST. - * @return {ASTNode} An AST node that wraps the previous field, - * connection, workspace or block. Or null if no node exists to the left. - * null. - */ -ASTNode.prototype.prev = function() { - switch (this.type_) { - case ASTNode.types.STACK: - return this.navigateBetweenStacks_(false); - - case ASTNode.types.OUTPUT: - return null; - - case ASTNode.types.FIELD: - return this.findPrevForField_(); - - case ASTNode.types.INPUT: - return this.findPrevForInput_(); - - case ASTNode.types.BLOCK: { - const block = /** @type {!Block} */ (this.location_); - const topConnection = getParentConnection(block); - return ASTNode.createConnectionNode(topConnection); - } - case ASTNode.types.PREVIOUS: { - const connection = /** @type {!Connection} */ (this.location_); - const targetConnection = connection.targetConnection; - if (targetConnection && !targetConnection.getParentInput()) { - return ASTNode.createConnectionNode(targetConnection); - } - break; - } - case ASTNode.types.NEXT: { - const connection = /** @type {!Connection} */ (this.location_); - return ASTNode.createBlockNode(connection.getSourceBlock()); - } - } - - return null; -}; - -/** - * Find the next element that is one position above and all the way to the left - * of the current location. - * @return {ASTNode} An AST node that wraps the next field, connection, - * workspace or block. Or null if we are at the workspace level. - */ -ASTNode.prototype.out = function() { - switch (this.type_) { - case ASTNode.types.STACK: { - const block = /** @type {!Block} */ (this.location_); - const blockPos = block.getRelativeToSurfaceXY(); - // TODO: Make sure this is in the bounds of the workspace. - const wsCoordinate = - new Coordinate(blockPos.x, blockPos.y + ASTNode.DEFAULT_OFFSET_Y); - return ASTNode.createWorkspaceNode(block.workspace, wsCoordinate); - } - case ASTNode.types.OUTPUT: { - const connection = /** @type {!Connection} */ (this.location_); - const target = connection.targetConnection; - if (target) { - return ASTNode.createConnectionNode(target); - } - return ASTNode.createStackNode(connection.getSourceBlock()); - } - case ASTNode.types.FIELD: { - const field = /** @type {!Field} */ (this.location_); - return ASTNode.createBlockNode(field.getSourceBlock()); - } - case ASTNode.types.INPUT: { - const connection = /** @type {!Connection} */ (this.location_); - return ASTNode.createBlockNode(connection.getSourceBlock()); - } - case ASTNode.types.BLOCK: { - const block = /** @type {!Block} */ (this.location_); - return this.getOutAstNodeForBlock_(block); - } - case ASTNode.types.PREVIOUS: { - const connection = /** @type {!Connection} */ (this.location_); - return this.getOutAstNodeForBlock_(connection.getSourceBlock()); - } - case ASTNode.types.NEXT: { - const connection = /** @type {!Connection} */ (this.location_); - return this.getOutAstNodeForBlock_(connection.getSourceBlock()); - } - } - - return null; -}; - exports.ASTNode = ASTNode; diff --git a/core/keyboard_nav/basic_cursor.js b/core/keyboard_nav/basic_cursor.js index c6b5c0ba5..fa16bc4eb 100644 --- a/core/keyboard_nav/basic_cursor.js +++ b/core/keyboard_nav/basic_cursor.js @@ -17,7 +17,6 @@ */ goog.module('Blockly.BasicCursor'); -const object = goog.require('Blockly.utils.object'); const registry = goog.require('Blockly.registry'); const {ASTNode} = goog.require('Blockly.ASTNode'); const {Cursor} = goog.require('Blockly.Cursor'); @@ -27,14 +26,192 @@ const {Cursor} = goog.require('Blockly.Cursor'); * Class for a basic cursor. * This will allow the user to get to all nodes in the AST by hitting next or * previous. - * @constructor * @extends {Cursor} * @alias Blockly.BasicCursor */ -const BasicCursor = function() { - BasicCursor.superClass_.constructor.call(this); -}; -object.inherits(BasicCursor, Cursor); +class BasicCursor extends Cursor { + /** + * @alias Blockly.BasicCursor + */ + constructor() { + super(); + } + + /** + * Find the next node in the pre order traversal. + * @return {?ASTNode} The next node, or null if the current node is + * not set or there is no next value. + * @override + */ + next() { + const curNode = this.getCurNode(); + if (!curNode) { + return null; + } + const newNode = this.getNextNode_(curNode, this.validNode_); + + if (newNode) { + this.setCurNode(newNode); + } + return newNode; + } + + /** + * For a basic cursor we only have the ability to go next and previous, so + * in will also allow the user to get to the next node in the pre order + * traversal. + * @return {?ASTNode} The next node, or null if the current node is + * not set or there is no next value. + * @override + */ + in() { + return this.next(); + } + + /** + * Find the previous node in the pre order traversal. + * @return {?ASTNode} The previous node, or null if the current node + * is not set or there is no previous value. + * @override + */ + prev() { + const curNode = this.getCurNode(); + if (!curNode) { + return null; + } + const newNode = this.getPreviousNode_(curNode, this.validNode_); + + if (newNode) { + this.setCurNode(newNode); + } + return newNode; + } + + /** + * For a basic cursor we only have the ability to go next and previous, so + * out will allow the user to get to the previous node in the pre order + * traversal. + * @return {?ASTNode} The previous node, or null if the current node is + * not set or there is no previous value. + * @override + */ + out() { + return this.prev(); + } + + /** + * Uses pre order traversal to navigate the Blockly AST. This will allow + * a user to easily navigate the entire Blockly AST without having to go in + * and out levels on the tree. + * @param {?ASTNode} node The current position in the AST. + * @param {!function(ASTNode) : boolean} isValid A function true/false + * depending on whether the given node should be traversed. + * @return {?ASTNode} The next node in the traversal. + * @protected + */ + getNextNode_(node, isValid) { + if (!node) { + return null; + } + const newNode = node.in() || node.next(); + if (isValid(newNode)) { + return newNode; + } else if (newNode) { + return this.getNextNode_(newNode, isValid); + } + const siblingOrParent = this.findSiblingOrParent_(node.out()); + if (isValid(siblingOrParent)) { + return siblingOrParent; + } else if (siblingOrParent) { + return this.getNextNode_(siblingOrParent, isValid); + } + return null; + } + + /** + * Reverses the pre order traversal in order to find the previous node. This + * will allow a user to easily navigate the entire Blockly AST without having + * to go in and out levels on the tree. + * @param {?ASTNode} node The current position in the AST. + * @param {!function(ASTNode) : boolean} isValid A function true/false + * depending on whether the given node should be traversed. + * @return {?ASTNode} The previous node in the traversal or null if no + * previous node exists. + * @protected + */ + getPreviousNode_(node, isValid) { + if (!node) { + return null; + } + let newNode = node.prev(); + + if (newNode) { + newNode = this.getRightMostChild_(newNode); + } else { + newNode = node.out(); + } + if (isValid(newNode)) { + return newNode; + } else if (newNode) { + return this.getPreviousNode_(newNode, isValid); + } + return null; + } + + /** + * Decides what nodes to traverse and which ones to skip. Currently, it + * skips output, stack and workspace nodes. + * @param {?ASTNode} node The AST node to check whether it is valid. + * @return {boolean} True if the node should be visited, false otherwise. + * @protected + */ + validNode_(node) { + let isValid = false; + const type = node && node.getType(); + if (type === ASTNode.types.OUTPUT || type === ASTNode.types.INPUT || + type === ASTNode.types.FIELD || type === ASTNode.types.NEXT || + type === ASTNode.types.PREVIOUS || type === ASTNode.types.WORKSPACE) { + isValid = true; + } + return isValid; + } + + /** + * From the given node find either the next valid sibling or parent. + * @param {?ASTNode} node The current position in the AST. + * @return {?ASTNode} The parent AST node or null if there are no + * valid parents. + * @private + */ + findSiblingOrParent_(node) { + if (!node) { + return null; + } + const nextNode = node.next(); + if (nextNode) { + return nextNode; + } + return this.findSiblingOrParent_(node.out()); + } + + /** + * Get the right most child of a node. + * @param {?ASTNode} node The node to find the right most child of. + * @return {?ASTNode} The right most child of the given node, or the node + * if no child exists. + * @private + */ + getRightMostChild_(node) { + if (!node.in()) { + return node; + } + let newNode = node.in(); + while (newNode.next()) { + newNode = newNode.next(); + } + return this.getRightMostChild_(newNode); + } +} /** * Name used for registering a basic cursor. @@ -42,182 +219,6 @@ object.inherits(BasicCursor, Cursor); */ BasicCursor.registrationName = 'basicCursor'; -/** - * Find the next node in the pre order traversal. - * @return {?ASTNode} The next node, or null if the current node is - * not set or there is no next value. - * @override - */ -BasicCursor.prototype.next = function() { - const curNode = this.getCurNode(); - if (!curNode) { - return null; - } - const newNode = this.getNextNode_(curNode, this.validNode_); - - if (newNode) { - this.setCurNode(newNode); - } - return newNode; -}; - -/** - * For a basic cursor we only have the ability to go next and previous, so - * in will also allow the user to get to the next node in the pre order - * traversal. - * @return {?ASTNode} The next node, or null if the current node is - * not set or there is no next value. - * @override - */ -BasicCursor.prototype.in = function() { - return this.next(); -}; - -/** - * Find the previous node in the pre order traversal. - * @return {?ASTNode} The previous node, or null if the current node - * is not set or there is no previous value. - * @override - */ -BasicCursor.prototype.prev = function() { - const curNode = this.getCurNode(); - if (!curNode) { - return null; - } - const newNode = this.getPreviousNode_(curNode, this.validNode_); - - if (newNode) { - this.setCurNode(newNode); - } - return newNode; -}; - -/** - * For a basic cursor we only have the ability to go next and previous, so - * out will allow the user to get to the previous node in the pre order - * traversal. - * @return {?ASTNode} The previous node, or null if the current node is - * not set or there is no previous value. - * @override - */ -BasicCursor.prototype.out = function() { - return this.prev(); -}; - -/** - * Uses pre order traversal to navigate the Blockly AST. This will allow - * a user to easily navigate the entire Blockly AST without having to go in - * and out levels on the tree. - * @param {?ASTNode} node The current position in the AST. - * @param {!function(ASTNode) : boolean} isValid A function true/false - * depending on whether the given node should be traversed. - * @return {?ASTNode} The next node in the traversal. - * @protected - */ -BasicCursor.prototype.getNextNode_ = function(node, isValid) { - if (!node) { - return null; - } - const newNode = node.in() || node.next(); - if (isValid(newNode)) { - return newNode; - } else if (newNode) { - return this.getNextNode_(newNode, isValid); - } - const siblingOrParent = this.findSiblingOrParent_(node.out()); - if (isValid(siblingOrParent)) { - return siblingOrParent; - } else if (siblingOrParent) { - return this.getNextNode_(siblingOrParent, isValid); - } - return null; -}; - -/** - * Reverses the pre order traversal in order to find the previous node. This - * will allow a user to easily navigate the entire Blockly AST without having to - * go in and out levels on the tree. - * @param {?ASTNode} node The current position in the AST. - * @param {!function(ASTNode) : boolean} isValid A function true/false - * depending on whether the given node should be traversed. - * @return {?ASTNode} The previous node in the traversal or null if no - * previous node exists. - * @protected - */ -BasicCursor.prototype.getPreviousNode_ = function(node, isValid) { - if (!node) { - return null; - } - let newNode = node.prev(); - - if (newNode) { - newNode = this.getRightMostChild_(newNode); - } else { - newNode = node.out(); - } - if (isValid(newNode)) { - return newNode; - } else if (newNode) { - return this.getPreviousNode_(newNode, isValid); - } - return null; -}; - -/** - * Decides what nodes to traverse and which ones to skip. Currently, it - * skips output, stack and workspace nodes. - * @param {?ASTNode} node The AST node to check whether it is valid. - * @return {boolean} True if the node should be visited, false otherwise. - * @protected - */ -BasicCursor.prototype.validNode_ = function(node) { - let isValid = false; - const type = node && node.getType(); - if (type === ASTNode.types.OUTPUT || type === ASTNode.types.INPUT || - type === ASTNode.types.FIELD || type === ASTNode.types.NEXT || - type === ASTNode.types.PREVIOUS || type === ASTNode.types.WORKSPACE) { - isValid = true; - } - return isValid; -}; - -/** - * From the given node find either the next valid sibling or parent. - * @param {?ASTNode} node The current position in the AST. - * @return {?ASTNode} The parent AST node or null if there are no - * valid parents. - * @private - */ -BasicCursor.prototype.findSiblingOrParent_ = function(node) { - if (!node) { - return null; - } - const nextNode = node.next(); - if (nextNode) { - return nextNode; - } - return this.findSiblingOrParent_(node.out()); -}; - - -/** - * Get the right most child of a node. - * @param {?ASTNode} node The node to find the right most child of. - * @return {?ASTNode} The right most child of the given node, or the node - * if no child exists. - * @private - */ -BasicCursor.prototype.getRightMostChild_ = function(node) { - if (!node.in()) { - return node; - } - let newNode = node.in(); - while (newNode.next()) { - newNode = newNode.next(); - } - return this.getRightMostChild_(newNode); -}; - registry.register( registry.Type.CURSOR, BasicCursor.registrationName, BasicCursor); diff --git a/core/keyboard_nav/cursor.js b/core/keyboard_nav/cursor.js index 6360fb866..64975dc9d 100644 --- a/core/keyboard_nav/cursor.js +++ b/core/keyboard_nav/cursor.js @@ -17,7 +17,6 @@ */ goog.module('Blockly.Cursor'); -const object = goog.require('Blockly.utils.object'); const registry = goog.require('Blockly.registry'); const {ASTNode} = goog.require('Blockly.ASTNode'); const {Marker} = goog.require('Blockly.Marker'); @@ -25,117 +24,120 @@ const {Marker} = goog.require('Blockly.Marker'); /** * Class for a cursor. * A cursor controls how a user navigates the Blockly AST. - * @constructor * @extends {Marker} * @alias Blockly.Cursor */ -const Cursor = function() { - Cursor.superClass_.constructor.call(this); +class Cursor extends Marker { + /** + * @alias Blockly.Cursor + */ + constructor() { + super(); + + /** + * @override + */ + this.type = 'cursor'; + } /** - * @override + * Find the next connection, field, or block. + * @return {ASTNode} The next element, or null if the current node is + * not set or there is no next value. + * @public */ - this.type = 'cursor'; -}; -object.inherits(Cursor, Marker); + next() { + const curNode = this.getCurNode(); + if (!curNode) { + return null; + } -/** - * Find the next connection, field, or block. - * @return {ASTNode} The next element, or null if the current node is - * not set or there is no next value. - * @public - */ -Cursor.prototype.next = function() { - const curNode = this.getCurNode(); - if (!curNode) { - return null; + let newNode = curNode.next(); + while (newNode && newNode.next() && + (newNode.getType() === ASTNode.types.NEXT || + newNode.getType() === ASTNode.types.BLOCK)) { + newNode = newNode.next(); + } + + if (newNode) { + this.setCurNode(newNode); + } + return newNode; } - let newNode = curNode.next(); - while (newNode && newNode.next() && - (newNode.getType() === ASTNode.types.NEXT || - newNode.getType() === ASTNode.types.BLOCK)) { - newNode = newNode.next(); + /** + * Find the in connection or field. + * @return {ASTNode} The in element, or null if the current node is + * not set or there is no in value. + * @public + */ + in() { + let curNode = this.getCurNode(); + if (!curNode) { + return null; + } + // If we are on a previous or output connection, go to the block level + // before performing next operation. + if (curNode.getType() === ASTNode.types.PREVIOUS || + curNode.getType() === ASTNode.types.OUTPUT) { + curNode = curNode.next(); + } + const newNode = curNode.in(); + + if (newNode) { + this.setCurNode(newNode); + } + return newNode; } - if (newNode) { - this.setCurNode(newNode); - } - return newNode; -}; + /** + * Find the previous connection, field, or block. + * @return {ASTNode} The previous element, or null if the current node + * is not set or there is no previous value. + * @public + */ + prev() { + const curNode = this.getCurNode(); + if (!curNode) { + return null; + } + let newNode = curNode.prev(); -/** - * Find the in connection or field. - * @return {ASTNode} The in element, or null if the current node is - * not set or there is no in value. - * @public - */ -Cursor.prototype.in = function() { - let curNode = this.getCurNode(); - if (!curNode) { - return null; - } - // If we are on a previous or output connection, go to the block level before - // performing next operation. - if (curNode.getType() === ASTNode.types.PREVIOUS || - curNode.getType() === ASTNode.types.OUTPUT) { - curNode = curNode.next(); - } - const newNode = curNode.in(); + while (newNode && newNode.prev() && + (newNode.getType() === ASTNode.types.NEXT || + newNode.getType() === ASTNode.types.BLOCK)) { + newNode = newNode.prev(); + } - if (newNode) { - this.setCurNode(newNode); - } - return newNode; -}; - -/** - * Find the previous connection, field, or block. - * @return {ASTNode} The previous element, or null if the current node - * is not set or there is no previous value. - * @public - */ -Cursor.prototype.prev = function() { - const curNode = this.getCurNode(); - if (!curNode) { - return null; - } - let newNode = curNode.prev(); - - while (newNode && newNode.prev() && - (newNode.getType() === ASTNode.types.NEXT || - newNode.getType() === ASTNode.types.BLOCK)) { - newNode = newNode.prev(); + if (newNode) { + this.setCurNode(newNode); + } + return newNode; } - if (newNode) { - this.setCurNode(newNode); - } - return newNode; -}; + /** + * Find the out connection, field, or block. + * @return {ASTNode} The out element, or null if the current node is + * not set or there is no out value. + * @public + */ + out() { + const curNode = this.getCurNode(); + if (!curNode) { + return null; + } + let newNode = curNode.out(); -/** - * Find the out connection, field, or block. - * @return {ASTNode} The out element, or null if the current node is - * not set or there is no out value. - * @public - */ -Cursor.prototype.out = function() { - const curNode = this.getCurNode(); - if (!curNode) { - return null; - } - let newNode = curNode.out(); + if (newNode && newNode.getType() === ASTNode.types.BLOCK) { + newNode = newNode.prev() || newNode; + } - if (newNode && newNode.getType() === ASTNode.types.BLOCK) { - newNode = newNode.prev() || newNode; + if (newNode) { + this.setCurNode(newNode); + } + return newNode; } - - if (newNode) { - this.setCurNode(newNode); - } - return newNode; -}; +} registry.register(registry.Type.CURSOR, registry.DEFAULT, Cursor); diff --git a/core/keyboard_nav/marker.js b/core/keyboard_nav/marker.js index a38ad0c8e..43f690e1f 100644 --- a/core/keyboard_nav/marker.js +++ b/core/keyboard_nav/marker.js @@ -26,105 +26,109 @@ const {MarkerSvg} = goog.requireType('Blockly.blockRendering.MarkerSvg'); /** * Class for a marker. * This is used in keyboard navigation to save a location in the Blockly AST. - * @constructor * @alias Blockly.Marker */ -const Marker = function() { +class Marker { /** - * The colour of the marker. - * @type {?string} + * Constructs a new Marker instance. */ - this.colour = null; + constructor() { + /** + * The colour of the marker. + * @type {?string} + */ + this.colour = null; + + /** + * The current location of the marker. + * @type {ASTNode} + * @private + */ + this.curNode_ = null; + + /** + * The object in charge of drawing the visual representation of the current + * node. + * @type {MarkerSvg} + * @private + */ + this.drawer_ = null; + + /** + * The type of the marker. + * @type {string} + */ + this.type = 'marker'; + } /** - * The current location of the marker. - * @type {ASTNode} - * @private + * Sets the object in charge of drawing the marker. + * @param {MarkerSvg} drawer The object in charge of + * drawing the marker. */ - this.curNode_ = null; + setDrawer(drawer) { + this.drawer_ = drawer; + } /** - * The object in charge of drawing the visual representation of the current - * node. - * @type {MarkerSvg} - * @private + * Get the current drawer for the marker. + * @return {MarkerSvg} The object in charge of drawing + * the marker. */ - this.drawer_ = null; + getDrawer() { + return this.drawer_; + } /** - * The type of the marker. - * @type {string} + * Gets the current location of the marker. + * @return {ASTNode} The current field, connection, or block the marker + * is on. */ - this.type = 'marker'; -}; - -/** - * Sets the object in charge of drawing the marker. - * @param {MarkerSvg} drawer The object in charge of - * drawing the marker. - */ -Marker.prototype.setDrawer = function(drawer) { - this.drawer_ = drawer; -}; - -/** - * Get the current drawer for the marker. - * @return {MarkerSvg} The object in charge of drawing - * the marker. - */ -Marker.prototype.getDrawer = function() { - return this.drawer_; -}; - -/** - * Gets the current location of the marker. - * @return {ASTNode} The current field, connection, or block the marker - * is on. - */ -Marker.prototype.getCurNode = function() { - return this.curNode_; -}; - -/** - * Set the location of the marker and call the update method. - * Setting isStack to true will only work if the newLocation is the top most - * output or previous connection on a stack. - * @param {ASTNode} newNode The new location of the marker. - */ -Marker.prototype.setCurNode = function(newNode) { - const oldNode = this.curNode_; - this.curNode_ = newNode; - if (this.drawer_) { - this.drawer_.draw(oldNode, this.curNode_); + getCurNode() { + return this.curNode_; } -}; -/** - * Redraw the current marker. - * @package - */ -Marker.prototype.draw = function() { - if (this.drawer_) { - this.drawer_.draw(this.curNode_, this.curNode_); + /** + * Set the location of the marker and call the update method. + * Setting isStack to true will only work if the newLocation is the top most + * output or previous connection on a stack. + * @param {ASTNode} newNode The new location of the marker. + */ + setCurNode(newNode) { + const oldNode = this.curNode_; + this.curNode_ = newNode; + if (this.drawer_) { + this.drawer_.draw(oldNode, this.curNode_); + } } -}; -/** - * Hide the marker SVG. - */ -Marker.prototype.hide = function() { - if (this.drawer_) { - this.drawer_.hide(); + /** + * Redraw the current marker. + * @package + */ + draw() { + if (this.drawer_) { + this.drawer_.draw(this.curNode_, this.curNode_); + } } -}; -/** - * Dispose of this marker. - */ -Marker.prototype.dispose = function() { - if (this.getDrawer()) { - this.getDrawer().dispose(); + /** + * Hide the marker SVG. + */ + hide() { + if (this.drawer_) { + this.drawer_.hide(); + } } -}; + + /** + * Dispose of this marker. + */ + dispose() { + if (this.getDrawer()) { + this.getDrawer().dispose(); + } + } +} exports.Marker = Marker; diff --git a/core/keyboard_nav/tab_navigate_cursor.js b/core/keyboard_nav/tab_navigate_cursor.js index 88aaeb357..97207c46c 100644 --- a/core/keyboard_nav/tab_navigate_cursor.js +++ b/core/keyboard_nav/tab_navigate_cursor.js @@ -17,7 +17,6 @@ */ goog.module('Blockly.TabNavigateCursor'); -const object = goog.require('Blockly.utils.object'); const {ASTNode} = goog.require('Blockly.ASTNode'); const {BasicCursor} = goog.require('Blockly.BasicCursor'); /* eslint-disable-next-line no-unused-vars */ @@ -26,32 +25,28 @@ const {Field} = goog.requireType('Blockly.Field'); /** * A cursor for navigating between tab navigable fields. - * @constructor * @extends {BasicCursor} * @alias Blockly.TabNavigateCursor */ -const TabNavigateCursor = function() { - TabNavigateCursor.superClass_.constructor.call(this); -}; -object.inherits(TabNavigateCursor, BasicCursor); - -/** - * Skip all nodes except for tab navigable fields. - * @param {?ASTNode} node The AST node to check whether it is valid. - * @return {boolean} True if the node should be visited, false otherwise. - * @override - */ -TabNavigateCursor.prototype.validNode_ = function(node) { - let isValid = false; - const type = node && node.getType(); - if (node) { - const location = /** @type {Field} */ (node.getLocation()); - if (type === ASTNode.types.FIELD && location && location.isTabNavigable() && - location.isClickable()) { - isValid = true; +class TabNavigateCursor extends BasicCursor { + /** + * Skip all nodes except for tab navigable fields. + * @param {?ASTNode} node The AST node to check whether it is valid. + * @return {boolean} True if the node should be visited, false otherwise. + * @override + */ + validNode_(node) { + let isValid = false; + const type = node && node.getType(); + if (node) { + const location = /** @type {Field} */ (node.getLocation()); + if (type === ASTNode.types.FIELD && location && + location.isTabNavigable() && location.isClickable()) { + isValid = true; + } } + return isValid; } - return isValid; -}; +} exports.TabNavigateCursor = TabNavigateCursor; diff --git a/core/marker_manager.js b/core/marker_manager.js index 035f6a4c4..16a3eeb4b 100644 --- a/core/marker_manager.js +++ b/core/marker_manager.js @@ -25,40 +25,183 @@ const {WorkspaceSvg} = goog.requireType('Blockly.WorkspaceSvg'); /** * Class to manage the multiple markers and the cursor on a workspace. - * @param {!WorkspaceSvg} workspace The workspace for the marker manager. - * @constructor * @alias Blockly.MarkerManager - * @package */ -const MarkerManager = function(workspace) { +class MarkerManager { /** - * The cursor. - * @type {?Cursor} - * @private + * @param {!WorkspaceSvg} workspace The workspace for the marker manager. + * @package */ - this.cursor_ = null; + constructor(workspace) { + /** + * The cursor. + * @type {?Cursor} + * @private + */ + this.cursor_ = null; + + /** + * The cursor's SVG element. + * @type {?SVGElement} + * @private + */ + this.cursorSvg_ = null; + + /** + * The map of markers for the workspace. + * @type {!Object} + * @private + */ + this.markers_ = Object.create(null); + + /** + * The workspace this marker manager is associated with. + * @type {!WorkspaceSvg} + * @private + */ + this.workspace_ = workspace; + + /** + * The marker's SVG element. + * @type {?SVGElement} + * @private + */ + this.markerSvg_ = null; + } /** - * The cursor's SVG element. - * @type {?SVGElement} - * @private + * Register the marker by adding it to the map of markers. + * @param {string} id A unique identifier for the marker. + * @param {!Marker} marker The marker to register. */ - this.cursorSvg_ = null; + registerMarker(id, marker) { + if (this.markers_[id]) { + this.unregisterMarker(id); + } + marker.setDrawer(this.workspace_.getRenderer().makeMarkerDrawer( + this.workspace_, marker)); + this.setMarkerSvg(marker.getDrawer().createDom()); + this.markers_[id] = marker; + } /** - * The map of markers for the workspace. - * @type {!Object} - * @private + * Unregister the marker by removing it from the map of markers. + * @param {string} id The ID of the marker to unregister. */ - this.markers_ = Object.create(null); + unregisterMarker(id) { + const marker = this.markers_[id]; + if (marker) { + marker.dispose(); + delete this.markers_[id]; + } else { + throw Error( + 'Marker with ID ' + id + ' does not exist. ' + + 'Can only unregister markers that exist.'); + } + } /** - * The workspace this marker manager is associated with. - * @type {!WorkspaceSvg} - * @private + * Get the cursor for the workspace. + * @return {?Cursor} The cursor for this workspace. */ - this.workspace_ = workspace; -}; + getCursor() { + return this.cursor_; + } + + /** + * Get a single marker that corresponds to the given ID. + * @param {string} id A unique identifier for the marker. + * @return {?Marker} The marker that corresponds to the given ID, + * or null if none exists. + */ + getMarker(id) { + return this.markers_[id] || null; + } + + /** + * Sets the cursor and initializes the drawer for use with keyboard + * navigation. + * @param {Cursor} cursor The cursor used to move around this workspace. + */ + setCursor(cursor) { + if (this.cursor_ && this.cursor_.getDrawer()) { + this.cursor_.getDrawer().dispose(); + } + this.cursor_ = cursor; + if (this.cursor_) { + const drawer = this.workspace_.getRenderer().makeMarkerDrawer( + this.workspace_, this.cursor_); + this.cursor_.setDrawer(drawer); + this.setCursorSvg(this.cursor_.getDrawer().createDom()); + } + } + + /** + * Add the cursor SVG to this workspace SVG group. + * @param {?SVGElement} cursorSvg The SVG root of the cursor to be added to + * the workspace SVG group. + * @package + */ + setCursorSvg(cursorSvg) { + if (!cursorSvg) { + this.cursorSvg_ = null; + return; + } + + this.workspace_.getBlockCanvas().appendChild(cursorSvg); + this.cursorSvg_ = cursorSvg; + } + + /** + * Add the marker SVG to this workspaces SVG group. + * @param {?SVGElement} markerSvg The SVG root of the marker to be added to + * the workspace SVG group. + * @package + */ + setMarkerSvg(markerSvg) { + if (!markerSvg) { + this.markerSvg_ = null; + return; + } + + if (this.workspace_.getBlockCanvas()) { + if (this.cursorSvg_) { + this.workspace_.getBlockCanvas().insertBefore( + markerSvg, this.cursorSvg_); + } else { + this.workspace_.getBlockCanvas().appendChild(markerSvg); + } + } + } + + /** + * Redraw the attached cursor SVG if needed. + * @package + */ + updateMarkers() { + if (this.workspace_.keyboardAccessibilityMode && this.cursorSvg_) { + this.workspace_.getCursor().draw(); + } + } + + /** + * Dispose of the marker manager. + * Go through and delete all markers associated with this marker manager. + * @suppress {checkTypes} + * @package + */ + dispose() { + const markerIds = Object.keys(this.markers_); + for (let i = 0, markerId; (markerId = markerIds[i]); i++) { + this.unregisterMarker(markerId); + } + this.markers_ = null; + if (this.cursor_) { + this.cursor_.dispose(); + this.cursor_ = null; + } + } +} /** * The name of the local marker. @@ -67,135 +210,4 @@ const MarkerManager = function(workspace) { */ MarkerManager.LOCAL_MARKER = 'local_marker_1'; -/** - * Register the marker by adding it to the map of markers. - * @param {string} id A unique identifier for the marker. - * @param {!Marker} marker The marker to register. - */ -MarkerManager.prototype.registerMarker = function(id, marker) { - if (this.markers_[id]) { - this.unregisterMarker(id); - } - marker.setDrawer( - this.workspace_.getRenderer().makeMarkerDrawer(this.workspace_, marker)); - this.setMarkerSvg(marker.getDrawer().createDom()); - this.markers_[id] = marker; -}; - -/** - * Unregister the marker by removing it from the map of markers. - * @param {string} id The ID of the marker to unregister. - */ -MarkerManager.prototype.unregisterMarker = function(id) { - const marker = this.markers_[id]; - if (marker) { - marker.dispose(); - delete this.markers_[id]; - } else { - throw Error( - 'Marker with ID ' + id + ' does not exist. ' + - 'Can only unregister markers that exist.'); - } -}; - -/** - * Get the cursor for the workspace. - * @return {?Cursor} The cursor for this workspace. - */ -MarkerManager.prototype.getCursor = function() { - return this.cursor_; -}; - -/** - * Get a single marker that corresponds to the given ID. - * @param {string} id A unique identifier for the marker. - * @return {?Marker} The marker that corresponds to the given ID, - * or null if none exists. - */ -MarkerManager.prototype.getMarker = function(id) { - return this.markers_[id] || null; -}; - -/** - * Sets the cursor and initializes the drawer for use with keyboard navigation. - * @param {Cursor} cursor The cursor used to move around this workspace. - */ -MarkerManager.prototype.setCursor = function(cursor) { - if (this.cursor_ && this.cursor_.getDrawer()) { - this.cursor_.getDrawer().dispose(); - } - this.cursor_ = cursor; - if (this.cursor_) { - const drawer = this.workspace_.getRenderer().makeMarkerDrawer( - this.workspace_, this.cursor_); - this.cursor_.setDrawer(drawer); - this.setCursorSvg(this.cursor_.getDrawer().createDom()); - } -}; - -/** - * Add the cursor SVG to this workspace SVG group. - * @param {?SVGElement} cursorSvg The SVG root of the cursor to be added to the - * workspace SVG group. - * @package - */ -MarkerManager.prototype.setCursorSvg = function(cursorSvg) { - if (!cursorSvg) { - this.cursorSvg_ = null; - return; - } - - this.workspace_.getBlockCanvas().appendChild(cursorSvg); - this.cursorSvg_ = cursorSvg; -}; - -/** - * Add the marker SVG to this workspaces SVG group. - * @param {?SVGElement} markerSvg The SVG root of the marker to be added to the - * workspace SVG group. - * @package - */ -MarkerManager.prototype.setMarkerSvg = function(markerSvg) { - if (!markerSvg) { - this.markerSvg_ = null; - return; - } - - if (this.workspace_.getBlockCanvas()) { - if (this.cursorSvg_) { - this.workspace_.getBlockCanvas().insertBefore(markerSvg, this.cursorSvg_); - } else { - this.workspace_.getBlockCanvas().appendChild(markerSvg); - } - } -}; - -/** - * Redraw the attached cursor SVG if needed. - * @package - */ -MarkerManager.prototype.updateMarkers = function() { - if (this.workspace_.keyboardAccessibilityMode && this.cursorSvg_) { - this.workspace_.getCursor().draw(); - } -}; - -/** - * Dispose of the marker manager. - * Go through and delete all markers associated with this marker manager. - * @suppress {checkTypes} - * @package - */ -MarkerManager.prototype.dispose = function() { - const markerIds = Object.keys(this.markers_); - for (let i = 0, markerId; (markerId = markerIds[i]); i++) { - this.unregisterMarker(markerId); - } - this.markers_ = null; - if (this.cursor_) { - this.cursor_.dispose(); - this.cursor_ = null; - } -}; - exports.MarkerManager = MarkerManager; diff --git a/core/menu.js b/core/menu.js index a39b9c52c..27f57e735 100644 --- a/core/menu.js +++ b/core/menu.js @@ -29,449 +29,454 @@ const {Size} = goog.requireType('Blockly.utils.Size'); /** * A basic menu class. - * @constructor * @alias Blockly.Menu */ -const Menu = function() { +const Menu = class { /** - * Array of menu items. - * (Nulls are never in the array, but typing the array as nullable prevents - * the compiler from objecting to .indexOf(null)) - * @type {!Array} - * @private + * Constructs a new Menu instance. */ - this.menuItems_ = []; + constructor() { + /** + * Array of menu items. + * (Nulls are never in the array, but typing the array as nullable prevents + * the compiler from objecting to .indexOf(null)) + * @type {!Array} + * @private + */ + this.menuItems_ = []; + + /** + * Coordinates of the mousedown event that caused this menu to open. Used to + * prevent the consequent mouseup event due to a simple click from + * activating a menu item immediately. + * @type {?Coordinate} + * @package + */ + this.openingCoords = null; + + /** + * This is the element that we will listen to the real focus events on. + * A value of null means no menu item is highlighted. + * @type {?MenuItem} + * @private + */ + this.highlightedItem_ = null; + + /** + * Mouse over event data. + * @type {?browserEvents.Data} + * @private + */ + this.mouseOverHandler_ = null; + + /** + * Click event data. + * @type {?browserEvents.Data} + * @private + */ + this.clickHandler_ = null; + + /** + * Mouse enter event data. + * @type {?browserEvents.Data} + * @private + */ + this.mouseEnterHandler_ = null; + + /** + * Mouse leave event data. + * @type {?browserEvents.Data} + * @private + */ + this.mouseLeaveHandler_ = null; + + /** + * Key down event data. + * @type {?browserEvents.Data} + * @private + */ + this.onKeyDownHandler_ = null; + + /** + * The menu's root DOM element. + * @type {?HTMLDivElement} + * @private + */ + this.element_ = null; + + /** + * ARIA name for this menu. + * @type {?aria.Role} + * @private + */ + this.roleName_ = null; + } /** - * Coordinates of the mousedown event that caused this menu to open. Used to - * prevent the consequent mouseup event due to a simple click from activating - * a menu item immediately. - * @type {?Coordinate} + * Add a new menu item to the bottom of this menu. + * @param {!MenuItem} menuItem Menu item to append. + */ + addChild(menuItem) { + this.menuItems_.push(menuItem); + } + + /** + * Creates the menu DOM. + * @param {!Element} container Element upon which to append this menu. + */ + render(container) { + const element = + /** @type {!HTMLDivElement} */ (document.createElement('div')); + // goog-menu is deprecated, use blocklyMenu. May 2020. + element.className = 'blocklyMenu goog-menu blocklyNonSelectable'; + element.tabIndex = 0; + if (this.roleName_) { + aria.setRole(element, this.roleName_); + } + this.element_ = element; + + // Add menu items. + for (let i = 0, menuItem; (menuItem = this.menuItems_[i]); i++) { + element.appendChild(menuItem.createDom()); + } + + // Add event handlers. + this.mouseOverHandler_ = browserEvents.conditionalBind( + element, 'mouseover', this, this.handleMouseOver_, true); + this.clickHandler_ = browserEvents.conditionalBind( + element, 'click', this, this.handleClick_, true); + this.mouseEnterHandler_ = browserEvents.conditionalBind( + element, 'mouseenter', this, this.handleMouseEnter_, true); + this.mouseLeaveHandler_ = browserEvents.conditionalBind( + element, 'mouseleave', this, this.handleMouseLeave_, true); + this.onKeyDownHandler_ = browserEvents.conditionalBind( + element, 'keydown', this, this.handleKeyEvent_); + + container.appendChild(element); + } + + /** + * Gets the menu's element. + * @return {?Element} The DOM element. * @package */ - this.openingCoords = null; + getElement() { + return this.element_; + } /** - * This is the element that we will listen to the real focus events on. - * A value of null means no menu item is highlighted. - * @type {?MenuItem} - * @private + * Focus the menu element. + * @package */ - this.highlightedItem_ = null; + focus() { + const el = this.getElement(); + if (el) { + el.focus({preventScroll: true}); + dom.addClass(el, 'blocklyFocused'); + } + } /** - * Mouse over event data. - * @type {?browserEvents.Data} + * Blur the menu element. * @private */ - this.mouseOverHandler_ = null; + blur_() { + const el = this.getElement(); + if (el) { + el.blur(); + dom.removeClass(el, 'blocklyFocused'); + } + } /** - * Click event data. - * @type {?browserEvents.Data} - * @private + * Set the menu accessibility role. + * @param {!aria.Role} roleName role name. + * @package */ - this.clickHandler_ = null; + setRole(roleName) { + this.roleName_ = roleName; + } /** - * Mouse enter event data. - * @type {?browserEvents.Data} - * @private + * Dispose of this menu. */ - this.mouseEnterHandler_ = null; + dispose() { + // Remove event handlers. + if (this.mouseOverHandler_) { + browserEvents.unbind(this.mouseOverHandler_); + this.mouseOverHandler_ = null; + } + if (this.clickHandler_) { + browserEvents.unbind(this.clickHandler_); + this.clickHandler_ = null; + } + if (this.mouseEnterHandler_) { + browserEvents.unbind(this.mouseEnterHandler_); + this.mouseEnterHandler_ = null; + } + if (this.mouseLeaveHandler_) { + browserEvents.unbind(this.mouseLeaveHandler_); + this.mouseLeaveHandler_ = null; + } + if (this.onKeyDownHandler_) { + browserEvents.unbind(this.onKeyDownHandler_); + this.onKeyDownHandler_ = null; + } + + // Remove menu items. + for (let i = 0, menuItem; (menuItem = this.menuItems_[i]); i++) { + menuItem.dispose(); + } + this.element_ = null; + } + + // Child component management. /** - * Mouse leave event data. - * @type {?browserEvents.Data} + * Returns the child menu item that owns the given DOM element, + * or null if no such menu item is found. + * @param {Element} elem DOM element whose owner is to be returned. + * @return {?MenuItem} Menu item for which the DOM element belongs to. * @private */ - this.mouseLeaveHandler_ = null; - - /** - * Key down event data. - * @type {?browserEvents.Data} - * @private - */ - this.onKeyDownHandler_ = null; - - /** - * The menu's root DOM element. - * @type {?Element} - * @private - */ - this.element_ = null; - - /** - * ARIA name for this menu. - * @type {?aria.Role} - * @private - */ - this.roleName_ = null; -}; - - -/** - * Add a new menu item to the bottom of this menu. - * @param {!MenuItem} menuItem Menu item to append. - */ -Menu.prototype.addChild = function(menuItem) { - this.menuItems_.push(menuItem); -}; - -/** - * Creates the menu DOM. - * @param {!Element} container Element upon which to append this menu. - */ -Menu.prototype.render = function(container) { - const element = - /** @type {!HTMLDivElement} */ (document.createElement('div')); - // goog-menu is deprecated, use blocklyMenu. May 2020. - element.className = 'blocklyMenu goog-menu blocklyNonSelectable'; - element.tabIndex = 0; - if (this.roleName_) { - aria.setRole(element, this.roleName_); - } - this.element_ = element; - - // Add menu items. - for (let i = 0, menuItem; (menuItem = this.menuItems_[i]); i++) { - element.appendChild(menuItem.createDom()); - } - - // Add event handlers. - this.mouseOverHandler_ = browserEvents.conditionalBind( - element, 'mouseover', this, this.handleMouseOver_, true); - this.clickHandler_ = browserEvents.conditionalBind( - element, 'click', this, this.handleClick_, true); - this.mouseEnterHandler_ = browserEvents.conditionalBind( - element, 'mouseenter', this, this.handleMouseEnter_, true); - this.mouseLeaveHandler_ = browserEvents.conditionalBind( - element, 'mouseleave', this, this.handleMouseLeave_, true); - this.onKeyDownHandler_ = browserEvents.conditionalBind( - element, 'keydown', this, this.handleKeyEvent_); - - container.appendChild(element); -}; - -/** - * Gets the menu's element. - * @return {?Element} The DOM element. - * @package - */ -Menu.prototype.getElement = function() { - return this.element_; -}; - -/** - * Focus the menu element. - * @package - */ -Menu.prototype.focus = function() { - const el = this.getElement(); - if (el) { - el.focus({preventScroll: true}); - dom.addClass(el, 'blocklyFocused'); - } -}; - -/** - * Blur the menu element. - * @private - */ -Menu.prototype.blur_ = function() { - const el = this.getElement(); - if (el) { - el.blur(); - dom.removeClass(el, 'blocklyFocused'); - } -}; - -/** - * Set the menu accessibility role. - * @param {!aria.Role} roleName role name. - * @package - */ -Menu.prototype.setRole = function(roleName) { - this.roleName_ = roleName; -}; - -/** - * Dispose of this menu. - */ -Menu.prototype.dispose = function() { - // Remove event handlers. - if (this.mouseOverHandler_) { - browserEvents.unbind(this.mouseOverHandler_); - this.mouseOverHandler_ = null; - } - if (this.clickHandler_) { - browserEvents.unbind(this.clickHandler_); - this.clickHandler_ = null; - } - if (this.mouseEnterHandler_) { - browserEvents.unbind(this.mouseEnterHandler_); - this.mouseEnterHandler_ = null; - } - if (this.mouseLeaveHandler_) { - browserEvents.unbind(this.mouseLeaveHandler_); - this.mouseLeaveHandler_ = null; - } - if (this.onKeyDownHandler_) { - browserEvents.unbind(this.onKeyDownHandler_); - this.onKeyDownHandler_ = null; - } - - // Remove menu items. - for (let i = 0, menuItem; (menuItem = this.menuItems_[i]); i++) { - menuItem.dispose(); - } - this.element_ = null; -}; - -// Child component management. - -/** - * Returns the child menu item that owns the given DOM element, - * or null if no such menu item is found. - * @param {Element} elem DOM element whose owner is to be returned. - * @return {?MenuItem} Menu item for which the DOM element belongs to. - * @private - */ -Menu.prototype.getMenuItem_ = function(elem) { - const menuElem = this.getElement(); - // Node might be the menu border (resulting in no associated menu item), or - // a menu item's div, or some element within the menu item. - // Walk up parents until one meets either the menu's root element, or - // a menu item's div. - while (elem && elem !== menuElem) { - if (dom.hasClass(elem, 'blocklyMenuItem')) { - // Having found a menu item's div, locate that menu item in this menu. - for (let i = 0, menuItem; (menuItem = this.menuItems_[i]); i++) { - if (menuItem.getElement() === elem) { - return menuItem; + getMenuItem_(elem) { + const menuElem = this.getElement(); + // Node might be the menu border (resulting in no associated menu item), or + // a menu item's div, or some element within the menu item. + // Walk up parents until one meets either the menu's root element, or + // a menu item's div. + while (elem && elem !== menuElem) { + if (dom.hasClass(elem, 'blocklyMenuItem')) { + // Having found a menu item's div, locate that menu item in this menu. + for (let i = 0, menuItem; (menuItem = this.menuItems_[i]); i++) { + if (menuItem.getElement() === elem) { + return menuItem; + } } } + elem = elem.parentElement; } - elem = elem.parentElement; + return null; } - return null; -}; -// Highlight management. + // Highlight management. -/** - * Highlights the given menu item, or clears highlighting if null. - * @param {?MenuItem} item Item to highlight, or null. - * @package - */ -Menu.prototype.setHighlighted = function(item) { - const currentHighlighted = this.highlightedItem_; - if (currentHighlighted) { - currentHighlighted.setHighlighted(false); - this.highlightedItem_ = null; - } - if (item) { - item.setHighlighted(true); - this.highlightedItem_ = item; - // Bring the highlighted item into view. This has no effect if the menu is - // not scrollable. - const el = /** @type {!Element} */ (this.getElement()); - style.scrollIntoContainerView( - /** @type {!Element} */ (item.getElement()), el); - - aria.setState(el, aria.State.ACTIVEDESCENDANT, item.getId()); - } -}; - -/** - * Highlights the next highlightable item (or the first if nothing is currently - * highlighted). - * @package - */ -Menu.prototype.highlightNext = function() { - const index = this.menuItems_.indexOf(this.highlightedItem_); - this.highlightHelper_(index, 1); -}; - -/** - * Highlights the previous highlightable item (or the last if nothing is - * currently highlighted). - * @package - */ -Menu.prototype.highlightPrevious = function() { - const index = this.menuItems_.indexOf(this.highlightedItem_); - this.highlightHelper_(index < 0 ? this.menuItems_.length : index, -1); -}; - -/** - * Highlights the first highlightable item. - * @private - */ -Menu.prototype.highlightFirst_ = function() { - this.highlightHelper_(-1, 1); -}; - -/** - * Highlights the last highlightable item. - * @private - */ -Menu.prototype.highlightLast_ = function() { - this.highlightHelper_(this.menuItems_.length, -1); -}; - -/** - * Helper function that manages the details of moving the highlight among - * child menuitems in response to keyboard events. - * @param {number} startIndex Start index. - * @param {number} delta Step direction: 1 to go down, -1 to go up. - * @private - */ -Menu.prototype.highlightHelper_ = function(startIndex, delta) { - let index = startIndex + delta; - let menuItem; - while ((menuItem = this.menuItems_[index])) { - if (menuItem.isEnabled()) { - this.setHighlighted(menuItem); - break; + /** + * Highlights the given menu item, or clears highlighting if null. + * @param {?MenuItem} item Item to highlight, or null. + * @package + */ + setHighlighted(item) { + const currentHighlighted = this.highlightedItem_; + if (currentHighlighted) { + currentHighlighted.setHighlighted(false); + this.highlightedItem_ = null; + } + if (item) { + item.setHighlighted(true); + this.highlightedItem_ = item; + // Bring the highlighted item into view. This has no effect if the menu is + // not scrollable. + const el = /** @type {!Element} */ (this.getElement()); + style.scrollIntoContainerView( + /** @type {!Element} */ (item.getElement()), el); + + aria.setState(el, aria.State.ACTIVEDESCENDANT, item.getId()); } - index += delta; } -}; -// Mouse events. + /** + * Highlights the next highlightable item (or the first if nothing is + * currently highlighted). + * @package + */ + highlightNext() { + const index = this.menuItems_.indexOf(this.highlightedItem_); + this.highlightHelper_(index, 1); + } -/** - * Handles mouseover events. Highlight menuitems as the user hovers over them. - * @param {!Event} e Mouse event to handle. - * @private - */ -Menu.prototype.handleMouseOver_ = function(e) { - const menuItem = this.getMenuItem_(/** @type {Element} */ (e.target)); + /** + * Highlights the previous highlightable item (or the last if nothing is + * currently highlighted). + * @package + */ + highlightPrevious() { + const index = this.menuItems_.indexOf(this.highlightedItem_); + this.highlightHelper_(index < 0 ? this.menuItems_.length : index, -1); + } - if (menuItem) { - if (menuItem.isEnabled()) { - if (this.highlightedItem_ !== menuItem) { + /** + * Highlights the first highlightable item. + * @private + */ + highlightFirst_() { + this.highlightHelper_(-1, 1); + } + + /** + * Highlights the last highlightable item. + * @private + */ + highlightLast_() { + this.highlightHelper_(this.menuItems_.length, -1); + } + + /** + * Helper function that manages the details of moving the highlight among + * child menuitems in response to keyboard events. + * @param {number} startIndex Start index. + * @param {number} delta Step direction: 1 to go down, -1 to go up. + * @private + */ + highlightHelper_(startIndex, delta) { + let index = startIndex + delta; + let menuItem; + while ((menuItem = this.menuItems_[index])) { + if (menuItem.isEnabled()) { this.setHighlighted(menuItem); + break; } - } else { + index += delta; + } + } + + // Mouse events. + + /** + * Handles mouseover events. Highlight menuitems as the user hovers over them. + * @param {!Event} e Mouse event to handle. + * @private + */ + handleMouseOver_(e) { + const menuItem = this.getMenuItem_(/** @type {Element} */ (e.target)); + + if (menuItem) { + if (menuItem.isEnabled()) { + if (this.highlightedItem_ !== menuItem) { + this.setHighlighted(menuItem); + } + } else { + this.setHighlighted(null); + } + } + } + + /** + * Handles click events. Pass the event onto the child menuitem to handle. + * @param {!Event} e Click event to handle. + * @private + */ + handleClick_(e) { + const oldCoords = this.openingCoords; + // Clear out the saved opening coords immediately so they're not used twice. + this.openingCoords = null; + if (oldCoords && typeof e.clientX === 'number') { + const newCoords = new Coordinate(e.clientX, e.clientY); + if (Coordinate.distance(oldCoords, newCoords) < 1) { + // This menu was opened by a mousedown and we're handling the consequent + // click event. The coords haven't changed, meaning this was the same + // opening event. Don't do the usual behavior because the menu just + // popped up under the mouse and the user didn't mean to activate this + // item. + return; + } + } + + const menuItem = this.getMenuItem_(/** @type {Element} */ (e.target)); + if (menuItem) { + menuItem.performAction(); + } + } + + /** + * Handles mouse enter events. Focus the element. + * @param {!Event} _e Mouse event to handle. + * @private + */ + handleMouseEnter_(_e) { + this.focus(); + } + + /** + * Handles mouse leave events. Blur and clear highlight. + * @param {!Event} _e Mouse event to handle. + * @private + */ + handleMouseLeave_(_e) { + if (this.getElement()) { + this.blur_(); this.setHighlighted(null); } } -}; -/** - * Handles click events. Pass the event onto the child menuitem to handle. - * @param {!Event} e Click event to handle. - * @private - */ -Menu.prototype.handleClick_ = function(e) { - const oldCoords = this.openingCoords; - // Clear out the saved opening coords immediately so they're not used twice. - this.openingCoords = null; - if (oldCoords && typeof e.clientX === 'number') { - const newCoords = new Coordinate(e.clientX, e.clientY); - if (Coordinate.distance(oldCoords, newCoords) < 1) { - // This menu was opened by a mousedown and we're handling the consequent - // click event. The coords haven't changed, meaning this was the same - // opening event. Don't do the usual behavior because the menu just popped - // up under the mouse and the user didn't mean to activate this item. + // Keyboard events. + + /** + * Attempts to handle a keyboard event, if the menu item is enabled, by + * calling + * {@link handleKeyEventInternal_}. + * @param {!Event} e Key event to handle. + * @private + */ + handleKeyEvent_(e) { + if (!this.menuItems_.length) { + // Empty menu. return; } - } - - const menuItem = this.getMenuItem_(/** @type {Element} */ (e.target)); - if (menuItem) { - menuItem.performAction(); - } -}; - -/** - * Handles mouse enter events. Focus the element. - * @param {!Event} _e Mouse event to handle. - * @private - */ -Menu.prototype.handleMouseEnter_ = function(_e) { - this.focus(); -}; - -/** - * Handles mouse leave events. Blur and clear highlight. - * @param {!Event} _e Mouse event to handle. - * @private - */ -Menu.prototype.handleMouseLeave_ = function(_e) { - if (this.getElement()) { - this.blur_(); - this.setHighlighted(null); - } -}; - -// Keyboard events. - -/** - * Attempts to handle a keyboard event, if the menu item is enabled, by calling - * {@link handleKeyEventInternal_}. - * @param {!Event} e Key event to handle. - * @private - */ -Menu.prototype.handleKeyEvent_ = function(e) { - if (!this.menuItems_.length) { - // Empty menu. - return; - } - if (e.shiftKey || e.ctrlKey || e.metaKey || e.altKey) { - // Do not handle the key event if any modifier key is pressed. - return; - } - - const highlighted = this.highlightedItem_; - switch (e.keyCode) { - case KeyCodes.ENTER: - case KeyCodes.SPACE: - if (highlighted) { - highlighted.performAction(); - } - break; - - case KeyCodes.UP: - this.highlightPrevious(); - break; - - case KeyCodes.DOWN: - this.highlightNext(); - break; - - case KeyCodes.PAGE_UP: - case KeyCodes.HOME: - this.highlightFirst_(); - break; - - case KeyCodes.PAGE_DOWN: - case KeyCodes.END: - this.highlightLast_(); - break; - - default: - // Not a key the menu is interested in. + if (e.shiftKey || e.ctrlKey || e.metaKey || e.altKey) { + // Do not handle the key event if any modifier key is pressed. return; - } - // The menu used this key, don't let it have secondary effects. - e.preventDefault(); - e.stopPropagation(); -}; + } -/** - * Get the size of a rendered menu. - * @return {!Size} Object with width and height properties. - * @package - */ -Menu.prototype.getSize = function() { - const menuDom = this.getElement(); - const menuSize = style.getSize(/** @type {!Element} */ - (menuDom)); - // Recalculate height for the total content, not only box height. - menuSize.height = menuDom.scrollHeight; - return menuSize; + const highlighted = this.highlightedItem_; + switch (e.keyCode) { + case KeyCodes.ENTER: + case KeyCodes.SPACE: + if (highlighted) { + highlighted.performAction(); + } + break; + + case KeyCodes.UP: + this.highlightPrevious(); + break; + + case KeyCodes.DOWN: + this.highlightNext(); + break; + + case KeyCodes.PAGE_UP: + case KeyCodes.HOME: + this.highlightFirst_(); + break; + + case KeyCodes.PAGE_DOWN: + case KeyCodes.END: + this.highlightLast_(); + break; + + default: + // Not a key the menu is interested in. + return; + } + // The menu used this key, don't let it have secondary effects. + e.preventDefault(); + e.stopPropagation(); + } + + /** + * Get the size of a rendered menu. + * @return {!Size} Object with width and height properties. + * @package + */ + getSize() { + const menuDom = this.getElement(); + const menuSize = style.getSize(/** @type {!Element} */ + (menuDom)); + // Recalculate height for the total content, not only box height. + menuSize.height = menuDom.scrollHeight; + return menuSize; + } }; exports.Menu = Menu; diff --git a/core/menuitem.js b/core/menuitem.js index f82f49922..3169573b6 100644 --- a/core/menuitem.js +++ b/core/menuitem.js @@ -22,265 +22,271 @@ const idGenerator = goog.require('Blockly.utils.idGenerator'); /** * Class representing an item in a menu. - * - * @param {string|!HTMLElement} content Text caption to display as the content - * of the item, or a HTML element to display. - * @param {string=} opt_value Data/model associated with the menu item. - * @constructor * @alias Blockly.MenuItem */ -const MenuItem = function(content, opt_value) { +const MenuItem = class { /** - * Human-readable text of this menu item, or the HTML element to display. - * @type {string|!HTMLElement} - * @private + * @param {string|!HTMLElement} content Text caption to display as the content + * of the item, or a HTML element to display. + * @param {string=} opt_value Data/model associated with the menu item. */ - this.content_ = content; + constructor(content, opt_value) { + /** + * Human-readable text of this menu item, or the HTML element to display. + * @type {string|!HTMLElement} + * @private + */ + this.content_ = content; - /** - * Machine-readable value of this menu item. - * @type {string|undefined} - * @private - */ - this.value_ = opt_value; + /** + * Machine-readable value of this menu item. + * @type {string|undefined} + * @private + */ + this.value_ = opt_value; - /** - * Is the menu item clickable, as opposed to greyed-out. - * @type {boolean} - * @private - */ - this.enabled_ = true; + /** + * Is the menu item clickable, as opposed to greyed-out. + * @type {boolean} + * @private + */ + this.enabled_ = true; - /** - * The DOM element for the menu item. - * @type {?Element} - * @private - */ - this.element_ = null; + /** + * The DOM element for the menu item. + * @type {?HTMLDivElement} + * @private + */ + this.element_ = null; - /** - * Whether the menu item is rendered right-to-left. - * @type {boolean} - * @private - */ - this.rightToLeft_ = false; + /** + * Whether the menu item is rendered right-to-left. + * @type {boolean} + * @private + */ + this.rightToLeft_ = false; - /** - * ARIA name for this menu. - * @type {?aria.Role} - * @private - */ - this.roleName_ = null; + /** + * ARIA name for this menu. + * @type {?aria.Role} + * @private + */ + this.roleName_ = null; - /** - * Is this menu item checkable. - * @type {boolean} - * @private - */ - this.checkable_ = false; + /** + * Is this menu item checkable. + * @type {boolean} + * @private + */ + this.checkable_ = false; - /** - * Is this menu item currently checked. - * @type {boolean} - * @private - */ - this.checked_ = false; + /** + * Is this menu item currently checked. + * @type {boolean} + * @private + */ + this.checked_ = false; - /** - * Is this menu item currently highlighted. - * @type {boolean} - * @private - */ - this.highlight_ = false; + /** + * Is this menu item currently highlighted. + * @type {boolean} + * @private + */ + this.highlight_ = false; - /** - * Bound function to call when this menu item is clicked. - * @type {?Function} - * @private - */ - this.actionHandler_ = null; -}; - - -/** - * Creates the menuitem's DOM. - * @return {!Element} Completed DOM. - */ -MenuItem.prototype.createDom = function() { - const element = document.createElement('div'); - element.id = idGenerator.getNextUniqueId(); - this.element_ = element; - - // Set class and style - // goog-menuitem* is deprecated, use blocklyMenuItem*. May 2020. - element.className = 'blocklyMenuItem goog-menuitem ' + - (this.enabled_ ? '' : 'blocklyMenuItemDisabled goog-menuitem-disabled ') + - (this.checked_ ? 'blocklyMenuItemSelected goog-option-selected ' : '') + - (this.highlight_ ? 'blocklyMenuItemHighlight goog-menuitem-highlight ' : - '') + - (this.rightToLeft_ ? 'blocklyMenuItemRtl goog-menuitem-rtl ' : ''); - - const content = document.createElement('div'); - content.className = 'blocklyMenuItemContent goog-menuitem-content'; - // Add a checkbox for checkable menu items. - if (this.checkable_) { - const checkbox = document.createElement('div'); - checkbox.className = 'blocklyMenuItemCheckbox goog-menuitem-checkbox'; - content.appendChild(checkbox); + /** + * Bound function to call when this menu item is clicked. + * @type {?Function} + * @private + */ + this.actionHandler_ = null; } - let contentDom = /** @type {!HTMLElement} */ (this.content_); - if (typeof this.content_ === 'string') { - contentDom = document.createTextNode(this.content_); + /** + * Creates the menuitem's DOM. + * @return {!Element} Completed DOM. + */ + createDom() { + const element = + /** @type {!HTMLDivElement} */ (document.createElement('div')); + element.id = idGenerator.getNextUniqueId(); + this.element_ = element; + + // Set class and style + // goog-menuitem* is deprecated, use blocklyMenuItem*. May 2020. + element.className = 'blocklyMenuItem goog-menuitem ' + + (this.enabled_ ? '' : + 'blocklyMenuItemDisabled goog-menuitem-disabled ') + + (this.checked_ ? 'blocklyMenuItemSelected goog-option-selected ' : '') + + (this.highlight_ ? 'blocklyMenuItemHighlight goog-menuitem-highlight ' : + '') + + (this.rightToLeft_ ? 'blocklyMenuItemRtl goog-menuitem-rtl ' : ''); + + const content = + /** @type {!HTMLDivElement} */ (document.createElement('div')); + content.className = 'blocklyMenuItemContent goog-menuitem-content'; + // Add a checkbox for checkable menu items. + if (this.checkable_) { + const checkbox = + /** @type {!HTMLDivElement} */ (document.createElement('div')); + checkbox.className = 'blocklyMenuItemCheckbox goog-menuitem-checkbox'; + content.appendChild(checkbox); + } + + let contentDom = /** @type {!HTMLElement} */ (this.content_); + if (typeof this.content_ === 'string') { + contentDom = document.createTextNode(this.content_); + } + content.appendChild(contentDom); + element.appendChild(content); + + // Initialize ARIA role and state. + if (this.roleName_) { + aria.setRole(element, this.roleName_); + } + aria.setState( + element, aria.State.SELECTED, + (this.checkable_ && this.checked_) || false); + aria.setState(element, aria.State.DISABLED, !this.enabled_); + + return element; } - content.appendChild(contentDom); - element.appendChild(content); - // Initialize ARIA role and state. - if (this.roleName_) { - aria.setRole(element, this.roleName_); + /** + * Dispose of this menu item. + */ + dispose() { + this.element_ = null; } - aria.setState( - element, aria.State.SELECTED, - (this.checkable_ && this.checked_) || false); - aria.setState(element, aria.State.DISABLED, !this.enabled_); - return element; -}; + /** + * Gets the menu item's element. + * @return {?Element} The DOM element. + * @package + */ + getElement() { + return this.element_; + } -/** - * Dispose of this menu item. - */ -MenuItem.prototype.dispose = function() { - this.element_ = null; -}; + /** + * Gets the unique ID for this menu item. + * @return {string} Unique component ID. + * @package + */ + getId() { + return this.element_.id; + } -/** - * Gets the menu item's element. - * @return {?Element} The DOM element. - * @package - */ -MenuItem.prototype.getElement = function() { - return this.element_; -}; + /** + * Gets the value associated with the menu item. + * @return {*} value Value associated with the menu item. + * @package + */ + getValue() { + return this.value_; + } -/** - * Gets the unique ID for this menu item. - * @return {string} Unique component ID. - * @package - */ -MenuItem.prototype.getId = function() { - return this.element_.id; -}; + /** + * Set menu item's rendering direction. + * @param {boolean} rtl True if RTL, false if LTR. + * @package + */ + setRightToLeft(rtl) { + this.rightToLeft_ = rtl; + } -/** - * Gets the value associated with the menu item. - * @return {*} value Value associated with the menu item. - * @package - */ -MenuItem.prototype.getValue = function() { - return this.value_; -}; + /** + * Set the menu item's accessibility role. + * @param {!aria.Role} roleName Role name. + * @package + */ + setRole(roleName) { + this.roleName_ = roleName; + } -/** - * Set menu item's rendering direction. - * @param {boolean} rtl True if RTL, false if LTR. - * @package - */ -MenuItem.prototype.setRightToLeft = function(rtl) { - this.rightToLeft_ = rtl; -}; + /** + * Sets the menu item to be checkable or not. Set to true for menu items + * that represent checkable options. + * @param {boolean} checkable Whether the menu item is checkable. + * @package + */ + setCheckable(checkable) { + this.checkable_ = checkable; + } -/** - * Set the menu item's accessibility role. - * @param {!aria.Role} roleName Role name. - * @package - */ -MenuItem.prototype.setRole = function(roleName) { - this.roleName_ = roleName; -}; + /** + * Checks or unchecks the component. + * @param {boolean} checked Whether to check or uncheck the component. + * @package + */ + setChecked(checked) { + this.checked_ = checked; + } -/** - * Sets the menu item to be checkable or not. Set to true for menu items - * that represent checkable options. - * @param {boolean} checkable Whether the menu item is checkable. - * @package - */ -MenuItem.prototype.setCheckable = function(checkable) { - this.checkable_ = checkable; -}; + /** + * Highlights or unhighlights the component. + * @param {boolean} highlight Whether to highlight or unhighlight the + * component. + * @package + */ + setHighlighted(highlight) { + this.highlight_ = highlight; -/** - * Checks or unchecks the component. - * @param {boolean} checked Whether to check or uncheck the component. - * @package - */ -MenuItem.prototype.setChecked = function(checked) { - this.checked_ = checked; -}; - -/** - * Highlights or unhighlights the component. - * @param {boolean} highlight Whether to highlight or unhighlight the component. - * @package - */ -MenuItem.prototype.setHighlighted = function(highlight) { - this.highlight_ = highlight; - - const el = this.getElement(); - if (el && this.isEnabled()) { - // goog-menuitem-highlight is deprecated, use blocklyMenuItemHighlight. - // May 2020. - const name = 'blocklyMenuItemHighlight'; - const nameDep = 'goog-menuitem-highlight'; - if (highlight) { - dom.addClass(el, name); - dom.addClass(el, nameDep); - } else { - dom.removeClass(el, name); - dom.removeClass(el, nameDep); + const el = this.getElement(); + if (el && this.isEnabled()) { + // goog-menuitem-highlight is deprecated, use blocklyMenuItemHighlight. + // May 2020. + const name = 'blocklyMenuItemHighlight'; + const nameDep = 'goog-menuitem-highlight'; + if (highlight) { + dom.addClass(el, name); + dom.addClass(el, nameDep); + } else { + dom.removeClass(el, name); + dom.removeClass(el, nameDep); + } } } -}; -/** - * Returns true if the menu item is enabled, false otherwise. - * @return {boolean} Whether the menu item is enabled. - * @package - */ -MenuItem.prototype.isEnabled = function() { - return this.enabled_; -}; - -/** - * Enables or disables the menu item. - * @param {boolean} enabled Whether to enable or disable the menu item. - * @package - */ -MenuItem.prototype.setEnabled = function(enabled) { - this.enabled_ = enabled; -}; - -/** - * Performs the appropriate action when the menu item is activated - * by the user. - * @package - */ -MenuItem.prototype.performAction = function() { - if (this.isEnabled() && this.actionHandler_) { - this.actionHandler_(this); + /** + * Returns true if the menu item is enabled, false otherwise. + * @return {boolean} Whether the menu item is enabled. + * @package + */ + isEnabled() { + return this.enabled_; } -}; -/** - * Set the handler that's called when the menu item is activated by the user. - * `obj` will be used as the 'this' object in the function when called. - * @param {function(!MenuItem)} fn The handler. - * @param {!Object} obj Used as the 'this' object in fn when called. - * @package - */ -MenuItem.prototype.onAction = function(fn, obj) { - this.actionHandler_ = fn.bind(obj); + /** + * Enables or disables the menu item. + * @param {boolean} enabled Whether to enable or disable the menu item. + * @package + */ + setEnabled(enabled) { + this.enabled_ = enabled; + } + + /** + * Performs the appropriate action when the menu item is activated + * by the user. + * @package + */ + performAction() { + if (this.isEnabled() && this.actionHandler_) { + this.actionHandler_(this); + } + } + + /** + * Set the handler that's called when the menu item is activated by the user. + * `obj` will be used as the 'this' object in the function when called. + * @param {function(!MenuItem)} fn The handler. + * @param {!Object} obj Used as the 'this' object in fn when called. + * @package + */ + onAction(fn, obj) { + this.actionHandler_ = fn.bind(obj); + } }; exports.MenuItem = MenuItem; diff --git a/core/metrics_manager.js b/core/metrics_manager.js index 072a812a7..1d3b7c9ba 100644 --- a/core/metrics_manager.js +++ b/core/metrics_manager.js @@ -32,20 +32,409 @@ const {WorkspaceSvg} = goog.requireType('Blockly.WorkspaceSvg'); /** * The manager for all workspace metrics calculations. - * @param {!WorkspaceSvg} workspace The workspace to calculate metrics - * for. * @implements {IMetricsManager} - * @constructor * @alias Blockly.MetricsManager */ -const MetricsManager = function(workspace) { +class MetricsManager { /** - * The workspace to calculate metrics for. - * @type {!WorkspaceSvg} + * @param {!WorkspaceSvg} workspace The workspace to calculate metrics + * for. + */ + constructor(workspace) { + /** + * The workspace to calculate metrics for. + * @type {!WorkspaceSvg} + * @protected + */ + this.workspace_ = workspace; + } + + /** + * Gets the dimensions of the given workspace component, in pixel coordinates. + * @param {?IToolbox|?IFlyout} elem The element to get the + * dimensions of, or null. It should be a toolbox or flyout, and should + * implement getWidth() and getHeight(). + * @return {!Size} An object containing width and height + * attributes, which will both be zero if elem did not exist. * @protected */ - this.workspace_ = workspace; -}; + getDimensionsPx_(elem) { + let width = 0; + let height = 0; + if (elem) { + width = elem.getWidth(); + height = elem.getHeight(); + } + return new Size(width, height); + } + + /** + * Gets the width and the height of the flyout on the workspace in pixel + * coordinates. Returns 0 for the width and height if the workspace has a + * category toolbox instead of a simple toolbox. + * @param {boolean=} opt_own Whether to only return the workspace's own + * flyout. + * @return {!MetricsManager.ToolboxMetrics} The width and height of the + * flyout. + * @public + */ + getFlyoutMetrics(opt_own) { + const flyoutDimensions = + this.getDimensionsPx_(this.workspace_.getFlyout(opt_own)); + return { + width: flyoutDimensions.width, + height: flyoutDimensions.height, + position: this.workspace_.toolboxPosition, + }; + } + + /** + * Gets the width, height and position of the toolbox on the workspace in + * pixel coordinates. Returns 0 for the width and height if the workspace has + * a simple toolbox instead of a category toolbox. To get the width and height + * of a + * simple toolbox @see {@link getFlyoutMetrics}. + * @return {!MetricsManager.ToolboxMetrics} The object with the width, + * height and position of the toolbox. + * @public + */ + getToolboxMetrics() { + const toolboxDimensions = + this.getDimensionsPx_(this.workspace_.getToolbox()); + + return { + width: toolboxDimensions.width, + height: toolboxDimensions.height, + position: this.workspace_.toolboxPosition, + }; + } + + /** + * Gets the width and height of the workspace's parent SVG element in pixel + * coordinates. This area includes the toolbox and the visible workspace area. + * @return {!Size} The width and height of the workspace's parent + * SVG element. + * @public + */ + getSvgMetrics() { + return this.workspace_.getCachedParentSvgSize(); + } + + /** + * Gets the absolute left and absolute top in pixel coordinates. + * This is where the visible workspace starts in relation to the SVG + * container. + * @return {!MetricsManager.AbsoluteMetrics} The absolute metrics for + * the workspace. + * @public + */ + getAbsoluteMetrics() { + let absoluteLeft = 0; + const toolboxMetrics = this.getToolboxMetrics(); + const flyoutMetrics = this.getFlyoutMetrics(true); + const doesToolboxExist = !!this.workspace_.getToolbox(); + const doesFlyoutExist = !!this.workspace_.getFlyout(true); + const toolboxPosition = + doesToolboxExist ? toolboxMetrics.position : flyoutMetrics.position; + + const atLeft = toolboxPosition === toolboxUtils.Position.LEFT; + const atTop = toolboxPosition === toolboxUtils.Position.TOP; + if (doesToolboxExist && atLeft) { + absoluteLeft = toolboxMetrics.width; + } else if (doesFlyoutExist && atLeft) { + absoluteLeft = flyoutMetrics.width; + } + let absoluteTop = 0; + if (doesToolboxExist && atTop) { + absoluteTop = toolboxMetrics.height; + } else if (doesFlyoutExist && atTop) { + absoluteTop = flyoutMetrics.height; + } + + return { + top: absoluteTop, + left: absoluteLeft, + }; + } + + /** + * Gets the metrics for the visible workspace in either pixel or workspace + * coordinates. The visible workspace does not include the toolbox or flyout. + * @param {boolean=} opt_getWorkspaceCoordinates True to get the view metrics + * in workspace coordinates, false to get them in pixel coordinates. + * @return {!MetricsManager.ContainerRegion} The width, height, top and + * left of the viewport in either workspace coordinates or pixel + * coordinates. + * @public + */ + getViewMetrics(opt_getWorkspaceCoordinates) { + const scale = opt_getWorkspaceCoordinates ? this.workspace_.scale : 1; + const svgMetrics = this.getSvgMetrics(); + const toolboxMetrics = this.getToolboxMetrics(); + const flyoutMetrics = this.getFlyoutMetrics(true); + const doesToolboxExist = !!this.workspace_.getToolbox(); + const toolboxPosition = + doesToolboxExist ? toolboxMetrics.position : flyoutMetrics.position; + + if (this.workspace_.getToolbox()) { + if (toolboxPosition === toolboxUtils.Position.TOP || + toolboxPosition === toolboxUtils.Position.BOTTOM) { + svgMetrics.height -= toolboxMetrics.height; + } else if ( + toolboxPosition === toolboxUtils.Position.LEFT || + toolboxPosition === toolboxUtils.Position.RIGHT) { + svgMetrics.width -= toolboxMetrics.width; + } + } else if (this.workspace_.getFlyout(true)) { + if (toolboxPosition === toolboxUtils.Position.TOP || + toolboxPosition === toolboxUtils.Position.BOTTOM) { + svgMetrics.height -= flyoutMetrics.height; + } else if ( + toolboxPosition === toolboxUtils.Position.LEFT || + toolboxPosition === toolboxUtils.Position.RIGHT) { + svgMetrics.width -= flyoutMetrics.width; + } + } + return { + height: svgMetrics.height / scale, + width: svgMetrics.width / scale, + top: -this.workspace_.scrollY / scale, + left: -this.workspace_.scrollX / scale, + }; + } + + /** + * Gets content metrics in either pixel or workspace coordinates. + * The content area is a rectangle around all the top bounded elements on the + * workspace (workspace comments and blocks). + * @param {boolean=} opt_getWorkspaceCoordinates True to get the content + * metrics in workspace coordinates, false to get them in pixel + * coordinates. + * @return {!MetricsManager.ContainerRegion} The + * metrics for the content container. + * @public + */ + getContentMetrics(opt_getWorkspaceCoordinates) { + const scale = opt_getWorkspaceCoordinates ? 1 : this.workspace_.scale; + + // Block bounding box is in workspace coordinates. + const blockBox = this.workspace_.getBlocksBoundingBox(); + + return { + height: (blockBox.bottom - blockBox.top) * scale, + width: (blockBox.right - blockBox.left) * scale, + top: blockBox.top * scale, + left: blockBox.left * scale, + }; + } + + /** + * Returns whether the scroll area has fixed edges. + * @return {boolean} Whether the scroll area has fixed edges. + * @package + */ + hasFixedEdges() { + // This exists for optimization of bump logic. + return !this.workspace_.isMovableHorizontally() || + !this.workspace_.isMovableVertically(); + } + + /** + * Computes the fixed edges of the scroll area. + * @param {!MetricsManager.ContainerRegion=} opt_viewMetrics The view + * metrics if they have been previously computed. Passing in null may + * cause the view metrics to be computed again, if it is needed. + * @return {!MetricsManager.FixedEdges} The fixed edges of the scroll + * area. + * @protected + */ + getComputedFixedEdges_(opt_viewMetrics) { + if (!this.hasFixedEdges()) { + // Return early if there are no edges. + return {}; + } + + const hScrollEnabled = this.workspace_.isMovableHorizontally(); + const vScrollEnabled = this.workspace_.isMovableVertically(); + + const viewMetrics = opt_viewMetrics || this.getViewMetrics(false); + + const edges = {}; + if (!vScrollEnabled) { + edges.top = viewMetrics.top; + edges.bottom = viewMetrics.top + viewMetrics.height; + } + if (!hScrollEnabled) { + edges.left = viewMetrics.left; + edges.right = viewMetrics.left + viewMetrics.width; + } + return edges; + } + + /** + * Returns the content area with added padding. + * @param {!MetricsManager.ContainerRegion} viewMetrics The view + * metrics. + * @param {!MetricsManager.ContainerRegion} contentMetrics The content + * metrics. + * @return {{top: number, bottom: number, left: number, right: number}} The + * padded content area. + * @protected + */ + getPaddedContent_(viewMetrics, contentMetrics) { + const contentBottom = contentMetrics.top + contentMetrics.height; + const contentRight = contentMetrics.left + contentMetrics.width; + + const viewWidth = viewMetrics.width; + const viewHeight = viewMetrics.height; + const halfWidth = viewWidth / 2; + const halfHeight = viewHeight / 2; + + // Add a padding around the content that is at least half a screen wide. + // Ensure padding is wide enough that blocks can scroll over entire screen. + const top = + Math.min(contentMetrics.top - halfHeight, contentBottom - viewHeight); + const left = + Math.min(contentMetrics.left - halfWidth, contentRight - viewWidth); + const bottom = + Math.max(contentBottom + halfHeight, contentMetrics.top + viewHeight); + const right = + Math.max(contentRight + halfWidth, contentMetrics.left + viewWidth); + + return {top: top, bottom: bottom, left: left, right: right}; + } + + /** + * Returns the metrics for the scroll area of the workspace. + * @param {boolean=} opt_getWorkspaceCoordinates True to get the scroll + * metrics in workspace coordinates, false to get them in pixel + * coordinates. + * @param {!MetricsManager.ContainerRegion=} opt_viewMetrics The view + * metrics if they have been previously computed. Passing in null may + * cause the view metrics to be computed again, if it is needed. + * @param {!MetricsManager.ContainerRegion=} opt_contentMetrics The + * content metrics if they have been previously computed. Passing in null + * may cause the content metrics to be computed again, if it is needed. + * @return {!MetricsManager.ContainerRegion} The metrics for the scroll + * container. + */ + getScrollMetrics( + opt_getWorkspaceCoordinates, opt_viewMetrics, opt_contentMetrics) { + const scale = opt_getWorkspaceCoordinates ? this.workspace_.scale : 1; + const viewMetrics = opt_viewMetrics || this.getViewMetrics(false); + const contentMetrics = opt_contentMetrics || this.getContentMetrics(); + const fixedEdges = this.getComputedFixedEdges_(viewMetrics); + + // Add padding around content. + const paddedContent = this.getPaddedContent_(viewMetrics, contentMetrics); + + // Use combination of fixed bounds and padded content to make scroll area. + const top = + fixedEdges.top !== undefined ? fixedEdges.top : paddedContent.top; + const left = + fixedEdges.left !== undefined ? fixedEdges.left : paddedContent.left; + const bottom = fixedEdges.bottom !== undefined ? fixedEdges.bottom : + paddedContent.bottom; + const right = + fixedEdges.right !== undefined ? fixedEdges.right : paddedContent.right; + + return { + top: top / scale, + left: left / scale, + width: (right - left) / scale, + height: (bottom - top) / scale, + }; + } + + /** + * Returns common metrics used by UI elements. + * @return {!MetricsManager.UiMetrics} The UI metrics. + */ + getUiMetrics() { + return { + viewMetrics: this.getViewMetrics(), + absoluteMetrics: this.getAbsoluteMetrics(), + toolboxMetrics: this.getToolboxMetrics(), + }; + } + + /** + * Returns an object with all the metrics required to size scrollbars for a + * top level workspace. The following properties are computed: + * Coordinate system: pixel coordinates, -left, -up, +right, +down + * .viewHeight: Height of the visible portion of the workspace. + * .viewWidth: Width of the visible portion of the workspace. + * .contentHeight: Height of the content. + * .contentWidth: Width of the content. + * .scrollHeight: Height of the scroll area. + * .scrollWidth: Width of the scroll area. + * .svgHeight: Height of the Blockly div (the view + the toolbox, + * simple or otherwise), + * .svgWidth: Width of the Blockly div (the view + the toolbox, + * simple or otherwise), + * .viewTop: Top-edge of the visible portion of the workspace, relative to + * the workspace origin. + * .viewLeft: Left-edge of the visible portion of the workspace, relative to + * the workspace origin. + * .contentTop: Top-edge of the content, relative to the workspace origin. + * .contentLeft: Left-edge of the content relative to the workspace origin. + * .scrollTop: Top-edge of the scroll area, relative to the workspace origin. + * .scrollLeft: Left-edge of the scroll area relative to the workspace origin. + * .absoluteTop: Top-edge of the visible portion of the workspace, relative + * to the blocklyDiv. + * .absoluteLeft: Left-edge of the visible portion of the workspace, relative + * to the blocklyDiv. + * .toolboxWidth: Width of the toolbox, if it exists. Otherwise zero. + * .toolboxHeight: Height of the toolbox, if it exists. Otherwise zero. + * .flyoutWidth: Width of the flyout if it is always open. Otherwise zero. + * .flyoutHeight: Height of the flyout if it is always open. Otherwise zero. + * .toolboxPosition: Top, bottom, left or right. Use TOOLBOX_AT constants to + * compare. + * @return {!Metrics} Contains size and position metrics of a top + * level workspace. + * @public + */ + getMetrics() { + const toolboxMetrics = this.getToolboxMetrics(); + const flyoutMetrics = this.getFlyoutMetrics(true); + const svgMetrics = this.getSvgMetrics(); + const absoluteMetrics = this.getAbsoluteMetrics(); + const viewMetrics = this.getViewMetrics(); + const contentMetrics = this.getContentMetrics(); + const scrollMetrics = + this.getScrollMetrics(false, viewMetrics, contentMetrics); + + return { + contentHeight: contentMetrics.height, + contentWidth: contentMetrics.width, + contentTop: contentMetrics.top, + contentLeft: contentMetrics.left, + + scrollHeight: scrollMetrics.height, + scrollWidth: scrollMetrics.width, + scrollTop: scrollMetrics.top, + scrollLeft: scrollMetrics.left, + + viewHeight: viewMetrics.height, + viewWidth: viewMetrics.width, + viewTop: viewMetrics.top, + viewLeft: viewMetrics.left, + + absoluteTop: absoluteMetrics.top, + absoluteLeft: absoluteMetrics.left, + + svgHeight: svgMetrics.height, + svgWidth: svgMetrics.width, + + toolboxWidth: toolboxMetrics.width, + toolboxHeight: toolboxMetrics.height, + toolboxPosition: toolboxMetrics.position, + + flyoutWidth: flyoutMetrics.width, + flyoutHeight: flyoutMetrics.height, + }; + } +} /** * Describes the width, height and location of the toolbox on the main @@ -57,6 +446,7 @@ const MetricsManager = function(workspace) { * }} */ MetricsManager.ToolboxMetrics; + /** * Describes where the viewport starts in relation to the workspace SVG. * @typedef {{ @@ -65,8 +455,10 @@ MetricsManager.ToolboxMetrics; * }} */ MetricsManager.AbsoluteMetrics; + /** - * All the measurements needed to describe the size and location of a container. + * All the measurements needed to describe the size and location of a + * container. * @typedef {{ * height: number, * width: number, @@ -75,6 +467,7 @@ MetricsManager.AbsoluteMetrics; * }} */ MetricsManager.ContainerRegion; + /** * Describes fixed edges of the workspace. * @typedef {{ @@ -85,6 +478,7 @@ MetricsManager.ContainerRegion; * }} */ MetricsManager.FixedEdges; + /** * Common metrics used for UI elements. * @typedef {{ @@ -94,387 +488,6 @@ MetricsManager.FixedEdges; * }} */ MetricsManager.UiMetrics; -/** - * Gets the dimensions of the given workspace component, in pixel coordinates. - * @param {?IToolbox|?IFlyout} elem The element to get the - * dimensions of, or null. It should be a toolbox or flyout, and should - * implement getWidth() and getHeight(). - * @return {!Size} An object containing width and height - * attributes, which will both be zero if elem did not exist. - * @protected - */ -MetricsManager.prototype.getDimensionsPx_ = function(elem) { - let width = 0; - let height = 0; - if (elem) { - width = elem.getWidth(); - height = elem.getHeight(); - } - return new Size(width, height); -}; - -/** - * Gets the width and the height of the flyout on the workspace in pixel - * coordinates. Returns 0 for the width and height if the workspace has a - * category toolbox instead of a simple toolbox. - * @param {boolean=} opt_own Whether to only return the workspace's own flyout. - * @return {!MetricsManager.ToolboxMetrics} The width and height of the - * flyout. - * @public - */ -MetricsManager.prototype.getFlyoutMetrics = function(opt_own) { - const flyoutDimensions = - this.getDimensionsPx_(this.workspace_.getFlyout(opt_own)); - return { - width: flyoutDimensions.width, - height: flyoutDimensions.height, - position: this.workspace_.toolboxPosition, - }; -}; - -/** - * Gets the width, height and position of the toolbox on the workspace in pixel - * coordinates. Returns 0 for the width and height if the workspace has a simple - * toolbox instead of a category toolbox. To get the width and height of a - * simple toolbox @see {@link getFlyoutMetrics}. - * @return {!MetricsManager.ToolboxMetrics} The object with the width, - * height and position of the toolbox. - * @public - */ -MetricsManager.prototype.getToolboxMetrics = function() { - const toolboxDimensions = this.getDimensionsPx_(this.workspace_.getToolbox()); - - return { - width: toolboxDimensions.width, - height: toolboxDimensions.height, - position: this.workspace_.toolboxPosition, - }; -}; - -/** - * Gets the width and height of the workspace's parent SVG element in pixel - * coordinates. This area includes the toolbox and the visible workspace area. - * @return {!Size} The width and height of the workspace's parent - * SVG element. - * @public - */ -MetricsManager.prototype.getSvgMetrics = function() { - return this.workspace_.getCachedParentSvgSize(); -}; - -/** - * Gets the absolute left and absolute top in pixel coordinates. - * This is where the visible workspace starts in relation to the SVG container. - * @return {!MetricsManager.AbsoluteMetrics} The absolute metrics for - * the workspace. - * @public - */ -MetricsManager.prototype.getAbsoluteMetrics = function() { - let absoluteLeft = 0; - const toolboxMetrics = this.getToolboxMetrics(); - const flyoutMetrics = this.getFlyoutMetrics(true); - const doesToolboxExist = !!this.workspace_.getToolbox(); - const doesFlyoutExist = !!this.workspace_.getFlyout(true); - const toolboxPosition = - doesToolboxExist ? toolboxMetrics.position : flyoutMetrics.position; - - const atLeft = toolboxPosition === toolboxUtils.Position.LEFT; - const atTop = toolboxPosition === toolboxUtils.Position.TOP; - if (doesToolboxExist && atLeft) { - absoluteLeft = toolboxMetrics.width; - } else if (doesFlyoutExist && atLeft) { - absoluteLeft = flyoutMetrics.width; - } - let absoluteTop = 0; - if (doesToolboxExist && atTop) { - absoluteTop = toolboxMetrics.height; - } else if (doesFlyoutExist && atTop) { - absoluteTop = flyoutMetrics.height; - } - - return { - top: absoluteTop, - left: absoluteLeft, - }; -}; - -/** - * Gets the metrics for the visible workspace in either pixel or workspace - * coordinates. The visible workspace does not include the toolbox or flyout. - * @param {boolean=} opt_getWorkspaceCoordinates True to get the view metrics in - * workspace coordinates, false to get them in pixel coordinates. - * @return {!MetricsManager.ContainerRegion} The width, height, top and - * left of the viewport in either workspace coordinates or pixel - * coordinates. - * @public - */ -MetricsManager.prototype.getViewMetrics = function( - opt_getWorkspaceCoordinates) { - const scale = opt_getWorkspaceCoordinates ? this.workspace_.scale : 1; - const svgMetrics = this.getSvgMetrics(); - const toolboxMetrics = this.getToolboxMetrics(); - const flyoutMetrics = this.getFlyoutMetrics(true); - const doesToolboxExist = !!this.workspace_.getToolbox(); - const toolboxPosition = - doesToolboxExist ? toolboxMetrics.position : flyoutMetrics.position; - - if (this.workspace_.getToolbox()) { - if (toolboxPosition === toolboxUtils.Position.TOP || - toolboxPosition === toolboxUtils.Position.BOTTOM) { - svgMetrics.height -= toolboxMetrics.height; - } else if ( - toolboxPosition === toolboxUtils.Position.LEFT || - toolboxPosition === toolboxUtils.Position.RIGHT) { - svgMetrics.width -= toolboxMetrics.width; - } - } else if (this.workspace_.getFlyout(true)) { - if (toolboxPosition === toolboxUtils.Position.TOP || - toolboxPosition === toolboxUtils.Position.BOTTOM) { - svgMetrics.height -= flyoutMetrics.height; - } else if ( - toolboxPosition === toolboxUtils.Position.LEFT || - toolboxPosition === toolboxUtils.Position.RIGHT) { - svgMetrics.width -= flyoutMetrics.width; - } - } - return { - height: svgMetrics.height / scale, - width: svgMetrics.width / scale, - top: -this.workspace_.scrollY / scale, - left: -this.workspace_.scrollX / scale, - }; -}; - -/** - * Gets content metrics in either pixel or workspace coordinates. - * The content area is a rectangle around all the top bounded elements on the - * workspace (workspace comments and blocks). - * @param {boolean=} opt_getWorkspaceCoordinates True to get the content metrics - * in workspace coordinates, false to get them in pixel coordinates. - * @return {!MetricsManager.ContainerRegion} The - * metrics for the content container. - * @public - */ -MetricsManager.prototype.getContentMetrics = function( - opt_getWorkspaceCoordinates) { - const scale = opt_getWorkspaceCoordinates ? 1 : this.workspace_.scale; - - // Block bounding box is in workspace coordinates. - const blockBox = this.workspace_.getBlocksBoundingBox(); - - return { - height: (blockBox.bottom - blockBox.top) * scale, - width: (blockBox.right - blockBox.left) * scale, - top: blockBox.top * scale, - left: blockBox.left * scale, - }; -}; - -/** - * Returns whether the scroll area has fixed edges. - * @return {boolean} Whether the scroll area has fixed edges. - * @package - */ -MetricsManager.prototype.hasFixedEdges = function() { - // This exists for optimization of bump logic. - return !this.workspace_.isMovableHorizontally() || - !this.workspace_.isMovableVertically(); -}; - -/** - * Computes the fixed edges of the scroll area. - * @param {!MetricsManager.ContainerRegion=} opt_viewMetrics The view - * metrics if they have been previously computed. Passing in null may cause - * the view metrics to be computed again, if it is needed. - * @return {!MetricsManager.FixedEdges} The fixed edges of the scroll - * area. - * @protected - */ -MetricsManager.prototype.getComputedFixedEdges_ = function(opt_viewMetrics) { - if (!this.hasFixedEdges()) { - // Return early if there are no edges. - return {}; - } - - const hScrollEnabled = this.workspace_.isMovableHorizontally(); - const vScrollEnabled = this.workspace_.isMovableVertically(); - - const viewMetrics = opt_viewMetrics || this.getViewMetrics(false); - - const edges = {}; - if (!vScrollEnabled) { - edges.top = viewMetrics.top; - edges.bottom = viewMetrics.top + viewMetrics.height; - } - if (!hScrollEnabled) { - edges.left = viewMetrics.left; - edges.right = viewMetrics.left + viewMetrics.width; - } - return edges; -}; - -/** - * Returns the content area with added padding. - * @param {!MetricsManager.ContainerRegion} viewMetrics The view - * metrics. - * @param {!MetricsManager.ContainerRegion} contentMetrics The content - * metrics. - * @return {{top: number, bottom: number, left: number, right: number}} The - * padded content area. - * @protected - */ -MetricsManager.prototype.getPaddedContent_ = function( - viewMetrics, contentMetrics) { - const contentBottom = contentMetrics.top + contentMetrics.height; - const contentRight = contentMetrics.left + contentMetrics.width; - - const viewWidth = viewMetrics.width; - const viewHeight = viewMetrics.height; - const halfWidth = viewWidth / 2; - const halfHeight = viewHeight / 2; - - // Add a padding around the content that is at least half a screen wide. - // Ensure padding is wide enough that blocks can scroll over entire screen. - const top = - Math.min(contentMetrics.top - halfHeight, contentBottom - viewHeight); - const left = - Math.min(contentMetrics.left - halfWidth, contentRight - viewWidth); - const bottom = - Math.max(contentBottom + halfHeight, contentMetrics.top + viewHeight); - const right = - Math.max(contentRight + halfWidth, contentMetrics.left + viewWidth); - - return {top: top, bottom: bottom, left: left, right: right}; -}; - -/** - * Returns the metrics for the scroll area of the workspace. - * @param {boolean=} opt_getWorkspaceCoordinates True to get the scroll metrics - * in workspace coordinates, false to get them in pixel coordinates. - * @param {!MetricsManager.ContainerRegion=} opt_viewMetrics The view - * metrics if they have been previously computed. Passing in null may cause - * the view metrics to be computed again, if it is needed. - * @param {!MetricsManager.ContainerRegion=} opt_contentMetrics The - * content metrics if they have been previously computed. Passing in null - * may cause the content metrics to be computed again, if it is needed. - * @return {!MetricsManager.ContainerRegion} The metrics for the scroll - * container. - */ -MetricsManager.prototype.getScrollMetrics = function( - opt_getWorkspaceCoordinates, opt_viewMetrics, opt_contentMetrics) { - const scale = opt_getWorkspaceCoordinates ? this.workspace_.scale : 1; - const viewMetrics = opt_viewMetrics || this.getViewMetrics(false); - const contentMetrics = opt_contentMetrics || this.getContentMetrics(); - const fixedEdges = this.getComputedFixedEdges_(viewMetrics); - - // Add padding around content. - const paddedContent = this.getPaddedContent_(viewMetrics, contentMetrics); - - // Use combination of fixed bounds and padded content to make scroll area. - const top = fixedEdges.top !== undefined ? fixedEdges.top : paddedContent.top; - const left = - fixedEdges.left !== undefined ? fixedEdges.left : paddedContent.left; - const bottom = fixedEdges.bottom !== undefined ? fixedEdges.bottom : - paddedContent.bottom; - const right = - fixedEdges.right !== undefined ? fixedEdges.right : paddedContent.right; - - return { - top: top / scale, - left: left / scale, - width: (right - left) / scale, - height: (bottom - top) / scale, - }; -}; - -/** - * Returns common metrics used by UI elements. - * @return {!MetricsManager.UiMetrics} The UI metrics. - */ -MetricsManager.prototype.getUiMetrics = function() { - return { - viewMetrics: this.getViewMetrics(), - absoluteMetrics: this.getAbsoluteMetrics(), - toolboxMetrics: this.getToolboxMetrics(), - }; -}; - -/** - * Returns an object with all the metrics required to size scrollbars for a - * top level workspace. The following properties are computed: - * Coordinate system: pixel coordinates, -left, -up, +right, +down - * .viewHeight: Height of the visible portion of the workspace. - * .viewWidth: Width of the visible portion of the workspace. - * .contentHeight: Height of the content. - * .contentWidth: Width of the content. - * .scrollHeight: Height of the scroll area. - * .scrollWidth: Width of the scroll area. - * .svgHeight: Height of the Blockly div (the view + the toolbox, - * simple or otherwise), - * .svgWidth: Width of the Blockly div (the view + the toolbox, - * simple or otherwise), - * .viewTop: Top-edge of the visible portion of the workspace, relative to - * the workspace origin. - * .viewLeft: Left-edge of the visible portion of the workspace, relative to - * the workspace origin. - * .contentTop: Top-edge of the content, relative to the workspace origin. - * .contentLeft: Left-edge of the content relative to the workspace origin. - * .scrollTop: Top-edge of the scroll area, relative to the workspace origin. - * .scrollLeft: Left-edge of the scroll area relative to the workspace origin. - * .absoluteTop: Top-edge of the visible portion of the workspace, relative - * to the blocklyDiv. - * .absoluteLeft: Left-edge of the visible portion of the workspace, relative - * to the blocklyDiv. - * .toolboxWidth: Width of the toolbox, if it exists. Otherwise zero. - * .toolboxHeight: Height of the toolbox, if it exists. Otherwise zero. - * .flyoutWidth: Width of the flyout if it is always open. Otherwise zero. - * .flyoutHeight: Height of the flyout if it is always open. Otherwise zero. - * .toolboxPosition: Top, bottom, left or right. Use TOOLBOX_AT constants to - * compare. - * @return {!Metrics} Contains size and position metrics of a top - * level workspace. - * @public - */ -MetricsManager.prototype.getMetrics = function() { - const toolboxMetrics = this.getToolboxMetrics(); - const flyoutMetrics = this.getFlyoutMetrics(true); - const svgMetrics = this.getSvgMetrics(); - const absoluteMetrics = this.getAbsoluteMetrics(); - const viewMetrics = this.getViewMetrics(); - const contentMetrics = this.getContentMetrics(); - const scrollMetrics = - this.getScrollMetrics(false, viewMetrics, contentMetrics); - - return { - contentHeight: contentMetrics.height, - contentWidth: contentMetrics.width, - contentTop: contentMetrics.top, - contentLeft: contentMetrics.left, - - scrollHeight: scrollMetrics.height, - scrollWidth: scrollMetrics.width, - scrollTop: scrollMetrics.top, - scrollLeft: scrollMetrics.left, - - viewHeight: viewMetrics.height, - viewWidth: viewMetrics.width, - viewTop: viewMetrics.top, - viewLeft: viewMetrics.left, - - absoluteTop: absoluteMetrics.top, - absoluteLeft: absoluteMetrics.left, - - svgHeight: svgMetrics.height, - svgWidth: svgMetrics.width, - - toolboxWidth: toolboxMetrics.width, - toolboxHeight: toolboxMetrics.height, - toolboxPosition: toolboxMetrics.position, - - flyoutWidth: flyoutMetrics.width, - flyoutHeight: flyoutMetrics.height, - }; -}; registry.register( registry.Type.METRICS_MANAGER, registry.DEFAULT, MetricsManager); diff --git a/core/mutator.js b/core/mutator.js index 5e4e9ace6..08b36533e 100644 --- a/core/mutator.js +++ b/core/mutator.js @@ -17,14 +17,12 @@ */ goog.module('Blockly.Mutator'); -/* eslint-disable-next-line no-unused-vars */ -const Abstract = goog.requireType('Blockly.Events.Abstract'); const dom = goog.require('Blockly.utils.dom'); const eventUtils = goog.require('Blockly.Events.utils'); -const internalConstants = goog.require('Blockly.internalConstants'); -const object = goog.require('Blockly.utils.object'); const toolbox = goog.require('Blockly.utils.toolbox'); const xml = goog.require('Blockly.utils.xml'); +/* eslint-disable-next-line no-unused-vars */ +const {Abstract} = goog.requireType('Blockly.Events.Abstract'); const {BlockChange} = goog.require('Blockly.Events.BlockChange'); /* eslint-disable-next-line no-unused-vars */ const {BlockSvg} = goog.requireType('Blockly.BlockSvg'); @@ -33,6 +31,7 @@ const {BlocklyOptions} = goog.requireType('Blockly.BlocklyOptions'); /* eslint-disable-next-line no-unused-vars */ const {Block} = goog.requireType('Blockly.Block'); const {Bubble} = goog.require('Blockly.Bubble'); +const {config} = goog.require('Blockly.config'); /* eslint-disable-next-line no-unused-vars */ const {Connection} = goog.requireType('Blockly.Connection'); /* eslint-disable-next-line no-unused-vars */ @@ -41,504 +40,536 @@ const {Icon} = goog.require('Blockly.Icon'); const {Options} = goog.require('Blockly.Options'); const {Svg} = goog.require('Blockly.utils.Svg'); const {WorkspaceSvg} = goog.require('Blockly.WorkspaceSvg'); -/* eslint-disable-next-line no-unused-vars */ -const {Workspace} = goog.requireType('Blockly.Workspace'); /** @suppress {extraRequire} */ goog.require('Blockly.Events.BubbleOpen'); /** * Class for a mutator dialog. - * @param {!Array} quarkNames List of names of sub-blocks for flyout. * @extends {Icon} - * @constructor * @alias Blockly.Mutator */ -const Mutator = function(quarkNames) { - Mutator.superClass_.constructor.call(this, null); - this.quarkNames_ = quarkNames; -}; -object.inherits(Mutator, Icon); +class Mutator extends Icon { + /** + * @param {!Array} quarkNames List of names of sub-blocks for flyout. + */ + constructor(quarkNames) { + super(null); + this.quarkNames_ = quarkNames; -/** - * Workspace in the mutator's bubble. - * @type {?WorkspaceSvg} - * @private - */ -Mutator.prototype.workspace_ = null; + /** + * Workspace in the mutator's bubble. + * @type {?WorkspaceSvg} + * @private + */ + this.workspace_ = null; -/** - * Width of workspace. - * @private - */ -Mutator.prototype.workspaceWidth_ = 0; + /** + * Width of workspace. + * @type {number} + * @private + */ + this.workspaceWidth_ = 0; -/** - * Height of workspace. - * @private - */ -Mutator.prototype.workspaceHeight_ = 0; + /** + * Height of workspace. + * @type {number} + * @private + */ + this.workspaceHeight_ = 0; -/** - * Set the block this mutator is associated with. - * @param {!BlockSvg} block The block associated with this mutator. - * @package - */ -Mutator.prototype.setBlock = function(block) { - this.block_ = block; -}; + /** + * The SVG element that is the parent of the mutator workspace, or null if + * not created. + * @type {?SVGSVGElement} + * @private + */ + this.svgDialog_ = null; -/** - * Returns the workspace inside this mutator icon's bubble. - * @return {?WorkspaceSvg} The workspace inside this mutator icon's - * bubble or null if the mutator isn't open. - * @package - */ -Mutator.prototype.getWorkspace = function() { - return this.workspace_; -}; + /** + * The root block of the mutator workspace, created by decomposing the + * source block. + * @type {?BlockSvg} + * @private + */ + this.rootBlock_ = null; -/** - * Draw the mutator icon. - * @param {!Element} group The icon group. - * @protected - */ -Mutator.prototype.drawIcon_ = function(group) { - // Square with rounded corners. - dom.createSvgElement( - Svg.RECT, { - 'class': 'blocklyIconShape', - 'rx': '4', - 'ry': '4', - 'height': '16', - 'width': '16', - }, - group); - // Gear teeth. - dom.createSvgElement( - Svg.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); - // Axle hole. - dom.createSvgElement( - Svg.CIRCLE, - {'class': 'blocklyIconShape', 'r': '2.7', 'cx': '8', 'cy': '8'}, group); -}; - -/** - * Clicking on the icon toggles if the mutator bubble is visible. - * Disable if block is uneditable. - * @param {!Event} e Mouse click event. - * @protected - * @override - */ -Mutator.prototype.iconClick_ = function(e) { - if (this.block_.isEditable()) { - Icon.prototype.iconClick_.call(this, e); + /** + * Function registered on the main workspace to update the mutator contents + * when the main workspace changes. + * @type {?Function} + * @private + */ + this.sourceListener_ = null; } -}; -/** - * Create the editor for the mutator's bubble. - * @return {!SVGElement} The top-level node of the editor. - * @private - */ -Mutator.prototype.createEditor_ = function() { - /* Create the editor. Here's the markup that will be generated: - - [Workspace] - - */ - this.svgDialog_ = dom.createSvgElement( - Svg.SVG, {'x': Bubble.BORDER_WIDTH, 'y': Bubble.BORDER_WIDTH}, null); - // Convert the list of names into a list of XML objects for the flyout. - let quarkXml; - if (this.quarkNames_.length) { - quarkXml = xml.createElement('xml'); - for (let i = 0, quarkName; (quarkName = this.quarkNames_[i]); i++) { - const element = xml.createElement('block'); - element.setAttribute('type', quarkName); - quarkXml.appendChild(element); - } - } else { - quarkXml = null; + /** + * Set the block this mutator is associated with. + * @param {!BlockSvg} block The block associated with this mutator. + * @package + */ + setBlock(block) { + this.block_ = block; } - const workspaceOptions = new Options( - /** @type {!BlocklyOptions} */ - ({ - // If you want to enable disabling, also remove the - // event filter from workspaceChanged_ . - 'disable': false, - 'parentWorkspace': this.block_.workspace, - 'media': this.block_.workspace.options.pathToMedia, - 'rtl': this.block_.RTL, - 'horizontalLayout': false, - 'renderer': this.block_.workspace.options.renderer, - 'rendererOverrides': this.block_.workspace.options.rendererOverrides, - })); - workspaceOptions.toolboxPosition = - this.block_.RTL ? toolbox.Position.RIGHT : toolbox.Position.LEFT; - const hasFlyout = !!quarkXml; - if (hasFlyout) { - workspaceOptions.languageTree = toolbox.convertToolboxDefToJson(quarkXml); + + /** + * Returns the workspace inside this mutator icon's bubble. + * @return {?WorkspaceSvg} The workspace inside this mutator icon's + * bubble or null if the mutator isn't open. + * @package + */ + getWorkspace() { + return this.workspace_; } - this.workspace_ = new WorkspaceSvg(workspaceOptions); - this.workspace_.isMutator = true; - this.workspace_.addChangeListener(eventUtils.disableOrphans); - // Mutator flyouts go inside the mutator workspace's rather than in - // a top level SVG. Instead of handling scale themselves, mutators - // inherit scale from the parent workspace. - // To fix this, scale needs to be applied at a different level in the DOM. - const flyoutSvg = hasFlyout ? this.workspace_.addFlyout(Svg.G) : null; - const background = this.workspace_.createDom('blocklyMutatorBackground'); - - if (flyoutSvg) { - // Insert the flyout after the but before the block canvas so that - // the flyout is underneath in z-order. This makes blocks layering during - // dragging work properly. - background.insertBefore(flyoutSvg, this.workspace_.svgBlockCanvas_); + /** + * Draw the mutator icon. + * @param {!Element} group The icon group. + * @protected + */ + drawIcon_(group) { + // Square with rounded corners. + dom.createSvgElement( + Svg.RECT, { + 'class': 'blocklyIconShape', + 'rx': '4', + 'ry': '4', + 'height': '16', + 'width': '16', + }, + group); + // Gear teeth. + dom.createSvgElement( + Svg.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); + // Axle hole. + dom.createSvgElement( + Svg.CIRCLE, + {'class': 'blocklyIconShape', 'r': '2.7', 'cx': '8', 'cy': '8'}, group); } - this.svgDialog_.appendChild(background); - return this.svgDialog_; -}; - -/** - * Add or remove the UI indicating if this icon may be clicked or not. - */ -Mutator.prototype.updateEditable = function() { - Mutator.superClass_.updateEditable.call(this); - if (!this.block_.isInFlyout) { + /** + * Clicking on the icon toggles if the mutator bubble is visible. + * Disable if block is uneditable. + * @param {!Event} e Mouse click event. + * @protected + * @override + */ + iconClick_(e) { if (this.block_.isEditable()) { - if (this.iconGroup_) { - dom.removeClass( - /** @type {!Element} */ (this.iconGroup_), - 'blocklyIconGroupReadonly'); + Icon.prototype.iconClick_.call(this, e); + } + } + + /** + * Create the editor for the mutator's bubble. + * @return {!SVGElement} The top-level node of the editor. + * @private + */ + createEditor_() { + /* Create the editor. Here's the markup that will be generated: + + [Workspace] + + */ + this.svgDialog_ = dom.createSvgElement( + Svg.SVG, {'x': Bubble.BORDER_WIDTH, 'y': Bubble.BORDER_WIDTH}, null); + // Convert the list of names into a list of XML objects for the flyout. + let quarkXml; + if (this.quarkNames_.length) { + quarkXml = xml.createElement('xml'); + for (let i = 0, quarkName; (quarkName = this.quarkNames_[i]); i++) { + const element = xml.createElement('block'); + element.setAttribute('type', quarkName); + quarkXml.appendChild(element); } } else { - // Close any mutator bubble. Icon is not clickable. - this.setVisible(false); - if (this.iconGroup_) { - dom.addClass( - /** @type {!Element} */ (this.iconGroup_), - 'blocklyIconGroupReadonly'); + quarkXml = null; + } + const workspaceOptions = new Options( + /** @type {!BlocklyOptions} */ + ({ + // If you want to enable disabling, also remove the + // event filter from workspaceChanged_ . + 'disable': false, + 'parentWorkspace': this.block_.workspace, + 'media': this.block_.workspace.options.pathToMedia, + 'rtl': this.block_.RTL, + 'horizontalLayout': false, + 'renderer': this.block_.workspace.options.renderer, + 'rendererOverrides': this.block_.workspace.options.rendererOverrides, + })); + workspaceOptions.toolboxPosition = + this.block_.RTL ? toolbox.Position.RIGHT : toolbox.Position.LEFT; + const hasFlyout = !!quarkXml; + if (hasFlyout) { + workspaceOptions.languageTree = toolbox.convertToolboxDefToJson(quarkXml); + } + this.workspace_ = new WorkspaceSvg(workspaceOptions); + this.workspace_.isMutator = true; + this.workspace_.addChangeListener(eventUtils.disableOrphans); + + // Mutator flyouts go inside the mutator workspace's rather than in + // a top level SVG. Instead of handling scale themselves, mutators + // inherit scale from the parent workspace. + // To fix this, scale needs to be applied at a different level in the DOM. + const flyoutSvg = hasFlyout ? this.workspace_.addFlyout(Svg.G) : null; + const background = this.workspace_.createDom('blocklyMutatorBackground'); + + if (flyoutSvg) { + // Insert the flyout after the but before the block canvas so that + // the flyout is underneath in z-order. This makes blocks layering during + // dragging work properly. + background.insertBefore(flyoutSvg, this.workspace_.svgBlockCanvas_); + } + this.svgDialog_.appendChild(background); + + return this.svgDialog_; + } + + /** + * Add or remove the UI indicating if this icon may be clicked or not. + */ + updateEditable() { + super.updateEditable(); + if (!this.block_.isInFlyout) { + if (this.block_.isEditable()) { + if (this.iconGroup_) { + dom.removeClass( + /** @type {!Element} */ (this.iconGroup_), + 'blocklyIconGroupReadonly'); + } + } else { + // Close any mutator bubble. Icon is not clickable. + this.setVisible(false); + if (this.iconGroup_) { + dom.addClass( + /** @type {!Element} */ (this.iconGroup_), + 'blocklyIconGroupReadonly'); + } } } } -}; -/** - * Resize the bubble to match the size of the workspace. - * @private - */ -Mutator.prototype.resizeBubble_ = function() { - const doubleBorderWidth = 2 * Bubble.BORDER_WIDTH; - const workspaceSize = this.workspace_.getCanvas().getBBox(); - let width = workspaceSize.width + workspaceSize.x; - let height = workspaceSize.height + doubleBorderWidth * 3; - const flyout = this.workspace_.getFlyout(); - if (flyout) { - const flyoutScrollMetrics = - flyout.getWorkspace().getMetricsManager().getScrollMetrics(); - height = Math.max(height, flyoutScrollMetrics.height + 20); - width += flyout.getWidth(); - } - if (this.block_.RTL) { - width = -workspaceSize.x; - } - width += doubleBorderWidth * 3; - // Only resize if the size difference is significant. Eliminates shuddering. - if (Math.abs(this.workspaceWidth_ - width) > doubleBorderWidth || - Math.abs(this.workspaceHeight_ - height) > doubleBorderWidth) { - // Record some layout information for workspace metrics. - this.workspaceWidth_ = width; - this.workspaceHeight_ = height; - // Resize the bubble. - this.bubble_.setBubbleSize( - width + doubleBorderWidth, height + doubleBorderWidth); - this.svgDialog_.setAttribute('width', this.workspaceWidth_); - this.svgDialog_.setAttribute('height', this.workspaceHeight_); - this.workspace_.setCachedParentSvgSize( - this.workspaceWidth_, this.workspaceHeight_); - } - - if (this.block_.RTL) { - // Scroll the workspace to always left-align. - const translation = 'translate(' + this.workspaceWidth_ + ',0)'; - this.workspace_.getCanvas().setAttribute('transform', translation); - } - this.workspace_.resize(); -}; - -/** - * A method handler for when the bubble is moved. - * @private - */ -Mutator.prototype.onBubbleMove_ = function() { - if (this.workspace_) { - this.workspace_.recordDragTargets(); - } -}; - -/** - * Show or hide the mutator bubble. - * @param {boolean} visible True if the bubble should be visible. - */ -Mutator.prototype.setVisible = function(visible) { - if (visible === this.isVisible()) { - // No change. - return; - } - eventUtils.fire(new (eventUtils.get(eventUtils.BUBBLE_OPEN))( - this.block_, visible, 'mutator')); - if (visible) { - // Create the bubble. - this.bubble_ = new Bubble( - /** @type {!WorkspaceSvg} */ (this.block_.workspace), - this.createEditor_(), this.block_.pathObject.svgPath, - /** @type {!Coordinate} */ (this.iconXY_), null, null); - // Expose this mutator's block's ID on its top-level SVG group. - this.bubble_.setSvgId(this.block_.id); - this.bubble_.registerMoveEvent(this.onBubbleMove_.bind(this)); - const tree = this.workspace_.options.languageTree; + /** + * Resize the bubble to match the size of the workspace. + * @private + */ + resizeBubble_() { + const doubleBorderWidth = 2 * Bubble.BORDER_WIDTH; + const workspaceSize = this.workspace_.getCanvas().getBBox(); + let width = workspaceSize.width + workspaceSize.x; + let height = workspaceSize.height + doubleBorderWidth * 3; const flyout = this.workspace_.getFlyout(); - if (tree) { - flyout.init(this.workspace_); - flyout.show(tree); - } - - this.rootBlock_ = this.block_.decompose(this.workspace_); - const blocks = this.rootBlock_.getDescendants(false); - for (let i = 0, child; (child = blocks[i]); i++) { - child.render(); - } - // The root block should not be draggable or deletable. - this.rootBlock_.setMovable(false); - this.rootBlock_.setDeletable(false); - let margin; - let x; if (flyout) { - margin = flyout.CORNER_RADIUS * 2; - x = this.rootBlock_.RTL ? flyout.getWidth() + margin : margin; - } else { - margin = 16; - x = margin; + const flyoutScrollMetrics = + flyout.getWorkspace().getMetricsManager().getScrollMetrics(); + height = Math.max(height, flyoutScrollMetrics.height + 20); + width += flyout.getWidth(); } if (this.block_.RTL) { - x = -x; + width = -workspaceSize.x; } - this.rootBlock_.moveBy(x, margin); - // Save the initial connections, then listen for further changes. - if (this.block_.saveConnections) { - const thisMutator = this; - const mutatorBlock = - /** @type {{saveConnections: function(!Block)}} */ (this.block_); - mutatorBlock.saveConnections(this.rootBlock_); - this.sourceListener_ = function() { - mutatorBlock.saveConnections(thisMutator.rootBlock_); - }; - this.block_.workspace.addChangeListener(this.sourceListener_); + width += doubleBorderWidth * 3; + // Only resize if the size difference is significant. Eliminates + // shuddering. + if (Math.abs(this.workspaceWidth_ - width) > doubleBorderWidth || + Math.abs(this.workspaceHeight_ - height) > doubleBorderWidth) { + // Record some layout information for workspace metrics. + this.workspaceWidth_ = width; + this.workspaceHeight_ = height; + // Resize the bubble. + this.bubble_.setBubbleSize( + width + doubleBorderWidth, height + doubleBorderWidth); + this.svgDialog_.setAttribute('width', this.workspaceWidth_); + this.svgDialog_.setAttribute('height', this.workspaceHeight_); + this.workspace_.setCachedParentSvgSize( + this.workspaceWidth_, this.workspaceHeight_); } - this.resizeBubble_(); - // When the mutator's workspace changes, update the source block. - this.workspace_.addChangeListener(this.workspaceChanged_.bind(this)); - // Update the source block immediately after the bubble becomes visible. - this.updateWorkspace_(); - this.applyColour(); - } else { - // Dispose of the bubble. - this.svgDialog_ = null; - this.workspace_.dispose(); - this.workspace_ = null; - this.rootBlock_ = null; - this.bubble_.dispose(); - this.bubble_ = null; - this.workspaceWidth_ = 0; - this.workspaceHeight_ = 0; - if (this.sourceListener_) { - this.block_.workspace.removeChangeListener(this.sourceListener_); - this.sourceListener_ = null; + + if (this.block_.RTL) { + // Scroll the workspace to always left-align. + const translation = 'translate(' + this.workspaceWidth_ + ',0)'; + this.workspace_.getCanvas().setAttribute('transform', translation); + } + this.workspace_.resize(); + } + + /** + * A method handler for when the bubble is moved. + * @private + */ + onBubbleMove_() { + if (this.workspace_) { + this.workspace_.recordDragTargets(); } } -}; -/** - * Fired whenever a change is made to the mutator's workspace. - * @param {!Abstract} e Custom data for event. - * @private - */ -Mutator.prototype.workspaceChanged_ = function(e) { - if (!(e.isUiEvent || - (e.type === eventUtils.CHANGE && e.element === 'disabled') || - e.type === eventUtils.CREATE)) { - this.updateWorkspace_(); - } -}; - -/** - * Updates the source block when the mutator's blocks are changed. - * Bump down any block that's too high. - * @private - */ -Mutator.prototype.updateWorkspace_ = function() { - if (!this.workspace_.isDragging()) { - const blocks = this.workspace_.getTopBlocks(false); - const MARGIN = 20; - - for (let b = 0, block; (block = blocks[b]); b++) { - const blockXY = block.getRelativeToSurfaceXY(); - - // Bump any block that's above the top back inside. - if (blockXY.y < MARGIN) { - block.moveBy(0, MARGIN - blockXY.y); + /** + * Show or hide the mutator bubble. + * @param {boolean} visible True if the bubble should be visible. + */ + setVisible(visible) { + if (visible === this.isVisible()) { + // No change. + return; + } + eventUtils.fire(new (eventUtils.get(eventUtils.BUBBLE_OPEN))( + this.block_, visible, 'mutator')); + if (visible) { + // Create the bubble. + this.bubble_ = new Bubble( + /** @type {!WorkspaceSvg} */ (this.block_.workspace), + this.createEditor_(), this.block_.pathObject.svgPath, + /** @type {!Coordinate} */ (this.iconXY_), null, null); + // Expose this mutator's block's ID on its top-level SVG group. + this.bubble_.setSvgId(this.block_.id); + this.bubble_.registerMoveEvent(this.onBubbleMove_.bind(this)); + const tree = this.workspace_.options.languageTree; + const flyout = this.workspace_.getFlyout(); + if (tree) { + flyout.init(this.workspace_); + flyout.show(tree); } - // Bump any block overlapping the flyout back inside. - if (block.RTL) { - let right = -MARGIN; - const flyout = this.workspace_.getFlyout(); - if (flyout) { - right -= flyout.getWidth(); - } - if (blockXY.x > right) { - block.moveBy(right - blockXY.x, 0); - } - } else if (blockXY.x < MARGIN) { - block.moveBy(MARGIN - blockXY.x, 0); + + this.rootBlock_ = this.block_.decompose(this.workspace_); + const blocks = this.rootBlock_.getDescendants(false); + for (let i = 0, child; (child = blocks[i]); i++) { + child.render(); + } + // The root block should not be draggable or deletable. + this.rootBlock_.setMovable(false); + this.rootBlock_.setDeletable(false); + let margin; + let x; + if (flyout) { + margin = flyout.CORNER_RADIUS * 2; + x = this.rootBlock_.RTL ? flyout.getWidth() + margin : margin; + } else { + margin = 16; + x = margin; + } + if (this.block_.RTL) { + x = -x; + } + this.rootBlock_.moveBy(x, margin); + // Save the initial connections, then listen for further changes. + if (this.block_.saveConnections) { + const thisRootBlock = this.rootBlock_; + this.block_.saveConnections(thisRootBlock); + this.sourceListener_ = () => { + if (this.block_) { + this.block_.saveConnections(thisRootBlock); + } + }; + this.block_.workspace.addChangeListener(this.sourceListener_); } - } - } - - // When the mutator's workspace changes, update the source block. - if (this.rootBlock_.workspace === this.workspace_) { - eventUtils.setGroup(true); - const block = /** @type {!BlockSvg} */ (this.block_); - const oldExtraState = BlockChange.getExtraBlockState_(block); - - // Switch off rendering while the source block is rebuilt. - const savedRendered = block.rendered; - // TODO(#4288): We should not be setting the rendered property to false. - block.rendered = false; - - // Allow the source block to rebuild itself. - block.compose(this.rootBlock_); - // Restore rendering and show the changes. - block.rendered = savedRendered; - // Mutation may have added some elements that need initializing. - block.initSvg(); - - if (block.rendered) { - block.render(); - } - - const newExtraState = BlockChange.getExtraBlockState_(block); - if (oldExtraState !== newExtraState) { - eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))( - block, 'mutation', null, oldExtraState, newExtraState)); - // Ensure that any bump is part of this mutation's event group. - const group = eventUtils.getGroup(); - setTimeout(function() { - eventUtils.setGroup(group); - block.bumpNeighbours(); - eventUtils.setGroup(false); - }, internalConstants.BUMP_DELAY); - } - - // Don't update the bubble until the drag has ended, to avoid moving blocks - // under the cursor. - if (!this.workspace_.isDragging()) { this.resizeBubble_(); + // When the mutator's workspace changes, update the source block. + this.workspace_.addChangeListener(this.workspaceChanged_.bind(this)); + // Update the source block immediately after the bubble becomes visible. + this.updateWorkspace_(); + this.applyColour(); + } else { + // Dispose of the bubble. + this.svgDialog_ = null; + this.workspace_.dispose(); + this.workspace_ = null; + this.rootBlock_ = null; + this.bubble_.dispose(); + this.bubble_ = null; + this.workspaceWidth_ = 0; + this.workspaceHeight_ = 0; + if (this.sourceListener_) { + this.block_.workspace.removeChangeListener(this.sourceListener_); + this.sourceListener_ = null; + } } - eventUtils.setGroup(false); } -}; -/** - * Dispose of this mutator. - */ -Mutator.prototype.dispose = function() { - this.block_.mutator = null; - Icon.prototype.dispose.call(this); -}; + /** + * Fired whenever a change is made to the mutator's workspace. + * @param {!Abstract} e Custom data for event. + * @private + */ + workspaceChanged_(e) { + if (!(e.isUiEvent || + (e.type === eventUtils.CHANGE && + /** @type {!BlockChange} */ (e).element === 'disabled') || + e.type === eventUtils.CREATE)) { + this.updateWorkspace_(); + } + } -/** - * Update the styles on all blocks in the mutator. - * @public - */ -Mutator.prototype.updateBlockStyle = function() { - const ws = this.workspace_; + /** + * Updates the source block when the mutator's blocks are changed. + * Bump down any block that's too high. + * @private + */ + updateWorkspace_() { + if (!this.workspace_.isDragging()) { + const blocks = this.workspace_.getTopBlocks(false); + const MARGIN = 20; - if (ws && ws.getAllBlocks(false)) { - const workspaceBlocks = ws.getAllBlocks(false); - for (let i = 0, block; (block = workspaceBlocks[i]); i++) { - block.setStyle(block.getStyleName()); + for (let b = 0, block; (block = blocks[b]); b++) { + const blockXY = block.getRelativeToSurfaceXY(); + + // Bump any block that's above the top back inside. + if (blockXY.y < MARGIN) { + block.moveBy(0, MARGIN - blockXY.y); + } + // Bump any block overlapping the flyout back inside. + if (block.RTL) { + let right = -MARGIN; + const flyout = this.workspace_.getFlyout(); + if (flyout) { + right -= flyout.getWidth(); + } + if (blockXY.x > right) { + block.moveBy(right - blockXY.x, 0); + } + } else if (blockXY.x < MARGIN) { + block.moveBy(MARGIN - blockXY.x, 0); + } + } } - const flyout = ws.getFlyout(); - if (flyout) { - const flyoutBlocks = flyout.workspace_.getAllBlocks(false); - for (let i = 0, block; (block = flyoutBlocks[i]); i++) { + // When the mutator's workspace changes, update the source block. + if (this.rootBlock_.workspace === this.workspace_) { + const existingGroup = eventUtils.getGroup(); + if (!existingGroup) { + eventUtils.setGroup(true); + } + const block = /** @type {!BlockSvg} */ (this.block_); + const oldExtraState = BlockChange.getExtraBlockState_(block); + + // Switch off rendering while the source block is rebuilt. + const savedRendered = block.rendered; + // TODO(#4288): We should not be setting the rendered property to false. + block.rendered = false; + + // Allow the source block to rebuild itself. + block.compose(this.rootBlock_); + // Restore rendering and show the changes. + block.rendered = savedRendered; + // Mutation may have added some elements that need initializing. + block.initSvg(); + + if (block.rendered) { + block.render(); + } + + const newExtraState = BlockChange.getExtraBlockState_(block); + if (oldExtraState !== newExtraState) { + eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))( + block, 'mutation', null, oldExtraState, newExtraState)); + // Ensure that any bump is part of this mutation's event group. + const mutationGroup = eventUtils.getGroup(); + setTimeout(function() { + const oldGroup = eventUtils.getGroup(); + eventUtils.setGroup(mutationGroup); + block.bumpNeighbours(); + eventUtils.setGroup(oldGroup); + }, config.bumpDelay); + } + + // Don't update the bubble until the drag has ended, to avoid moving + // blocks under the cursor. + if (!this.workspace_.isDragging()) { + this.resizeBubble_(); + } + eventUtils.setGroup(existingGroup); + } + } + + /** + * Dispose of this mutator. + */ + dispose() { + this.block_.mutator = null; + Icon.prototype.dispose.call(this); + } + + /** + * Update the styles on all blocks in the mutator. + * @public + */ + updateBlockStyle() { + const ws = this.workspace_; + + if (ws && ws.getAllBlocks(false)) { + const workspaceBlocks = ws.getAllBlocks(false); + for (let i = 0, block; (block = workspaceBlocks[i]); i++) { block.setStyle(block.getStyleName()); } - } - } -}; -/** - * Reconnect an block to a mutated input. - * @param {Connection} connectionChild Connection on child block. - * @param {!Block} block Parent block. - * @param {string} inputName Name of input on parent block. - * @return {boolean} True iff a reconnection was made, false otherwise. - */ -Mutator.reconnect = function(connectionChild, block, inputName) { - if (!connectionChild || !connectionChild.getSourceBlock().workspace) { - return false; // No connection or block has been deleted. - } - const connectionParent = block.getInput(inputName).connection; - const currentParent = connectionChild.targetBlock(); - if ((!currentParent || currentParent === block) && - connectionParent.targetConnection !== connectionChild) { - if (connectionParent.isConnected()) { - // There's already something connected here. Get rid of it. - connectionParent.disconnect(); - } - connectionParent.connect(connectionChild); - return true; - } - return false; -}; - -/** - * Get the parent workspace of a workspace that is inside a mutator, taking into - * account whether it is a flyout. - * @param {Workspace} workspace The workspace that is inside a mutator. - * @return {?Workspace} The mutator's parent workspace or null. - * @public - */ -Mutator.findParentWs = function(workspace) { - let outerWs = null; - if (workspace && workspace.options) { - const 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; + const flyout = ws.getFlyout(); + if (flyout) { + const flyoutBlocks = flyout.workspace_.getAllBlocks(false); + for (let i = 0, block; (block = flyoutBlocks[i]); i++) { + block.setStyle(block.getStyleName()); + } } - } else if (parent) { - outerWs = parent; } } - return outerWs; -}; + + /** + * Reconnect an block to a mutated input. + * @param {Connection} connectionChild Connection on child block. + * @param {!Block} block Parent block. + * @param {string} inputName Name of input on parent block. + * @return {boolean} True iff a reconnection was made, false otherwise. + */ + static reconnect(connectionChild, block, inputName) { + if (!connectionChild || !connectionChild.getSourceBlock().workspace) { + return false; // No connection or block has been deleted. + } + const connectionParent = block.getInput(inputName).connection; + const currentParent = connectionChild.targetBlock(); + if ((!currentParent || currentParent === block) && + connectionParent.targetConnection !== connectionChild) { + if (connectionParent.isConnected()) { + // There's already something connected here. Get rid of it. + connectionParent.disconnect(); + } + connectionParent.connect(connectionChild); + return true; + } + return false; + } + + /** + * Get the parent workspace of a workspace that is inside a mutator, taking + * into account whether it is a flyout. + * @param {WorkspaceSvg} workspace The workspace that is inside a mutator. + * @return {?WorkspaceSvg} The mutator's parent workspace or null. + * @public + */ + static findParentWs(workspace) { + let outerWs = null; + if (workspace && workspace.options) { + const 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; + } +} exports.Mutator = Mutator; diff --git a/core/names.js b/core/names.js index 1fad1e00f..8424a417b 100644 --- a/core/names.js +++ b/core/names.js @@ -27,23 +27,235 @@ goog.requireType('Blockly.Procedures'); /** * Class for a database of entity names (variables, procedures, etc). - * @param {string} reservedWords A comma-separated string of words that are - * illegal for use as names in a language (e.g. 'new,if,this,...'). - * @param {string=} opt_variablePrefix Some languages need a '$' or a namespace - * before all variable names (but not procedure names). - * @constructor * @alias Blockly.Names */ -const Names = function(reservedWords, opt_variablePrefix) { - this.variablePrefix_ = opt_variablePrefix || ''; - this.reservedDict_ = Object.create(null); - if (reservedWords) { - const splitWords = reservedWords.split(','); - for (let i = 0; i < splitWords.length; i++) { - this.reservedDict_[splitWords[i]] = true; +const Names = class { + /** + * @param {string} reservedWords A comma-separated string of words that are + * illegal for use as names in a language (e.g. 'new,if,this,...'). + * @param {string=} opt_variablePrefix Some languages need a '$' or a + * namespace before all variable names (but not procedure names). + */ + constructor(reservedWords, opt_variablePrefix) { + /** + * The prefix to attach to variable names in generated code. + * @type {string} + * @private + */ + this.variablePrefix_ = opt_variablePrefix || ''; + + /** + * A dictionary of reserved words. + * @type {Object} + * @private + */ + this.reservedDict_ = Object.create(null); + + /** + * A map from type (e.g. name, procedure) to maps from names to generated + * names. + * @type {Object>} + * @private + */ + this.db_ = Object.create(null); + + /** + * A map from used names to booleans to avoid collisions. + * @type {Object} + * @private + */ + this.dbReverse_ = Object.create(null); + + /** + * The variable map from the workspace, containing Blockly variable models. + * @type {?VariableMap} + * @private + */ + this.variableMap_ = null; + + if (reservedWords) { + const splitWords = reservedWords.split(','); + for (let i = 0; i < splitWords.length; i++) { + this.reservedDict_[splitWords[i]] = true; + } + } + this.reset(); + } + + /** + * Empty the database and start from scratch. The reserved words are kept. + */ + reset() { + 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 {!VariableMap} map The map to track. + */ + setVariableMap(map) { + this.variableMap_ = map; + } + + /** + * Get the name for a user-defined variable, based on its ID. + * This should only be used for variables of NameType VARIABLE. + * @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 + */ + getNameForUserVariable_(id) { + if (!this.variableMap_) { + console.warn( + 'Deprecated call to Names.prototype.getName without ' + + 'defining a variable map. To fix, add the following code in your ' + + 'generator\'s init() function:\n' + + 'Blockly.YourGeneratorName.nameDB_.setVariableMap(' + + 'workspace.getVariableMap());'); + return null; + } + const variable = this.variableMap_.getVariableById(id); + if (variable) { + return variable.name; + } + return null; + } + + /** + * Generate names for user variables, but only ones that are being used. + * @param {!Workspace} workspace Workspace to generate variables from. + */ + populateVariables(workspace) { + const variables = Variables.allUsedVarModels(workspace); + for (let i = 0; i < variables.length; i++) { + this.getName(variables[i].getId(), NameType.VARIABLE); } } - this.reset(); + + /** + * Generate names for procedures. + * @param {!Workspace} workspace Workspace to generate procedures from. + */ + populateProcedures(workspace) { + let procedures = + goog.module.get('Blockly.Procedures').allProcedures(workspace); + // Flatten the return vs no-return procedure lists. + procedures = procedures[0].concat(procedures[1]); + for (let i = 0; i < procedures.length; i++) { + this.getName(procedures[i][0], NameType.PROCEDURE); + } + } + + /** + * Convert a Blockly entity name to a legal exportable entity name. + * @param {string} nameOrId The Blockly entity name (no constraints) or + * variable ID. + * @param {NameType|string} type The type of the name in Blockly + * ('VARIABLE', 'PROCEDURE', 'DEVELOPER_VARIABLE', etc...). + * @return {string} An entity name that is legal in the exported language. + */ + getName(nameOrId, type) { + let name = nameOrId; + if (type === NameType.VARIABLE) { + const varName = this.getNameForUserVariable_(nameOrId); + if (varName) { + // Successful ID lookup. + name = varName; + } + } + const normalizedName = name.toLowerCase(); + + const isVar = + type === NameType.VARIABLE || type === NameType.DEVELOPER_VARIABLE; + + const prefix = isVar ? this.variablePrefix_ : ''; + if (!(type in this.db_)) { + this.db_[type] = Object.create(null); + } + const typeDb = this.db_[type]; + if (normalizedName in typeDb) { + return prefix + typeDb[normalizedName]; + } + const safeName = this.getDistinctName(name, type); + typeDb[normalizedName] = safeName.substr(prefix.length); + return safeName; + } + + /** + * Return a list of all known user-created names of a specified name type. + * @param {NameType|string} type The type of entity in Blockly + * ('VARIABLE', 'PROCEDURE', 'DEVELOPER_VARIABLE', etc...). + * @return {!Array} A list of Blockly entity names (no constraints). + */ + getUserNames(type) { + const typeDb = this.db_[type] || {}; + return Object.keys(typeDb); + } + + /** + * Convert a Blockly entity name to a legal exportable entity name. + * Ensure that this is a new name not overlapping any previously defined name. + * Also check against list of reserved words for the current language and + * ensure name doesn't collide. + * @param {string} name The Blockly entity name (no constraints). + * @param {NameType|string} type The type of entity in Blockly + * ('VARIABLE', 'PROCEDURE', 'DEVELOPER_VARIABLE', etc...). + * @return {string} An entity name that is legal in the exported language. + */ + getDistinctName(name, type) { + let safeName = this.safeName_(name); + let i = ''; + while (this.dbReverse_[safeName + i] || + (safeName + i) in this.reservedDict_) { + // Collision with existing name. Create a unique name. + i = i ? i + 1 : 2; + } + safeName += i; + this.dbReverse_[safeName] = true; + const isVar = + type === NameType.VARIABLE || type === NameType.DEVELOPER_VARIABLE; + const prefix = isVar ? this.variablePrefix_ : ''; + return prefix + safeName; + } + + /** + * Given a proposed entity name, generate a name that conforms to the + * [_A-Za-z][_A-Za-z0-9]* format that most languages consider legal for + * variable and function names. + * @param {string} name Potentially illegal entity name. + * @return {string} Safe entity name. + * @private + */ + safeName_(name) { + if (!name) { + name = Msg['UNNAMED_KEY'] || 'unnamed'; + } else { + // Unfortunately names in non-latin characters will look like + // _E9_9F_B3_E4_B9_90 which is pretty meaningless. + // https://github.com/google/blockly/issues/1654 + name = encodeURI(name.replace(/ /g, '_')).replace(/[^\w]/g, '_'); + // Most languages don't allow names with leading numbers. + if ('0123456789'.indexOf(name[0]) !== -1) { + name = 'my_' + name; + } + } + return name; + } + + /** + * Do the given two entity names refer to the same entity? + * Blockly names are case-insensitive. + * @param {string} name1 First name. + * @param {string} name2 Second name. + * @return {boolean} True if names are the same. + */ + static equals(name1, name2) { + // name1.localeCompare(name2) is slower. + return name1.toLowerCase() === name2.toLowerCase(); + } }; /** @@ -65,6 +277,10 @@ const NameType = { }; exports.NameType = NameType; +// We have to export NameType here so that it is accessible under the old name +// `Blockly.Names.NameType` +Names.NameType = NameType; + /** * Constant to separate developer variable names from user-defined variable * names when running generators. @@ -74,179 +290,4 @@ exports.NameType = NameType; */ Names.DEVELOPER_VARIABLE_TYPE = NameType.DEVELOPER_VARIABLE; -/** - * Empty the database and start from scratch. The reserved words are kept. - */ -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 {!VariableMap} map The map to track. - */ -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 NameType VARIABLE. - * @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 - */ -Names.prototype.getNameForUserVariable_ = function(id) { - if (!this.variableMap_) { - console.warn( - 'Deprecated call to Names.prototype.getName without ' + - 'defining a variable map. To fix, add the following code in your ' + - 'generator\'s init() function:\n' + - 'Blockly.YourGeneratorName.nameDB_.setVariableMap(' + - 'workspace.getVariableMap());'); - return null; - } - const variable = this.variableMap_.getVariableById(id); - if (variable) { - return variable.name; - } - return null; -}; - -/** - * Generate names for user variables, but only ones that are being used. - * @param {!Workspace} workspace Workspace to generate variables from. - */ -Names.prototype.populateVariables = function(workspace) { - const variables = Variables.allUsedVarModels(workspace); - for (let i = 0; i < variables.length; i++) { - this.getName(variables[i].getId(), NameType.VARIABLE); - } -}; - -/** - * Generate names for procedures. - * @param {!Workspace} workspace Workspace to generate procedures from. - */ -Names.prototype.populateProcedures = function(workspace) { - let procedures = - goog.module.get('Blockly.Procedures').allProcedures(workspace); - // Flatten the return vs no-return procedure lists. - procedures = procedures[0].concat(procedures[1]); - for (let i = 0; i < procedures.length; i++) { - this.getName(procedures[i][0], NameType.PROCEDURE); - } -}; - -/** - * Convert a Blockly entity name to a legal exportable entity name. - * @param {string} nameOrId The Blockly entity name (no constraints) or - * variable ID. - * @param {NameType|string} type The type of the name in Blockly - * ('VARIABLE', 'PROCEDURE', 'DEVELOPER_VARIABLE', etc...). - * @return {string} An entity name that is legal in the exported language. - */ -Names.prototype.getName = function(nameOrId, type) { - let name = nameOrId; - if (type === NameType.VARIABLE) { - const varName = this.getNameForUserVariable_(nameOrId); - if (varName) { - // Successful ID lookup. - name = varName; - } - } - const normalizedName = name.toLowerCase(); - - const isVar = - type === NameType.VARIABLE || type === NameType.DEVELOPER_VARIABLE; - - const prefix = isVar ? this.variablePrefix_ : ''; - if (!(type in this.db_)) { - this.db_[type] = Object.create(null); - } - const typeDb = this.db_[type]; - if (normalizedName in typeDb) { - return prefix + typeDb[normalizedName]; - } - const safeName = this.getDistinctName(name, type); - typeDb[normalizedName] = safeName.substr(prefix.length); - return safeName; -}; - -/** - * Return a list of all known user-created names of a specified name type. - * @param {NameType|string} type The type of entity in Blockly - * ('VARIABLE', 'PROCEDURE', 'DEVELOPER_VARIABLE', etc...). - * @return {!Array} A list of Blockly entity names (no constraints). - */ -Names.prototype.getUserNames = function(type) { - const typeDb = this.db_[type] || {}; - return Object.keys(typeDb); -}; - -/** - * Convert a Blockly entity name to a legal exportable entity name. - * Ensure that this is a new name not overlapping any previously defined name. - * Also check against list of reserved words for the current language and - * ensure name doesn't collide. - * @param {string} name The Blockly entity name (no constraints). - * @param {NameType|string} type The type of entity in Blockly - * ('VARIABLE', 'PROCEDURE', 'DEVELOPER_VARIABLE', etc...). - * @return {string} An entity name that is legal in the exported language. - */ -Names.prototype.getDistinctName = function(name, type) { - let safeName = this.safeName_(name); - let i = ''; - while (this.dbReverse_[safeName + i] || - (safeName + i) in this.reservedDict_) { - // Collision with existing name. Create a unique name. - i = i ? i + 1 : 2; - } - safeName += i; - this.dbReverse_[safeName] = true; - const isVar = - type === NameType.VARIABLE || type === NameType.DEVELOPER_VARIABLE; - const prefix = isVar ? this.variablePrefix_ : ''; - return prefix + safeName; -}; - -/** - * Given a proposed entity name, generate a name that conforms to the - * [_A-Za-z][_A-Za-z0-9]* format that most languages consider legal for - * variable and function names. - * @param {string} name Potentially illegal entity name. - * @return {string} Safe entity name. - * @private - */ -Names.prototype.safeName_ = function(name) { - if (!name) { - name = Msg['UNNAMED_KEY'] || 'unnamed'; - } else { - // Unfortunately names in non-latin characters will look like - // _E9_9F_B3_E4_B9_90 which is pretty meaningless. - // https://github.com/google/blockly/issues/1654 - name = encodeURI(name.replace(/ /g, '_')).replace(/[^\w]/g, '_'); - // Most languages don't allow names with leading numbers. - if ('0123456789'.indexOf(name[0]) !== -1) { - name = 'my_' + name; - } - } - return name; -}; - -/** - * Do the given two entity names refer to the same entity? - * Blockly names are case-insensitive. - * @param {string} name1 First name. - * @param {string} name2 Second name. - * @return {boolean} True if names are the same. - */ -Names.equals = function(name1, name2) { - // name1.localeCompare(name2) is slower. - return name1.toLowerCase() === name2.toLowerCase(); -}; - exports.Names = Names; diff --git a/core/options.js b/core/options.js index af7ea45b6..af6994377 100644 --- a/core/options.js +++ b/core/options.js @@ -31,163 +31,318 @@ const {WorkspaceSvg} = goog.requireType('Blockly.WorkspaceSvg'); /** * Parse the user-specified options, using reasonable defaults where behaviour * is unspecified. - * @param {!BlocklyOptions} options Dictionary of options. - * Specification: - * https://developers.google.com/blockly/guides/get-started/web#configuration - * @constructor * @alias Blockly.Options */ -const Options = function(options) { - let toolboxJsonDef = null; - let hasCategories = false; - let hasTrashcan = false; - let hasCollapse = false; - let hasComments = false; - let hasDisable = false; - let hasSounds = false; - const readOnly = !!options['readOnly']; - if (!readOnly) { - toolboxJsonDef = toolbox.convertToolboxDefToJson(options['toolbox']); - hasCategories = toolbox.hasCategories(toolboxJsonDef); - hasTrashcan = options['trashcan']; - if (hasTrashcan === undefined) { - hasTrashcan = hasCategories; +class Options { + /** + * @param {!BlocklyOptions} options Dictionary of options. + * Specification: + * https://developers.google.com/blockly/guides/get-started/web#configuration + */ + constructor(options) { + let toolboxJsonDef = null; + let hasCategories = false; + let hasTrashcan = false; + let hasCollapse = false; + let hasComments = false; + let hasDisable = false; + let hasSounds = false; + const readOnly = !!options['readOnly']; + if (!readOnly) { + toolboxJsonDef = toolbox.convertToolboxDefToJson(options['toolbox']); + hasCategories = toolbox.hasCategories(toolboxJsonDef); + hasTrashcan = options['trashcan']; + if (hasTrashcan === undefined) { + hasTrashcan = hasCategories; + } + hasCollapse = options['collapse']; + if (hasCollapse === undefined) { + hasCollapse = hasCategories; + } + hasComments = options['comments']; + if (hasComments === undefined) { + hasComments = hasCategories; + } + hasDisable = options['disable']; + if (hasDisable === undefined) { + hasDisable = hasCategories; + } + hasSounds = options['sounds']; + if (hasSounds === undefined) { + hasSounds = true; + } } - hasCollapse = options['collapse']; - if (hasCollapse === undefined) { - hasCollapse = hasCategories; - } - hasComments = options['comments']; - if (hasComments === undefined) { - hasComments = hasCategories; - } - hasDisable = options['disable']; - if (hasDisable === undefined) { - hasDisable = hasCategories; - } - hasSounds = options['sounds']; - if (hasSounds === undefined) { - hasSounds = true; - } - } - let maxTrashcanContents = options['maxTrashcanContents']; - if (hasTrashcan) { - if (maxTrashcanContents === undefined) { - maxTrashcanContents = 32; + let maxTrashcanContents = options['maxTrashcanContents']; + if (hasTrashcan) { + if (maxTrashcanContents === undefined) { + maxTrashcanContents = 32; + } + } else { + maxTrashcanContents = 0; } - } else { - maxTrashcanContents = 0; - } - const rtl = !!options['rtl']; - let horizontalLayout = options['horizontalLayout']; - if (horizontalLayout === undefined) { - horizontalLayout = false; - } - let toolboxAtStart = options['toolboxPosition']; - toolboxAtStart = toolboxAtStart !== 'end'; + const rtl = !!options['rtl']; + let horizontalLayout = options['horizontalLayout']; + if (horizontalLayout === undefined) { + horizontalLayout = false; + } + let toolboxAtStart = options['toolboxPosition']; + toolboxAtStart = toolboxAtStart !== 'end'; - /** @type {!toolbox.Position} */ - let toolboxPosition; - if (horizontalLayout) { - toolboxPosition = - toolboxAtStart ? toolbox.Position.TOP : toolbox.Position.BOTTOM; - } else { - toolboxPosition = (toolboxAtStart === rtl) ? toolbox.Position.RIGHT : - toolbox.Position.LEFT; - } + /** @type {!toolbox.Position} */ + let toolboxPosition; + if (horizontalLayout) { + toolboxPosition = + toolboxAtStart ? toolbox.Position.TOP : toolbox.Position.BOTTOM; + } else { + toolboxPosition = (toolboxAtStart === rtl) ? toolbox.Position.RIGHT : + toolbox.Position.LEFT; + } - let hasCss = options['css']; - if (hasCss === undefined) { - hasCss = true; - } - let pathToMedia = 'https://blockly-demo.appspot.com/static/media/'; - if (options['media']) { - pathToMedia = options['media']; - } else if (options['path']) { - // 'path' is a deprecated option which has been replaced by 'media'. - pathToMedia = options['path'] + 'media/'; - } - let oneBasedIndex; - if (options['oneBasedIndex'] === undefined) { - oneBasedIndex = true; - } else { - oneBasedIndex = !!options['oneBasedIndex']; - } - const renderer = options['renderer'] || 'geras'; + let hasCss = options['css']; + if (hasCss === undefined) { + hasCss = true; + } + let pathToMedia = 'https://blockly-demo.appspot.com/static/media/'; + if (options['media']) { + pathToMedia = options['media']; + } else if (options['path']) { + // 'path' is a deprecated option which has been replaced by 'media'. + pathToMedia = options['path'] + 'media/'; + } + let oneBasedIndex; + if (options['oneBasedIndex'] === undefined) { + oneBasedIndex = true; + } else { + oneBasedIndex = !!options['oneBasedIndex']; + } + const renderer = options['renderer'] || 'geras'; - const plugins = options['plugins'] || {}; + const plugins = options['plugins'] || {}; - /** @type {boolean} */ - this.RTL = rtl; - /** @type {boolean} */ - this.oneBasedIndex = oneBasedIndex; - /** @type {boolean} */ - this.collapse = hasCollapse; - /** @type {boolean} */ - this.comments = hasComments; - /** @type {boolean} */ - this.disable = hasDisable; - /** @type {boolean} */ - this.readOnly = readOnly; - /** @type {number} */ - this.maxBlocks = options['maxBlocks'] || Infinity; - /** @type {?Object} */ - this.maxInstances = options['maxInstances']; - /** @type {string} */ - this.pathToMedia = pathToMedia; - /** @type {boolean} */ - this.hasCategories = hasCategories; - /** @type {!Options.MoveOptions} */ - this.moveOptions = Options.parseMoveOptions_(options, hasCategories); - /** @deprecated January 2019 */ - this.hasScrollbars = !!this.moveOptions.scrollbars; - /** @type {boolean} */ - this.hasTrashcan = hasTrashcan; - /** @type {number} */ - this.maxTrashcanContents = maxTrashcanContents; - /** @type {boolean} */ - this.hasSounds = hasSounds; - /** @type {boolean} */ - this.hasCss = hasCss; - /** @type {boolean} */ - this.horizontalLayout = horizontalLayout; - /** @type {?toolbox.ToolboxInfo} */ - this.languageTree = toolboxJsonDef; - /** @type {!Options.GridOptions} */ - this.gridOptions = Options.parseGridOptions_(options); - /** @type {!Options.ZoomOptions} */ - this.zoomOptions = Options.parseZoomOptions_(options); - /** @type {!toolbox.Position} */ - this.toolboxPosition = toolboxPosition; - /** @type {!Theme} */ - this.theme = Options.parseThemeOptions_(options); - /** @type {string} */ - this.renderer = renderer; - /** @type {?Object} */ - this.rendererOverrides = options['rendererOverrides']; + /** @type {boolean} */ + this.RTL = rtl; + /** @type {boolean} */ + this.oneBasedIndex = oneBasedIndex; + /** @type {boolean} */ + this.collapse = hasCollapse; + /** @type {boolean} */ + this.comments = hasComments; + /** @type {boolean} */ + this.disable = hasDisable; + /** @type {boolean} */ + this.readOnly = readOnly; + /** @type {number} */ + this.maxBlocks = options['maxBlocks'] || Infinity; + /** @type {?Object} */ + this.maxInstances = options['maxInstances']; + /** @type {string} */ + this.pathToMedia = pathToMedia; + /** @type {boolean} */ + this.hasCategories = hasCategories; + /** @type {!Options.MoveOptions} */ + this.moveOptions = Options.parseMoveOptions_(options, hasCategories); + /** @deprecated January 2019 */ + this.hasScrollbars = !!this.moveOptions.scrollbars; + /** @type {boolean} */ + this.hasTrashcan = hasTrashcan; + /** @type {number} */ + this.maxTrashcanContents = maxTrashcanContents; + /** @type {boolean} */ + this.hasSounds = hasSounds; + /** @type {boolean} */ + this.hasCss = hasCss; + /** @type {boolean} */ + this.horizontalLayout = horizontalLayout; + /** @type {?toolbox.ToolboxInfo} */ + this.languageTree = toolboxJsonDef; + /** @type {!Options.GridOptions} */ + this.gridOptions = Options.parseGridOptions_(options); + /** @type {!Options.ZoomOptions} */ + this.zoomOptions = Options.parseZoomOptions_(options); + /** @type {!toolbox.Position} */ + this.toolboxPosition = toolboxPosition; + /** @type {!Theme} */ + this.theme = Options.parseThemeOptions_(options); + /** @type {string} */ + this.renderer = renderer; + /** @type {?Object} */ + this.rendererOverrides = options['rendererOverrides']; + + /** + * The SVG element for the grid pattern. + * Created during injection. + * @type {?SVGElement} + */ + this.gridPattern = null; + + /** + * The parent of the current workspace, or null if there is no parent + * workspace. We can assert that this is of type WorkspaceSvg as opposed to + * Workspace as this is only used in a rendered workspace. + * @type {?WorkspaceSvg} + */ + this.parentWorkspace = options['parentWorkspace']; + + /** + * Map of plugin type to name of registered plugin or plugin class. + * @type {!Object} + */ + this.plugins = plugins; + + /** + * If set, sets the translation of the workspace to match the scrollbars. + * @type {undefined|function(!{x:number,y:number}):void} A function that + * sets the translation of the workspace to match the scrollbars. The + * argument Contains an x and/or y property which is a float between 0 + * and 1 specifying the degree of scrolling. + */ + this.setMetrics = undefined; + + /** + * @type {undefined|function():!Metrics} A function that returns a metrics + * object that describes the current workspace. + */ + this.getMetrics = undefined; + } /** - * The SVG element for the grid pattern. - * Created during injection. - * @type {?SVGElement} + * Parse the user-specified move options, using reasonable defaults where + * behaviour is unspecified. + * @param {!Object} options Dictionary of options. + * @param {boolean} hasCategories Whether the workspace has categories or not. + * @return {!Options.MoveOptions} Normalized move options. + * @private */ - this.gridPattern = null; + static parseMoveOptions_(options, hasCategories) { + const move = options['move'] || {}; + const moveOptions = {}; + if (move['scrollbars'] === undefined && + options['scrollbars'] === undefined) { + moveOptions.scrollbars = hasCategories; + } else if (typeof move['scrollbars'] === 'object') { + moveOptions.scrollbars = {}; + moveOptions.scrollbars.horizontal = !!move['scrollbars']['horizontal']; + moveOptions.scrollbars.vertical = !!move['scrollbars']['vertical']; + // Convert scrollbars object to boolean if they have the same value. + // This allows us to easily check for whether any scrollbars exist using + // !!moveOptions.scrollbars. + if (moveOptions.scrollbars.horizontal && + moveOptions.scrollbars.vertical) { + moveOptions.scrollbars = true; + } else if ( + !moveOptions.scrollbars.horizontal && + !moveOptions.scrollbars.vertical) { + moveOptions.scrollbars = false; + } + } else { + moveOptions.scrollbars = !!move['scrollbars'] || !!options['scrollbars']; + } + + if (!moveOptions.scrollbars || move['wheel'] === undefined) { + // Defaults to true if single-direction scroll is enabled. + moveOptions.wheel = typeof moveOptions.scrollbars === 'object'; + } else { + moveOptions.wheel = !!move['wheel']; + } + if (!moveOptions.scrollbars) { + moveOptions.drag = false; + } else if (move['drag'] === undefined) { + // Defaults to true if scrollbars is true. + moveOptions.drag = true; + } else { + moveOptions.drag = !!move['drag']; + } + return moveOptions; + } /** - * The parent of the current workspace, or null if there is no parent - * workspace. We can assert that this is of type WorkspaceSvg as opposed to - * Workspace as this is only used in a rendered workspace. - * @type {WorkspaceSvg} + * Parse the user-specified zoom options, using reasonable defaults where + * behaviour is unspecified. See zoom documentation: + * https://developers.google.com/blockly/guides/configure/web/zoom + * @param {!Object} options Dictionary of options. + * @return {!Options.ZoomOptions} Normalized zoom options. + * @private */ - this.parentWorkspace = options['parentWorkspace']; + static parseZoomOptions_(options) { + const zoom = options['zoom'] || {}; + const zoomOptions = {}; + if (zoom['controls'] === undefined) { + zoomOptions.controls = false; + } else { + zoomOptions.controls = !!zoom['controls']; + } + if (zoom['wheel'] === undefined) { + zoomOptions.wheel = false; + } else { + zoomOptions.wheel = !!zoom['wheel']; + } + if (zoom['startScale'] === undefined) { + zoomOptions.startScale = 1; + } else { + zoomOptions.startScale = Number(zoom['startScale']); + } + if (zoom['maxScale'] === undefined) { + zoomOptions.maxScale = 3; + } else { + zoomOptions.maxScale = Number(zoom['maxScale']); + } + if (zoom['minScale'] === undefined) { + zoomOptions.minScale = 0.3; + } else { + zoomOptions.minScale = Number(zoom['minScale']); + } + if (zoom['scaleSpeed'] === undefined) { + zoomOptions.scaleSpeed = 1.2; + } else { + zoomOptions.scaleSpeed = Number(zoom['scaleSpeed']); + } + if (zoom['pinch'] === undefined) { + zoomOptions.pinch = zoomOptions.wheel || zoomOptions.controls; + } else { + zoomOptions.pinch = !!zoom['pinch']; + } + return zoomOptions; + } /** - * Map of plugin type to name of registered plugin or plugin class. - * @type {!Object} + * Parse the user-specified grid options, using reasonable defaults where + * behaviour is unspecified. See grid documentation: + * https://developers.google.com/blockly/guides/configure/web/grid + * @param {!Object} options Dictionary of options. + * @return {!Options.GridOptions} Normalized grid options. + * @private */ - this.plugins = plugins; -}; + static parseGridOptions_(options) { + const grid = options['grid'] || {}; + const gridOptions = {}; + gridOptions.spacing = Number(grid['spacing']) || 0; + gridOptions.colour = grid['colour'] || '#888'; + gridOptions.length = + (grid['length'] === undefined) ? 1 : Number(grid['length']); + gridOptions.snap = gridOptions.spacing > 0 && !!grid['snap']; + return gridOptions; + } + + /** + * Parse the user-specified theme options, using the classic theme as a + * default. https://developers.google.com/blockly/guides/configure/web/themes + * @param {!Object} options Dictionary of options. + * @return {!Theme} A Blockly Theme. + * @private + */ + static parseThemeOptions_(options) { + const theme = options['theme'] || Classic; + if (typeof theme === 'string') { + return /** @type {!Theme} */ ( + registry.getObject(registry.Type.THEME, theme)); + } else if (theme instanceof Theme) { + return /** @type {!Theme} */ (theme); + } + return Theme.defineTheme( + theme.name || ('builtin' + idGenerator.getNextUniqueId()), theme); + } +} /** * Grid Options. @@ -233,153 +388,4 @@ Options.ScrollbarOptions; */ Options.ZoomOptions; -/** - * If set, sets the translation of the workspace to match the scrollbars. - * @param {!{x:number,y:number}} xyRatio Contains an x and/or y property which - * is a float between 0 and 1 specifying the degree of scrolling. - * @return {void} - */ -Options.prototype.setMetrics; - -/** - * Return an object with the metrics required to size the workspace. - * @return {!Metrics} Contains size and position metrics. - */ -Options.prototype.getMetrics; - -/** - * Parse the user-specified move options, using reasonable defaults where - * behaviour is unspecified. - * @param {!Object} options Dictionary of options. - * @param {boolean} hasCategories Whether the workspace has categories or not. - * @return {!Options.MoveOptions} Normalized move options. - * @private - */ -Options.parseMoveOptions_ = function(options, hasCategories) { - const move = options['move'] || {}; - const moveOptions = {}; - if (move['scrollbars'] === undefined && options['scrollbars'] === undefined) { - moveOptions.scrollbars = hasCategories; - } else if (typeof move['scrollbars'] === 'object') { - moveOptions.scrollbars = {}; - moveOptions.scrollbars.horizontal = !!move['scrollbars']['horizontal']; - moveOptions.scrollbars.vertical = !!move['scrollbars']['vertical']; - // Convert scrollbars object to boolean if they have the same value. - // This allows us to easily check for whether any scrollbars exist using - // !!moveOptions.scrollbars. - if (moveOptions.scrollbars.horizontal && moveOptions.scrollbars.vertical) { - moveOptions.scrollbars = true; - } else if ( - !moveOptions.scrollbars.horizontal && - !moveOptions.scrollbars.vertical) { - moveOptions.scrollbars = false; - } - } else { - moveOptions.scrollbars = !!move['scrollbars'] || !!options['scrollbars']; - } - - if (!moveOptions.scrollbars || move['wheel'] === undefined) { - // Defaults to true if single-direction scroll is enabled. - moveOptions.wheel = typeof moveOptions.scrollbars === 'object'; - } else { - moveOptions.wheel = !!move['wheel']; - } - if (!moveOptions.scrollbars) { - moveOptions.drag = false; - } else if (move['drag'] === undefined) { - // Defaults to true if scrollbars is true. - moveOptions.drag = true; - } else { - moveOptions.drag = !!move['drag']; - } - return moveOptions; -}; - -/** - * Parse the user-specified zoom options, using reasonable defaults where - * behaviour is unspecified. See zoom documentation: - * https://developers.google.com/blockly/guides/configure/web/zoom - * @param {!Object} options Dictionary of options. - * @return {!Options.ZoomOptions} Normalized zoom options. - * @private - */ -Options.parseZoomOptions_ = function(options) { - const zoom = options['zoom'] || {}; - const zoomOptions = {}; - if (zoom['controls'] === undefined) { - zoomOptions.controls = false; - } else { - zoomOptions.controls = !!zoom['controls']; - } - if (zoom['wheel'] === undefined) { - zoomOptions.wheel = false; - } else { - zoomOptions.wheel = !!zoom['wheel']; - } - if (zoom['startScale'] === undefined) { - zoomOptions.startScale = 1; - } else { - zoomOptions.startScale = Number(zoom['startScale']); - } - if (zoom['maxScale'] === undefined) { - zoomOptions.maxScale = 3; - } else { - zoomOptions.maxScale = Number(zoom['maxScale']); - } - if (zoom['minScale'] === undefined) { - zoomOptions.minScale = 0.3; - } else { - zoomOptions.minScale = Number(zoom['minScale']); - } - if (zoom['scaleSpeed'] === undefined) { - zoomOptions.scaleSpeed = 1.2; - } else { - zoomOptions.scaleSpeed = Number(zoom['scaleSpeed']); - } - if (zoom['pinch'] === undefined) { - zoomOptions.pinch = zoomOptions.wheel || zoomOptions.controls; - } else { - zoomOptions.pinch = !!zoom['pinch']; - } - return zoomOptions; -}; - -/** - * Parse the user-specified grid options, using reasonable defaults where - * behaviour is unspecified. See grid documentation: - * https://developers.google.com/blockly/guides/configure/web/grid - * @param {!Object} options Dictionary of options. - * @return {!Options.GridOptions} Normalized grid options. - * @private - */ -Options.parseGridOptions_ = function(options) { - const grid = options['grid'] || {}; - const gridOptions = {}; - gridOptions.spacing = Number(grid['spacing']) || 0; - gridOptions.colour = grid['colour'] || '#888'; - gridOptions.length = - (grid['length'] === undefined) ? 1 : Number(grid['length']); - gridOptions.snap = gridOptions.spacing > 0 && !!grid['snap']; - return gridOptions; -}; - -/** - * Parse the user-specified theme options, using the classic theme as a default. - * https://developers.google.com/blockly/guides/configure/web/themes - * @param {!Object} options Dictionary of options. - * @return {!Theme} A Blockly Theme. - * @private - */ -Options.parseThemeOptions_ = function(options) { - const theme = options['theme'] || Classic; - if (typeof theme === 'string') { - return /** @type {!Theme} */ ( - registry.getObject(registry.Type.THEME, theme)); - } else if (theme instanceof Theme) { - return /** @type {!Theme} */ (theme); - } - return Theme.defineTheme( - theme.name || ('builtin' + idGenerator.getNextUniqueId()), theme); -}; - exports.Options = Options; diff --git a/core/procedures.js b/core/procedures.js index a6c819f59..f15a72b01 100644 --- a/core/procedures.js +++ b/core/procedures.js @@ -15,16 +15,20 @@ */ goog.module('Blockly.Procedures'); -/* eslint-disable-next-line no-unused-vars */ -const Abstract = goog.requireType('Blockly.Events.Abstract'); const Variables = goog.require('Blockly.Variables'); const Xml = goog.require('Blockly.Xml'); const eventUtils = goog.require('Blockly.Events.utils'); const utilsXml = goog.require('Blockly.utils.xml'); +/* eslint-disable-next-line no-unused-vars */ +const {Abstract} = goog.requireType('Blockly.Events.Abstract'); const {Blocks} = goog.require('Blockly.blocks'); /* eslint-disable-next-line no-unused-vars */ const {Block} = goog.requireType('Blockly.Block'); /* eslint-disable-next-line no-unused-vars */ +const {BubbleOpen} = goog.requireType('Blockly.Events.BubbleOpen'); +/* eslint-disable-next-line no-unused-vars */ +const {BlockSvg} = goog.requireType('Blockly.BlockSvg'); +/* eslint-disable-next-line no-unused-vars */ const {Field} = goog.requireType('Blockly.Field'); const {Msg} = goog.require('Blockly.Msg'); const {Names} = goog.require('Blockly.Names'); @@ -160,8 +164,9 @@ const isNameUsed = function(name, workspace, opt_exclude) { if (blocks[i] === opt_exclude) { continue; } - if (blocks[i].getProcedureDef) { - const procedureBlock = /** @type {!ProcedureBlock} */ (blocks[i]); + // Assume it is a procedure block so we can check. + const procedureBlock = /** @type {!ProcedureBlock} */ (blocks[i]); + if (procedureBlock.getProcedureDef) { const procName = procedureBlock.getProcedureDef(); if (Names.equals(procName[0], name)) { return true; @@ -191,8 +196,9 @@ const rename = function(name) { // Rename any callers. const blocks = this.getSourceBlock().workspace.getAllBlocks(false); for (let i = 0; i < blocks.length; i++) { - if (blocks[i].renameProcedure) { - const procedureBlock = /** @type {!ProcedureBlock} */ (blocks[i]); + // Assume it is a procedure so we can check. + const procedureBlock = /** @type {!ProcedureBlock} */ (blocks[i]); + if (procedureBlock.renameProcedure) { procedureBlock.renameProcedure( /** @type {string} */ (oldName), legalName); } @@ -204,7 +210,7 @@ exports.rename = rename; /** * Construct the blocks required by the flyout for the procedure category. - * @param {!Workspace} workspace The workspace containing procedures. + * @param {!WorkspaceSvg} workspace The workspace containing procedures. * @return {!Array} Array of XML block elements. * @alias Blockly.Procedures.flyoutCategory */ @@ -291,7 +297,7 @@ exports.flyoutCategory = flyoutCategory; /** * Updates the procedure mutator's flyout so that the arg block is not a * duplicate of another arg. - * @param {!Workspace} workspace The procedure mutator's workspace. This + * @param {!WorkspaceSvg} workspace The procedure mutator's workspace. This * workspace's flyout is what is being updated. */ const updateMutatorFlyout = function(workspace) { @@ -325,17 +331,21 @@ const updateMutatorFlyout = function(workspace) { * @package */ const mutatorOpenListener = function(e) { - if (!(e.type === eventUtils.BUBBLE_OPEN && e.bubbleType === 'mutator' && - e.isOpen)) { + if (e.type !== eventUtils.BUBBLE_OPEN) { return; } - const workspaceId = /** @type {string} */ (e.workspaceId); - const block = Workspace.getById(workspaceId).getBlockById(e.blockId); + const bubbleEvent = /** @type {!BubbleOpen} */ (e); + if (!(bubbleEvent.bubbleType === 'mutator' && bubbleEvent.isOpen)) { + return; + } + const workspaceId = /** @type {string} */ (bubbleEvent.workspaceId); + const block = /** @type {!BlockSvg} */ + (Workspace.getById(workspaceId).getBlockById(bubbleEvent.blockId)); const type = block.type; if (type !== 'procedures_defnoreturn' && type !== 'procedures_defreturn') { return; } - const workspace = block.mutator.getWorkspace(); + const workspace = /** @type {!WorkspaceSvg} */ (block.mutator.getWorkspace()); updateMutatorFlyout(workspace); workspace.addChangeListener(mutatorChangeListener); }; @@ -370,8 +380,9 @@ const getCallers = function(name, workspace) { const blocks = workspace.getAllBlocks(false); // Iterate through every block and check the name. for (let i = 0; i < blocks.length; i++) { - if (blocks[i].getProcedureCall) { - const procedureBlock = /** @type {!ProcedureBlock} */ (blocks[i]); + // Assume it is a procedure block so we can check. + const procedureBlock = /** @type {!ProcedureBlock} */ (blocks[i]); + if (procedureBlock.getProcedureCall) { const procName = procedureBlock.getProcedureCall(); // Procedure name may be null if the block is only half-built. if (procName && Names.equals(procName, name)) { @@ -427,8 +438,9 @@ const getDefinition = function(name, workspace) { // rely on getProcedureDef. const blocks = workspace.getAllBlocks(false); for (let i = 0; i < blocks.length; i++) { - if (blocks[i].getProcedureDef) { - const procedureBlock = /** @type {!ProcedureBlock} */ (blocks[i]); + // Assume it is a procedure block so we can check. + const procedureBlock = /** @type {!ProcedureBlock} */ (blocks[i]); + if (procedureBlock.getProcedureDef) { const tuple = procedureBlock.getProcedureDef(); if (tuple && Names.equals(tuple[0], name)) { return blocks[i]; // Can't use procedureBlock var due to type check. diff --git a/core/registry.js b/core/registry.js index b2420f86a..991d25b2a 100644 --- a/core/registry.js +++ b/core/registry.js @@ -18,7 +18,7 @@ goog.module('Blockly.registry'); /* eslint-disable-next-line no-unused-vars */ -const Abstract = goog.requireType('Blockly.Events.Abstract'); +const {Abstract} = goog.requireType('Blockly.Events.Abstract'); /* eslint-disable-next-line no-unused-vars */ const {Cursor} = goog.requireType('Blockly.Cursor'); /* eslint-disable-next-line no-unused-vars */ @@ -73,28 +73,31 @@ exports.DEFAULT = DEFAULT; /** * A name with the type of the element stored in the generic. - * @param {string} name The name of the registry type. - * @constructor * @template T * @alias Blockly.registry.Type */ -const Type = function(name) { +class Type { /** - * @type {string} - * @private + * @param {string} name The name of the registry type. */ - this.name_ = name; -}; + constructor(name) { + /** + * @type {string} + * @private + */ + this.name_ = name; + } + + /** + * Returns the name of the type. + * @return {string} The name. + */ + toString() { + return this.name_; + } +} exports.Type = Type; -/** - * Returns the name of the type. - * @return {string} The name. - * @override - */ -Type.prototype.toString = function() { - return this.name_; -}; /** @type {!Type} */ Type.CONNECTION_CHECKER = new Type('connectionChecker'); diff --git a/core/rendered_connection.js b/core/rendered_connection.js index 5f52a54e1..23ab17d33 100644 --- a/core/rendered_connection.js +++ b/core/rendered_connection.js @@ -19,13 +19,13 @@ const common = goog.require('Blockly.common'); const dom = goog.require('Blockly.utils.dom'); const eventUtils = goog.require('Blockly.Events.utils'); const internalConstants = goog.require('Blockly.internalConstants'); -const object = goog.require('Blockly.utils.object'); const svgPaths = goog.require('Blockly.utils.svgPaths'); const svgMath = goog.require('Blockly.utils.svgMath'); /* eslint-disable-next-line no-unused-vars */ const {BlockSvg} = goog.requireType('Blockly.BlockSvg'); /* eslint-disable-next-line no-unused-vars */ const {Block} = goog.requireType('Blockly.Block'); +const {config} = goog.require('Blockly.config'); /* eslint-disable-next-line no-unused-vars */ const {ConnectionDB} = goog.requireType('Blockly.ConnectionDB'); const {ConnectionType} = goog.require('Blockly.ConnectionType'); @@ -34,54 +34,538 @@ const {Coordinate} = goog.require('Blockly.utils.Coordinate'); const {Svg} = goog.require('Blockly.utils.Svg'); +/** + * Maximum randomness in workspace units for bumping a block. + * @const + */ +const BUMP_RANDOMNESS = 10; + /** * Class for a connection between blocks that may be rendered on screen. - * @param {!BlockSvg} source The block establishing this connection. - * @param {number} type The type of the connection. * @extends {Connection} - * @constructor * @alias Blockly.RenderedConnection */ -const RenderedConnection = function(source, type) { - RenderedConnection.superClass_.constructor.call(this, source, type); +class RenderedConnection extends Connection { + /** + * @param {!BlockSvg} source The block establishing this connection. + * @param {number} type The type of the connection. + */ + constructor(source, type) { + super(source, type); + + /** @type {!BlockSvg} */ + this.sourceBlock_; + + /** + * Connection database for connections of this type on the current + * workspace. + * @const {!ConnectionDB} + * @private + */ + this.db_ = source.workspace.connectionDBList[type]; + + /** + * Connection database for connections compatible with this type on the + * current workspace. + * @const {!ConnectionDB} + * @private + */ + this.dbOpposite_ = + source.workspace + .connectionDBList[internalConstants.OPPOSITE_TYPE[type]]; + + /** + * Workspace units, (0, 0) is top left of block. + * @type {!Coordinate} + * @private + */ + this.offsetInBlock_ = new Coordinate(0, 0); + + /** + * Describes the state of this connection's tracked-ness. + * @type {RenderedConnection.TrackedState} + * @private + */ + this.trackedState_ = RenderedConnection.TrackedState.WILL_TRACK; + + /** + * Connection this connection connects to. Null if not connected. + * @type {RenderedConnection} + */ + this.targetConnection = null; + } /** - * Connection database for connections of this type on the current workspace. - * @const {!ConnectionDB} - * @private + * Dispose of this connection. Remove it from the database (if it is + * tracked) and call the super-function to deal with connected blocks. + * @override + * @package */ - this.db_ = source.workspace.connectionDBList[type]; + dispose() { + super.dispose(); + if (this.trackedState_ === RenderedConnection.TrackedState.TRACKED) { + this.db_.removeConnection(this, this.y); + } + } /** - * Connection database for connections compatible with this type on the - * current workspace. - * @const {!ConnectionDB} - * @private + * Get the source block for this connection. + * @return {!BlockSvg} The source block. + * @override */ - this.dbOpposite_ = - source.workspace.connectionDBList[internalConstants.OPPOSITE_TYPE[type]]; + getSourceBlock() { + return /** @type {!BlockSvg} */ (super.getSourceBlock()); + } /** - * Workspace units, (0, 0) is top left of block. - * @type {!Coordinate} - * @private + * Returns the block that this connection connects to. + * @return {?BlockSvg} The connected block or null if none is connected. + * @override */ - this.offsetInBlock_ = new Coordinate(0, 0); + targetBlock() { + return /** @type {BlockSvg} */ (super.targetBlock()); + } /** - * Describes the state of this connection's tracked-ness. - * @type {RenderedConnection.TrackedState} - * @private + * Returns the distance between this connection and another connection in + * workspace units. + * @param {!Connection} otherConnection The other connection to measure + * the distance to. + * @return {number} The distance between connections, in workspace units. */ - this.trackedState_ = RenderedConnection.TrackedState.WILL_TRACK; + distanceFrom(otherConnection) { + const xDiff = this.x - otherConnection.x; + const yDiff = this.y - otherConnection.y; + return Math.sqrt(xDiff * xDiff + yDiff * yDiff); + } /** - * Connection this connection connects to. Null if not connected. - * @type {RenderedConnection} + * Move the block(s) belonging to the connection to a point where they don't + * visually interfere with the specified connection. + * @param {!RenderedConnection} staticConnection The connection to move away + * from. + * @package */ - this.targetConnection = null; -}; -object.inherits(RenderedConnection, Connection); + bumpAwayFrom(staticConnection) { + if (this.sourceBlock_.workspace.isDragging()) { + // Don't move blocks around while the user is doing the same. + return; + } + // Move the root block. + let rootBlock = this.sourceBlock_.getRootBlock(); + if (rootBlock.isInFlyout) { + // Don't move blocks around in a flyout. + return; + } + let reverse = false; + if (!rootBlock.isMovable()) { + // Can't bump an uneditable block away. + // Check to see if the other block is movable. + rootBlock = staticConnection.getSourceBlock().getRootBlock(); + if (!rootBlock.isMovable()) { + return; + } + // Swap the connections and move the 'static' connection instead. + staticConnection = this; + reverse = true; + } + // Raise it to the top for extra visibility. + const selected = common.getSelected() == rootBlock; + selected || rootBlock.addSelect(); + let dx = (staticConnection.x + config.snapRadius + + Math.floor(Math.random() * BUMP_RANDOMNESS)) - + this.x; + let dy = (staticConnection.y + config.snapRadius + + Math.floor(Math.random() * BUMP_RANDOMNESS)) - + this.y; + if (reverse) { + // When reversing a bump due to an uneditable block, bump up. + dy = -dy; + } + if (rootBlock.RTL) { + dx = (staticConnection.x - config.snapRadius - + Math.floor(Math.random() * BUMP_RANDOMNESS)) - + this.x; + } + rootBlock.moveBy(dx, dy); + selected || rootBlock.removeSelect(); + } + + /** + * Change the connection's coordinates. + * @param {number} x New absolute x coordinate, in workspace coordinates. + * @param {number} y New absolute y coordinate, in workspace coordinates. + */ + moveTo(x, y) { + if (this.trackedState_ === RenderedConnection.TrackedState.WILL_TRACK) { + this.db_.addConnection(this, y); + this.trackedState_ = RenderedConnection.TrackedState.TRACKED; + } else if (this.trackedState_ === RenderedConnection.TrackedState.TRACKED) { + this.db_.removeConnection(this, this.y); + this.db_.addConnection(this, y); + } + this.x = x; + this.y = y; + } + + /** + * Change the connection's coordinates. + * @param {number} dx Change to x coordinate, in workspace units. + * @param {number} dy Change to y coordinate, in workspace units. + */ + moveBy(dx, dy) { + this.moveTo(this.x + dx, this.y + dy); + } + + /** + * Move this connection to the location given by its offset within the block + * and the location of the block's top left corner. + * @param {!Coordinate} blockTL The location of the top left + * corner of the block, in workspace coordinates. + */ + moveToOffset(blockTL) { + this.moveTo( + blockTL.x + this.offsetInBlock_.x, blockTL.y + this.offsetInBlock_.y); + } + + /** + * Set the offset of this connection relative to the top left of its block. + * @param {number} x The new relative x, in workspace units. + * @param {number} y The new relative y, in workspace units. + */ + setOffsetInBlock(x, y) { + this.offsetInBlock_.x = x; + this.offsetInBlock_.y = y; + } + + /** + * Get the offset of this connection relative to the top left of its block. + * @return {!Coordinate} The offset of the connection. + * @package + */ + getOffsetInBlock() { + return this.offsetInBlock_; + } + + /** + * Move the blocks on either side of this connection right next to each other. + * @package + */ + tighten() { + const dx = this.targetConnection.x - this.x; + const dy = this.targetConnection.y - this.y; + if (dx !== 0 || dy !== 0) { + const block = this.targetBlock(); + const svgRoot = block.getSvgRoot(); + if (!svgRoot) { + throw Error('block is not rendered.'); + } + // Workspace coordinates. + const xy = svgMath.getRelativeXY(svgRoot); + block.getSvgRoot().setAttribute( + 'transform', 'translate(' + (xy.x - dx) + ',' + (xy.y - dy) + ')'); + block.moveConnections(-dx, -dy); + } + } + + /** + * Find the closest compatible connection to this connection. + * All parameters are in workspace units. + * @param {number} maxLimit The maximum radius to another connection. + * @param {!Coordinate} dxy Offset between this connection's location + * in the database and the current location (as a result of dragging). + * @return {!{connection: ?Connection, radius: number}} Contains two + * properties: 'connection' which is either another connection or null, + * and 'radius' which is the distance. + */ + closest(maxLimit, dxy) { + return this.dbOpposite_.searchForClosest(this, maxLimit, dxy); + } + + /** + * Add highlighting around this connection. + */ + highlight() { + let steps; + const sourceBlockSvg = /** @type {!BlockSvg} */ (this.sourceBlock_); + const renderConstants = + sourceBlockSvg.workspace.getRenderer().getConstants(); + const shape = renderConstants.shapeFor(this); + if (this.type === ConnectionType.INPUT_VALUE || + this.type === ConnectionType.OUTPUT_VALUE) { + // Vertical line, puzzle tab, vertical line. + const yLen = renderConstants.TAB_OFFSET_FROM_TOP; + steps = svgPaths.moveBy(0, -yLen) + svgPaths.lineOnAxis('v', yLen) + + shape.pathDown + svgPaths.lineOnAxis('v', yLen); + } else { + const xLen = + renderConstants.NOTCH_OFFSET_LEFT - renderConstants.CORNER_RADIUS; + // Horizontal line, notch, horizontal line. + steps = svgPaths.moveBy(-xLen, 0) + svgPaths.lineOnAxis('h', xLen) + + shape.pathLeft + svgPaths.lineOnAxis('h', xLen); + } + const xy = this.sourceBlock_.getRelativeToSurfaceXY(); + const x = this.x - xy.x; + const y = this.y - xy.y; + Connection.highlightedPath_ = dom.createSvgElement( + Svg.PATH, { + 'class': 'blocklyHighlightedConnectionPath', + 'd': steps, + 'transform': 'translate(' + x + ',' + y + ')' + + (this.sourceBlock_.RTL ? ' scale(-1 1)' : ''), + }, + this.sourceBlock_.getSvgRoot()); + } + + /** + * Remove the highlighting around this connection. + */ + unhighlight() { + dom.removeNode(Connection.highlightedPath_); + delete Connection.highlightedPath_; + } + + /** + * Set whether this connections is tracked in the database or not. + * @param {boolean} doTracking If true, start tracking. If false, stop + * tracking. + * @package + */ + setTracking(doTracking) { + if ((doTracking && + this.trackedState_ === RenderedConnection.TrackedState.TRACKED) || + (!doTracking && + this.trackedState_ === RenderedConnection.TrackedState.UNTRACKED)) { + return; + } + if (this.sourceBlock_.isInFlyout) { + // Don't bother maintaining a database of connections in a flyout. + return; + } + if (doTracking) { + this.db_.addConnection(this, this.y); + this.trackedState_ = RenderedConnection.TrackedState.TRACKED; + return; + } + if (this.trackedState_ === RenderedConnection.TrackedState.TRACKED) { + this.db_.removeConnection(this, this.y); + } + this.trackedState_ = RenderedConnection.TrackedState.UNTRACKED; + } + + /** + * Stop tracking this connection, as well as all down-stream connections on + * any block attached to this connection. This happens when a block is + * collapsed. + * + * Also closes down-stream icons/bubbles. + * @package + */ + stopTrackingAll() { + this.setTracking(false); + if (this.targetConnection) { + const blocks = this.targetBlock().getDescendants(false); + for (let i = 0; i < blocks.length; i++) { + const block = blocks[i]; + // Stop tracking connections of all children. + const connections = block.getConnections_(true); + for (let j = 0; j < connections.length; j++) { + /** @type {!RenderedConnection} */ (connections[j]) + .setTracking(false); + } + // Close all bubbles of all children. + const icons = block.getIcons(); + for (let j = 0; j < icons.length; j++) { + icons[j].setVisible(false); + } + } + } + } + + /** + * Start tracking this connection, as well as all down-stream connections on + * any block attached to this connection. This happens when a block is + * expanded. + * @return {!Array} List of blocks to render. + */ + startTrackingAll() { + this.setTracking(true); + // All blocks that are not tracked must start tracking before any + // rendering takes place, since rendering requires knowing the dimensions + // of lower blocks. Also, since rendering a block renders all its parents, + // we only need to render the leaf nodes. + let renderList = []; + if (this.type !== ConnectionType.INPUT_VALUE && + this.type !== ConnectionType.NEXT_STATEMENT) { + // Only spider down. + return renderList; + } + const block = this.targetBlock(); + if (block) { + let connections; + if (block.isCollapsed()) { + // This block should only be partially revealed since it is collapsed. + connections = []; + block.outputConnection && connections.push(block.outputConnection); + block.nextConnection && connections.push(block.nextConnection); + block.previousConnection && connections.push(block.previousConnection); + } else { + // Show all connections of this block. + connections = block.getConnections_(true); + } + for (let i = 0; i < connections.length; i++) { + renderList.push.apply(renderList, connections[i].startTrackingAll()); + } + if (!renderList.length) { + // Leaf block. + renderList = [block]; + } + } + return renderList; + } + + /** + * Behavior after a connection attempt fails. + * Bumps this connection away from the other connection. Called when an + * attempted connection fails. + * @param {!Connection} otherConnection Connection that this connection + * failed to connect to. + * @package + */ + onFailedConnect(otherConnection) { + const block = this.getSourceBlock(); + if (eventUtils.getRecordUndo()) { + const group = eventUtils.getGroup(); + setTimeout(function() { + if (!block.isDisposed() && !block.getParent()) { + eventUtils.setGroup(group); + this.bumpAwayFrom( + /** @type {!RenderedConnection} */ (otherConnection)); + eventUtils.setGroup(false); + } + }.bind(this), config.bumpDelay); + } + } + + /** + * Disconnect two blocks that are connected by this connection. + * @param {!Block} parentBlock The superior block. + * @param {!Block} childBlock The inferior block. + * @protected + * @override + */ + disconnectInternal_(parentBlock, childBlock) { + super.disconnectInternal_(parentBlock, childBlock); + const renderedParent = /** @type {!BlockSvg} */ (parentBlock); + const renderedChild = /** @type {!BlockSvg} */ (childBlock); + + // Rerender the parent so that it may reflow. + if (renderedParent.rendered) { + renderedParent.render(); + } + if (renderedChild.rendered) { + renderedChild.updateDisabled(); + renderedChild.render(); + // Reset visibility, since the child is now a top block. + renderedChild.getSvgRoot().style.display = 'block'; + } + } + + /** + * Respawn the shadow block if there was one connected to the this connection. + * Render/rerender blocks as needed. + * @protected + * @override + */ + respawnShadow_() { + super.respawnShadow_(); + const blockShadow = this.targetBlock(); + if (!blockShadow) { + return; + } + blockShadow.initSvg(); + blockShadow.render(false); + + const parentBlock = this.getSourceBlock(); + if (parentBlock.rendered) { + parentBlock.render(); + } + } + + /** + * Find all nearby compatible connections to this connection. + * Type checking does not apply, since this function is used for bumping. + * @param {number} maxLimit The maximum radius to another connection, in + * workspace units. + * @return {!Array} List of connections. + * @package + */ + neighbours(maxLimit) { + return this.dbOpposite_.getNeighbours(this, maxLimit); + } + + /** + * Connect two connections together. This is the connection on the superior + * block. Rerender blocks as needed. + * @param {!Connection} childConnection Connection on inferior block. + * @protected + */ + connect_(childConnection) { + super.connect_(childConnection); + + const renderedChildConnection = /** @type {!RenderedConnection} */ + (childConnection); + + const parentConnection = this; + const parentBlock = parentConnection.getSourceBlock(); + const childBlock = renderedChildConnection.getSourceBlock(); + const parentRendered = parentBlock.rendered; + const childRendered = childBlock.rendered; + + if (parentRendered) { + parentBlock.updateDisabled(); + } + if (childRendered) { + childBlock.updateDisabled(); + } + if (parentRendered && childRendered) { + if (parentConnection.type === ConnectionType.NEXT_STATEMENT || + parentConnection.type === ConnectionType.PREVIOUS_STATEMENT) { + // Child block may need to square off its corners if it is in a stack. + // Rendering a child will render its parent. + childBlock.render(); + } else { + // Child block does not change shape. Rendering the parent node will + // move its connected children into position. + parentBlock.render(); + } + } + + // The input the child block is connected to (if any). + const parentInput = parentBlock.getInputWithBlock(childBlock); + if (parentInput) { + const visible = parentInput.isVisible(); + childBlock.getSvgRoot().style.display = visible ? 'block' : 'none'; + } + } + + /** + * Function to be called when this connection's compatible types have changed. + * @protected + */ + onCheckChanged_() { + // The new value type may not be compatible with the existing connection. + if (this.isConnected() && + (!this.targetConnection || + !this.getConnectionChecker().canConnect( + this, this.targetConnection, false))) { + const child = this.isSuperior() ? this.targetBlock() : this.sourceBlock_; + child.unplug(); + // Bump away. + this.sourceBlock_.bumpNeighbours(); + } + } +} /** * Enum for different kinds of tracked states. @@ -101,469 +585,4 @@ RenderedConnection.TrackedState = { TRACKED: 1, }; -/** - * Dispose of this connection. Remove it from the database (if it is - * tracked) and call the super-function to deal with connected blocks. - * @override - * @package - */ -RenderedConnection.prototype.dispose = function() { - RenderedConnection.superClass_.dispose.call(this); - if (this.trackedState_ === RenderedConnection.TrackedState.TRACKED) { - this.db_.removeConnection(this, this.y); - } -}; - -/** - * Get the source block for this connection. - * @return {!BlockSvg} The source block. - * @override - */ -RenderedConnection.prototype.getSourceBlock = function() { - return /** @type {!BlockSvg} */ ( - RenderedConnection.superClass_.getSourceBlock.call(this)); -}; - -/** - * Returns the block that this connection connects to. - * @return {?BlockSvg} The connected block or null if none is connected. - * @override - */ -RenderedConnection.prototype.targetBlock = function() { - return /** @type {BlockSvg} */ ( - RenderedConnection.superClass_.targetBlock.call(this)); -}; - -/** - * Returns the distance between this connection and another connection in - * workspace units. - * @param {!Connection} otherConnection The other connection to measure - * the distance to. - * @return {number} The distance between connections, in workspace units. - */ -RenderedConnection.prototype.distanceFrom = function(otherConnection) { - const xDiff = this.x - otherConnection.x; - const yDiff = this.y - otherConnection.y; - return Math.sqrt(xDiff * xDiff + yDiff * yDiff); -}; - -/** - * Move the block(s) belonging to the connection to a point where they don't - * visually interfere with the specified connection. - * @param {!Connection} staticConnection The connection to move away - * from. - * @package - */ -RenderedConnection.prototype.bumpAwayFrom = function(staticConnection) { - if (this.sourceBlock_.workspace.isDragging()) { - // Don't move blocks around while the user is doing the same. - return; - } - // Move the root block. - let rootBlock = this.sourceBlock_.getRootBlock(); - if (rootBlock.isInFlyout) { - // Don't move blocks around in a flyout. - return; - } - let reverse = false; - if (!rootBlock.isMovable()) { - // Can't bump an uneditable block away. - // Check to see if the other block is movable. - rootBlock = staticConnection.getSourceBlock().getRootBlock(); - if (!rootBlock.isMovable()) { - return; - } - // Swap the connections and move the 'static' connection instead. - staticConnection = this; - reverse = true; - } - // Raise it to the top for extra visibility. - const selected = common.getSelected() == rootBlock; - selected || rootBlock.addSelect(); - let dx = (staticConnection.x + internalConstants.SNAP_RADIUS + - Math.floor(Math.random() * internalConstants.BUMP_RANDOMNESS)) - - this.x; - let dy = (staticConnection.y + internalConstants.SNAP_RADIUS + - Math.floor(Math.random() * internalConstants.BUMP_RANDOMNESS)) - - this.y; - if (reverse) { - // When reversing a bump due to an uneditable block, bump up. - dy = -dy; - } - if (rootBlock.RTL) { - dx = (staticConnection.x - internalConstants.SNAP_RADIUS - - Math.floor(Math.random() * internalConstants.BUMP_RANDOMNESS)) - - this.x; - } - rootBlock.moveBy(dx, dy); - selected || rootBlock.removeSelect(); -}; - -/** - * Change the connection's coordinates. - * @param {number} x New absolute x coordinate, in workspace coordinates. - * @param {number} y New absolute y coordinate, in workspace coordinates. - */ -RenderedConnection.prototype.moveTo = function(x, y) { - if (this.trackedState_ === RenderedConnection.TrackedState.WILL_TRACK) { - this.db_.addConnection(this, y); - this.trackedState_ = RenderedConnection.TrackedState.TRACKED; - } else if (this.trackedState_ === RenderedConnection.TrackedState.TRACKED) { - this.db_.removeConnection(this, this.y); - this.db_.addConnection(this, y); - } - this.x = x; - this.y = y; -}; - -/** - * Change the connection's coordinates. - * @param {number} dx Change to x coordinate, in workspace units. - * @param {number} dy Change to y coordinate, in workspace units. - */ -RenderedConnection.prototype.moveBy = function(dx, dy) { - this.moveTo(this.x + dx, this.y + dy); -}; - -/** - * Move this connection to the location given by its offset within the block and - * the location of the block's top left corner. - * @param {!Coordinate} blockTL The location of the top left - * corner of the block, in workspace coordinates. - */ -RenderedConnection.prototype.moveToOffset = function(blockTL) { - this.moveTo( - blockTL.x + this.offsetInBlock_.x, blockTL.y + this.offsetInBlock_.y); -}; - -/** - * Set the offset of this connection relative to the top left of its block. - * @param {number} x The new relative x, in workspace units. - * @param {number} y The new relative y, in workspace units. - */ -RenderedConnection.prototype.setOffsetInBlock = function(x, y) { - this.offsetInBlock_.x = x; - this.offsetInBlock_.y = y; -}; - -/** - * Get the offset of this connection relative to the top left of its block. - * @return {!Coordinate} The offset of the connection. - * @package - */ -RenderedConnection.prototype.getOffsetInBlock = function() { - return this.offsetInBlock_; -}; - -/** - * Move the blocks on either side of this connection right next to each other. - * @package - */ -RenderedConnection.prototype.tighten = function() { - const dx = this.targetConnection.x - this.x; - const dy = this.targetConnection.y - this.y; - if (dx !== 0 || dy !== 0) { - const block = this.targetBlock(); - const svgRoot = block.getSvgRoot(); - if (!svgRoot) { - throw Error('block is not rendered.'); - } - // Workspace coordinates. - const xy = svgMath.getRelativeXY(svgRoot); - block.getSvgRoot().setAttribute( - 'transform', 'translate(' + (xy.x - dx) + ',' + (xy.y - dy) + ')'); - block.moveConnections(-dx, -dy); - } -}; - -/** - * Find the closest compatible connection to this connection. - * All parameters are in workspace units. - * @param {number} maxLimit The maximum radius to another connection. - * @param {!Coordinate} dxy Offset between this connection's location - * in the database and the current location (as a result of dragging). - * @return {!{connection: ?Connection, radius: number}} Contains two - * properties: 'connection' which is either another connection or null, - * and 'radius' which is the distance. - */ -RenderedConnection.prototype.closest = function(maxLimit, dxy) { - return this.dbOpposite_.searchForClosest(this, maxLimit, dxy); -}; - -/** - * Add highlighting around this connection. - */ -RenderedConnection.prototype.highlight = function() { - let steps; - const sourceBlockSvg = /** @type {!BlockSvg} */ (this.sourceBlock_); - const renderConstants = sourceBlockSvg.workspace.getRenderer().getConstants(); - const shape = renderConstants.shapeFor(this); - if (this.type === ConnectionType.INPUT_VALUE || - this.type === ConnectionType.OUTPUT_VALUE) { - // Vertical line, puzzle tab, vertical line. - const yLen = renderConstants.TAB_OFFSET_FROM_TOP; - steps = svgPaths.moveBy(0, -yLen) + svgPaths.lineOnAxis('v', yLen) + - shape.pathDown + svgPaths.lineOnAxis('v', yLen); - } else { - const xLen = - renderConstants.NOTCH_OFFSET_LEFT - renderConstants.CORNER_RADIUS; - // Horizontal line, notch, horizontal line. - steps = svgPaths.moveBy(-xLen, 0) + svgPaths.lineOnAxis('h', xLen) + - shape.pathLeft + svgPaths.lineOnAxis('h', xLen); - } - const xy = this.sourceBlock_.getRelativeToSurfaceXY(); - const x = this.x - xy.x; - const y = this.y - xy.y; - Connection.highlightedPath_ = dom.createSvgElement( - Svg.PATH, { - 'class': 'blocklyHighlightedConnectionPath', - 'd': steps, - 'transform': 'translate(' + x + ',' + y + ')' + - (this.sourceBlock_.RTL ? ' scale(-1 1)' : ''), - }, - this.sourceBlock_.getSvgRoot()); -}; - -/** - * Remove the highlighting around this connection. - */ -RenderedConnection.prototype.unhighlight = function() { - dom.removeNode(Connection.highlightedPath_); - delete Connection.highlightedPath_; -}; - -/** - * Set whether this connections is tracked in the database or not. - * @param {boolean} doTracking If true, start tracking. If false, stop tracking. - * @package - */ -RenderedConnection.prototype.setTracking = function(doTracking) { - if ((doTracking && - this.trackedState_ === RenderedConnection.TrackedState.TRACKED) || - (!doTracking && - this.trackedState_ === RenderedConnection.TrackedState.UNTRACKED)) { - return; - } - if (this.sourceBlock_.isInFlyout) { - // Don't bother maintaining a database of connections in a flyout. - return; - } - if (doTracking) { - this.db_.addConnection(this, this.y); - this.trackedState_ = RenderedConnection.TrackedState.TRACKED; - return; - } - if (this.trackedState_ === RenderedConnection.TrackedState.TRACKED) { - this.db_.removeConnection(this, this.y); - } - this.trackedState_ = RenderedConnection.TrackedState.UNTRACKED; -}; - -/** - * Stop tracking this connection, as well as all down-stream connections on - * any block attached to this connection. This happens when a block is - * collapsed. - * - * Also closes down-stream icons/bubbles. - * @package - */ -RenderedConnection.prototype.stopTrackingAll = function() { - this.setTracking(false); - if (this.targetConnection) { - const blocks = this.targetBlock().getDescendants(false); - for (let i = 0; i < blocks.length; i++) { - const block = blocks[i]; - // Stop tracking connections of all children. - const connections = block.getConnections_(true); - for (let j = 0; j < connections.length; j++) { - connections[j].setTracking(false); - } - // Close all bubbles of all children. - const icons = block.getIcons(); - for (let j = 0; j < icons.length; j++) { - icons[j].setVisible(false); - } - } - } -}; - -/** - * Start tracking this connection, as well as all down-stream connections on - * any block attached to this connection. This happens when a block is expanded. - * @return {!Array} List of blocks to render. - */ -RenderedConnection.prototype.startTrackingAll = function() { - this.setTracking(true); - // All blocks that are not tracked must start tracking before any - // rendering takes place, since rendering requires knowing the dimensions - // of lower blocks. Also, since rendering a block renders all its parents, - // we only need to render the leaf nodes. - let renderList = []; - if (this.type !== ConnectionType.INPUT_VALUE && - this.type !== ConnectionType.NEXT_STATEMENT) { - // Only spider down. - return renderList; - } - const block = this.targetBlock(); - if (block) { - let connections; - if (block.isCollapsed()) { - // This block should only be partially revealed since it is collapsed. - connections = []; - block.outputConnection && connections.push(block.outputConnection); - block.nextConnection && connections.push(block.nextConnection); - block.previousConnection && connections.push(block.previousConnection); - } else { - // Show all connections of this block. - connections = block.getConnections_(true); - } - for (let i = 0; i < connections.length; i++) { - renderList.push.apply(renderList, connections[i].startTrackingAll()); - } - if (!renderList.length) { - // Leaf block. - renderList = [block]; - } - } - return renderList; -}; - -/** - * Behavior after a connection attempt fails. - * Bumps this connection away from the other connection. Called when an - * attempted connection fails. - * @param {!Connection} otherConnection Connection that this connection - * failed to connect to. - * @package - */ -RenderedConnection.prototype.onFailedConnect = function(otherConnection) { - const block = this.getSourceBlock(); - if (eventUtils.getRecordUndo()) { - const group = eventUtils.getGroup(); - setTimeout(function() { - if (!block.isDisposed() && !block.getParent()) { - eventUtils.setGroup(group); - this.bumpAwayFrom(otherConnection); - eventUtils.setGroup(false); - } - }.bind(this), internalConstants.BUMP_DELAY); - } -}; - - -/** - * Disconnect two blocks that are connected by this connection. - * @param {!Block} parentBlock The superior block. - * @param {!Block} childBlock The inferior block. - * @protected - * @override - */ -RenderedConnection.prototype.disconnectInternal_ = function( - parentBlock, childBlock) { - RenderedConnection.superClass_.disconnectInternal_.call( - this, parentBlock, childBlock); - // Rerender the parent so that it may reflow. - if (parentBlock.rendered) { - parentBlock.render(); - } - if (childBlock.rendered) { - childBlock.updateDisabled(); - childBlock.render(); - // Reset visibility, since the child is now a top block. - childBlock.getSvgRoot().style.display = 'block'; - } -}; - -/** - * Respawn the shadow block if there was one connected to the this connection. - * Render/rerender blocks as needed. - * @protected - * @override - */ -RenderedConnection.prototype.respawnShadow_ = function() { - RenderedConnection.superClass_.respawnShadow_.call(this); - const blockShadow = this.targetBlock(); - if (!blockShadow) { - return; - } - blockShadow.initSvg(); - blockShadow.render(false); - - const parentBlock = this.getSourceBlock(); - if (parentBlock.rendered) { - parentBlock.render(); - } -}; - -/** - * Find all nearby compatible connections to this connection. - * Type checking does not apply, since this function is used for bumping. - * @param {number} maxLimit The maximum radius to another connection, in - * workspace units. - * @return {!Array} List of connections. - * @package - */ -RenderedConnection.prototype.neighbours = function(maxLimit) { - return this.dbOpposite_.getNeighbours(this, maxLimit); -}; - -/** - * Connect two connections together. This is the connection on the superior - * block. Rerender blocks as needed. - * @param {!Connection} childConnection Connection on inferior block. - * @protected - */ -RenderedConnection.prototype.connect_ = function(childConnection) { - RenderedConnection.superClass_.connect_.call(this, childConnection); - - const parentConnection = this; - const parentBlock = parentConnection.getSourceBlock(); - const childBlock = childConnection.getSourceBlock(); - const parentRendered = parentBlock.rendered; - const childRendered = childBlock.rendered; - - if (parentRendered) { - parentBlock.updateDisabled(); - } - if (childRendered) { - childBlock.updateDisabled(); - } - if (parentRendered && childRendered) { - if (parentConnection.type === ConnectionType.NEXT_STATEMENT || - parentConnection.type === ConnectionType.PREVIOUS_STATEMENT) { - // Child block may need to square off its corners if it is in a stack. - // Rendering a child will render its parent. - childBlock.render(); - } else { - // Child block does not change shape. Rendering the parent node will - // move its connected children into position. - parentBlock.render(); - } - } - - // The input the child block is connected to (if any). - const parentInput = parentBlock.getInputWithBlock(childBlock); - if (parentInput) { - const visible = parentInput.isVisible(); - childBlock.getSvgRoot().style.display = visible ? 'block' : 'none'; - } -}; - -/** - * Function to be called when this connection's compatible types have changed. - * @protected - */ -RenderedConnection.prototype.onCheckChanged_ = function() { - // The new value type may not be compatible with the existing connection. - if (this.isConnected() && - (!this.targetConnection || - !this.getConnectionChecker().canConnect( - this, this.targetConnection, false))) { - const child = this.isSuperior() ? this.targetBlock() : this.sourceBlock_; - child.unplug(); - // Bump away. - this.sourceBlock_.bumpNeighbours(); - } -}; - exports.RenderedConnection = RenderedConnection; diff --git a/core/renderers/common/block_rendering.js b/core/renderers/common/block_rendering.js index c98a31c76..44ffaaace 100644 --- a/core/renderers/common/block_rendering.js +++ b/core/renderers/common/block_rendering.js @@ -56,11 +56,13 @@ const {Types} = goog.require('Blockly.blockRendering.Types'); * @return {boolean} Whether the debugger is turned on. * @alias Blockly.blockRendering.isDebuggerEnabled * @package + * @deprecated */ const isDebuggerEnabled = function() { deprecation.warn( 'Blockly.blockRendering.isDebuggerEnabled()', 'September 2021', - 'September 2022', 'Blockly.blockRendering.debug.isDebuggerEnabled()'); + 'September 2022', + 'the debug renderer in @blockly/dev-tools (See https://www.npmjs.com/package/@blockly/dev-tools.)'); return debug.isDebuggerEnabled(); }; exports.isDebuggerEnabled = isDebuggerEnabled; @@ -91,11 +93,13 @@ exports.unregister = unregister; * Turn on the blocks debugger. * @package * @alias Blockly.blockRendering.startDebugger + * @deprecated */ const startDebugger = function() { deprecation.warn( 'Blockly.blockRendering.startDebugger()', 'September 2021', - 'September 2022', 'Blockly.blockRendering.debug.startDebugger()'); + 'September 2022', + 'the debug renderer in @blockly/dev-tools (See https://www.npmjs.com/package/@blockly/dev-tools.)'); debug.startDebugger(); }; exports.startDebugger = startDebugger; @@ -104,11 +108,13 @@ exports.startDebugger = startDebugger; * Turn off the blocks debugger. * @package * @alias Blockly.blockRendering.stopDebugger + * @deprecated */ const stopDebugger = function() { deprecation.warn( 'Blockly.blockRendering.stopDebugger()', 'September 2021', - 'September 2022', 'Blockly.blockRendering.debug.stopDebugger()'); + 'September 2022', + 'the debug renderer in @blockly/dev-tools (See https://www.npmjs.com/package/@blockly/dev-tools.)'); debug.stopDebugger(); }; exports.stopDebugger = stopDebugger; diff --git a/core/renderers/common/constants.js b/core/renderers/common/constants.js index 81401c6c6..49ace7025 100644 --- a/core/renderers/common/constants.js +++ b/core/renderers/common/constants.js @@ -31,1213 +31,1220 @@ const {Theme} = goog.requireType('Blockly.Theme'); /** * An object that provides constants for rendering blocks. - * @constructor - * @package * @alias Blockly.blockRendering.ConstantProvider */ -const ConstantProvider = function() { +class ConstantProvider { /** - * The size of an empty spacer. - * @type {number} - */ - this.NO_PADDING = 0; - - /** - * The size of small padding. - * @type {number} - */ - this.SMALL_PADDING = 3; - - /** - * The size of medium padding. - * @type {number} - */ - this.MEDIUM_PADDING = 5; - - /** - * The size of medium-large padding. - * @type {number} - */ - this.MEDIUM_LARGE_PADDING = 8; - - /** - * The size of large padding. - * @type {number} - */ - this.LARGE_PADDING = 10; - - /** - * Offset from the top of the row for placing fields on inline input rows - * and statement input rows. - * Matches existing rendering (in 2019). - * @type {number} - */ - this.TALL_INPUT_FIELD_OFFSET_Y = this.MEDIUM_PADDING; - - /** - * The height of the puzzle tab used for input and output connections. - * @type {number} - */ - this.TAB_HEIGHT = 15; - - /** - * The offset from the top of the block at which a puzzle tab is positioned. - * @type {number} - */ - this.TAB_OFFSET_FROM_TOP = 5; - - /** - * Vertical overlap of the puzzle tab, used to make it look more like a puzzle - * piece. - * @type {number} - */ - this.TAB_VERTICAL_OVERLAP = 2.5; - - /** - * The width of the puzzle tab used for input and output connections. - * @type {number} - */ - this.TAB_WIDTH = 8; - - /** - * The width of the notch used for previous and next connections. - * @type {number} - */ - this.NOTCH_WIDTH = 15; - - /** - * The height of the notch used for previous and next connections. - * @type {number} - */ - this.NOTCH_HEIGHT = 4; - - /** - * The minimum width of the block. - * @type {number} - */ - this.MIN_BLOCK_WIDTH = 12; - - this.EMPTY_BLOCK_SPACER_HEIGHT = 16; - - /** - * The minimum height of a dummy input row. - * @type {number} - */ - this.DUMMY_INPUT_MIN_HEIGHT = this.TAB_HEIGHT; - - /** - * The minimum height of a dummy input row in a shadow block. - * @type {number} - */ - this.DUMMY_INPUT_SHADOW_MIN_HEIGHT = this.TAB_HEIGHT; - - /** - * Rounded corner radius. - * @type {number} - */ - this.CORNER_RADIUS = 8; - - /** - * Offset from the left side of a block or the inside of a statement input to - * the left side of the notch. - * @type {number} - */ - this.NOTCH_OFFSET_LEFT = 15; - - /** - * Additional offset added to the statement input's width to account for the - * notch. - * @type {number} - */ - this.STATEMENT_INPUT_NOTCH_OFFSET = this.NOTCH_OFFSET_LEFT; - - this.STATEMENT_BOTTOM_SPACER = 0; - this.STATEMENT_INPUT_PADDING_LEFT = 20; - - /** - * Vertical padding between consecutive statement inputs. - * @type {number} - */ - this.BETWEEN_STATEMENT_PADDING_Y = 4; - - /** - * The top row's minimum height. - * @type {number} - */ - this.TOP_ROW_MIN_HEIGHT = this.MEDIUM_PADDING; - - /** - * The top row's minimum height if it precedes a statement. - * @type {number} - */ - this.TOP_ROW_PRECEDES_STATEMENT_MIN_HEIGHT = this.LARGE_PADDING; - - /** - * The bottom row's minimum height. - * @type {number} - */ - this.BOTTOM_ROW_MIN_HEIGHT = this.MEDIUM_PADDING; - - /** - * The bottom row's minimum height if it follows a statement input. - * @type {number} - */ - this.BOTTOM_ROW_AFTER_STATEMENT_MIN_HEIGHT = this.LARGE_PADDING; - - /** - * Whether to add a 'hat' on top of all blocks with no previous or output - * connections. Can be overridden by 'hat' property on Theme.BlockStyle. - * @type {boolean} - */ - this.ADD_START_HATS = false; - - /** - * Height of the top hat. - * @type {number} - */ - this.START_HAT_HEIGHT = 15; - - /** - * Width of the top hat. - * @type {number} - */ - this.START_HAT_WIDTH = 100; - - this.SPACER_DEFAULT_HEIGHT = 15; - - this.MIN_BLOCK_HEIGHT = 24; - - this.EMPTY_INLINE_INPUT_PADDING = 14.5; - - /** - * The height of an empty inline input. - * @type {number} - */ - this.EMPTY_INLINE_INPUT_HEIGHT = this.TAB_HEIGHT + 11; - - this.EXTERNAL_VALUE_INPUT_PADDING = 2; - - /** - * The height of an empty statement input. Note that in the old rendering - * this varies slightly depending on whether the block has external or inline - * inputs. In the new rendering this is consistent. It seems unlikely that - * the old behaviour was intentional. - * @type {number} - */ - this.EMPTY_STATEMENT_INPUT_HEIGHT = this.MIN_BLOCK_HEIGHT; - - this.START_POINT = svgPaths.moveBy(0, 0); - - /** - * Height of SVG path for jagged teeth at the end of collapsed blocks. - * @type {number} - */ - this.JAGGED_TEETH_HEIGHT = 12; - - /** - * Width of SVG path for jagged teeth at the end of collapsed blocks. - * @type {number} - */ - this.JAGGED_TEETH_WIDTH = 6; - - /** - * Point size of text. - * @type {number} - */ - this.FIELD_TEXT_FONTSIZE = 11; - - /** - * Text font weight. - * @type {string} - */ - this.FIELD_TEXT_FONTWEIGHT = 'normal'; - - /** - * Text font family. - * @type {string} - */ - this.FIELD_TEXT_FONTFAMILY = 'sans-serif'; - - /** - * Height of text. This constant is dynamically set in ``setFontConstants_`` - * to be the height of the text based on the font used. - * @type {number} - */ - this.FIELD_TEXT_HEIGHT = -1; // Dynamically set. - - /** - * Text baseline. This constant is dynamically set in ``setFontConstants_`` - * to be the baseline of the text based on the font used. - * @type {number} - */ - this.FIELD_TEXT_BASELINE = -1; // Dynamically set. - - /** - * A field's border rect corner radius. - * @type {number} - */ - this.FIELD_BORDER_RECT_RADIUS = 4; - - /** - * A field's border rect default height. - * @type {number} - */ - this.FIELD_BORDER_RECT_HEIGHT = 16; - - /** - * A field's border rect X padding. - * @type {number} - */ - this.FIELD_BORDER_RECT_X_PADDING = 5; - - /** - * A field's border rect Y padding. - * @type {number} - */ - this.FIELD_BORDER_RECT_Y_PADDING = 3; - - /** - * The backing colour of a field's border rect. - * @type {string} * @package */ - this.FIELD_BORDER_RECT_COLOUR = '#fff'; + constructor() { + /** + * The size of an empty spacer. + * @type {number} + */ + this.NO_PADDING = 0; - /** - * A field's text element's dominant baseline. - * @type {boolean} - */ - this.FIELD_TEXT_BASELINE_CENTER = !userAgent.IE && !userAgent.EDGE; + /** + * The size of small padding. + * @type {number} + */ + this.SMALL_PADDING = 3; - /** - * A dropdown field's border rect height. - * @type {number} - */ - this.FIELD_DROPDOWN_BORDER_RECT_HEIGHT = this.FIELD_BORDER_RECT_HEIGHT; + /** + * The size of medium padding. + * @type {number} + */ + this.MEDIUM_PADDING = 5; - /** - * Whether or not a dropdown field should add a border rect when in a shadow - * block. - * @type {boolean} - */ - this.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW = false; + /** + * The size of medium-large padding. + * @type {number} + */ + this.MEDIUM_LARGE_PADDING = 8; - /** - * Whether or not a dropdown field's div should be coloured to match the - * block colours. - * @type {boolean} - */ - this.FIELD_DROPDOWN_COLOURED_DIV = false; + /** + * The size of large padding. + * @type {number} + */ + this.LARGE_PADDING = 10; - /** - * Whether or not a dropdown field uses a text or SVG arrow. - * @type {boolean} - */ - this.FIELD_DROPDOWN_SVG_ARROW = false; + /** + * Offset from the top of the row for placing fields on inline input rows + * and statement input rows. + * Matches existing rendering (in 2019). + * @type {number} + */ + this.TALL_INPUT_FIELD_OFFSET_Y = this.MEDIUM_PADDING; - /** - * A dropdown field's SVG arrow padding. - * @type {number} - */ - this.FIELD_DROPDOWN_SVG_ARROW_PADDING = this.FIELD_BORDER_RECT_X_PADDING; + /** + * The height of the puzzle tab used for input and output connections. + * @type {number} + */ + this.TAB_HEIGHT = 15; - /** - * A dropdown field's SVG arrow size. - * @type {number} - */ - this.FIELD_DROPDOWN_SVG_ARROW_SIZE = 12; + /** + * The offset from the top of the block at which a puzzle tab is positioned. + * @type {number} + */ + this.TAB_OFFSET_FROM_TOP = 5; - /** - * A dropdown field's SVG arrow datauri. - * @type {string} - */ - this.FIELD_DROPDOWN_SVG_ARROW_DATAURI = - 'data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllci' + - 'AxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMi43MSIgaG' + - 'VpZ2h0PSI4Ljc5IiB2aWV3Qm94PSIwIDAgMTIuNzEgOC43OSI+PHRpdGxlPmRyb3Bkb3duLW' + - 'Fycm93PC90aXRsZT48ZyBvcGFjaXR5PSIwLjEiPjxwYXRoIGQ9Ik0xMi43MSwyLjQ0QTIuND' + - 'EsMi40MSwwLDAsMSwxMiw0LjE2TDguMDgsOC4wOGEyLjQ1LDIuNDUsMCwwLDEtMy40NSwwTD' + - 'AuNzIsNC4xNkEyLjQyLDIuNDIsMCwwLDEsMCwyLjQ0LDIuNDgsMi40OCwwLDAsMSwuNzEuNz' + - 'FDMSwwLjQ3LDEuNDMsMCw2LjM2LDBTMTEuNzUsMC40NiwxMiwuNzFBMi40NCwyLjQ0LDAsMC' + - 'wxLDEyLjcxLDIuNDRaIiBmaWxsPSIjMjMxZjIwIi8+PC9nPjxwYXRoIGQ9Ik02LjM2LDcuNz' + - 'lhMS40MywxLjQzLDAsMCwxLTEtLjQyTDEuNDIsMy40NWExLjQ0LDEuNDQsMCwwLDEsMC0yYz' + - 'AuNTYtLjU2LDkuMzEtMC41Niw5Ljg3LDBhMS40NCwxLjQ0LDAsMCwxLDAsMkw3LjM3LDcuMz' + - 'dBMS40MywxLjQzLDAsMCwxLDYuMzYsNy43OVoiIGZpbGw9IiNmZmYiLz48L3N2Zz4='; + /** + * Vertical overlap of the puzzle tab, used to make it look more like a + * puzzle piece. + * @type {number} + */ + this.TAB_VERTICAL_OVERLAP = 2.5; - /** - * Whether or not to show a box shadow around the widget div. This is only a - * feature of full block fields. - * @type {boolean} - */ - this.FIELD_TEXTINPUT_BOX_SHADOW = false; + /** + * The width of the puzzle tab used for input and output connections. + * @type {number} + */ + this.TAB_WIDTH = 8; - /** - * Whether or not the colour field should display its colour value on the - * entire block. - * @type {boolean} - */ - this.FIELD_COLOUR_FULL_BLOCK = false; + /** + * The width of the notch used for previous and next connections. + * @type {number} + */ + this.NOTCH_WIDTH = 15; - /** - * A colour field's default width. - * @type {number} - */ - this.FIELD_COLOUR_DEFAULT_WIDTH = 26; + /** + * The height of the notch used for previous and next connections. + * @type {number} + */ + this.NOTCH_HEIGHT = 4; - /** - * A colour field's default height. - * @type {number} - */ - this.FIELD_COLOUR_DEFAULT_HEIGHT = this.FIELD_BORDER_RECT_HEIGHT; + /** + * The minimum width of the block. + * @type {number} + */ + this.MIN_BLOCK_WIDTH = 12; - /** - * A checkbox field's X offset. - * @type {number} - */ - this.FIELD_CHECKBOX_X_OFFSET = this.FIELD_BORDER_RECT_X_PADDING - 3; + this.EMPTY_BLOCK_SPACER_HEIGHT = 16; - /** - * A random identifier used to ensure a unique ID is used for each - * filter/pattern for the case of multiple Blockly instances on a page. - * @type {string} - * @package - */ - this.randomIdentifier = String(Math.random()).substring(2); + /** + * The minimum height of a dummy input row. + * @type {number} + */ + this.DUMMY_INPUT_MIN_HEIGHT = this.TAB_HEIGHT; - /** - * The defs tag that contains all filters and patterns for this Blockly - * instance. - * @type {?SVGElement} - * @private - */ - this.defs_ = null; + /** + * The minimum height of a dummy input row in a shadow block. + * @type {number} + */ + this.DUMMY_INPUT_SHADOW_MIN_HEIGHT = this.TAB_HEIGHT; - /** - * The ID of the emboss filter, or the empty string if no filter is set. - * @type {string} - * @package - */ - this.embossFilterId = ''; + /** + * Rounded corner radius. + * @type {number} + */ + this.CORNER_RADIUS = 8; - /** - * The element to use for highlighting, or null if not set. - * @type {SVGElement} - * @private - */ - this.embossFilter_ = null; + /** + * Offset from the left side of a block or the inside of a statement input + * to the left side of the notch. + * @type {number} + */ + this.NOTCH_OFFSET_LEFT = 15; - /** - * The ID of the disabled pattern, or the empty string if no pattern is set. - * @type {string} - * @package - */ - this.disabledPatternId = ''; + /** + * Additional offset added to the statement input's width to account for the + * notch. + * @type {number} + */ + this.STATEMENT_INPUT_NOTCH_OFFSET = this.NOTCH_OFFSET_LEFT; - /** - * The element to use for disabled blocks, or null if not set. - * @type {SVGElement} - * @private - */ - this.disabledPattern_ = null; + this.STATEMENT_BOTTOM_SPACER = 0; + this.STATEMENT_INPUT_PADDING_LEFT = 20; - /** - * The ID of the debug filter, or the empty string if no pattern is set. - * @type {string} - * @package - */ - this.debugFilterId = ''; + /** + * Vertical padding between consecutive statement inputs. + * @type {number} + */ + this.BETWEEN_STATEMENT_PADDING_Y = 4; - /** - * The element to use for a debug highlight, or null if not set. - * @type {SVGElement} - * @private - */ - this.debugFilter_ = null; + /** + * The top row's minimum height. + * @type {number} + */ + this.TOP_ROW_MIN_HEIGHT = this.MEDIUM_PADDING; - /** - * The @@ -153,9 +153,9 @@ diff --git a/externs/svg-externs.js b/externs/svg-externs.js deleted file mode 100644 index e8a3326ab..000000000 --- a/externs/svg-externs.js +++ /dev/null @@ -1,10232 +0,0 @@ -/** - * Copyright 2017 The Closure Compiler Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview Generated by closureidl 0.2dev closureidl-parser-webkit 0.2dev dart-idl 7592. - * Parser: type=webkit dir=${WEBKIT}/Source/WebCore/_/_.idl dir=${WEBKIT}/Source/WebCore/_/_/_.idl dir=${WEBKIT}/Source/WebCore/Modules/_/_.idl - * Context0: id=webkit - * Filter0: annotate-platform platform=webkit - * Filter1: merge-supplemental - * Filter2: fix-nulls - * Filter3: fix-constructors - * Filter4: rename-interfaces - * Filter5: check-consistency - * Context1: id=closure - * Filter0: import context=webkit - * Filter1: closure:merge-overloaded-methods - * Filter2: closure:fix-callbacks remove=false - * Filter3: closure:fix-arrays remove=simple - * Filter4: closure:fix-optionals - * Filter5: closure:rename-reserved-params - * Filter6: closure:guess-interfaces - * Filter7: closure:expand-interfaces - * Output: format=js - * - * @externs - */ - - - -/** - * @constructor - */ -function SVGAnimatedNumberList(){} - - -/** - * @type {!SVGNumberList} - */ -SVGAnimatedNumberList.prototype.baseVal; - - -/** - * @type {!SVGNumberList} - */ -SVGAnimatedNumberList.prototype.animVal; - - - -/** - * @constructor - */ -function SVGLengthList(){} - - -/** - * @type {number} - */ -SVGLengthList.prototype.numberOfItems; - - -/** */ -SVGLengthList.prototype.clear = function(){}; - - -/** - * @param {!SVGLength} item - * @return {!SVGLength} - */ -SVGLengthList.prototype.initialize = function(item){}; - - -/** - * @param {number} index - * @return {!SVGLength} - */ -SVGLengthList.prototype.getItem = function(index){}; - - -/** - * @param {!SVGLength} item - * @param {number} index - * @return {!SVGLength} - */ -SVGLengthList.prototype.insertItemBefore = function(item, index){}; - - -/** - * @param {!SVGLength} item - * @param {number} index - * @return {!SVGLength} - */ -SVGLengthList.prototype.replaceItem = function(item, index){}; - - -/** - * @param {number} index - * @return {!SVGLength} - */ -SVGLengthList.prototype.removeItem = function(index){}; - - -/** - * @param {!SVGLength} item - * @return {!SVGLength} - */ -SVGLengthList.prototype.appendItem = function(item){}; - - - -/** - * @constructor - * @implements {EventTarget} - */ -function SVGElementInstance(){} - - -/** - * @type {!SVGElement} - */ -SVGElementInstance.prototype.correspondingElement; - - -/** - * @type {!SVGUseElement} - */ -SVGElementInstance.prototype.correspondingUseElement; - - -/** - * @type {!SVGElementInstance} - */ -SVGElementInstance.prototype.parentNode; - - -/** - * @type {!Array|!SVGElementInstanceList} - */ -SVGElementInstance.prototype.childNodes; - - -/** - * @type {!SVGElementInstance} - */ -SVGElementInstance.prototype.firstChild; - - -/** - * @type {!SVGElementInstance} - */ -SVGElementInstance.prototype.lastChild; - - -/** - * @type {!SVGElementInstance} - */ -SVGElementInstance.prototype.previousSibling; - - -/** - * @type {!SVGElementInstance} - */ -SVGElementInstance.prototype.nextSibling; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.onabort; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.onblur; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.onchange; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.onclick; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.oncontextmenu; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.ondblclick; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.onerror; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.onfocus; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.oninput; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.onkeydown; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.onkeypress; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.onkeyup; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.onload; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.onmousedown; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.onmousemove; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.onmouseout; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.onmouseover; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.onmouseup; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.onmousewheel; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.onbeforecut; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.oncut; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.onbeforecopy; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.oncopy; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.onbeforepaste; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.onpaste; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.ondragenter; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.ondragover; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.ondragleave; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.ondrop; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.ondragstart; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.ondrag; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.ondragend; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.onreset; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.onresize; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.onscroll; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.onsearch; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.onselect; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.onselectstart; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.onsubmit; - - -/** - * @type {!EventListener|function(!Event)} - */ -SVGElementInstance.prototype.onunload; - - -/** - * @override - */ -SVGElementInstance.prototype.addEventListener = function(type, listener, opt_useCapture){}; - - -/** - * @override - */ -SVGElementInstance.prototype.removeEventListener = function(type, listener, opt_useCapture){}; - - -/** - * @param {!Event} event - * @return {boolean} - * @override - */ -SVGElementInstance.prototype.dispatchEvent = function(event){}; - - - -/** - * @constructor - */ -function SVGAnimatedRect(){} - - -/** - * @type {!SVGRect} - */ -SVGAnimatedRect.prototype.baseVal; - - -/** - * @type {!SVGRect} - */ -SVGAnimatedRect.prototype.animVal; - - - -/** - * @constructor - */ -function SVGAnimatedString(){} - - -/** - * @type {string} - */ -SVGAnimatedString.prototype.baseVal; - - -/** - * @type {string} - */ -SVGAnimatedString.prototype.animVal; - - - -/** - * @interface - */ -function SVGStylable(){} - - -/** - * @type {!SVGAnimatedString} - */ -SVGStylable.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - */ -SVGStylable.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @interface - */ -function SVGExternalResourcesRequired(){} - - -/** - * @type {!SVGAnimatedBoolean} - */ -SVGExternalResourcesRequired.prototype.externalResourcesRequired; - - - -/** - * @constructor - */ -function SVGPoint(){} - - -/** - * @type {number} - */ -SVGPoint.prototype.x; - - -/** - * @type {number} - */ -SVGPoint.prototype.y; - - -/** - * @param {!SVGMatrix} matrix - * @return {!SVGPoint} - */ -SVGPoint.prototype.matrixTransform = function(matrix){}; - - - -/** - * @constructor - */ -function SVGMatrix(){} - - -/** - * @type {number} - */ -SVGMatrix.prototype.a; - - -/** - * @type {number} - */ -SVGMatrix.prototype.b; - - -/** - * @type {number} - */ -SVGMatrix.prototype.c; - - -/** - * @type {number} - */ -SVGMatrix.prototype.d; - - -/** - * @type {number} - */ -SVGMatrix.prototype.e; - - -/** - * @type {number} - */ -SVGMatrix.prototype.f; - - -/** - * @param {!SVGMatrix} secondMatrix - * @return {!SVGMatrix} - */ -SVGMatrix.prototype.multiply = function(secondMatrix){}; - - -/** - * @return {!SVGMatrix} - */ -SVGMatrix.prototype.inverse = function(){}; - - -/** - * @param {number} x - * @param {number} y - * @return {!SVGMatrix} - */ -SVGMatrix.prototype.translate = function(x, y){}; - - -/** - * @param {number} scaleFactor - * @return {!SVGMatrix} - */ -SVGMatrix.prototype.scale = function(scaleFactor){}; - - -/** - * @param {number} scaleFactorX - * @param {number} scaleFactorY - * @return {!SVGMatrix} - */ -SVGMatrix.prototype.scaleNonUniform = function(scaleFactorX, scaleFactorY){}; - - -/** - * @param {number} angle - * @return {!SVGMatrix} - */ -SVGMatrix.prototype.rotate = function(angle){}; - - -/** - * @param {number} x - * @param {number} y - * @return {!SVGMatrix} - */ -SVGMatrix.prototype.rotateFromVector = function(x, y){}; - - -/** - * @return {!SVGMatrix} - */ -SVGMatrix.prototype.flipX = function(){}; - - -/** - * @return {!SVGMatrix} - */ -SVGMatrix.prototype.flipY = function(){}; - - -/** - * @param {number} angle - * @return {!SVGMatrix} - */ -SVGMatrix.prototype.skewX = function(angle){}; - - -/** - * @param {number} angle - * @return {!SVGMatrix} - */ -SVGMatrix.prototype.skewY = function(angle){}; - - - -/** - * @constructor - */ -function SVGNumberList(){} - - -/** - * @type {number} - */ -SVGNumberList.prototype.numberOfItems; - - -/** */ -SVGNumberList.prototype.clear = function(){}; - - -/** - * @param {!SVGNumber} item - * @return {!SVGNumber} - */ -SVGNumberList.prototype.initialize = function(item){}; - - -/** - * @param {number} index - * @return {!SVGNumber} - */ -SVGNumberList.prototype.getItem = function(index){}; - - -/** - * @param {!SVGNumber} item - * @param {number} index - * @return {!SVGNumber} - */ -SVGNumberList.prototype.insertItemBefore = function(item, index){}; - - -/** - * @param {!SVGNumber} item - * @param {number} index - * @return {!SVGNumber} - */ -SVGNumberList.prototype.replaceItem = function(item, index){}; - - -/** - * @param {number} index - * @return {!SVGNumber} - */ -SVGNumberList.prototype.removeItem = function(index){}; - - -/** - * @param {!SVGNumber} item - * @return {!SVGNumber} - */ -SVGNumberList.prototype.appendItem = function(item){}; - - - -/** - * @interface - */ -function SVGLangSpace(){} - - -/** - * @type {string} - */ -SVGLangSpace.prototype.xmllang; - - -/** - * @type {string} - */ -SVGLangSpace.prototype.xmlspace; - - - -/** - * @constructor - */ -function SVGAnimatedLengthList(){} - - -/** - * @type {!SVGLengthList} - */ -SVGAnimatedLengthList.prototype.baseVal; - - -/** - * @type {!SVGLengthList} - */ -SVGAnimatedLengthList.prototype.animVal; - - - -/** - * @constructor - */ -function SVGAnimatedTransformList(){} - - -/** - * @type {!SVGTransformList} - */ -SVGAnimatedTransformList.prototype.baseVal; - - -/** - * @type {!SVGTransformList} - */ -SVGAnimatedTransformList.prototype.animVal; - - - -/** - * @constructor - */ -function SVGUnitTypes(){} - - -/** - * @const - * @type {number} - */ -SVGUnitTypes.SVG_UNIT_TYPE_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGUnitTypes.prototype.SVG_UNIT_TYPE_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGUnitTypes.SVG_UNIT_TYPE_USERSPACEONUSE; - - -/** - * @const - * @type {number} - */ -SVGUnitTypes.prototype.SVG_UNIT_TYPE_USERSPACEONUSE; - - -/** - * @const - * @type {number} - */ -SVGUnitTypes.SVG_UNIT_TYPE_OBJECTBOUNDINGBOX; - - -/** - * @const - * @type {number} - */ -SVGUnitTypes.prototype.SVG_UNIT_TYPE_OBJECTBOUNDINGBOX; - - - -/** - * @constructor - */ -function SVGLength(){} - - -/** - * @const - * @type {number} - */ -SVGLength.SVG_LENGTHTYPE_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGLength.prototype.SVG_LENGTHTYPE_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGLength.SVG_LENGTHTYPE_NUMBER; - - -/** - * @const - * @type {number} - */ -SVGLength.prototype.SVG_LENGTHTYPE_NUMBER; - - -/** - * @const - * @type {number} - */ -SVGLength.SVG_LENGTHTYPE_PERCENTAGE; - - -/** - * @const - * @type {number} - */ -SVGLength.prototype.SVG_LENGTHTYPE_PERCENTAGE; - - -/** - * @const - * @type {number} - */ -SVGLength.SVG_LENGTHTYPE_EMS; - - -/** - * @const - * @type {number} - */ -SVGLength.prototype.SVG_LENGTHTYPE_EMS; - - -/** - * @const - * @type {number} - */ -SVGLength.SVG_LENGTHTYPE_EXS; - - -/** - * @const - * @type {number} - */ -SVGLength.prototype.SVG_LENGTHTYPE_EXS; - - -/** - * @const - * @type {number} - */ -SVGLength.SVG_LENGTHTYPE_PX; - - -/** - * @const - * @type {number} - */ -SVGLength.prototype.SVG_LENGTHTYPE_PX; - - -/** - * @const - * @type {number} - */ -SVGLength.SVG_LENGTHTYPE_CM; - - -/** - * @const - * @type {number} - */ -SVGLength.prototype.SVG_LENGTHTYPE_CM; - - -/** - * @const - * @type {number} - */ -SVGLength.SVG_LENGTHTYPE_MM; - - -/** - * @const - * @type {number} - */ -SVGLength.prototype.SVG_LENGTHTYPE_MM; - - -/** - * @const - * @type {number} - */ -SVGLength.SVG_LENGTHTYPE_IN; - - -/** - * @const - * @type {number} - */ -SVGLength.prototype.SVG_LENGTHTYPE_IN; - - -/** - * @const - * @type {number} - */ -SVGLength.SVG_LENGTHTYPE_PT; - - -/** - * @const - * @type {number} - */ -SVGLength.prototype.SVG_LENGTHTYPE_PT; - - -/** - * @const - * @type {number} - */ -SVGLength.SVG_LENGTHTYPE_PC; - - -/** - * @const - * @type {number} - */ -SVGLength.prototype.SVG_LENGTHTYPE_PC; - - -/** - * @type {number} - */ -SVGLength.prototype.unitType; - - -/** - * @type {number} - */ -SVGLength.prototype.value; - - -/** - * @type {number} - */ -SVGLength.prototype.valueInSpecifiedUnits; - - -/** - * @type {string} - */ -SVGLength.prototype.valueAsString; - - -/** - * @param {number} unitType - * @param {number} valueInSpecifiedUnits - * @return {undefined} - */ -SVGLength.prototype.newValueSpecifiedUnits = function(unitType, valueInSpecifiedUnits){}; - - -/** - * @param {number} unitType - * @return {undefined} - */ -SVGLength.prototype.convertToSpecifiedUnits = function(unitType){}; - - - -/** - * @constructor - */ -function SVGAnimatedNumber(){} - - -/** - * @type {number} - */ -SVGAnimatedNumber.prototype.baseVal; - - -/** - * @type {number} - */ -SVGAnimatedNumber.prototype.animVal; - - - -/** - * @constructor - */ -function SVGAnimatedAngle(){} - - -/** - * @type {!SVGAngle} - */ -SVGAnimatedAngle.prototype.baseVal; - - -/** - * @type {!SVGAngle} - */ -SVGAnimatedAngle.prototype.animVal; - - - -/** - * @interface - * @extends {SVGStylable} - */ -function SVGFilterPrimitiveStandardAttributes(){} - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFilterPrimitiveStandardAttributes.prototype.x; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFilterPrimitiveStandardAttributes.prototype.y; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFilterPrimitiveStandardAttributes.prototype.width; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFilterPrimitiveStandardAttributes.prototype.height; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFilterPrimitiveStandardAttributes.prototype.result; - - - -/** - * @constructor - * @extends {SVGGradientElement} - */ -function SVGLinearGradientElement(){} - - -/** - * @type {!SVGAnimatedLength} - */ -SVGLinearGradientElement.prototype.x1; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGLinearGradientElement.prototype.y1; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGLinearGradientElement.prototype.x2; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGLinearGradientElement.prototype.y2; - - - -/** - * @constructor - * @extends {SVGGradientElement} - */ -function SVGRadialGradientElement(){} - - -/** - * @type {!SVGAnimatedLength} - */ -SVGRadialGradientElement.prototype.cx; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGRadialGradientElement.prototype.cy; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGRadialGradientElement.prototype.r; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGRadialGradientElement.prototype.fx; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGRadialGradientElement.prototype.fy; - - - -/** - * @constructor - */ -function SVGTransform(){} - - -/** - * @const - * @type {number} - */ -SVGTransform.SVG_TRANSFORM_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGTransform.prototype.SVG_TRANSFORM_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGTransform.SVG_TRANSFORM_MATRIX; - - -/** - * @const - * @type {number} - */ -SVGTransform.prototype.SVG_TRANSFORM_MATRIX; - - -/** - * @const - * @type {number} - */ -SVGTransform.SVG_TRANSFORM_TRANSLATE; - - -/** - * @const - * @type {number} - */ -SVGTransform.prototype.SVG_TRANSFORM_TRANSLATE; - - -/** - * @const - * @type {number} - */ -SVGTransform.SVG_TRANSFORM_SCALE; - - -/** - * @const - * @type {number} - */ -SVGTransform.prototype.SVG_TRANSFORM_SCALE; - - -/** - * @const - * @type {number} - */ -SVGTransform.SVG_TRANSFORM_ROTATE; - - -/** - * @const - * @type {number} - */ -SVGTransform.prototype.SVG_TRANSFORM_ROTATE; - - -/** - * @const - * @type {number} - */ -SVGTransform.SVG_TRANSFORM_SKEWX; - - -/** - * @const - * @type {number} - */ -SVGTransform.prototype.SVG_TRANSFORM_SKEWX; - - -/** - * @const - * @type {number} - */ -SVGTransform.SVG_TRANSFORM_SKEWY; - - -/** - * @const - * @type {number} - */ -SVGTransform.prototype.SVG_TRANSFORM_SKEWY; - - -/** - * @type {number} - */ -SVGTransform.prototype.type; - - -/** - * @type {!SVGMatrix} - */ -SVGTransform.prototype.matrix; - - -/** - * @type {number} - */ -SVGTransform.prototype.angle; - - -/** - * @param {!SVGMatrix} matrix - * @return {undefined} - */ -SVGTransform.prototype.setMatrix = function(matrix){}; - - -/** - * @param {number} tx - * @param {number} ty - * @return {undefined} - */ -SVGTransform.prototype.setTranslate = function(tx, ty){}; - - -/** - * @param {number} sx - * @param {number} sy - * @return {undefined} - */ -SVGTransform.prototype.setScale = function(sx, sy){}; - - -/** - * @param {number} angle - * @param {number} cx - * @param {number} cy - * @return {undefined} - */ -SVGTransform.prototype.setRotate = function(angle, cx, cy){}; - - -/** - * @param {number} angle - * @return {undefined} - */ -SVGTransform.prototype.setSkewX = function(angle){}; - - -/** - * @param {number} angle - * @return {undefined} - */ -SVGTransform.prototype.setSkewY = function(angle){}; - - - -/** - * @interface - */ -function SVGZoomAndPan(){} - -/** - * @type {number} - */ -SVGZoomAndPan.prototype.zoomAndPan; - - - -/** - * @constructor - */ -function SVGPreserveAspectRatio(){} - - -/** - * @const - * @type {number} - */ -SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGPreserveAspectRatio.prototype.SVG_PRESERVEASPECTRATIO_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_NONE; - - -/** - * @const - * @type {number} - */ -SVGPreserveAspectRatio.prototype.SVG_PRESERVEASPECTRATIO_NONE; - - -/** - * @const - * @type {number} - */ -SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMINYMIN; - - -/** - * @const - * @type {number} - */ -SVGPreserveAspectRatio.prototype.SVG_PRESERVEASPECTRATIO_XMINYMIN; - - -/** - * @const - * @type {number} - */ -SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMIDYMIN; - - -/** - * @const - * @type {number} - */ -SVGPreserveAspectRatio.prototype.SVG_PRESERVEASPECTRATIO_XMIDYMIN; - - -/** - * @const - * @type {number} - */ -SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMIN; - - -/** - * @const - * @type {number} - */ -SVGPreserveAspectRatio.prototype.SVG_PRESERVEASPECTRATIO_XMAXYMIN; - - -/** - * @const - * @type {number} - */ -SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMINYMID; - - -/** - * @const - * @type {number} - */ -SVGPreserveAspectRatio.prototype.SVG_PRESERVEASPECTRATIO_XMINYMID; - - -/** - * @const - * @type {number} - */ -SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMIDYMID; - - -/** - * @const - * @type {number} - */ -SVGPreserveAspectRatio.prototype.SVG_PRESERVEASPECTRATIO_XMIDYMID; - - -/** - * @const - * @type {number} - */ -SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMID; - - -/** - * @const - * @type {number} - */ -SVGPreserveAspectRatio.prototype.SVG_PRESERVEASPECTRATIO_XMAXYMID; - - -/** - * @const - * @type {number} - */ -SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMINYMAX; - - -/** - * @const - * @type {number} - */ -SVGPreserveAspectRatio.prototype.SVG_PRESERVEASPECTRATIO_XMINYMAX; - - -/** - * @const - * @type {number} - */ -SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMIDYMAX; - - -/** - * @const - * @type {number} - */ -SVGPreserveAspectRatio.prototype.SVG_PRESERVEASPECTRATIO_XMIDYMAX; - - -/** - * @const - * @type {number} - */ -SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMAX; - - -/** - * @const - * @type {number} - */ -SVGPreserveAspectRatio.prototype.SVG_PRESERVEASPECTRATIO_XMAXYMAX; - - -/** - * @const - * @type {number} - */ -SVGPreserveAspectRatio.SVG_MEETORSLICE_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGPreserveAspectRatio.prototype.SVG_MEETORSLICE_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGPreserveAspectRatio.SVG_MEETORSLICE_MEET; - - -/** - * @const - * @type {number} - */ -SVGPreserveAspectRatio.prototype.SVG_MEETORSLICE_MEET; - - -/** - * @const - * @type {number} - */ -SVGPreserveAspectRatio.SVG_MEETORSLICE_SLICE; - - -/** - * @const - * @type {number} - */ -SVGPreserveAspectRatio.prototype.SVG_MEETORSLICE_SLICE; - - -/** - * @type {number} - */ -SVGPreserveAspectRatio.prototype.align; - - -/** - * @type {number} - */ -SVGPreserveAspectRatio.prototype.meetOrSlice; - - - -/** - * @constructor - */ -function SVGRect(){} - - -/** - * @type {number} - */ -SVGRect.prototype.x; - - -/** - * @type {number} - */ -SVGRect.prototype.y; - - -/** - * @type {number} - */ -SVGRect.prototype.width; - - -/** - * @type {number} - */ -SVGRect.prototype.height; - - - -/** - * @constructor - */ -function SVGException(){} - - -/** - * @const - * @type {number} - */ -SVGException.SVG_WRONG_TYPE_ERR; - - -/** - * @const - * @type {number} - */ -SVGException.prototype.SVG_WRONG_TYPE_ERR; - - -/** - * @const - * @type {number} - */ -SVGException.SVG_INVALID_VALUE_ERR; - - -/** - * @const - * @type {number} - */ -SVGException.prototype.SVG_INVALID_VALUE_ERR; - - -/** - * @const - * @type {number} - */ -SVGException.SVG_MATRIX_NOT_INVERTABLE; - - -/** - * @const - * @type {number} - */ -SVGException.prototype.SVG_MATRIX_NOT_INVERTABLE; - - -/** - * @type {number} - */ -SVGException.prototype.code; - - -/** - * @type {string} - */ -SVGException.prototype.name; - - -/** - * @type {string} - */ -SVGException.prototype.message; - - -/** - * @return {string} - * @override - */ -SVGException.prototype.toString = function(){}; - - - -/** - * @constructor - * @extends {CSSValue} - */ -function SVGColor(){} - - -/** - * @const - * @type {number} - */ -SVGColor.SVG_COLORTYPE_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGColor.prototype.SVG_COLORTYPE_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGColor.SVG_COLORTYPE_RGBCOLOR; - - -/** - * @const - * @type {number} - */ -SVGColor.prototype.SVG_COLORTYPE_RGBCOLOR; - - -/** - * @const - * @type {number} - */ -SVGColor.SVG_COLORTYPE_RGBCOLOR_ICCCOLOR; - - -/** - * @const - * @type {number} - */ -SVGColor.prototype.SVG_COLORTYPE_RGBCOLOR_ICCCOLOR; - - -/** - * @const - * @type {number} - */ -SVGColor.SVG_COLORTYPE_CURRENTCOLOR; - - -/** - * @const - * @type {number} - */ -SVGColor.prototype.SVG_COLORTYPE_CURRENTCOLOR; - - -/** - * @type {number} - */ -SVGColor.prototype.colorType; - - -/** - * @type {!RGBColor} - */ -SVGColor.prototype.rgbColor; - - -/** - * @param {string} rgbColor - * @return {undefined} - */ -SVGColor.prototype.setRGBColor = function(rgbColor){}; - - -/** - * @param {string} rgbColor - * @param {string} iccColor - * @return {undefined} - */ -SVGColor.prototype.setRGBColorICCColor = function(rgbColor, iccColor){}; - - -/** - * @param {number} colorType - * @param {string} rgbColor - * @param {string} iccColor - * @return {undefined} - */ -SVGColor.prototype.setColor = function(colorType, rgbColor, iccColor){}; - - - -/** - * @constructor - * @extends {SVGColor} - */ -function SVGPaint(){} - - -/** - * @const - * @type {number} - */ -SVGPaint.SVG_PAINTTYPE_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGPaint.prototype.SVG_PAINTTYPE_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGPaint.SVG_PAINTTYPE_RGBCOLOR; - - -/** - * @const - * @type {number} - */ -SVGPaint.prototype.SVG_PAINTTYPE_RGBCOLOR; - - -/** - * @const - * @type {number} - */ -SVGPaint.SVG_PAINTTYPE_RGBCOLOR_ICCCOLOR; - - -/** - * @const - * @type {number} - */ -SVGPaint.prototype.SVG_PAINTTYPE_RGBCOLOR_ICCCOLOR; - - -/** - * @const - * @type {number} - */ -SVGPaint.SVG_PAINTTYPE_NONE; - - -/** - * @const - * @type {number} - */ -SVGPaint.prototype.SVG_PAINTTYPE_NONE; - - -/** - * @const - * @type {number} - */ -SVGPaint.SVG_PAINTTYPE_CURRENTCOLOR; - - -/** - * @const - * @type {number} - */ -SVGPaint.prototype.SVG_PAINTTYPE_CURRENTCOLOR; - - -/** - * @const - * @type {number} - */ -SVGPaint.SVG_PAINTTYPE_URI_NONE; - - -/** - * @const - * @type {number} - */ -SVGPaint.prototype.SVG_PAINTTYPE_URI_NONE; - - -/** - * @const - * @type {number} - */ -SVGPaint.SVG_PAINTTYPE_URI_CURRENTCOLOR; - - -/** - * @const - * @type {number} - */ -SVGPaint.prototype.SVG_PAINTTYPE_URI_CURRENTCOLOR; - - -/** - * @const - * @type {number} - */ -SVGPaint.SVG_PAINTTYPE_URI_RGBCOLOR; - - -/** - * @const - * @type {number} - */ -SVGPaint.prototype.SVG_PAINTTYPE_URI_RGBCOLOR; - - -/** - * @const - * @type {number} - */ -SVGPaint.SVG_PAINTTYPE_URI_RGBCOLOR_ICCCOLOR; - - -/** - * @const - * @type {number} - */ -SVGPaint.prototype.SVG_PAINTTYPE_URI_RGBCOLOR_ICCCOLOR; - - -/** - * @const - * @type {number} - */ -SVGPaint.SVG_PAINTTYPE_URI; - - -/** - * @const - * @type {number} - */ -SVGPaint.prototype.SVG_PAINTTYPE_URI; - - -/** - * @type {number} - */ -SVGPaint.prototype.paintType; - - -/** - * @type {string} - */ -SVGPaint.prototype.uri; - - -/** - * @param {string} uri - * @return {undefined} - */ -SVGPaint.prototype.setUri = function(uri){}; - - -/** - * @param {number} paintType - * @param {string} uri - * @param {string} rgbColor - * @param {string} iccColor - * @return {undefined} - */ -SVGPaint.prototype.setPaint = function(paintType, uri, rgbColor, iccColor){}; - - - -/** - * @constructor - */ -function SVGPointList(){} - - -/** - * @type {number} - */ -SVGPointList.prototype.numberOfItems; - - -/** */ -SVGPointList.prototype.clear = function(){}; - - -/** - * @param {!SVGPoint} item - * @return {!SVGPoint} - */ -SVGPointList.prototype.initialize = function(item){}; - - -/** - * @param {number} index - * @return {!SVGPoint} - */ -SVGPointList.prototype.getItem = function(index){}; - - -/** - * @param {!SVGPoint} item - * @param {number} index - * @return {!SVGPoint} - */ -SVGPointList.prototype.insertItemBefore = function(item, index){}; - - -/** - * @param {!SVGPoint} item - * @param {number} index - * @return {!SVGPoint} - */ -SVGPointList.prototype.replaceItem = function(item, index){}; - - -/** - * @param {number} index - * @return {!SVGPoint} - */ -SVGPointList.prototype.removeItem = function(index){}; - - -/** - * @param {!SVGPoint} item - * @return {!SVGPoint} - */ -SVGPointList.prototype.appendItem = function(item){}; - - - -/** - * @constructor - */ -function SVGTransformList(){} - - -/** - * @type {number} - */ -SVGTransformList.prototype.numberOfItems; - - -/** */ -SVGTransformList.prototype.clear = function(){}; - - -/** - * @param {!SVGTransform} item - * @return {!SVGTransform} - */ -SVGTransformList.prototype.initialize = function(item){}; - - -/** - * @param {number} index - * @return {!SVGTransform} - */ -SVGTransformList.prototype.getItem = function(index){}; - - -/** - * @param {!SVGTransform} item - * @param {number} index - * @return {!SVGTransform} - */ -SVGTransformList.prototype.insertItemBefore = function(item, index){}; - - -/** - * @param {!SVGTransform} item - * @param {number} index - * @return {!SVGTransform} - */ -SVGTransformList.prototype.replaceItem = function(item, index){}; - - -/** - * @param {number} index - * @return {!SVGTransform} - */ -SVGTransformList.prototype.removeItem = function(index){}; - - -/** - * @param {!SVGTransform} item - * @return {!SVGTransform} - */ -SVGTransformList.prototype.appendItem = function(item){}; - - -/** - * @param {!SVGMatrix} matrix - * @return {!SVGTransform} - */ -SVGTransformList.prototype.createSVGTransformFromMatrix = function(matrix){}; - - -/** - * @return {!SVGTransform} - */ -SVGTransformList.prototype.consolidate = function(){}; - - - -/** - * @constructor - * @extends {UIEvent} - */ -function SVGZoomEvent(){} - - -/** - * @type {!SVGRect} - */ -SVGZoomEvent.prototype.zoomRectScreen; - - -/** - * @type {number} - */ -SVGZoomEvent.prototype.previousScale; - - -/** - * @type {!SVGPoint} - */ -SVGZoomEvent.prototype.previousTranslate; - - -/** - * @type {number} - */ -SVGZoomEvent.prototype.newScale; - - -/** - * @type {!SVGPoint} - */ -SVGZoomEvent.prototype.newTranslate; - - - -/** - * @constructor - * @extends {SVGTextContentElement} - */ -function SVGTextPositioningElement(){} - - -/** - * @type {!SVGAnimatedLengthList} - */ -SVGTextPositioningElement.prototype.x; - - -/** - * @type {!SVGAnimatedLengthList} - */ -SVGTextPositioningElement.prototype.y; - - -/** - * @type {!SVGAnimatedLengthList} - */ -SVGTextPositioningElement.prototype.dx; - - -/** - * @type {!SVGAnimatedLengthList} - */ -SVGTextPositioningElement.prototype.dy; - - -/** - * @type {!SVGAnimatedNumberList} - */ -SVGTextPositioningElement.prototype.rotate; - - - -/** - * @constructor - * @extends {SVGTextPositioningElement} - */ -function SVGTSpanElement(){} - - - -/** - * @constructor - */ -function SVGStringList(){} - - -/** - * @type {number} - */ -SVGStringList.prototype.numberOfItems; - - -/** */ -SVGStringList.prototype.clear = function(){}; - - -/** - * @param {string} item - * @return {string} - */ -SVGStringList.prototype.initialize = function(item){}; - - -/** - * @param {number} index - * @return {string} - */ -SVGStringList.prototype.getItem = function(index){}; - - -/** - * @param {string} item - * @param {number} index - * @return {string} - */ -SVGStringList.prototype.insertItemBefore = function(item, index){}; - - -/** - * @param {string} item - * @param {number} index - * @return {string} - */ -SVGStringList.prototype.replaceItem = function(item, index){}; - - -/** - * @param {number} index - * @return {string} - */ -SVGStringList.prototype.removeItem = function(index){}; - - -/** - * @param {string} item - * @return {string} - */ -SVGStringList.prototype.appendItem = function(item){}; - - - -/** - * @interface - */ -function SVGURIReference(){} - - -/** - * @type {!SVGAnimatedString} - */ -SVGURIReference.prototype.href; - - - -/** - * @constructor - * @extends {SVGTextPositioningElement} - * @implements {SVGURIReference} - */ -function SVGTRefElement(){} - - -/** - * @type {!SVGAnimatedString} - */ -SVGTRefElement.prototype.href; - - - -/** - * @constructor - * @extends {SVGTextContentElement} - * @implements {SVGURIReference} - */ -function SVGTextPathElement(){} - - -/** - * @const - * @type {number} - */ -SVGTextPathElement.TEXTPATH_METHODTYPE_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGTextPathElement.prototype.TEXTPATH_METHODTYPE_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGTextPathElement.TEXTPATH_METHODTYPE_ALIGN; - - -/** - * @const - * @type {number} - */ -SVGTextPathElement.prototype.TEXTPATH_METHODTYPE_ALIGN; - - -/** - * @const - * @type {number} - */ -SVGTextPathElement.TEXTPATH_METHODTYPE_STRETCH; - - -/** - * @const - * @type {number} - */ -SVGTextPathElement.prototype.TEXTPATH_METHODTYPE_STRETCH; - - -/** - * @const - * @type {number} - */ -SVGTextPathElement.TEXTPATH_SPACINGTYPE_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGTextPathElement.prototype.TEXTPATH_SPACINGTYPE_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGTextPathElement.TEXTPATH_SPACINGTYPE_AUTO; - - -/** - * @const - * @type {number} - */ -SVGTextPathElement.prototype.TEXTPATH_SPACINGTYPE_AUTO; - - -/** - * @const - * @type {number} - */ -SVGTextPathElement.TEXTPATH_SPACINGTYPE_EXACT; - - -/** - * @const - * @type {number} - */ -SVGTextPathElement.prototype.TEXTPATH_SPACINGTYPE_EXACT; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGTextPathElement.prototype.startOffset; - - -/** - * @type {!SVGAnimatedEnumeration} - */ -SVGTextPathElement.prototype.method; - - -/** - * @type {!SVGAnimatedEnumeration} - */ -SVGTextPathElement.prototype.spacing; - - -/** - * @type {!SVGAnimatedString} - */ -SVGTextPathElement.prototype.href; - - - -/** - * @constructor - * @extends {SVGTextPositioningElement} - * @implements {SVGURIReference} - */ -function SVGAltGlyphElement(){} - - -/** - * @type {string} - */ -SVGAltGlyphElement.prototype.glyphRef; - - -/** - * @type {string} - */ -SVGAltGlyphElement.prototype.format; - - -/** - * @type {!SVGAnimatedString} - */ -SVGAltGlyphElement.prototype.href; - - - -/** - * @constructor - * @extends {SVGComponentTransferFunctionElement} - */ -function SVGFEFuncGElement(){} - - - -/** - * @constructor - * @extends {SVGComponentTransferFunctionElement} - */ -function SVGFEFuncAElement(){} - - - -/** - * @constructor - * @extends {SVGComponentTransferFunctionElement} - */ -function SVGFEFuncRElement(){} - - - -/** - * @constructor - */ -function SVGNumber(){} - - -/** - * @type {number} - */ -SVGNumber.prototype.value; - - - -/** - * @interface - */ -function SVGTests(){} - - -/** - * @type {!SVGStringList} - */ -SVGTests.prototype.requiredFeatures; - - -/** - * @type {!SVGStringList} - */ -SVGTests.prototype.requiredExtensions; - - -/** - * @type {!SVGStringList} - */ -SVGTests.prototype.systemLanguage; - - -/** - * @param {string=} opt_extension - * @return {boolean} - */ -SVGTests.prototype.hasExtension = function(opt_extension){}; - - - -/** - * @constructor - */ -function SVGPathSeg(){} - - -/** - * @const - * @type {number} - */ -SVGPathSeg.PATHSEG_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.prototype.PATHSEG_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.PATHSEG_CLOSEPATH; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.prototype.PATHSEG_CLOSEPATH; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.PATHSEG_MOVETO_ABS; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.prototype.PATHSEG_MOVETO_ABS; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.PATHSEG_MOVETO_REL; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.prototype.PATHSEG_MOVETO_REL; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.PATHSEG_LINETO_ABS; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.prototype.PATHSEG_LINETO_ABS; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.PATHSEG_LINETO_REL; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.prototype.PATHSEG_LINETO_REL; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.prototype.PATHSEG_CURVETO_CUBIC_ABS; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.prototype.PATHSEG_CURVETO_CUBIC_REL; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.prototype.PATHSEG_CURVETO_QUADRATIC_ABS; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.prototype.PATHSEG_CURVETO_QUADRATIC_REL; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.PATHSEG_ARC_ABS; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.prototype.PATHSEG_ARC_ABS; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.PATHSEG_ARC_REL; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.prototype.PATHSEG_ARC_REL; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.prototype.PATHSEG_LINETO_HORIZONTAL_ABS; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.prototype.PATHSEG_LINETO_HORIZONTAL_REL; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.prototype.PATHSEG_LINETO_VERTICAL_ABS; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.prototype.PATHSEG_LINETO_VERTICAL_REL; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.prototype.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.prototype.PATHSEG_CURVETO_CUBIC_SMOOTH_REL; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.prototype.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL; - - -/** - * @const - * @type {number} - */ -SVGPathSeg.prototype.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL; - - -/** - * @type {number} - */ -SVGPathSeg.prototype.pathSegType; - - -/** - * @type {string} - */ -SVGPathSeg.prototype.pathSegTypeAsLetter; - - - -/** - * @constructor - * @extends {SVGPathSeg} - */ -function SVGPathSegCurvetoQuadraticSmoothAbs(){} - - -/** - * @type {number} - */ -SVGPathSegCurvetoQuadraticSmoothAbs.prototype.x; - - -/** - * @type {number} - */ -SVGPathSegCurvetoQuadraticSmoothAbs.prototype.y; - - - -/** - * @constructor - * @extends {SVGPathSeg} - */ -function SVGPathSegArcRel(){} - - -/** - * @type {number} - */ -SVGPathSegArcRel.prototype.x; - - -/** - * @type {number} - */ -SVGPathSegArcRel.prototype.y; - - -/** - * @type {number} - */ -SVGPathSegArcRel.prototype.r1; - - -/** - * @type {number} - */ -SVGPathSegArcRel.prototype.r2; - - -/** - * @type {number} - */ -SVGPathSegArcRel.prototype.angle; - - -/** - * @type {boolean} - */ -SVGPathSegArcRel.prototype.largeArcFlag; - - -/** - * @type {boolean} - */ -SVGPathSegArcRel.prototype.sweepFlag; - - - -/** - * @constructor - * @extends {SVGPathSeg} - */ -function SVGPathSegCurvetoQuadraticAbs(){} - - -/** - * @type {number} - */ -SVGPathSegCurvetoQuadraticAbs.prototype.x; - - -/** - * @type {number} - */ -SVGPathSegCurvetoQuadraticAbs.prototype.y; - - -/** - * @type {number} - */ -SVGPathSegCurvetoQuadraticAbs.prototype.x1; - - -/** - * @type {number} - */ -SVGPathSegCurvetoQuadraticAbs.prototype.y1; - - - -/** - * @constructor - * @extends {SVGPathSeg} - */ -function SVGPathSegLinetoHorizontalRel(){} - - -/** - * @type {number} - */ -SVGPathSegLinetoHorizontalRel.prototype.x; - - - -/** - * @constructor - * @extends {SVGPathSeg} - */ -function SVGPathSegLinetoRel(){} - - -/** - * @type {number} - */ -SVGPathSegLinetoRel.prototype.x; - - -/** - * @type {number} - */ -SVGPathSegLinetoRel.prototype.y; - - - -/** - * @constructor - * @extends {SVGPathSeg} - */ -function SVGPathSegCurvetoCubicRel(){} - - -/** - * @type {number} - */ -SVGPathSegCurvetoCubicRel.prototype.x; - - -/** - * @type {number} - */ -SVGPathSegCurvetoCubicRel.prototype.y; - - -/** - * @type {number} - */ -SVGPathSegCurvetoCubicRel.prototype.x1; - - -/** - * @type {number} - */ -SVGPathSegCurvetoCubicRel.prototype.y1; - - -/** - * @type {number} - */ -SVGPathSegCurvetoCubicRel.prototype.x2; - - -/** - * @type {number} - */ -SVGPathSegCurvetoCubicRel.prototype.y2; - - - -/** - * @constructor - * @extends {SVGPathSeg} - */ -function SVGPathSegMovetoAbs(){} - - -/** - * @type {number} - */ -SVGPathSegMovetoAbs.prototype.x; - - -/** - * @type {number} - */ -SVGPathSegMovetoAbs.prototype.y; - - - -/** - * @constructor - * @extends {SVGPathSeg} - */ -function SVGPathSegCurvetoQuadraticSmoothRel(){} - - -/** - * @type {number} - */ -SVGPathSegCurvetoQuadraticSmoothRel.prototype.x; - - -/** - * @type {number} - */ -SVGPathSegCurvetoQuadraticSmoothRel.prototype.y; - - - -/** - * @constructor - * @extends {SVGPathSeg} - */ -function SVGPathSegLinetoVerticalRel(){} - - -/** - * @type {number} - */ -SVGPathSegLinetoVerticalRel.prototype.y; - - - -/** - * @constructor - * @extends {SVGPathSeg} - */ -function SVGPathSegLinetoAbs(){} - - -/** - * @type {number} - */ -SVGPathSegLinetoAbs.prototype.x; - - -/** - * @type {number} - */ -SVGPathSegLinetoAbs.prototype.y; - - - -/** - * @constructor - * @extends {SVGPathSeg} - */ -function SVGPathSegCurvetoCubicSmoothRel(){} - - -/** - * @type {number} - */ -SVGPathSegCurvetoCubicSmoothRel.prototype.x; - - -/** - * @type {number} - */ -SVGPathSegCurvetoCubicSmoothRel.prototype.y; - - -/** - * @type {number} - */ -SVGPathSegCurvetoCubicSmoothRel.prototype.x2; - - -/** - * @type {number} - */ -SVGPathSegCurvetoCubicSmoothRel.prototype.y2; - - - -/** - * @constructor - * @extends {SVGPathSeg} - */ -function SVGPathSegLinetoHorizontalAbs(){} - - -/** - * @type {number} - */ -SVGPathSegLinetoHorizontalAbs.prototype.x; - - - -/** - * @constructor - * @extends {SVGPathSeg} - */ -function SVGPathSegCurvetoQuadraticRel(){} - - -/** - * @type {number} - */ -SVGPathSegCurvetoQuadraticRel.prototype.x; - - -/** - * @type {number} - */ -SVGPathSegCurvetoQuadraticRel.prototype.y; - - -/** - * @type {number} - */ -SVGPathSegCurvetoQuadraticRel.prototype.x1; - - -/** - * @type {number} - */ -SVGPathSegCurvetoQuadraticRel.prototype.y1; - - - -/** - * @constructor - * @extends {SVGPathSeg} - */ -function SVGPathSegCurvetoCubicSmoothAbs(){} - - -/** - * @type {number} - */ -SVGPathSegCurvetoCubicSmoothAbs.prototype.x; - - -/** - * @type {number} - */ -SVGPathSegCurvetoCubicSmoothAbs.prototype.y; - - -/** - * @type {number} - */ -SVGPathSegCurvetoCubicSmoothAbs.prototype.x2; - - -/** - * @type {number} - */ -SVGPathSegCurvetoCubicSmoothAbs.prototype.y2; - - - -/** - * @constructor - * @extends {SVGPathSeg} - */ -function SVGPathSegCurvetoCubicAbs(){} - - -/** - * @type {number} - */ -SVGPathSegCurvetoCubicAbs.prototype.x; - - -/** - * @type {number} - */ -SVGPathSegCurvetoCubicAbs.prototype.y; - - -/** - * @type {number} - */ -SVGPathSegCurvetoCubicAbs.prototype.x1; - - -/** - * @type {number} - */ -SVGPathSegCurvetoCubicAbs.prototype.y1; - - -/** - * @type {number} - */ -SVGPathSegCurvetoCubicAbs.prototype.x2; - - -/** - * @type {number} - */ -SVGPathSegCurvetoCubicAbs.prototype.y2; - - - -/** - * @constructor - * @extends {SVGPathSeg} - */ -function SVGPathSegClosePath(){} - - - -/** - * @interface - */ -function SVGFitToViewBox(){} - - -/** - * @type {!SVGAnimatedRect} - */ -SVGFitToViewBox.prototype.viewBox; - - -/** - * @type {!SVGAnimatedPreserveAspectRatio} - */ -SVGFitToViewBox.prototype.preserveAspectRatio; - - - -/** - * @constructor - * @implements {SVGZoomAndPan} - * @implements {SVGFitToViewBox} - */ -function SVGViewSpec(){} - - -/** - * @type {!SVGTransformList} - */ -SVGViewSpec.prototype.transform; - - -/** - * @type {!SVGElement} - */ -SVGViewSpec.prototype.viewTarget; - - -/** - * @type {string} - */ -SVGViewSpec.prototype.viewBoxString; - - -/** - * @type {string} - */ -SVGViewSpec.prototype.preserveAspectRatioString; - - -/** - * @type {string} - */ -SVGViewSpec.prototype.transformString; - - -/** - * @type {string} - */ -SVGViewSpec.prototype.viewTargetString; - - -/** - * @const - * @type {number} - */ -SVGViewSpec.SVG_ZOOMANDPAN_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGViewSpec.prototype.SVG_ZOOMANDPAN_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGViewSpec.SVG_ZOOMANDPAN_DISABLE; - - -/** - * @const - * @type {number} - */ -SVGViewSpec.prototype.SVG_ZOOMANDPAN_DISABLE; - - -/** - * @const - * @type {number} - */ -SVGViewSpec.SVG_ZOOMANDPAN_MAGNIFY; - - -/** - * @const - * @type {number} - */ -SVGViewSpec.prototype.SVG_ZOOMANDPAN_MAGNIFY; - - -/** - * @type {number} - */ -SVGViewSpec.prototype.zoomAndPan; - - -/** - * @type {!SVGAnimatedRect} - */ -SVGViewSpec.prototype.viewBox; - - -/** - * @type {!SVGAnimatedPreserveAspectRatio} - */ -SVGViewSpec.prototype.preserveAspectRatio; - - - -/** - * @constructor - */ -function SVGRenderingIntent(){} - - -/** - * @const - * @type {number} - */ -SVGRenderingIntent.RENDERING_INTENT_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGRenderingIntent.prototype.RENDERING_INTENT_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGRenderingIntent.RENDERING_INTENT_AUTO; - - -/** - * @const - * @type {number} - */ -SVGRenderingIntent.prototype.RENDERING_INTENT_AUTO; - - -/** - * @const - * @type {number} - */ -SVGRenderingIntent.RENDERING_INTENT_PERCEPTUAL; - - -/** - * @const - * @type {number} - */ -SVGRenderingIntent.prototype.RENDERING_INTENT_PERCEPTUAL; - - -/** - * @const - * @type {number} - */ -SVGRenderingIntent.RENDERING_INTENT_RELATIVE_COLORIMETRIC; - - -/** - * @const - * @type {number} - */ -SVGRenderingIntent.prototype.RENDERING_INTENT_RELATIVE_COLORIMETRIC; - - -/** - * @const - * @type {number} - */ -SVGRenderingIntent.RENDERING_INTENT_SATURATION; - - -/** - * @const - * @type {number} - */ -SVGRenderingIntent.prototype.RENDERING_INTENT_SATURATION; - - -/** - * @const - * @type {number} - */ -SVGRenderingIntent.RENDERING_INTENT_ABSOLUTE_COLORIMETRIC; - - -/** - * @const - * @type {number} - */ -SVGRenderingIntent.prototype.RENDERING_INTENT_ABSOLUTE_COLORIMETRIC; - - - -/** - * @constructor - * @extends {SVGPathSeg} - */ -function SVGPathSegLinetoVerticalAbs(){} - - -/** - * @type {number} - */ -SVGPathSegLinetoVerticalAbs.prototype.y; - - - -/** - * @constructor - */ -function SVGPathSegList(){} - - -/** - * @type {number} - */ -SVGPathSegList.prototype.numberOfItems; - - -/** */ -SVGPathSegList.prototype.clear = function(){}; - - -/** - * @param {!SVGPathSeg} newItem - * @return {!SVGPathSeg} - */ -SVGPathSegList.prototype.initialize = function(newItem){}; - - -/** - * @param {number} index - * @return {!SVGPathSeg} - */ -SVGPathSegList.prototype.getItem = function(index){}; - - -/** - * @param {!SVGPathSeg} newItem - * @param {number} index - * @return {!SVGPathSeg} - */ -SVGPathSegList.prototype.insertItemBefore = function(newItem, index){}; - - -/** - * @param {!SVGPathSeg} newItem - * @param {number} index - * @return {!SVGPathSeg} - */ -SVGPathSegList.prototype.replaceItem = function(newItem, index){}; - - -/** - * @param {number} index - * @return {!SVGPathSeg} - */ -SVGPathSegList.prototype.removeItem = function(index){}; - - -/** - * @param {!SVGPathSeg} newItem - * @return {!SVGPathSeg} - */ -SVGPathSegList.prototype.appendItem = function(newItem){}; - - - -/** - * @constructor - * @extends {Document} - */ -function SVGDocument(){} - - -/** - * @type {!SVGSVGElement} - */ -SVGDocument.prototype.rootElement; - - -/** - * @param {string=} opt_eventType - * @return {!Event} - * @override - */ -SVGDocument.prototype.createEvent = function(opt_eventType){}; - - - -/** - * @constructor - * @extends {SVGElement} - */ -function SVGAltGlyphItemElement(){} - - - -/** - * @constructor - * @extends {SVGElement} - */ -function SVGFontFaceFormatElement(){} - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGFilterPrimitiveStandardAttributes} - */ -function SVGFECompositeElement(){} - - -/** - * @const - * @type {number} - */ -SVGFECompositeElement.SVG_FECOMPOSITE_OPERATOR_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGFECompositeElement.prototype.SVG_FECOMPOSITE_OPERATOR_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGFECompositeElement.SVG_FECOMPOSITE_OPERATOR_OVER; - - -/** - * @const - * @type {number} - */ -SVGFECompositeElement.prototype.SVG_FECOMPOSITE_OPERATOR_OVER; - - -/** - * @const - * @type {number} - */ -SVGFECompositeElement.SVG_FECOMPOSITE_OPERATOR_IN; - - -/** - * @const - * @type {number} - */ -SVGFECompositeElement.prototype.SVG_FECOMPOSITE_OPERATOR_IN; - - -/** - * @const - * @type {number} - */ -SVGFECompositeElement.SVG_FECOMPOSITE_OPERATOR_OUT; - - -/** - * @const - * @type {number} - */ -SVGFECompositeElement.prototype.SVG_FECOMPOSITE_OPERATOR_OUT; - - -/** - * @const - * @type {number} - */ -SVGFECompositeElement.SVG_FECOMPOSITE_OPERATOR_ATOP; - - -/** - * @const - * @type {number} - */ -SVGFECompositeElement.prototype.SVG_FECOMPOSITE_OPERATOR_ATOP; - - -/** - * @const - * @type {number} - */ -SVGFECompositeElement.SVG_FECOMPOSITE_OPERATOR_XOR; - - -/** - * @const - * @type {number} - */ -SVGFECompositeElement.prototype.SVG_FECOMPOSITE_OPERATOR_XOR; - - -/** - * @const - * @type {number} - */ -SVGFECompositeElement.SVG_FECOMPOSITE_OPERATOR_ARITHMETIC; - - -/** - * @const - * @type {number} - */ -SVGFECompositeElement.prototype.SVG_FECOMPOSITE_OPERATOR_ARITHMETIC; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFECompositeElement.prototype.in1; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFECompositeElement.prototype.in2; - - -/** - * @type {!SVGAnimatedEnumeration} - */ -SVGFECompositeElement.prototype.operator; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFECompositeElement.prototype.k1; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFECompositeElement.prototype.k2; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFECompositeElement.prototype.k3; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFECompositeElement.prototype.k4; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFECompositeElement.prototype.x; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFECompositeElement.prototype.y; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFECompositeElement.prototype.width; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFECompositeElement.prototype.height; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFECompositeElement.prototype.result; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFECompositeElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGFECompositeElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGLangSpace} - * @implements {SVGStylable} - */ -function SVGDescElement(){} - - -/** - * @type {string} - */ -SVGDescElement.prototype.xmllang; - - -/** - * @type {string} - */ -SVGDescElement.prototype.xmlspace; - - -/** - * @type {!SVGAnimatedString} - */ -SVGDescElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGDescElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGTests} - * @implements {SVGLangSpace} - * @implements {SVGExternalResourcesRequired} - * @implements {SVGStylable} - * @implements {SVGTransformable} - */ -function SVGEllipseElement(){} - - -/** - * @type {!SVGAnimatedLength} - */ -SVGEllipseElement.prototype.cx; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGEllipseElement.prototype.cy; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGEllipseElement.prototype.rx; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGEllipseElement.prototype.ry; - - -/** - * @type {!SVGAnimatedBoolean} - */ -SVGEllipseElement.prototype.externalResourcesRequired; - - -/** - * @type {!SVGAnimatedTransformList} - */ -SVGEllipseElement.prototype.transform; - - -/** - * @type {string} - */ -SVGEllipseElement.prototype.xmllang; - - -/** - * @type {string} - */ -SVGEllipseElement.prototype.xmlspace; - - -/** - * @type {!SVGElement} - */ -SVGEllipseElement.prototype.nearestViewportElement; - - -/** - * @type {!SVGElement} - * @override - */ -SVGEllipseElement.prototype.farthestViewportElement; - - -/** - * @return {!SVGRect} - * @override - */ -SVGEllipseElement.prototype.getBBox = function(){}; - - -/** - * @return {!SVGMatrix} - * @override - */ -SVGEllipseElement.prototype.getCTM = function(){}; - - -/** - * @return {!SVGMatrix} - * @override - */ -SVGEllipseElement.prototype.getScreenCTM = function(){}; - - -/** - * @param {!SVGElement=} opt_element - * @return {!SVGMatrix} - * @override - */ -SVGEllipseElement.prototype.getTransformToElement = function(opt_element){}; - - -/** - * @type {!SVGStringList} - */ -SVGEllipseElement.prototype.requiredFeatures; - - -/** - * @type {!SVGStringList} - */ -SVGEllipseElement.prototype.requiredExtensions; - - -/** - * @type {!SVGStringList} - */ -SVGEllipseElement.prototype.systemLanguage; - - -/** - * @param {string=} opt_extension - * @return {boolean} - * @override - */ -SVGEllipseElement.prototype.hasExtension = function(opt_extension){}; - - -/** - * @type {!SVGAnimatedString} - */ -SVGEllipseElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGEllipseElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGFilterPrimitiveStandardAttributes} - */ -function SVGFESpecularLightingElement(){} - - -/** - * @type {!SVGAnimatedString} - */ -SVGFESpecularLightingElement.prototype.in1; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFESpecularLightingElement.prototype.surfaceScale; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFESpecularLightingElement.prototype.specularConstant; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFESpecularLightingElement.prototype.specularExponent; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFESpecularLightingElement.prototype.x; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFESpecularLightingElement.prototype.y; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFESpecularLightingElement.prototype.width; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFESpecularLightingElement.prototype.height; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFESpecularLightingElement.prototype.result; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFESpecularLightingElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGFESpecularLightingElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGFilterPrimitiveStandardAttributes} - */ -function SVGFEColorMatrixElement(){} - - -/** - * @const - * @type {number} - */ -SVGFEColorMatrixElement.SVG_FECOLORMATRIX_TYPE_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGFEColorMatrixElement.prototype.SVG_FECOLORMATRIX_TYPE_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGFEColorMatrixElement.SVG_FECOLORMATRIX_TYPE_MATRIX; - - -/** - * @const - * @type {number} - */ -SVGFEColorMatrixElement.prototype.SVG_FECOLORMATRIX_TYPE_MATRIX; - - -/** - * @const - * @type {number} - */ -SVGFEColorMatrixElement.SVG_FECOLORMATRIX_TYPE_SATURATE; - - -/** - * @const - * @type {number} - */ -SVGFEColorMatrixElement.prototype.SVG_FECOLORMATRIX_TYPE_SATURATE; - - -/** - * @const - * @type {number} - */ -SVGFEColorMatrixElement.SVG_FECOLORMATRIX_TYPE_HUEROTATE; - - -/** - * @const - * @type {number} - */ -SVGFEColorMatrixElement.prototype.SVG_FECOLORMATRIX_TYPE_HUEROTATE; - - -/** - * @const - * @type {number} - */ -SVGFEColorMatrixElement.SVG_FECOLORMATRIX_TYPE_LUMINANCETOALPHA; - - -/** - * @const - * @type {number} - */ -SVGFEColorMatrixElement.prototype.SVG_FECOLORMATRIX_TYPE_LUMINANCETOALPHA; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEColorMatrixElement.prototype.in1; - - -/** - * @type {!SVGAnimatedEnumeration} - */ -SVGFEColorMatrixElement.prototype.type; - - -/** - * @type {!SVGAnimatedNumberList} - */ -SVGFEColorMatrixElement.prototype.values; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEColorMatrixElement.prototype.x; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEColorMatrixElement.prototype.y; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEColorMatrixElement.prototype.width; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEColorMatrixElement.prototype.height; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEColorMatrixElement.prototype.result; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEColorMatrixElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGFEColorMatrixElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGLangSpace} - * @implements {SVGExternalResourcesRequired} - * @implements {SVGStylable} - * @implements {SVGFitToViewBox} - */ -function SVGSymbolElement(){} - - -/** - * @type {!SVGAnimatedBoolean} - */ -SVGSymbolElement.prototype.externalResourcesRequired; - - -/** - * @type {!SVGAnimatedRect} - */ -SVGSymbolElement.prototype.viewBox; - - -/** - * @type {!SVGAnimatedPreserveAspectRatio} - */ -SVGSymbolElement.prototype.preserveAspectRatio; - - -/** - * @type {string} - */ -SVGSymbolElement.prototype.xmllang; - - -/** - * @type {string} - */ -SVGSymbolElement.prototype.xmlspace; - - -/** - * @type {!SVGAnimatedString} - */ -SVGSymbolElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGSymbolElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGFilterPrimitiveStandardAttributes} - */ -function SVGFEConvolveMatrixElement(){} - - -/** - * @const - * @type {number} - */ -SVGFEConvolveMatrixElement.SVG_EDGEMODE_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGFEConvolveMatrixElement.prototype.SVG_EDGEMODE_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGFEConvolveMatrixElement.SVG_EDGEMODE_DUPLICATE; - - -/** - * @const - * @type {number} - */ -SVGFEConvolveMatrixElement.prototype.SVG_EDGEMODE_DUPLICATE; - - -/** - * @const - * @type {number} - */ -SVGFEConvolveMatrixElement.SVG_EDGEMODE_WRAP; - - -/** - * @const - * @type {number} - */ -SVGFEConvolveMatrixElement.prototype.SVG_EDGEMODE_WRAP; - - -/** - * @const - * @type {number} - */ -SVGFEConvolveMatrixElement.SVG_EDGEMODE_NONE; - - -/** - * @const - * @type {number} - */ -SVGFEConvolveMatrixElement.prototype.SVG_EDGEMODE_NONE; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEConvolveMatrixElement.prototype.in1; - - -/** - * @type {!SVGAnimatedInteger} - */ -SVGFEConvolveMatrixElement.prototype.orderX; - - -/** - * @type {!SVGAnimatedInteger} - */ -SVGFEConvolveMatrixElement.prototype.orderY; - - -/** - * @type {!SVGAnimatedNumberList} - */ -SVGFEConvolveMatrixElement.prototype.kernelMatrix; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFEConvolveMatrixElement.prototype.divisor; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFEConvolveMatrixElement.prototype.bias; - - -/** - * @type {!SVGAnimatedInteger} - */ -SVGFEConvolveMatrixElement.prototype.targetX; - - -/** - * @type {!SVGAnimatedInteger} - */ -SVGFEConvolveMatrixElement.prototype.targetY; - - -/** - * @type {!SVGAnimatedEnumeration} - */ -SVGFEConvolveMatrixElement.prototype.edgeMode; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFEConvolveMatrixElement.prototype.kernelUnitLengthX; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFEConvolveMatrixElement.prototype.kernelUnitLengthY; - - -/** - * @type {!SVGAnimatedBoolean} - */ -SVGFEConvolveMatrixElement.prototype.preserveAlpha; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEConvolveMatrixElement.prototype.x; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEConvolveMatrixElement.prototype.y; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEConvolveMatrixElement.prototype.width; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEConvolveMatrixElement.prototype.height; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEConvolveMatrixElement.prototype.result; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEConvolveMatrixElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGFEConvolveMatrixElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGFilterPrimitiveStandardAttributes} - */ -function SVGFEFloodElement(){} - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEFloodElement.prototype.x; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEFloodElement.prototype.y; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEFloodElement.prototype.width; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEFloodElement.prototype.height; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEFloodElement.prototype.result; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEFloodElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGFEFloodElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGStylable} - */ -function SVGStopElement(){} - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGStopElement.prototype.offset; - - -/** - * @type {!SVGAnimatedString} - */ -SVGStopElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGStopElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGElement} - */ -function SVGGlyphElement(){} - - - -/** - * @constructor - * @extends {SVGElement} - */ -function SVGHKernElement(){} - - - -/** - * @constructor - * @extends {SVGElement} - */ -function SVGVKernElement(){} - - - -/** - * @constructor - * @extends {SVGElement} - */ -function SVGFontElement(){} - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGFilterPrimitiveStandardAttributes} - */ -function SVGFEOffsetElement(){} - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEOffsetElement.prototype.in1; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFEOffsetElement.prototype.dx; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFEOffsetElement.prototype.dy; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEOffsetElement.prototype.x; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEOffsetElement.prototype.y; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEOffsetElement.prototype.width; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEOffsetElement.prototype.height; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEOffsetElement.prototype.result; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEOffsetElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGFEOffsetElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGURIReference} - * @implements {SVGLangSpace} - * @implements {SVGExternalResourcesRequired} - * @implements {SVGStylable} - */ -function SVGFilterElement(){} - - -/** - * @type {!SVGAnimatedEnumeration} - */ -SVGFilterElement.prototype.filterUnits; - - -/** - * @type {!SVGAnimatedEnumeration} - */ -SVGFilterElement.prototype.primitiveUnits; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFilterElement.prototype.x; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFilterElement.prototype.y; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFilterElement.prototype.width; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFilterElement.prototype.height; - - -/** - * @type {!SVGAnimatedInteger} - */ -SVGFilterElement.prototype.filterResX; - - -/** - * @type {!SVGAnimatedInteger} - */ -SVGFilterElement.prototype.filterResY; - - -/** - * @param {number=} opt_filterResX - * @param {number=} opt_filterResY - * @return {undefined} - */ -SVGFilterElement.prototype.setFilterRes = function(opt_filterResX, opt_filterResY){}; - - -/** - * @type {!SVGAnimatedBoolean} - */ -SVGFilterElement.prototype.externalResourcesRequired; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFilterElement.prototype.href; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFilterElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGFilterElement.prototype.getPresentationAttribute = function(opt_name){}; - - -/** - * @type {string} - */ -SVGFilterElement.prototype.xmllang; - - -/** - * @type {string} - */ -SVGFilterElement.prototype.xmlspace; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGFilterPrimitiveStandardAttributes} - */ -function SVGFEGaussianBlurElement(){} - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEGaussianBlurElement.prototype.in1; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFEGaussianBlurElement.prototype.stdDeviationX; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFEGaussianBlurElement.prototype.stdDeviationY; - - -/** - * @param {number=} opt_stdDeviationX - * @param {number=} opt_stdDeviationY - * @return {undefined} - */ -SVGFEGaussianBlurElement.prototype.setStdDeviation = function(opt_stdDeviationX, opt_stdDeviationY){}; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEGaussianBlurElement.prototype.x; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEGaussianBlurElement.prototype.y; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEGaussianBlurElement.prototype.width; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEGaussianBlurElement.prototype.height; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEGaussianBlurElement.prototype.result; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEGaussianBlurElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGFEGaussianBlurElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGElement} - */ -function SVGAltGlyphDefElement(){} - - - -/** - * @constructor - * @extends {SVGElement} - */ -function SVGComponentTransferFunctionElement(){} - - -/** - * @const - * @type {number} - */ -SVGComponentTransferFunctionElement.SVG_FECOMPONENTTRANSFER_TYPE_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGComponentTransferFunctionElement.prototype.SVG_FECOMPONENTTRANSFER_TYPE_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGComponentTransferFunctionElement.SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY; - - -/** - * @const - * @type {number} - */ -SVGComponentTransferFunctionElement.prototype.SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY; - - -/** - * @const - * @type {number} - */ -SVGComponentTransferFunctionElement.SVG_FECOMPONENTTRANSFER_TYPE_TABLE; - - -/** - * @const - * @type {number} - */ -SVGComponentTransferFunctionElement.prototype.SVG_FECOMPONENTTRANSFER_TYPE_TABLE; - - -/** - * @const - * @type {number} - */ -SVGComponentTransferFunctionElement.SVG_FECOMPONENTTRANSFER_TYPE_DISCRETE; - - -/** - * @const - * @type {number} - */ -SVGComponentTransferFunctionElement.prototype.SVG_FECOMPONENTTRANSFER_TYPE_DISCRETE; - - -/** - * @const - * @type {number} - */ -SVGComponentTransferFunctionElement.SVG_FECOMPONENTTRANSFER_TYPE_LINEAR; - - -/** - * @const - * @type {number} - */ -SVGComponentTransferFunctionElement.prototype.SVG_FECOMPONENTTRANSFER_TYPE_LINEAR; - - -/** - * @const - * @type {number} - */ -SVGComponentTransferFunctionElement.SVG_FECOMPONENTTRANSFER_TYPE_GAMMA; - - -/** - * @const - * @type {number} - */ -SVGComponentTransferFunctionElement.prototype.SVG_FECOMPONENTTRANSFER_TYPE_GAMMA; - - -/** - * @type {!SVGAnimatedEnumeration} - */ -SVGComponentTransferFunctionElement.prototype.type; - - -/** - * @type {!SVGAnimatedNumberList} - */ -SVGComponentTransferFunctionElement.prototype.tableValues; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGComponentTransferFunctionElement.prototype.slope; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGComponentTransferFunctionElement.prototype.intercept; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGComponentTransferFunctionElement.prototype.amplitude; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGComponentTransferFunctionElement.prototype.exponent; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGComponentTransferFunctionElement.prototype.offset; - - - -/** - * @constructor - * @extends {SVGElement} - */ -function SVGMetadataElement(){} - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGLangSpace} - * @implements {SVGStylable} - */ -function SVGTitleElement(){} - - -/** - * @type {string} - */ -SVGTitleElement.prototype.xmllang; - - -/** - * @type {string} - */ -SVGTitleElement.prototype.xmlspace; - - -/** - * @type {!SVGAnimatedString} - */ -SVGTitleElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGTitleElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGURIReference} - * @implements {SVGTests} - * @implements {SVGExternalResourcesRequired} - */ -function SVGCursorElement(){} - - -/** - * @type {!SVGAnimatedLength} - */ -SVGCursorElement.prototype.x; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGCursorElement.prototype.y; - - -/** - * @type {!SVGAnimatedBoolean} - */ -SVGCursorElement.prototype.externalResourcesRequired; - - -/** - * @type {!SVGAnimatedString} - */ -SVGCursorElement.prototype.href; - - -/** - * @type {!SVGStringList} - */ -SVGCursorElement.prototype.requiredFeatures; - - -/** - * @type {!SVGStringList} - */ -SVGCursorElement.prototype.requiredExtensions; - - -/** - * @type {!SVGStringList} - */ -SVGCursorElement.prototype.systemLanguage; - - -/** - * @param {string=} opt_extension - * @return {boolean} - * @override - */ -SVGCursorElement.prototype.hasExtension = function(opt_extension){}; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGURIReference} - * @implements {SVGExternalResourcesRequired} - * @implements {SVGStylable} - */ -function SVGGradientElement(){} - - -/** - * @const - * @type {number} - */ -SVGGradientElement.SVG_SPREADMETHOD_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGGradientElement.prototype.SVG_SPREADMETHOD_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGGradientElement.SVG_SPREADMETHOD_PAD; - - -/** - * @const - * @type {number} - */ -SVGGradientElement.prototype.SVG_SPREADMETHOD_PAD; - - -/** - * @const - * @type {number} - */ -SVGGradientElement.SVG_SPREADMETHOD_REFLECT; - - -/** - * @const - * @type {number} - */ -SVGGradientElement.prototype.SVG_SPREADMETHOD_REFLECT; - - -/** - * @const - * @type {number} - */ -SVGGradientElement.SVG_SPREADMETHOD_REPEAT; - - -/** - * @const - * @type {number} - */ -SVGGradientElement.prototype.SVG_SPREADMETHOD_REPEAT; - - -/** - * @type {!SVGAnimatedEnumeration} - */ -SVGGradientElement.prototype.gradientUnits; - - -/** - * @type {!SVGAnimatedTransformList} - */ -SVGGradientElement.prototype.gradientTransform; - - -/** - * @type {!SVGAnimatedEnumeration} - */ -SVGGradientElement.prototype.spreadMethod; - - -/** - * @type {!SVGAnimatedBoolean} - */ -SVGGradientElement.prototype.externalResourcesRequired; - - -/** - * @type {!SVGAnimatedString} - */ -SVGGradientElement.prototype.href; - - -/** - * @type {!SVGAnimatedString} - */ -SVGGradientElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGGradientElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGLangSpace} - * @implements {SVGExternalResourcesRequired} - * @implements {SVGStylable} - * @implements {SVGFitToViewBox} - */ -function SVGMarkerElement(){} - - -/** - * @const - * @type {number} - */ -SVGMarkerElement.SVG_MARKERUNITS_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGMarkerElement.prototype.SVG_MARKERUNITS_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGMarkerElement.SVG_MARKERUNITS_USERSPACEONUSE; - - -/** - * @const - * @type {number} - */ -SVGMarkerElement.prototype.SVG_MARKERUNITS_USERSPACEONUSE; - - -/** - * @const - * @type {number} - */ -SVGMarkerElement.SVG_MARKERUNITS_STROKEWIDTH; - - -/** - * @const - * @type {number} - */ -SVGMarkerElement.prototype.SVG_MARKERUNITS_STROKEWIDTH; - - -/** - * @const - * @type {number} - */ -SVGMarkerElement.SVG_MARKER_ORIENT_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGMarkerElement.prototype.SVG_MARKER_ORIENT_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGMarkerElement.SVG_MARKER_ORIENT_AUTO; - - -/** - * @const - * @type {number} - */ -SVGMarkerElement.prototype.SVG_MARKER_ORIENT_AUTO; - - -/** - * @const - * @type {number} - */ -SVGMarkerElement.SVG_MARKER_ORIENT_ANGLE; - - -/** - * @const - * @type {number} - */ -SVGMarkerElement.prototype.SVG_MARKER_ORIENT_ANGLE; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGMarkerElement.prototype.refX; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGMarkerElement.prototype.refY; - - -/** - * @type {!SVGAnimatedEnumeration} - */ -SVGMarkerElement.prototype.markerUnits; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGMarkerElement.prototype.markerWidth; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGMarkerElement.prototype.markerHeight; - - -/** - * @type {!SVGAnimatedEnumeration} - */ -SVGMarkerElement.prototype.orientType; - - -/** - * @type {!SVGAnimatedAngle} - */ -SVGMarkerElement.prototype.orientAngle; - - -/** */ -SVGMarkerElement.prototype.setOrientToAuto = function(){}; - - -/** - * @param {!SVGAngle=} opt_angle - * @return {undefined} - */ -SVGMarkerElement.prototype.setOrientToAngle = function(opt_angle){}; - - -/** - * @type {!SVGAnimatedBoolean} - */ -SVGMarkerElement.prototype.externalResourcesRequired; - - -/** - * @type {!SVGAnimatedRect} - */ -SVGMarkerElement.prototype.viewBox; - - -/** - * @type {!SVGAnimatedPreserveAspectRatio} - */ -SVGMarkerElement.prototype.preserveAspectRatio; - - -/** - * @type {string} - */ -SVGMarkerElement.prototype.xmllang; - - -/** - * @type {string} - */ -SVGMarkerElement.prototype.xmlspace; - - -/** - * @type {!SVGAnimatedString} - */ -SVGMarkerElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGMarkerElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGTests} - * @implements {SVGLangSpace} - * @implements {SVGExternalResourcesRequired} - * @implements {SVGStylable} - */ -function SVGMaskElement(){} - - -/** - * @type {!SVGAnimatedEnumeration} - */ -SVGMaskElement.prototype.maskUnits; - - -/** - * @type {!SVGAnimatedEnumeration} - */ -SVGMaskElement.prototype.maskContentUnits; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGMaskElement.prototype.x; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGMaskElement.prototype.y; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGMaskElement.prototype.width; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGMaskElement.prototype.height; - - -/** - * @type {!SVGAnimatedBoolean} - */ -SVGMaskElement.prototype.externalResourcesRequired; - - -/** - * @type {!SVGStringList} - */ -SVGMaskElement.prototype.requiredFeatures; - - -/** - * @type {!SVGStringList} - */ -SVGMaskElement.prototype.requiredExtensions; - - -/** - * @type {!SVGStringList} - */ -SVGMaskElement.prototype.systemLanguage; - - -/** - * @param {string=} opt_extension - * @return {boolean} - * @override - */ -SVGMaskElement.prototype.hasExtension = function(opt_extension){}; - - -/** - * @type {!SVGAnimatedString} - */ -SVGMaskElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGMaskElement.prototype.getPresentationAttribute = function(opt_name){}; - - -/** - * @type {string} - */ -SVGMaskElement.prototype.xmllang; - - -/** - * @type {string} - */ -SVGMaskElement.prototype.xmlspace; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGExternalResourcesRequired} - * @implements {SVGFitToViewBox} - * @implements {SVGZoomAndPan} - */ -function SVGViewElement(){} - - -/** - * @type {!SVGStringList} - */ -SVGViewElement.prototype.viewTarget; - - -/** - * @type {!SVGAnimatedBoolean} - */ -SVGViewElement.prototype.externalResourcesRequired; - - -/** - * @const - * @type {number} - */ -SVGViewElement.SVG_ZOOMANDPAN_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGViewElement.prototype.SVG_ZOOMANDPAN_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGViewElement.SVG_ZOOMANDPAN_DISABLE; - - -/** - * @const - * @type {number} - */ -SVGViewElement.prototype.SVG_ZOOMANDPAN_DISABLE; - - -/** - * @const - * @type {number} - */ -SVGViewElement.SVG_ZOOMANDPAN_MAGNIFY; - - -/** - * @const - * @type {number} - */ -SVGViewElement.prototype.SVG_ZOOMANDPAN_MAGNIFY; - - -/** - * @type {number} - */ -SVGViewElement.prototype.zoomAndPan; - - -/** - * @type {!SVGAnimatedRect} - */ -SVGViewElement.prototype.viewBox; - - -/** - * @type {!SVGAnimatedPreserveAspectRatio} - */ -SVGViewElement.prototype.preserveAspectRatio; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGURIReference} - * @implements {SVGExternalResourcesRequired} - */ -function SVGMPathElement(){} - - -/** - * @type {!SVGAnimatedBoolean} - */ -SVGMPathElement.prototype.externalResourcesRequired; - - -/** - * @type {!SVGAnimatedString} - */ -SVGMPathElement.prototype.href; - - - -/** - * @constructor - * @extends {SVGElement} - */ -function SVGFontFaceSrcElement(){} - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGURIReference} - * @implements {SVGLangSpace} - * @implements {SVGExternalResourcesRequired} - * @implements {SVGFilterPrimitiveStandardAttributes} - */ -function SVGFEImageElement(){} - - -/** - * @type {!SVGAnimatedPreserveAspectRatio} - */ -SVGFEImageElement.prototype.preserveAspectRatio; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEImageElement.prototype.x; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEImageElement.prototype.y; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEImageElement.prototype.width; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEImageElement.prototype.height; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEImageElement.prototype.result; - - -/** - * @type {!SVGAnimatedBoolean} - */ -SVGFEImageElement.prototype.externalResourcesRequired; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEImageElement.prototype.href; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEImageElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGFEImageElement.prototype.getPresentationAttribute = function(opt_name){}; - - -/** - * @type {string} - */ -SVGFEImageElement.prototype.xmllang; - - -/** - * @type {string} - */ -SVGFEImageElement.prototype.xmlspace; - - - -/** - * @constructor - * @extends {SVGElement} - */ -function SVGFEDistantLightElement(){} - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFEDistantLightElement.prototype.azimuth; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFEDistantLightElement.prototype.elevation; - - - -/** - * @constructor - * @extends {SVGElement} - */ -function SVGFontFaceUriElement(){} - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGFilterPrimitiveStandardAttributes} - */ -function SVGFEDiffuseLightingElement(){} - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEDiffuseLightingElement.prototype.in1; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFEDiffuseLightingElement.prototype.surfaceScale; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFEDiffuseLightingElement.prototype.diffuseConstant; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFEDiffuseLightingElement.prototype.kernelUnitLengthX; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFEDiffuseLightingElement.prototype.kernelUnitLengthY; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEDiffuseLightingElement.prototype.x; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEDiffuseLightingElement.prototype.y; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEDiffuseLightingElement.prototype.width; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEDiffuseLightingElement.prototype.height; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEDiffuseLightingElement.prototype.result; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEDiffuseLightingElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGFEDiffuseLightingElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGFilterPrimitiveStandardAttributes} - */ -function SVGFEMorphologyElement(){} - - -/** - * @const - * @type {number} - */ -SVGFEMorphologyElement.SVG_MORPHOLOGY_OPERATOR_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGFEMorphologyElement.prototype.SVG_MORPHOLOGY_OPERATOR_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGFEMorphologyElement.SVG_MORPHOLOGY_OPERATOR_ERODE; - - -/** - * @const - * @type {number} - */ -SVGFEMorphologyElement.prototype.SVG_MORPHOLOGY_OPERATOR_ERODE; - - -/** - * @const - * @type {number} - */ -SVGFEMorphologyElement.SVG_MORPHOLOGY_OPERATOR_DILATE; - - -/** - * @const - * @type {number} - */ -SVGFEMorphologyElement.prototype.SVG_MORPHOLOGY_OPERATOR_DILATE; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEMorphologyElement.prototype.in1; - - -/** - * @type {!SVGAnimatedEnumeration} - */ -SVGFEMorphologyElement.prototype.operator; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFEMorphologyElement.prototype.radiusX; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFEMorphologyElement.prototype.radiusY; - - -/** - * @param {number=} opt_radiusX - * @param {number=} opt_radiusY - * @return {undefined} - */ -SVGFEMorphologyElement.prototype.setRadius = function(opt_radiusX, opt_radiusY){}; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEMorphologyElement.prototype.x; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEMorphologyElement.prototype.y; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEMorphologyElement.prototype.width; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEMorphologyElement.prototype.height; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEMorphologyElement.prototype.result; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEMorphologyElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGFEMorphologyElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGURIReference} - * @implements {SVGStylable} - */ -function SVGGlyphRefElement(){} - - -/** - * @type {string} - */ -SVGGlyphRefElement.prototype.glyphRef; - - -/** - * @type {string} - */ -SVGGlyphRefElement.prototype.format; - - -/** - * @type {number} - */ -SVGGlyphRefElement.prototype.x; - - -/** - * @type {number} - */ -SVGGlyphRefElement.prototype.y; - - -/** - * @type {number} - */ -SVGGlyphRefElement.prototype.dx; - - -/** - * @type {number} - */ -SVGGlyphRefElement.prototype.dy; - - -/** - * @type {!SVGAnimatedString} - */ -SVGGlyphRefElement.prototype.href; - - -/** - * @type {!SVGAnimatedString} - */ -SVGGlyphRefElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGGlyphRefElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGTests} - * @implements {SVGLangSpace} - * @implements {SVGExternalResourcesRequired} - * @implements {SVGStylable} - */ -function SVGTextContentElement(){} - - -/** - * @const - * @type {number} - */ -SVGTextContentElement.LENGTHADJUST_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGTextContentElement.prototype.LENGTHADJUST_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGTextContentElement.LENGTHADJUST_SPACING; - - -/** - * @const - * @type {number} - */ -SVGTextContentElement.prototype.LENGTHADJUST_SPACING; - - -/** - * @const - * @type {number} - */ -SVGTextContentElement.LENGTHADJUST_SPACINGANDGLYPHS; - - -/** - * @const - * @type {number} - */ -SVGTextContentElement.prototype.LENGTHADJUST_SPACINGANDGLYPHS; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGTextContentElement.prototype.textLength; - - -/** - * @type {!SVGAnimatedEnumeration} - */ -SVGTextContentElement.prototype.lengthAdjust; - - -/** - * @return {number} - */ -SVGTextContentElement.prototype.getNumberOfChars = function(){}; - - -/** - * @return {number} - */ -SVGTextContentElement.prototype.getComputedTextLength = function(){}; - - -/** - * @param {number=} opt_offset - * @param {number=} opt_length - * @return {number} - */ -SVGTextContentElement.prototype.getSubStringLength = function(opt_offset, opt_length){}; - - -/** - * @param {number=} opt_offset - * @return {!SVGPoint} - */ -SVGTextContentElement.prototype.getStartPositionOfChar = function(opt_offset){}; - - -/** - * @param {number=} opt_offset - * @return {!SVGPoint} - */ -SVGTextContentElement.prototype.getEndPositionOfChar = function(opt_offset){}; - - -/** - * @param {number=} opt_offset - * @return {!SVGRect} - */ -SVGTextContentElement.prototype.getExtentOfChar = function(opt_offset){}; - - -/** - * @param {number=} opt_offset - * @return {number} - */ -SVGTextContentElement.prototype.getRotationOfChar = function(opt_offset){}; - - -/** - * @param {!SVGPoint=} opt_point - * @return {number} - */ -SVGTextContentElement.prototype.getCharNumAtPosition = function(opt_point){}; - - -/** - * @param {number=} opt_offset - * @param {number=} opt_length - * @return {undefined} - */ -SVGTextContentElement.prototype.selectSubString = function(opt_offset, opt_length){}; - - -/** - * @type {!SVGAnimatedBoolean} - */ -SVGTextContentElement.prototype.externalResourcesRequired; - - -/** - * @type {!SVGStringList} - */ -SVGTextContentElement.prototype.requiredFeatures; - - -/** - * @type {!SVGStringList} - */ -SVGTextContentElement.prototype.requiredExtensions; - - -/** - * @type {!SVGStringList} - */ -SVGTextContentElement.prototype.systemLanguage; - - -/** - * @param {string=} opt_extension - * @return {boolean} - * @override - */ -SVGTextContentElement.prototype.hasExtension = function(opt_extension){}; - - -/** - * @type {!SVGAnimatedString} - */ -SVGTextContentElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGTextContentElement.prototype.getPresentationAttribute = function(opt_name){}; - - -/** - * @type {string} - */ -SVGTextContentElement.prototype.xmllang; - - -/** - * @type {string} - */ -SVGTextContentElement.prototype.xmlspace; - - - -/** - * @constructor - * @extends {SVGElement} - */ -function SVGFESpotLightElement(){} - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFESpotLightElement.prototype.x; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFESpotLightElement.prototype.y; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFESpotLightElement.prototype.z; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFESpotLightElement.prototype.pointsAtX; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFESpotLightElement.prototype.pointsAtY; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFESpotLightElement.prototype.pointsAtZ; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFESpotLightElement.prototype.specularExponent; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFESpotLightElement.prototype.limitingConeAngle; - - - -/** - * @constructor - * @extends {SVGElement} - */ -function SVGFontFaceNameElement(){} - - - -/** - * @constructor - * @extends {SVGElement} - */ -function SVGFEPointLightElement(){} - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFEPointLightElement.prototype.x; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFEPointLightElement.prototype.y; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFEPointLightElement.prototype.z; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGFilterPrimitiveStandardAttributes} - */ -function SVGFEDropShadowElement(){} - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEDropShadowElement.prototype.in1; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFEDropShadowElement.prototype.dx; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFEDropShadowElement.prototype.dy; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFEDropShadowElement.prototype.stdDeviationX; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFEDropShadowElement.prototype.stdDeviationY; - - -/** - * @param {number=} opt_stdDeviationX - * @param {number=} opt_stdDeviationY - * @return {undefined} - */ -SVGFEDropShadowElement.prototype.setStdDeviation = function(opt_stdDeviationX, opt_stdDeviationY){}; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEDropShadowElement.prototype.x; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEDropShadowElement.prototype.y; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEDropShadowElement.prototype.width; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEDropShadowElement.prototype.height; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEDropShadowElement.prototype.result; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEDropShadowElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGFEDropShadowElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGFilterPrimitiveStandardAttributes} - */ -function SVGFETileElement(){} - - -/** - * @type {!SVGAnimatedString} - */ -SVGFETileElement.prototype.in1; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFETileElement.prototype.x; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFETileElement.prototype.y; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFETileElement.prototype.width; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFETileElement.prototype.height; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFETileElement.prototype.result; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFETileElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGFETileElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGElement} - */ -function SVGMissingGlyphElement(){} - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGFilterPrimitiveStandardAttributes} - */ -function SVGFEBlendElement(){} - - -/** - * @const - * @type {number} - */ -SVGFEBlendElement.SVG_FEBLEND_MODE_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGFEBlendElement.prototype.SVG_FEBLEND_MODE_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGFEBlendElement.SVG_FEBLEND_MODE_NORMAL; - - -/** - * @const - * @type {number} - */ -SVGFEBlendElement.prototype.SVG_FEBLEND_MODE_NORMAL; - - -/** - * @const - * @type {number} - */ -SVGFEBlendElement.SVG_FEBLEND_MODE_MULTIPLY; - - -/** - * @const - * @type {number} - */ -SVGFEBlendElement.prototype.SVG_FEBLEND_MODE_MULTIPLY; - - -/** - * @const - * @type {number} - */ -SVGFEBlendElement.SVG_FEBLEND_MODE_SCREEN; - - -/** - * @const - * @type {number} - */ -SVGFEBlendElement.prototype.SVG_FEBLEND_MODE_SCREEN; - - -/** - * @const - * @type {number} - */ -SVGFEBlendElement.SVG_FEBLEND_MODE_DARKEN; - - -/** - * @const - * @type {number} - */ -SVGFEBlendElement.prototype.SVG_FEBLEND_MODE_DARKEN; - - -/** - * @const - * @type {number} - */ -SVGFEBlendElement.SVG_FEBLEND_MODE_LIGHTEN; - - -/** - * @const - * @type {number} - */ -SVGFEBlendElement.prototype.SVG_FEBLEND_MODE_LIGHTEN; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEBlendElement.prototype.in1; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEBlendElement.prototype.in2; - - -/** - * @type {!SVGAnimatedEnumeration} - */ -SVGFEBlendElement.prototype.mode; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEBlendElement.prototype.x; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEBlendElement.prototype.y; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEBlendElement.prototype.width; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEBlendElement.prototype.height; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEBlendElement.prototype.result; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEBlendElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGFEBlendElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGURIReference} - * @implements {SVGTests} - * @implements {SVGLangSpace} - * @implements {SVGExternalResourcesRequired} - * @implements {SVGStylable} - * @implements {SVGFitToViewBox} - */ -function SVGPatternElement(){} - - -/** - * @type {!SVGAnimatedEnumeration} - */ -SVGPatternElement.prototype.patternUnits; - - -/** - * @type {!SVGAnimatedEnumeration} - */ -SVGPatternElement.prototype.patternContentUnits; - - -/** - * @type {!SVGAnimatedTransformList} - */ -SVGPatternElement.prototype.patternTransform; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGPatternElement.prototype.x; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGPatternElement.prototype.y; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGPatternElement.prototype.width; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGPatternElement.prototype.height; - - -/** - * @type {!SVGAnimatedBoolean} - */ -SVGPatternElement.prototype.externalResourcesRequired; - - -/** - * @type {!SVGAnimatedRect} - */ -SVGPatternElement.prototype.viewBox; - - -/** - * @type {!SVGAnimatedPreserveAspectRatio} - */ -SVGPatternElement.prototype.preserveAspectRatio; - - -/** - * @type {!SVGAnimatedString} - */ -SVGPatternElement.prototype.href; - - -/** - * @type {string} - */ -SVGPatternElement.prototype.xmllang; - - -/** - * @type {string} - */ -SVGPatternElement.prototype.xmlspace; - - -/** - * @type {!SVGStringList} - */ -SVGPatternElement.prototype.requiredFeatures; - - -/** - * @type {!SVGStringList} - */ -SVGPatternElement.prototype.requiredExtensions; - - -/** - * @type {!SVGStringList} - */ -SVGPatternElement.prototype.systemLanguage; - - -/** - * @param {string=} opt_extension - * @return {boolean} - * @override - */ -SVGPatternElement.prototype.hasExtension = function(opt_extension){}; - - -/** - * @type {!SVGAnimatedString} - */ -SVGPatternElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGPatternElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGElement} - */ -function SVGFEMergeNodeElement(){} - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEMergeNodeElement.prototype.in1; - - - -/** - * @constructor - * @extends {SVGComponentTransferFunctionElement} - */ -function SVGFEFuncBElement(){} - - - -/** - * @constructor - * @extends {SVGPathSeg} - */ -function SVGPathSegMovetoRel(){} - - -/** - * @type {number} - */ -SVGPathSegMovetoRel.prototype.x; - - -/** - * @type {number} - */ -SVGPathSegMovetoRel.prototype.y; - - - -/** - * @interface - */ -function SVGLocatable(){} - - -/** - * @type {!SVGElement} - */ -SVGLocatable.prototype.nearestViewportElement; - - -/** - * @type {!SVGElement} - */ -SVGLocatable.prototype.farthestViewportElement; - - -/** - * @return {!SVGRect} - */ -SVGLocatable.prototype.getBBox = function(){}; - - -/** - * @return {!SVGMatrix} - */ -SVGLocatable.prototype.getCTM = function(){}; - - -/** - * @return {!SVGMatrix} - */ -SVGLocatable.prototype.getScreenCTM = function(){}; - - -/** - * @param {!SVGElement=} opt_element - * @return {!SVGMatrix} - */ -SVGLocatable.prototype.getTransformToElement = function(opt_element){}; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGTests} - * @implements {SVGLangSpace} - * @implements {SVGExternalResourcesRequired} - * @implements {SVGStylable} - * @implements {SVGLocatable} - * @implements {SVGFitToViewBox} - * @implements {SVGZoomAndPan} - */ -function SVGSVGElement(){} - - -/** - * @type {!SVGAnimatedLength} - */ -SVGSVGElement.prototype.x; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGSVGElement.prototype.y; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGSVGElement.prototype.width; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGSVGElement.prototype.height; - - -/** - * @type {string} - */ -SVGSVGElement.prototype.contentScriptType; - - -/** - * @type {string} - */ -SVGSVGElement.prototype.contentStyleType; - - -/** - * @type {!SVGRect} - */ -SVGSVGElement.prototype.viewport; - - -/** - * @type {number} - */ -SVGSVGElement.prototype.pixelUnitToMillimeterX; - - -/** - * @type {number} - */ -SVGSVGElement.prototype.pixelUnitToMillimeterY; - - -/** - * @type {number} - */ -SVGSVGElement.prototype.screenPixelToMillimeterX; - - -/** - * @type {number} - */ -SVGSVGElement.prototype.screenPixelToMillimeterY; - - -/** - * @type {boolean} - */ -SVGSVGElement.prototype.useCurrentView; - - -/** - * @type {number} - */ -SVGSVGElement.prototype.currentScale; - - -/** - * @type {!SVGPoint} - */ -SVGSVGElement.prototype.currentTranslate; - - -/** - * @param {number=} opt_maxWaitMilliseconds - * @return {number} - */ -SVGSVGElement.prototype.suspendRedraw = function(opt_maxWaitMilliseconds){}; - - -/** - * @param {number=} opt_suspendHandleId - * @return {undefined} - */ -SVGSVGElement.prototype.unsuspendRedraw = function(opt_suspendHandleId){}; - - -/** */ -SVGSVGElement.prototype.unsuspendRedrawAll = function(){}; - - -/** */ -SVGSVGElement.prototype.forceRedraw = function(){}; - - -/** */ -SVGSVGElement.prototype.pauseAnimations = function(){}; - - -/** */ -SVGSVGElement.prototype.unpauseAnimations = function(){}; - - -/** - * @return {boolean} - */ -SVGSVGElement.prototype.animationsPaused = function(){}; - - -/** - * @return {number} - */ -SVGSVGElement.prototype.getCurrentTime = function(){}; - - -/** - * @param {number=} opt_seconds - * @return {undefined} - */ -SVGSVGElement.prototype.setCurrentTime = function(opt_seconds){}; - - -/** - * @param {!SVGRect=} opt_rect - * @param {!SVGElement=} opt_referenceElement - * @return {!NodeList} - */ -SVGSVGElement.prototype.getIntersectionList = function(opt_rect, opt_referenceElement){}; - - -/** - * @param {!SVGRect=} opt_rect - * @param {!SVGElement=} opt_referenceElement - * @return {!NodeList} - */ -SVGSVGElement.prototype.getEnclosureList = function(opt_rect, opt_referenceElement){}; - - -/** - * @param {!SVGElement=} opt_element - * @param {!SVGRect=} opt_rect - * @return {boolean} - */ -SVGSVGElement.prototype.checkIntersection = function(opt_element, opt_rect){}; - - -/** - * @param {!SVGElement=} opt_element - * @param {!SVGRect=} opt_rect - * @return {boolean} - */ -SVGSVGElement.prototype.checkEnclosure = function(opt_element, opt_rect){}; - - -/** */ -SVGSVGElement.prototype.deselectAll = function(){}; - - -/** - * @return {!SVGNumber} - */ -SVGSVGElement.prototype.createSVGNumber = function(){}; - - -/** - * @return {!SVGLength} - */ -SVGSVGElement.prototype.createSVGLength = function(){}; - - -/** - * @return {!SVGAngle} - */ -SVGSVGElement.prototype.createSVGAngle = function(){}; - - -/** - * @return {!SVGPoint} - */ -SVGSVGElement.prototype.createSVGPoint = function(){}; - - -/** - * @return {!SVGMatrix} - */ -SVGSVGElement.prototype.createSVGMatrix = function(){}; - - -/** - * @return {!SVGRect} - */ -SVGSVGElement.prototype.createSVGRect = function(){}; - - -/** - * @return {!SVGTransform} - */ -SVGSVGElement.prototype.createSVGTransform = function(){}; - - -/** - * @param {!SVGMatrix=} opt_matrix - * @return {!SVGTransform} - */ -SVGSVGElement.prototype.createSVGTransformFromMatrix = function(opt_matrix){}; - - -/** - * @param {string=} opt_elementId - * @return {Element} - * @see https://developer.mozilla.org/en/docs/Web/API/SVGSVGElement - */ -SVGSVGElement.prototype.getElementById = function(opt_elementId){}; - - -/** - * @type {!SVGAnimatedBoolean} - */ -SVGSVGElement.prototype.externalResourcesRequired; - - -/** - * @type {!SVGAnimatedRect} - */ -SVGSVGElement.prototype.viewBox; - - -/** - * @type {!SVGAnimatedPreserveAspectRatio} - */ -SVGSVGElement.prototype.preserveAspectRatio; - - -/** - * @type {string} - */ -SVGSVGElement.prototype.xmllang; - - -/** - * @type {string} - */ -SVGSVGElement.prototype.xmlspace; - - -/** - * @const - * @type {number} - */ -SVGSVGElement.SVG_ZOOMANDPAN_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGSVGElement.prototype.SVG_ZOOMANDPAN_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGSVGElement.SVG_ZOOMANDPAN_DISABLE; - - -/** - * @const - * @type {number} - */ -SVGSVGElement.prototype.SVG_ZOOMANDPAN_DISABLE; - - -/** - * @const - * @type {number} - */ -SVGSVGElement.SVG_ZOOMANDPAN_MAGNIFY; - - -/** - * @const - * @type {number} - */ -SVGSVGElement.prototype.SVG_ZOOMANDPAN_MAGNIFY; - - -/** - * @type {number} - */ -SVGSVGElement.prototype.zoomAndPan; - - -/** - * @type {!SVGElement} - */ -SVGSVGElement.prototype.nearestViewportElement; - - -/** - * @type {!SVGElement} - */ -SVGSVGElement.prototype.farthestViewportElement; - - -/** - * @return {!SVGRect} - * @override - */ -SVGSVGElement.prototype.getBBox = function(){}; - - -/** - * @return {!SVGMatrix} - * @override - */ -SVGSVGElement.prototype.getCTM = function(){}; - - -/** - * @return {!SVGMatrix} - * @override - */ -SVGSVGElement.prototype.getScreenCTM = function(){}; - - -/** - * @param {!SVGElement=} opt_element - * @return {!SVGMatrix} - * @override - */ -SVGSVGElement.prototype.getTransformToElement = function(opt_element){}; - - -/** - * @type {!SVGStringList} - */ -SVGSVGElement.prototype.requiredFeatures; - - -/** - * @type {!SVGStringList} - */ -SVGSVGElement.prototype.requiredExtensions; - - -/** - * @type {!SVGStringList} - */ -SVGSVGElement.prototype.systemLanguage; - - -/** - * @param {string=} opt_extension - * @return {boolean} - * @override - */ -SVGSVGElement.prototype.hasExtension = function(opt_extension){}; - - -/** - * @type {!SVGAnimatedString} - */ -SVGSVGElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGSVGElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @interface - * @extends {SVGLocatable} - */ -function SVGTransformable(){} - - -/** - * @type {!SVGAnimatedTransformList} - */ -SVGTransformable.prototype.transform; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGTests} - * @implements {SVGLangSpace} - * @implements {SVGExternalResourcesRequired} - * @implements {SVGStylable} - * @implements {SVGTransformable} - */ -function SVGDefsElement(){} - - -/** - * @type {!SVGAnimatedBoolean} - */ -SVGDefsElement.prototype.externalResourcesRequired; - - -/** - * @type {!SVGAnimatedTransformList} - */ -SVGDefsElement.prototype.transform; - - -/** - * @type {string} - */ -SVGDefsElement.prototype.xmllang; - - -/** - * @type {string} - */ -SVGDefsElement.prototype.xmlspace; - - -/** - * @type {!SVGElement} - */ -SVGDefsElement.prototype.nearestViewportElement; - - -/** - * @type {!SVGElement} - */ -SVGDefsElement.prototype.farthestViewportElement; - - -/** - * @return {!SVGRect} - * @override - */ -SVGDefsElement.prototype.getBBox = function(){}; - - -/** - * @return {!SVGMatrix} - * @override - */ -SVGDefsElement.prototype.getCTM = function(){}; - - -/** - * @return {!SVGMatrix} - * @override - */ -SVGDefsElement.prototype.getScreenCTM = function(){}; - - -/** - * @param {!SVGElement=} opt_element - * @return {!SVGMatrix} - * @override - */ -SVGDefsElement.prototype.getTransformToElement = function(opt_element){}; - - -/** - * @type {!SVGStringList} - */ -SVGDefsElement.prototype.requiredFeatures; - - -/** - * @type {!SVGStringList} - */ -SVGDefsElement.prototype.requiredExtensions; - - -/** - * @type {!SVGStringList} - */ -SVGDefsElement.prototype.systemLanguage; - - -/** - * @param {string=} opt_extension - * @return {boolean} - * @override - */ -SVGDefsElement.prototype.hasExtension = function(opt_extension){}; - - -/** - * @type {!SVGAnimatedString} - */ -SVGDefsElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGDefsElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGTests} - * @implements {SVGLangSpace} - * @implements {SVGExternalResourcesRequired} - * @implements {SVGStylable} - * @implements {SVGTransformable} - */ -function SVGPolygonElement(){} - - -/** - * @type {!SVGPointList} - */ -SVGPolygonElement.prototype.points; - - -/** - * @type {!SVGPointList} - */ -SVGPolygonElement.prototype.animatedPoints; - - -/** - * @type {!SVGAnimatedBoolean} - */ -SVGPolygonElement.prototype.externalResourcesRequired; - - -/** - * @type {!SVGAnimatedTransformList} - */ -SVGPolygonElement.prototype.transform; - - -/** - * @type {string} - */ -SVGPolygonElement.prototype.xmllang; - - -/** - * @type {string} - */ -SVGPolygonElement.prototype.xmlspace; - - -/** - * @type {!SVGElement} - */ -SVGPolygonElement.prototype.nearestViewportElement; - - -/** - * @type {!SVGElement} - */ -SVGPolygonElement.prototype.farthestViewportElement; - - -/** - * @return {!SVGRect} - * @override - */ -SVGPolygonElement.prototype.getBBox = function(){}; - - -/** - * @return {!SVGMatrix} - * @override - */ -SVGPolygonElement.prototype.getCTM = function(){}; - - -/** - * @return {!SVGMatrix} - * @override - */ -SVGPolygonElement.prototype.getScreenCTM = function(){}; - - -/** - * @param {!SVGElement=} opt_element - * @return {!SVGMatrix} - * @override - */ -SVGPolygonElement.prototype.getTransformToElement = function(opt_element){}; - - -/** - * @type {!SVGStringList} - */ -SVGPolygonElement.prototype.requiredFeatures; - - -/** - * @type {!SVGStringList} - */ -SVGPolygonElement.prototype.requiredExtensions; - - -/** - * @type {!SVGStringList} - */ -SVGPolygonElement.prototype.systemLanguage; - - -/** - * @param {string=} opt_extension - * @return {boolean} - * @override - */ -SVGPolygonElement.prototype.hasExtension = function(opt_extension){}; - - -/** - * @type {!SVGAnimatedString} - */ -SVGPolygonElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGPolygonElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGTests} - * @implements {SVGLangSpace} - * @implements {SVGExternalResourcesRequired} - * @implements {SVGStylable} - * @implements {SVGTransformable} - */ -function SVGPathElement(){} - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGPathElement.prototype.pathLength; - - -/** - * @type {!SVGPathSegList} - */ -SVGPathElement.prototype.pathSegList; - - -/** - * @type {!SVGPathSegList} - */ -SVGPathElement.prototype.normalizedPathSegList; - - -/** - * @type {!SVGPathSegList} - */ -SVGPathElement.prototype.animatedPathSegList; - - -/** - * @type {!SVGPathSegList} - */ -SVGPathElement.prototype.animatedNormalizedPathSegList; - - -/** - * @return {number} - */ -SVGPathElement.prototype.getTotalLength = function(){}; - - -/** - * @param {number=} opt_distance - * @return {!SVGPoint} - */ -SVGPathElement.prototype.getPointAtLength = function(opt_distance){}; - - -/** - * @param {number=} opt_distance - * @return {number} - */ -SVGPathElement.prototype.getPathSegAtLength = function(opt_distance){}; - - -/** - * @return {!SVGPathSegClosePath} - */ -SVGPathElement.prototype.createSVGPathSegClosePath = function(){}; - - -/** - * @param {number=} opt_x - * @param {number=} opt_y - * @return {!SVGPathSegMovetoAbs} - */ -SVGPathElement.prototype.createSVGPathSegMovetoAbs = function(opt_x, opt_y){}; - - -/** - * @param {number=} opt_x - * @param {number=} opt_y - * @return {!SVGPathSegMovetoRel} - */ -SVGPathElement.prototype.createSVGPathSegMovetoRel = function(opt_x, opt_y){}; - - -/** - * @param {number=} opt_x - * @param {number=} opt_y - * @return {!SVGPathSegLinetoAbs} - */ -SVGPathElement.prototype.createSVGPathSegLinetoAbs = function(opt_x, opt_y){}; - - -/** - * @param {number=} opt_x - * @param {number=} opt_y - * @return {!SVGPathSegLinetoRel} - */ -SVGPathElement.prototype.createSVGPathSegLinetoRel = function(opt_x, opt_y){}; - - -/** - * @param {number=} opt_x - * @param {number=} opt_y - * @param {number=} opt_x1 - * @param {number=} opt_y1 - * @param {number=} opt_x2 - * @param {number=} opt_y2 - * @return {!SVGPathSegCurvetoCubicAbs} - */ -SVGPathElement.prototype.createSVGPathSegCurvetoCubicAbs = function(opt_x, opt_y, opt_x1, opt_y1, opt_x2, opt_y2){}; - - -/** - * @param {number=} opt_x - * @param {number=} opt_y - * @param {number=} opt_x1 - * @param {number=} opt_y1 - * @param {number=} opt_x2 - * @param {number=} opt_y2 - * @return {!SVGPathSegCurvetoCubicRel} - */ -SVGPathElement.prototype.createSVGPathSegCurvetoCubicRel = function(opt_x, opt_y, opt_x1, opt_y1, opt_x2, opt_y2){}; - - -/** - * @param {number=} opt_x - * @param {number=} opt_y - * @param {number=} opt_x1 - * @param {number=} opt_y1 - * @return {!SVGPathSegCurvetoQuadraticAbs} - */ -SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticAbs = function(opt_x, opt_y, opt_x1, opt_y1){}; - - -/** - * @param {number=} opt_x - * @param {number=} opt_y - * @param {number=} opt_x1 - * @param {number=} opt_y1 - * @return {!SVGPathSegCurvetoQuadraticRel} - */ -SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticRel = function(opt_x, opt_y, opt_x1, opt_y1){}; - - -/** - * @param {number=} opt_x - * @param {number=} opt_y - * @param {number=} opt_r1 - * @param {number=} opt_r2 - * @param {number=} opt_angle - * @param {boolean=} opt_largeArcFlag - * @param {boolean=} opt_sweepFlag - * @return {!SVGPathSegArcAbs} - */ -SVGPathElement.prototype.createSVGPathSegArcAbs = function(opt_x, opt_y, opt_r1, opt_r2, opt_angle, opt_largeArcFlag, opt_sweepFlag){}; - - -/** - * @param {number=} opt_x - * @param {number=} opt_y - * @param {number=} opt_r1 - * @param {number=} opt_r2 - * @param {number=} opt_angle - * @param {boolean=} opt_largeArcFlag - * @param {boolean=} opt_sweepFlag - * @return {!SVGPathSegArcRel} - */ -SVGPathElement.prototype.createSVGPathSegArcRel = function(opt_x, opt_y, opt_r1, opt_r2, opt_angle, opt_largeArcFlag, opt_sweepFlag){}; - - -/** - * @param {number=} opt_x - * @return {!SVGPathSegLinetoHorizontalAbs} - */ -SVGPathElement.prototype.createSVGPathSegLinetoHorizontalAbs = function(opt_x){}; - - -/** - * @param {number=} opt_x - * @return {!SVGPathSegLinetoHorizontalRel} - */ -SVGPathElement.prototype.createSVGPathSegLinetoHorizontalRel = function(opt_x){}; - - -/** - * @param {number=} opt_y - * @return {!SVGPathSegLinetoVerticalAbs} - */ -SVGPathElement.prototype.createSVGPathSegLinetoVerticalAbs = function(opt_y){}; - - -/** - * @param {number=} opt_y - * @return {!SVGPathSegLinetoVerticalRel} - */ -SVGPathElement.prototype.createSVGPathSegLinetoVerticalRel = function(opt_y){}; - - -/** - * @param {number=} opt_x - * @param {number=} opt_y - * @param {number=} opt_x2 - * @param {number=} opt_y2 - * @return {!SVGPathSegCurvetoCubicSmoothAbs} - */ -SVGPathElement.prototype.createSVGPathSegCurvetoCubicSmoothAbs = function(opt_x, opt_y, opt_x2, opt_y2){}; - - -/** - * @param {number=} opt_x - * @param {number=} opt_y - * @param {number=} opt_x2 - * @param {number=} opt_y2 - * @return {!SVGPathSegCurvetoCubicSmoothRel} - */ -SVGPathElement.prototype.createSVGPathSegCurvetoCubicSmoothRel = function(opt_x, opt_y, opt_x2, opt_y2){}; - - -/** - * @param {number=} opt_x - * @param {number=} opt_y - * @return {!SVGPathSegCurvetoQuadraticSmoothAbs} - */ -SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticSmoothAbs = function(opt_x, opt_y){}; - - -/** - * @param {number=} opt_x - * @param {number=} opt_y - * @return {!SVGPathSegCurvetoQuadraticSmoothRel} - */ -SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticSmoothRel = function(opt_x, opt_y){}; - - -/** - * @type {!SVGAnimatedBoolean} - */ -SVGPathElement.prototype.externalResourcesRequired; - - -/** - * @type {!SVGAnimatedTransformList} - */ -SVGPathElement.prototype.transform; - - -/** - * @type {string} - */ -SVGPathElement.prototype.xmllang; - - -/** - * @type {string} - */ -SVGPathElement.prototype.xmlspace; - - -/** - * @type {!SVGElement} - */ -SVGPathElement.prototype.nearestViewportElement; - - -/** - * @type {!SVGElement} - */ -SVGPathElement.prototype.farthestViewportElement; - - -/** - * @return {!SVGRect} - * @override - */ -SVGPathElement.prototype.getBBox = function(){}; - - -/** - * @return {!SVGMatrix} - * @override - */ -SVGPathElement.prototype.getCTM = function(){}; - - -/** - * @return {!SVGMatrix} - * @override - */ -SVGPathElement.prototype.getScreenCTM = function(){}; - - -/** - * @param {!SVGElement=} opt_element - * @return {!SVGMatrix} - * @override - */ -SVGPathElement.prototype.getTransformToElement = function(opt_element){}; - - -/** - * @type {!SVGStringList} - */ -SVGPathElement.prototype.requiredFeatures; - - -/** - * @type {!SVGStringList} - */ -SVGPathElement.prototype.requiredExtensions; - - -/** - * @type {!SVGStringList} - */ -SVGPathElement.prototype.systemLanguage; - - -/** - * @param {string=} opt_extension - * @return {boolean} - * @override - */ -SVGPathElement.prototype.hasExtension = function(opt_extension){}; - - -/** - * @type {!SVGAnimatedString} - */ -SVGPathElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGPathElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGTests} - * @implements {SVGLangSpace} - * @implements {SVGExternalResourcesRequired} - * @implements {SVGStylable} - * @implements {SVGTransformable} - */ -function SVGCircleElement(){} - - -/** - * @type {!SVGAnimatedLength} - */ -SVGCircleElement.prototype.cx; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGCircleElement.prototype.cy; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGCircleElement.prototype.r; - - -/** - * @type {!SVGAnimatedBoolean} - */ -SVGCircleElement.prototype.externalResourcesRequired; - - -/** - * @type {!SVGAnimatedTransformList} - */ -SVGCircleElement.prototype.transform; - - -/** - * @type {string} - */ -SVGCircleElement.prototype.xmllang; - - -/** - * @type {string} - */ -SVGCircleElement.prototype.xmlspace; - - -/** - * @type {!SVGElement} - */ -SVGCircleElement.prototype.nearestViewportElement; - - -/** - * @type {!SVGElement} - */ -SVGCircleElement.prototype.farthestViewportElement; - - -/** - * @return {!SVGRect} - * @override - */ -SVGCircleElement.prototype.getBBox = function(){}; - - -/** - * @return {!SVGMatrix} - * @override - */ -SVGCircleElement.prototype.getCTM = function(){}; - - -/** - * @return {!SVGMatrix} - * @override - */ -SVGCircleElement.prototype.getScreenCTM = function(){}; - - -/** - * @param {!SVGElement=} opt_element - * @return {!SVGMatrix} - * @override - */ -SVGCircleElement.prototype.getTransformToElement = function(opt_element){}; - - -/** - * @type {!SVGStringList} - */ -SVGCircleElement.prototype.requiredFeatures; - - -/** - * @type {!SVGStringList} - */ -SVGCircleElement.prototype.requiredExtensions; - - -/** - * @type {!SVGStringList} - */ -SVGCircleElement.prototype.systemLanguage; - - -/** - * @param {string=} opt_extension - * @return {boolean} - * @override - */ -SVGCircleElement.prototype.hasExtension = function(opt_extension){}; - - -/** - * @type {!SVGAnimatedString} - */ -SVGCircleElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGCircleElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGURIReference} - * @implements {SVGTests} - * @implements {SVGLangSpace} - * @implements {SVGExternalResourcesRequired} - * @implements {SVGStylable} - * @implements {SVGTransformable} - */ -function SVGUseElement(){} - - -/** - * @type {!SVGAnimatedLength} - */ -SVGUseElement.prototype.x; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGUseElement.prototype.y; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGUseElement.prototype.width; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGUseElement.prototype.height; - - -/** - * @type {!SVGElementInstance} - */ -SVGUseElement.prototype.instanceRoot; - - -/** - * @type {!SVGElementInstance} - */ -SVGUseElement.prototype.animatedInstanceRoot; - - -/** - * @type {!SVGAnimatedBoolean} - */ -SVGUseElement.prototype.externalResourcesRequired; - - -/** - * @type {!SVGElement} - */ -SVGUseElement.prototype.nearestViewportElement; - - -/** - * @type {!SVGElement} - */ -SVGUseElement.prototype.farthestViewportElement; - - -/** - * @return {!SVGRect} - * @override - */ -SVGUseElement.prototype.getBBox = function(){}; - - -/** - * @return {!SVGMatrix} - * @override - */ -SVGUseElement.prototype.getCTM = function(){}; - - -/** - * @return {!SVGMatrix} - * @override - */ -SVGUseElement.prototype.getScreenCTM = function(){}; - - -/** - * @param {!SVGElement=} opt_element - * @return {!SVGMatrix} - * @override - */ -SVGUseElement.prototype.getTransformToElement = function(opt_element){}; - - -/** - * @type {!SVGAnimatedTransformList} - */ -SVGUseElement.prototype.transform; - - -/** - * @type {!SVGAnimatedString} - */ -SVGUseElement.prototype.href; - - -/** - * @type {string} - */ -SVGUseElement.prototype.xmllang; - - -/** - * @type {string} - */ -SVGUseElement.prototype.xmlspace; - - -/** - * @type {!SVGStringList} - */ -SVGUseElement.prototype.requiredFeatures; - - -/** - * @type {!SVGStringList} - */ -SVGUseElement.prototype.requiredExtensions; - - -/** - * @type {!SVGStringList} - */ -SVGUseElement.prototype.systemLanguage; - - -/** - * @param {string=} opt_extension - * @return {boolean} - * @override - */ -SVGUseElement.prototype.hasExtension = function(opt_extension){}; - - -/** - * @type {!SVGAnimatedString} - */ -SVGUseElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGUseElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGTests} - * @implements {SVGLangSpace} - * @implements {SVGExternalResourcesRequired} - * @implements {SVGStylable} - * @implements {SVGTransformable} - */ -function SVGPolylineElement(){} - - -/** - * @type {!SVGPointList} - */ -SVGPolylineElement.prototype.points; - - -/** - * @type {!SVGPointList} - */ -SVGPolylineElement.prototype.animatedPoints; - - -/** - * @type {!SVGAnimatedBoolean} - */ -SVGPolylineElement.prototype.externalResourcesRequired; - - -/** - * @type {!SVGAnimatedTransformList} - */ -SVGPolylineElement.prototype.transform; - - -/** - * @type {string} - */ -SVGPolylineElement.prototype.xmllang; - - -/** - * @type {string} - */ -SVGPolylineElement.prototype.xmlspace; - - -/** - * @type {!SVGElement} - */ -SVGPolylineElement.prototype.nearestViewportElement; - - -/** - * @type {!SVGElement} - */ -SVGPolylineElement.prototype.farthestViewportElement; - - -/** - * @return {!SVGRect} - * @override - */ -SVGPolylineElement.prototype.getBBox = function(){}; - - -/** - * @return {!SVGMatrix} - * @override - */ -SVGPolylineElement.prototype.getCTM = function(){}; - - -/** - * @return {!SVGMatrix} - * @override - */ -SVGPolylineElement.prototype.getScreenCTM = function(){}; - - -/** - * @param {!SVGElement=} opt_element - * @return {!SVGMatrix} - * @override - */ -SVGPolylineElement.prototype.getTransformToElement = function(opt_element){}; - - -/** - * @type {!SVGStringList} - */ -SVGPolylineElement.prototype.requiredFeatures; - - -/** - * @type {!SVGStringList} - */ -SVGPolylineElement.prototype.requiredExtensions; - - -/** - * @type {!SVGStringList} - */ -SVGPolylineElement.prototype.systemLanguage; - - -/** - * @param {string=} opt_extension - * @return {boolean} - * @override - */ -SVGPolylineElement.prototype.hasExtension = function(opt_extension){}; - - -/** - * @type {!SVGAnimatedString} - */ -SVGPolylineElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGPolylineElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGTests} - * @implements {SVGLangSpace} - * @implements {SVGExternalResourcesRequired} - * @implements {SVGStylable} - * @implements {SVGTransformable} - */ -function SVGGElement(){} - - -/** - * @type {!SVGAnimatedBoolean} - */ -SVGGElement.prototype.externalResourcesRequired; - - -/** - * @type {!SVGAnimatedTransformList} - */ -SVGGElement.prototype.transform; - - -/** - * @type {string} - */ -SVGGElement.prototype.xmllang; - - -/** - * @type {string} - */ -SVGGElement.prototype.xmlspace; - - -/** - * @type {!SVGElement} - */ -SVGGElement.prototype.nearestViewportElement; - - -/** - * @type {!SVGElement} - */ -SVGGElement.prototype.farthestViewportElement; - - -/** - * @return {!SVGRect} - * @override - */ -SVGGElement.prototype.getBBox = function(){}; - - -/** - * @return {!SVGMatrix} - * @override - */ -SVGGElement.prototype.getCTM = function(){}; - - -/** - * @return {!SVGMatrix} - * @override - */ -SVGGElement.prototype.getScreenCTM = function(){}; - - -/** - * @param {!SVGElement=} opt_element - * @return {!SVGMatrix} - * @override - */ -SVGGElement.prototype.getTransformToElement = function(opt_element){}; - - -/** - * @type {!SVGStringList} - */ -SVGGElement.prototype.requiredFeatures; - - -/** - * @type {!SVGStringList} - */ -SVGGElement.prototype.requiredExtensions; - - -/** - * @type {!SVGStringList} - */ -SVGGElement.prototype.systemLanguage; - - -/** - * @param {string=} opt_extension - * @return {boolean} - * @override - */ -SVGGElement.prototype.hasExtension = function(opt_extension){}; - - -/** - * @type {!SVGAnimatedString} - */ -SVGGElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGGElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGTests} - * @implements {SVGLangSpace} - * @implements {SVGExternalResourcesRequired} - * @implements {SVGStylable} - * @implements {SVGTransformable} - */ -function SVGSwitchElement(){} - - -/** - * @type {!SVGAnimatedBoolean} - */ -SVGSwitchElement.prototype.externalResourcesRequired; - - -/** - * @type {!SVGAnimatedTransformList} - */ -SVGSwitchElement.prototype.transform; - - -/** - * @type {string} - */ -SVGSwitchElement.prototype.xmllang; - - -/** - * @type {string} - */ -SVGSwitchElement.prototype.xmlspace; - - -/** - * @type {!SVGElement} - */ -SVGSwitchElement.prototype.nearestViewportElement; - - -/** - * @type {!SVGElement} - */ -SVGSwitchElement.prototype.farthestViewportElement; - - -/** - * @return {!SVGRect} - * @override - */ -SVGSwitchElement.prototype.getBBox = function(){}; - - -/** - * @return {!SVGMatrix} - * @override - */ -SVGSwitchElement.prototype.getCTM = function(){}; - - -/** - * @return {!SVGMatrix} - * @override - */ -SVGSwitchElement.prototype.getScreenCTM = function(){}; - - -/** - * @param {!SVGElement=} opt_element - * @return {!SVGMatrix} - * @override - */ -SVGSwitchElement.prototype.getTransformToElement = function(opt_element){}; - - -/** - * @type {!SVGStringList} - */ -SVGSwitchElement.prototype.requiredFeatures; - - -/** - * @type {!SVGStringList} - */ -SVGSwitchElement.prototype.requiredExtensions; - - -/** - * @type {!SVGStringList} - */ -SVGSwitchElement.prototype.systemLanguage; - - -/** - * @param {string=} opt_extension - * @return {boolean} - * @override - */ -SVGSwitchElement.prototype.hasExtension = function(opt_extension){}; - - -/** - * @type {!SVGAnimatedString} - */ -SVGSwitchElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGSwitchElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGURIReference} - * @implements {SVGTests} - * @implements {SVGLangSpace} - * @implements {SVGExternalResourcesRequired} - * @implements {SVGStylable} - * @implements {SVGTransformable} - */ -function SVGImageElement(){} - - -/** - * @type {!SVGAnimatedLength} - */ -SVGImageElement.prototype.x; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGImageElement.prototype.y; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGImageElement.prototype.width; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGImageElement.prototype.height; - - -/** - * @type {!SVGAnimatedPreserveAspectRatio} - */ -SVGImageElement.prototype.preserveAspectRatio; - - -/** - * @type {!SVGAnimatedBoolean} - */ -SVGImageElement.prototype.externalResourcesRequired; - - -/** - * @type {!SVGElement} - */ -SVGImageElement.prototype.nearestViewportElement; - - -/** - * @type {!SVGElement} - */ -SVGImageElement.prototype.farthestViewportElement; - - -/** - * @return {!SVGRect} - * @override - */ -SVGImageElement.prototype.getBBox = function(){}; - - -/** - * @return {!SVGMatrix} - * @override - */ -SVGImageElement.prototype.getCTM = function(){}; - - -/** - * @return {!SVGMatrix} - * @override - */ -SVGImageElement.prototype.getScreenCTM = function(){}; - - -/** - * @param {!SVGElement=} opt_element - * @return {!SVGMatrix} - * @override - */ -SVGImageElement.prototype.getTransformToElement = function(opt_element){}; - - -/** - * @type {!SVGAnimatedTransformList} - */ -SVGImageElement.prototype.transform; - - -/** - * @type {!SVGAnimatedString} - */ -SVGImageElement.prototype.href; - - -/** - * @type {string} - */ -SVGImageElement.prototype.xmllang; - - -/** - * @type {string} - */ -SVGImageElement.prototype.xmlspace; - - -/** - * @type {!SVGStringList} - */ -SVGImageElement.prototype.requiredFeatures; - - -/** - * @type {!SVGStringList} - */ -SVGImageElement.prototype.requiredExtensions; - - -/** - * @type {!SVGStringList} - */ -SVGImageElement.prototype.systemLanguage; - - -/** - * @param {string=} opt_extension - * @return {boolean} - * @override - */ -SVGImageElement.prototype.hasExtension = function(opt_extension){}; - - -/** - * @type {!SVGAnimatedString} - */ -SVGImageElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGImageElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGTests} - * @implements {SVGLangSpace} - * @implements {SVGExternalResourcesRequired} - * @implements {SVGStylable} - * @implements {SVGTransformable} - */ -function SVGRectElement(){} - - -/** - * @type {!SVGAnimatedLength} - */ -SVGRectElement.prototype.x; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGRectElement.prototype.y; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGRectElement.prototype.width; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGRectElement.prototype.height; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGRectElement.prototype.rx; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGRectElement.prototype.ry; - - -/** - * @type {!SVGAnimatedBoolean} - */ -SVGRectElement.prototype.externalResourcesRequired; - - -/** - * @type {!SVGAnimatedTransformList} - */ -SVGRectElement.prototype.transform; - - -/** - * @type {string} - */ -SVGRectElement.prototype.xmllang; - - -/** - * @type {string} - */ -SVGRectElement.prototype.xmlspace; - - -/** - * @type {!SVGElement} - */ -SVGRectElement.prototype.nearestViewportElement; - - -/** - * @type {!SVGElement} - */ -SVGRectElement.prototype.farthestViewportElement; - - -/** - * @return {!SVGRect} - * @override - */ -SVGRectElement.prototype.getBBox = function(){}; - - -/** - * @return {!SVGMatrix} - * @override - */ -SVGRectElement.prototype.getCTM = function(){}; - - -/** - * @return {!SVGMatrix} - * @override - */ -SVGRectElement.prototype.getScreenCTM = function(){}; - - -/** - * @param {!SVGElement=} opt_element - * @return {!SVGMatrix} - * @override - */ -SVGRectElement.prototype.getTransformToElement = function(opt_element){}; - - -/** - * @type {!SVGStringList} - */ -SVGRectElement.prototype.requiredFeatures; - - -/** - * @type {!SVGStringList} - */ -SVGRectElement.prototype.requiredExtensions; - - -/** - * @type {!SVGStringList} - */ -SVGRectElement.prototype.systemLanguage; - - -/** - * @param {string=} opt_extension - * @return {boolean} - * @override - */ -SVGRectElement.prototype.hasExtension = function(opt_extension){}; - - -/** - * @type {!SVGAnimatedString} - */ -SVGRectElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGRectElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGURIReference} - * @implements {SVGTests} - * @implements {SVGLangSpace} - * @implements {SVGExternalResourcesRequired} - * @implements {SVGStylable} - * @implements {SVGTransformable} - */ -function SVGAElement(){} - - -/** - * @type {!SVGAnimatedString} - */ -SVGAElement.prototype.target; - - -/** - * @type {!SVGAnimatedBoolean} - */ -SVGAElement.prototype.externalResourcesRequired; - - -/** - * @type {!SVGElement} - */ -SVGAElement.prototype.nearestViewportElement; - - -/** - * @type {!SVGElement} - */ -SVGAElement.prototype.farthestViewportElement; - - -/** - * @return {!SVGRect} - * @override - */ -SVGAElement.prototype.getBBox = function(){}; - - -/** - * @return {!SVGMatrix} - * @override - */ -SVGAElement.prototype.getCTM = function(){}; - - -/** - * @return {!SVGMatrix} - * @override - */ -SVGAElement.prototype.getScreenCTM = function(){}; - - -/** - * @param {!SVGElement=} opt_element - * @return {!SVGMatrix} - * @override - */ -SVGAElement.prototype.getTransformToElement = function(opt_element){}; - - -/** - * @type {!SVGAnimatedTransformList} - */ -SVGAElement.prototype.transform; - - -/** - * @type {!SVGAnimatedString} - */ -SVGAElement.prototype.href; - - -/** - * @type {string} - */ -SVGAElement.prototype.xmllang; - - -/** - * @type {string} - */ -SVGAElement.prototype.xmlspace; - - -/** - * @type {!SVGStringList} - */ -SVGAElement.prototype.requiredFeatures; - - -/** - * @type {!SVGStringList} - */ -SVGAElement.prototype.requiredExtensions; - - -/** - * @type {!SVGStringList} - */ -SVGAElement.prototype.systemLanguage; - - -/** - * @param {string=} opt_extension - * @return {boolean} - * @override - */ -SVGAElement.prototype.hasExtension = function(opt_extension){}; - - -/** - * @type {!SVGAnimatedString} - */ -SVGAElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGAElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - */ -function SVGAnimatedPreserveAspectRatio(){} - - -/** - * @type {!SVGPreserveAspectRatio} - */ -SVGAnimatedPreserveAspectRatio.prototype.baseVal; - - -/** - * @type {!SVGPreserveAspectRatio} - */ -SVGAnimatedPreserveAspectRatio.prototype.animVal; - - - -/** - * @constructor - */ -function SVGElementInstanceList(){} - - -/** - * @type {number} - */ -SVGElementInstanceList.prototype.length; - - -/** - * @param {number=} opt_index - * @return {!SVGElementInstance} - */ -SVGElementInstanceList.prototype.item = function(opt_index){}; - - - -/** - * @constructor - * @extends {Element} - */ -function SVGElement(){} - - -/** - * @type {string} - */ -SVGElement.prototype.id; - - -/** - * @type {string} - */ -SVGElement.prototype.xmlbase; - - -/** - * @type {!SVGSVGElement} - */ -SVGElement.prototype.ownerSVGElement; - - -/** - * @type {!SVGElement} - */ -SVGElement.prototype.viewportElement; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGFilterPrimitiveStandardAttributes} - */ -function SVGFETurbulenceElement(){} - - -/** - * @const - * @type {number} - */ -SVGFETurbulenceElement.SVG_TURBULENCE_TYPE_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGFETurbulenceElement.prototype.SVG_TURBULENCE_TYPE_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGFETurbulenceElement.SVG_TURBULENCE_TYPE_FRACTALNOISE; - - -/** - * @const - * @type {number} - */ -SVGFETurbulenceElement.prototype.SVG_TURBULENCE_TYPE_FRACTALNOISE; - - -/** - * @const - * @type {number} - */ -SVGFETurbulenceElement.SVG_TURBULENCE_TYPE_TURBULENCE; - - -/** - * @const - * @type {number} - */ -SVGFETurbulenceElement.prototype.SVG_TURBULENCE_TYPE_TURBULENCE; - - -/** - * @const - * @type {number} - */ -SVGFETurbulenceElement.SVG_STITCHTYPE_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGFETurbulenceElement.prototype.SVG_STITCHTYPE_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGFETurbulenceElement.SVG_STITCHTYPE_STITCH; - - -/** - * @const - * @type {number} - */ -SVGFETurbulenceElement.prototype.SVG_STITCHTYPE_STITCH; - - -/** - * @const - * @type {number} - */ -SVGFETurbulenceElement.SVG_STITCHTYPE_NOSTITCH; - - -/** - * @const - * @type {number} - */ -SVGFETurbulenceElement.prototype.SVG_STITCHTYPE_NOSTITCH; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFETurbulenceElement.prototype.baseFrequencyX; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFETurbulenceElement.prototype.baseFrequencyY; - - -/** - * @type {!SVGAnimatedInteger} - */ -SVGFETurbulenceElement.prototype.numOctaves; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFETurbulenceElement.prototype.seed; - - -/** - * @type {!SVGAnimatedEnumeration} - */ -SVGFETurbulenceElement.prototype.stitchTiles; - - -/** - * @type {!SVGAnimatedEnumeration} - */ -SVGFETurbulenceElement.prototype.type; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFETurbulenceElement.prototype.x; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFETurbulenceElement.prototype.y; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFETurbulenceElement.prototype.width; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFETurbulenceElement.prototype.height; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFETurbulenceElement.prototype.result; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFETurbulenceElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGFETurbulenceElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGPathSeg} - */ -function SVGPathSegArcAbs(){} - - -/** - * @type {number} - */ -SVGPathSegArcAbs.prototype.x; - - -/** - * @type {number} - */ -SVGPathSegArcAbs.prototype.y; - - -/** - * @type {number} - */ -SVGPathSegArcAbs.prototype.r1; - - -/** - * @type {number} - */ -SVGPathSegArcAbs.prototype.r2; - - -/** - * @type {number} - */ -SVGPathSegArcAbs.prototype.angle; - - -/** - * @type {boolean} - */ -SVGPathSegArcAbs.prototype.largeArcFlag; - - -/** - * @type {boolean} - */ -SVGPathSegArcAbs.prototype.sweepFlag; - - - -/** - * @constructor - */ -function SVGAnimatedBoolean(){} - - -/** - * @type {boolean} - */ -SVGAnimatedBoolean.prototype.baseVal; - - -/** - * @type {boolean} - */ -SVGAnimatedBoolean.prototype.animVal; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGLangSpace} - */ -function SVGStyleElement(){} - - -/** - * @type {boolean} - */ -SVGStyleElement.prototype.disabled; - - -/** - * @type {string} - */ -SVGStyleElement.prototype.type; - - -/** - * @type {string} - */ -SVGStyleElement.prototype.media; - - -/** - * @type {string} - */ -SVGStyleElement.prototype.title; - - -/** - * @type {string} - */ -SVGStyleElement.prototype.xmllang; - - -/** - * @type {string} - */ -SVGStyleElement.prototype.xmlspace; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGTests} - * @implements {SVGLangSpace} - * @implements {SVGExternalResourcesRequired} - * @implements {SVGStylable} - * @implements {SVGTransformable} - */ -function SVGClipPathElement(){} - - -/** - * @type {!SVGAnimatedEnumeration} - */ -SVGClipPathElement.prototype.clipPathUnits; - - -/** - * @type {!SVGAnimatedBoolean} - */ -SVGClipPathElement.prototype.externalResourcesRequired; - - -/** - * @type {!SVGAnimatedTransformList} - */ -SVGClipPathElement.prototype.transform; - - -/** - * @type {string} - */ -SVGClipPathElement.prototype.xmllang; - - -/** - * @type {string} - */ -SVGClipPathElement.prototype.xmlspace; - - -/** - * @type {!SVGElement} - */ -SVGClipPathElement.prototype.nearestViewportElement; - - -/** - * @type {!SVGElement} - */ -SVGClipPathElement.prototype.farthestViewportElement; - - -/** - * @return {!SVGRect} - * @override - */ -SVGClipPathElement.prototype.getBBox = function(){}; - - -/** - * @return {!SVGMatrix} - * @override - */ -SVGClipPathElement.prototype.getCTM = function(){}; - - -/** - * @return {!SVGMatrix} - * @override - */ -SVGClipPathElement.prototype.getScreenCTM = function(){}; - - -/** - * @param {!SVGElement=} opt_element - * @return {!SVGMatrix} - * @override - */ -SVGClipPathElement.prototype.getTransformToElement = function(opt_element){}; - - -/** - * @type {!SVGStringList} - */ -SVGClipPathElement.prototype.requiredFeatures; - - -/** - * @type {!SVGStringList} - */ -SVGClipPathElement.prototype.requiredExtensions; - - -/** - * @type {!SVGStringList} - */ -SVGClipPathElement.prototype.systemLanguage; - - -/** - * @param {string=} opt_extension - * @return {boolean} - * @override - */ -SVGClipPathElement.prototype.hasExtension = function(opt_extension){}; - - -/** - * @type {!SVGAnimatedString} - */ -SVGClipPathElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGClipPathElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @interface - */ -function ElementTimeControl(){} - - -/** - * @return {undefined} - */ -ElementTimeControl.prototype.beginElement = function(){}; - - -/** - * @param {number=} opt_offset - * @return {undefined} - */ -ElementTimeControl.prototype.beginElementAt = function(opt_offset){}; - - -/** - * @return {undefined} - */ -ElementTimeControl.prototype.endElement = function(){}; - - -/** - * @param {number=} opt_offset - * @return {undefined} - */ -ElementTimeControl.prototype.endElementAt = function(opt_offset){}; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGTests} - * @implements {SVGExternalResourcesRequired} - * @implements {ElementTimeControl} - */ -function SVGAnimationElement(){} - - -/** - * @type {!SVGElement} - */ -SVGAnimationElement.prototype.targetElement; - - -/** - * @return {number} - */ -SVGAnimationElement.prototype.getStartTime = function(){}; - - -/** - * @return {number} - */ -SVGAnimationElement.prototype.getCurrentTime = function(){}; - - -/** - * @return {number} - */ -SVGAnimationElement.prototype.getSimpleDuration = function(){}; - - -/** - * @type {!SVGAnimatedBoolean} - */ -SVGAnimationElement.prototype.externalResourcesRequired; - - -/** - * @type {!SVGStringList} - */ -SVGAnimationElement.prototype.requiredFeatures; - - -/** - * @type {!SVGStringList} - */ -SVGAnimationElement.prototype.requiredExtensions; - - -/** - * @type {!SVGStringList} - */ -SVGAnimationElement.prototype.systemLanguage; - - -/** - * @param {string=} opt_extension - * @return {boolean} - * @override - */ -SVGAnimationElement.prototype.hasExtension = function(opt_extension){}; - - -/** - * @override - * @return {undefined} - */ -SVGAnimationElement.prototype.beginElement = function(){}; - - -/** - * @param {number=} opt_offset - * @override - * @return {undefined} - */ -SVGAnimationElement.prototype.beginElementAt = function(opt_offset){}; - - -/** - * @override - * @return {undefined} - */ -SVGAnimationElement.prototype.endElement = function(){}; - - -/** - * @param {number=} opt_offset - * @override - * @return {undefined} - */ -SVGAnimationElement.prototype.endElementAt = function(opt_offset){}; - - - -/** - * @constructor - * @extends {SVGAnimationElement} - */ -function SVGAnimateMotionElement(){} - - - -/** - * @constructor - * @extends {SVGAnimationElement} - */ -function SVGAnimateElement(){} - - - -/** - * @constructor - * @extends {SVGAnimationElement} - */ -function SVGAnimateColorElement(){} - - - -/** - * @constructor - * @extends {SVGAnimationElement} - */ -function SVGAnimateTransformElement(){} - - - -/** - * @constructor - * @extends {SVGAnimationElement} - */ -function SVGSetElement(){} - - - -/** - * @constructor - */ -function SVGAngle(){} - - -/** - * @const - * @type {number} - */ -SVGAngle.SVG_ANGLETYPE_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGAngle.prototype.SVG_ANGLETYPE_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGAngle.SVG_ANGLETYPE_UNSPECIFIED; - - -/** - * @const - * @type {number} - */ -SVGAngle.prototype.SVG_ANGLETYPE_UNSPECIFIED; - - -/** - * @const - * @type {number} - */ -SVGAngle.SVG_ANGLETYPE_DEG; - - -/** - * @const - * @type {number} - */ -SVGAngle.prototype.SVG_ANGLETYPE_DEG; - - -/** - * @const - * @type {number} - */ -SVGAngle.SVG_ANGLETYPE_RAD; - - -/** - * @const - * @type {number} - */ -SVGAngle.prototype.SVG_ANGLETYPE_RAD; - - -/** - * @const - * @type {number} - */ -SVGAngle.SVG_ANGLETYPE_GRAD; - - -/** - * @const - * @type {number} - */ -SVGAngle.prototype.SVG_ANGLETYPE_GRAD; - - -/** - * @type {number} - */ -SVGAngle.prototype.unitType; - - -/** - * @type {number} - */ -SVGAngle.prototype.value; - - -/** - * @type {number} - */ -SVGAngle.prototype.valueInSpecifiedUnits; - - -/** - * @type {string} - */ -SVGAngle.prototype.valueAsString; - - -/** - * @param {number} unitType - * @param {number} valueInSpecifiedUnits - * @return {undefined} - */ -SVGAngle.prototype.newValueSpecifiedUnits = function(unitType, valueInSpecifiedUnits){}; - - -/** - * @param {number} unitType - * @return {undefined} - */ -SVGAngle.prototype.convertToSpecifiedUnits = function(unitType){}; - - - -/** - * @constructor - * @extends {SVGTextPositioningElement} - * @implements {SVGTransformable} - */ -function SVGTextElement(){} - - -/** - * @type {!SVGAnimatedTransformList} - */ -SVGTextElement.prototype.transform; - - -/** - * @type {!SVGElement} - */ -SVGTextElement.prototype.nearestViewportElement; - - -/** - * @type {!SVGElement} - */ -SVGTextElement.prototype.farthestViewportElement; - - -/** - * @return {!SVGRect} - * @override - */ -SVGTextElement.prototype.getBBox = function(){}; - - -/** - * @return {!SVGMatrix} - * @override - */ -SVGTextElement.prototype.getCTM = function(){}; - - -/** - * @return {!SVGMatrix} - * @override - */ -SVGTextElement.prototype.getScreenCTM = function(){}; - - -/** - * @param {!SVGElement=} opt_element - * @return {!SVGMatrix} - * @override - */ -SVGTextElement.prototype.getTransformToElement = function(opt_element){}; - - - -/** - * @constructor - * @extends {SVGElement} - */ -function SVGFontFaceElement(){} - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGTests} - * @implements {SVGLangSpace} - * @implements {SVGExternalResourcesRequired} - * @implements {SVGStylable} - * @implements {SVGTransformable} - */ -function SVGLineElement(){} - - -/** - * @type {!SVGAnimatedLength} - */ -SVGLineElement.prototype.x1; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGLineElement.prototype.y1; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGLineElement.prototype.x2; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGLineElement.prototype.y2; - - -/** - * @type {!SVGAnimatedBoolean} - */ -SVGLineElement.prototype.externalResourcesRequired; - - -/** - * @type {!SVGAnimatedTransformList} - */ -SVGLineElement.prototype.transform; - - -/** - * @type {string} - */ -SVGLineElement.prototype.xmllang; - - -/** - * @type {string} - */ -SVGLineElement.prototype.xmlspace; - - -/** - * @type {!SVGElement} - */ -SVGLineElement.prototype.nearestViewportElement; - - -/** - * @type {!SVGElement} - */ -SVGLineElement.prototype.farthestViewportElement; - - -/** - * @return {!SVGRect} - * @override - */ -SVGLineElement.prototype.getBBox = function(){}; - - -/** - * @return {!SVGMatrix} - * @override - */ -SVGLineElement.prototype.getCTM = function(){}; - - -/** - * @return {!SVGMatrix} - * @override - */ -SVGLineElement.prototype.getScreenCTM = function(){}; - - -/** - * @param {!SVGElement=} opt_element - * @return {!SVGMatrix} - * @override - */ -SVGLineElement.prototype.getTransformToElement = function(opt_element){}; - - -/** - * @type {!SVGStringList} - */ -SVGLineElement.prototype.requiredFeatures; - - -/** - * @type {!SVGStringList} - */ -SVGLineElement.prototype.requiredExtensions; - - -/** - * @type {!SVGStringList} - */ -SVGLineElement.prototype.systemLanguage; - - -/** - * @param {string=} opt_extension - * @return {boolean} - * @override - */ -SVGLineElement.prototype.hasExtension = function(opt_extension){}; - - -/** - * @type {!SVGAnimatedString} - */ -SVGLineElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGLineElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGFilterPrimitiveStandardAttributes} - */ -function SVGFEComponentTransferElement(){} - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEComponentTransferElement.prototype.in1; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEComponentTransferElement.prototype.x; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEComponentTransferElement.prototype.y; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEComponentTransferElement.prototype.width; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEComponentTransferElement.prototype.height; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEComponentTransferElement.prototype.result; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEComponentTransferElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGFEComponentTransferElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGFilterPrimitiveStandardAttributes} - */ -function SVGFEDisplacementMapElement(){} - - -/** - * @const - * @type {number} - */ -SVGFEDisplacementMapElement.SVG_CHANNEL_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGFEDisplacementMapElement.prototype.SVG_CHANNEL_UNKNOWN; - - -/** - * @const - * @type {number} - */ -SVGFEDisplacementMapElement.SVG_CHANNEL_R; - - -/** - * @const - * @type {number} - */ -SVGFEDisplacementMapElement.prototype.SVG_CHANNEL_R; - - -/** - * @const - * @type {number} - */ -SVGFEDisplacementMapElement.SVG_CHANNEL_G; - - -/** - * @const - * @type {number} - */ -SVGFEDisplacementMapElement.prototype.SVG_CHANNEL_G; - - -/** - * @const - * @type {number} - */ -SVGFEDisplacementMapElement.SVG_CHANNEL_B; - - -/** - * @const - * @type {number} - */ -SVGFEDisplacementMapElement.prototype.SVG_CHANNEL_B; - - -/** - * @const - * @type {number} - */ -SVGFEDisplacementMapElement.SVG_CHANNEL_A; - - -/** - * @const - * @type {number} - */ -SVGFEDisplacementMapElement.prototype.SVG_CHANNEL_A; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEDisplacementMapElement.prototype.in1; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEDisplacementMapElement.prototype.in2; - - -/** - * @type {!SVGAnimatedNumber} - */ -SVGFEDisplacementMapElement.prototype.scale; - - -/** - * @type {!SVGAnimatedEnumeration} - */ -SVGFEDisplacementMapElement.prototype.xChannelSelector; - - -/** - * @type {!SVGAnimatedEnumeration} - */ -SVGFEDisplacementMapElement.prototype.yChannelSelector; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEDisplacementMapElement.prototype.x; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEDisplacementMapElement.prototype.y; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEDisplacementMapElement.prototype.width; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEDisplacementMapElement.prototype.height; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEDisplacementMapElement.prototype.result; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEDisplacementMapElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGFEDisplacementMapElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - */ -function SVGAnimatedEnumeration(){} - - -/** - * @type {number} - */ -SVGAnimatedEnumeration.prototype.baseVal; - - -/** - * @type {number} - */ -SVGAnimatedEnumeration.prototype.animVal; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGFilterPrimitiveStandardAttributes} - */ -function SVGFEMergeElement(){} - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEMergeElement.prototype.x; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEMergeElement.prototype.y; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEMergeElement.prototype.width; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGFEMergeElement.prototype.height; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEMergeElement.prototype.result; - - -/** - * @type {!SVGAnimatedString} - */ -SVGFEMergeElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGFEMergeElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGTests} - * @implements {SVGLangSpace} - * @implements {SVGExternalResourcesRequired} - * @implements {SVGStylable} - * @implements {SVGTransformable} - */ -function SVGForeignObjectElement(){} - - -/** - * @type {!SVGAnimatedLength} - */ -SVGForeignObjectElement.prototype.x; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGForeignObjectElement.prototype.y; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGForeignObjectElement.prototype.width; - - -/** - * @type {!SVGAnimatedLength} - */ -SVGForeignObjectElement.prototype.height; - - -/** - * @type {!SVGAnimatedBoolean} - */ -SVGForeignObjectElement.prototype.externalResourcesRequired; - - -/** - * @type {!SVGAnimatedTransformList} - */ -SVGForeignObjectElement.prototype.transform; - - -/** - * @type {string} - */ -SVGForeignObjectElement.prototype.xmllang; - - -/** - * @type {string} - */ -SVGForeignObjectElement.prototype.xmlspace; - - -/** - * @type {!SVGElement} - */ -SVGForeignObjectElement.prototype.nearestViewportElement; - - -/** - * @type {!SVGElement} - */ -SVGForeignObjectElement.prototype.farthestViewportElement; - - -/** - * @return {!SVGRect} - * @override - */ -SVGForeignObjectElement.prototype.getBBox = function(){}; - - -/** - * @return {!SVGMatrix} - * @override - */ -SVGForeignObjectElement.prototype.getCTM = function(){}; - - -/** - * @return {!SVGMatrix} - * @override - */ -SVGForeignObjectElement.prototype.getScreenCTM = function(){}; - - -/** - * @param {!SVGElement=} opt_element - * @return {!SVGMatrix} - * @override - */ -SVGForeignObjectElement.prototype.getTransformToElement = function(opt_element){}; - - -/** - * @type {!SVGStringList} - */ -SVGForeignObjectElement.prototype.requiredFeatures; - - -/** - * @type {!SVGStringList} - */ -SVGForeignObjectElement.prototype.requiredExtensions; - - -/** - * @type {!SVGStringList} - */ -SVGForeignObjectElement.prototype.systemLanguage; - - -/** - * @param {string=} opt_extension - * @return {boolean} - * @override - */ -SVGForeignObjectElement.prototype.hasExtension = function(opt_extension){}; - - -/** - * @type {!SVGAnimatedString} - */ -SVGForeignObjectElement.prototype.className; - - -/** - * @param {string=} opt_name - * @return {!CSSValue} - * @override - */ -SVGForeignObjectElement.prototype.getPresentationAttribute = function(opt_name){}; - - - -/** - * @constructor - */ -function SVGAnimatedLength(){} - - -/** - * @type {!SVGLength} - */ -SVGAnimatedLength.prototype.baseVal; - - -/** - * @type {!SVGLength} - */ -SVGAnimatedLength.prototype.animVal; - - - -/** - * @constructor - * @extends {SVGElement} - * @implements {SVGURIReference} - * @implements {SVGExternalResourcesRequired} - */ -function SVGScriptElement(){} - - -/** - * @type {string} - */ -SVGScriptElement.prototype.type; - - -/** - * @type {!SVGAnimatedBoolean} - */ -SVGScriptElement.prototype.externalResourcesRequired; - - -/** - * @type {!SVGAnimatedString} - */ -SVGScriptElement.prototype.href; - - - -/** - * @constructor - */ -function SVGAnimatedInteger(){} - - -/** - * @type {number} - */ -SVGAnimatedInteger.prototype.baseVal; - - -/** - * @type {number} - */ -SVGAnimatedInteger.prototype.animVal; diff --git a/generators/dart/colour.js b/generators/dart/colour.js index 7f6f621ac..96aeaea51 100644 --- a/generators/dart/colour.js +++ b/generators/dart/colour.js @@ -24,17 +24,16 @@ Dart['colour_picker'] = function(block) { Dart['colour_random'] = function(block) { // Generate a random colour. - Dart.definitions_['import_dart_math'] = - 'import \'dart:math\' as Math;'; - const functionName = Dart.provideFunction_( - 'colour_random', - ['String ' + Dart.FUNCTION_NAME_PLACEHOLDER_ + '() {', - ' String hex = \'0123456789abcdef\';', - ' var rnd = new Math.Random();', - ' return \'#${hex[rnd.nextInt(16)]}${hex[rnd.nextInt(16)]}\'', - ' \'${hex[rnd.nextInt(16)]}${hex[rnd.nextInt(16)]}\'', - ' \'${hex[rnd.nextInt(16)]}${hex[rnd.nextInt(16)]}\';', - '}']); + Dart.definitions_['import_dart_math'] = "import 'dart:math' as Math;"; + const functionName = Dart.provideFunction_('colour_random', ` +String ${Dart.FUNCTION_NAME_PLACEHOLDER_}() { + String hex = '0123456789abcdef'; + var rnd = new Math.Random(); + return '#\${hex[rnd.nextInt(16)]}\${hex[rnd.nextInt(16)]}' + '\${hex[rnd.nextInt(16)]}\${hex[rnd.nextInt(16)]}' + '\${hex[rnd.nextInt(16)]}\${hex[rnd.nextInt(16)]}'; +} +`); const code = functionName + '()'; return [code, Dart.ORDER_UNARY_POSTFIX]; }; @@ -48,66 +47,59 @@ Dart['colour_rgb'] = function(block) { const blue = Dart.valueToCode(block, 'BLUE', Dart.ORDER_NONE) || 0; - Dart.definitions_['import_dart_math'] = - 'import \'dart:math\' as Math;'; - const functionName = Dart.provideFunction_( - 'colour_rgb', - ['String ' + Dart.FUNCTION_NAME_PLACEHOLDER_ + - '(num r, num g, num b) {', - ' num rn = (Math.max(Math.min(r, 100), 0) * 2.55).round();', - ' String rs = rn.toInt().toRadixString(16);', - ' rs = \'0$rs\';', - ' rs = rs.substring(rs.length - 2);', - ' num gn = (Math.max(Math.min(g, 100), 0) * 2.55).round();', - ' String gs = gn.toInt().toRadixString(16);', - ' gs = \'0$gs\';', - ' gs = gs.substring(gs.length - 2);', - ' num bn = (Math.max(Math.min(b, 100), 0) * 2.55).round();', - ' String bs = bn.toInt().toRadixString(16);', - ' bs = \'0$bs\';', - ' bs = bs.substring(bs.length - 2);', - ' return \'#$rs$gs$bs\';', - '}']); + Dart.definitions_['import_dart_math'] = "import 'dart:math' as Math;"; + const functionName = Dart.provideFunction_('colour_rgb', ` +String ${Dart.FUNCTION_NAME_PLACEHOLDER_}(num r, num g, num b) { + num rn = (Math.max(Math.min(r, 100), 0) * 2.55).round(); + String rs = rn.toInt().toRadixString(16); + rs = '0$rs'; + rs = rs.substring(rs.length - 2); + num gn = (Math.max(Math.min(g, 100), 0) * 2.55).round(); + String gs = gn.toInt().toRadixString(16); + gs = '0$gs'; + gs = gs.substring(gs.length - 2); + num bn = (Math.max(Math.min(b, 100), 0) * 2.55).round(); + String bs = bn.toInt().toRadixString(16); + bs = '0$bs'; + bs = bs.substring(bs.length - 2); + return '#$rs$gs$bs'; +} +`); const code = functionName + '(' + red + ', ' + green + ', ' + blue + ')'; return [code, Dart.ORDER_UNARY_POSTFIX]; }; Dart['colour_blend'] = function(block) { // Blend two colours together. - const c1 = Dart.valueToCode(block, 'COLOUR1', - Dart.ORDER_NONE) || '\'#000000\''; - const c2 = Dart.valueToCode(block, 'COLOUR2', - Dart.ORDER_NONE) || '\'#000000\''; - const ratio = Dart.valueToCode(block, 'RATIO', - Dart.ORDER_NONE) || 0.5; + const c1 = Dart.valueToCode(block, 'COLOUR1', Dart.ORDER_NONE) || "'#000000'"; + const c2 = Dart.valueToCode(block, 'COLOUR2', Dart.ORDER_NONE) || "'#000000'"; + const ratio = Dart.valueToCode(block, 'RATIO', Dart.ORDER_NONE) || 0.5; - Dart.definitions_['import_dart_math'] = - 'import \'dart:math\' as Math;'; - const functionName = Dart.provideFunction_( - 'colour_blend', - ['String ' + Dart.FUNCTION_NAME_PLACEHOLDER_ + - '(String c1, String c2, num ratio) {', - ' ratio = Math.max(Math.min(ratio, 1), 0);', - ' int r1 = int.parse(\'0x${c1.substring(1, 3)}\');', - ' int g1 = int.parse(\'0x${c1.substring(3, 5)}\');', - ' int b1 = int.parse(\'0x${c1.substring(5, 7)}\');', - ' int r2 = int.parse(\'0x${c2.substring(1, 3)}\');', - ' int g2 = int.parse(\'0x${c2.substring(3, 5)}\');', - ' int b2 = int.parse(\'0x${c2.substring(5, 7)}\');', - ' num rn = (r1 * (1 - ratio) + r2 * ratio).round();', - ' String rs = rn.toInt().toRadixString(16);', - ' num gn = (g1 * (1 - ratio) + g2 * ratio).round();', - ' String gs = gn.toInt().toRadixString(16);', - ' num bn = (b1 * (1 - ratio) + b2 * ratio).round();', - ' String bs = bn.toInt().toRadixString(16);', - ' rs = \'0$rs\';', - ' rs = rs.substring(rs.length - 2);', - ' gs = \'0$gs\';', - ' gs = gs.substring(gs.length - 2);', - ' bs = \'0$bs\';', - ' bs = bs.substring(bs.length - 2);', - ' return \'#$rs$gs$bs\';', - '}']); + Dart.definitions_['import_dart_math'] = "import 'dart:math' as Math;"; + const functionName = Dart.provideFunction_('colour_blend', ` +String ${Dart.FUNCTION_NAME_PLACEHOLDER_}(String c1, String c2, num ratio) { + ratio = Math.max(Math.min(ratio, 1), 0); + int r1 = int.parse('0x\${c1.substring(1, 3)}'); + int g1 = int.parse('0x\${c1.substring(3, 5)}'); + int b1 = int.parse('0x\${c1.substring(5, 7)}'); + int r2 = int.parse('0x\${c2.substring(1, 3)}'); + int g2 = int.parse('0x\${c2.substring(3, 5)}'); + int b2 = int.parse('0x\${c2.substring(5, 7)}'); + num rn = (r1 * (1 - ratio) + r2 * ratio).round(); + String rs = rn.toInt().toRadixString(16); + num gn = (g1 * (1 - ratio) + g2 * ratio).round(); + String gs = gn.toInt().toRadixString(16); + num bn = (b1 * (1 - ratio) + b2 * ratio).round(); + String bs = bn.toInt().toRadixString(16); + rs = '0$rs'; + rs = rs.substring(rs.length - 2); + gs = '0$gs'; + gs = gs.substring(gs.length - 2); + bs = '0$bs'; + bs = bs.substring(bs.length - 2); + return '#$rs$gs$bs'; +} +`); const code = functionName + '(' + c1 + ', ' + c2 + ', ' + ratio + ')'; return [code, Dart.ORDER_UNARY_POSTFIX]; }; diff --git a/generators/dart/lists.js b/generators/dart/lists.js index 5eefea91b..493cd7a83 100644 --- a/generators/dart/lists.js +++ b/generators/dart/lists.js @@ -58,7 +58,7 @@ Dart['lists_indexOf'] = function(block) { // Find an item in the list. const operator = block.getFieldValue('END') === 'FIRST' ? 'indexOf' : 'lastIndexOf'; - const item = Dart.valueToCode(block, 'FIND', Dart.ORDER_NONE) || '\'\''; + const item = Dart.valueToCode(block, 'FIND', Dart.ORDER_NONE) || "''"; const list = Dart.valueToCode(block, 'VALUE', Dart.ORDER_UNARY_POSTFIX) || '[]'; const code = list + '.' + operator + '(' + item + ')'; @@ -112,21 +112,23 @@ Dart['lists_getIndex'] = function(block) { } else if (mode === 'GET') { const at = Dart.getAdjusted(block, 'AT', 1); // We need to create a procedure to avoid reevaluating values. - const functionName = Dart.provideFunction_('lists_get_from_end', [ - 'dynamic ' + Dart.FUNCTION_NAME_PLACEHOLDER_ + - '(List my_list, num x) {', - ' x = my_list.length - x;', ' return my_list[x];', '}' - ]); + const functionName = Dart.provideFunction_('lists_get_from_end', ` +dynamic ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List my_list, num x) { + x = my_list.length - x; + return my_list[x]; +} +`); const code = functionName + '(' + list + ', ' + at + ')'; return [code, Dart.ORDER_UNARY_POSTFIX]; } else if (mode === 'GET_REMOVE') { const at = Dart.getAdjusted(block, 'AT', 1); // We need to create a procedure to avoid reevaluating values. - const functionName = Dart.provideFunction_('lists_remove_from_end', [ - 'dynamic ' + Dart.FUNCTION_NAME_PLACEHOLDER_ + - '(List my_list, num x) {', - ' x = my_list.length - x;', ' return my_list.removeAt(x);', '}' - ]); + const functionName = Dart.provideFunction_('lists_remove_from_end', ` +dynamic ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List my_list, num x) { + x = my_list.length - x; + return my_list.removeAt(x); +} +`); const code = functionName + '(' + list + ', ' + at + ')'; return [code, Dart.ORDER_UNARY_POSTFIX]; } @@ -195,21 +197,22 @@ Dart['lists_getIndex'] = function(block) { code += list + '.removeAt(' + xVar + ');\n'; return code; } else if (mode === 'GET') { - const functionName = Dart.provideFunction_('lists_get_random_item', [ - 'dynamic ' + Dart.FUNCTION_NAME_PLACEHOLDER_ + '(List my_list) {', - ' int x = new Math.Random().nextInt(my_list.length);', - ' return my_list[x];', '}' - ]); + const functionName = Dart.provideFunction_('lists_get_random_item', ` +dynamic ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List my_list) { + int x = new Math.Random().nextInt(my_list.length); + return my_list[x]; +} +`); const code = functionName + '(' + list + ')'; return [code, Dart.ORDER_UNARY_POSTFIX]; } else if (mode === 'GET_REMOVE') { const functionName = - Dart.provideFunction_('lists_remove_random_item', [ - 'dynamic ' + Dart.FUNCTION_NAME_PLACEHOLDER_ + - '(List my_list) {', - ' int x = new Math.Random().nextInt(my_list.length);', - ' return my_list.removeAt(x);', '}' - ]); + Dart.provideFunction_('lists_remove_random_item', ` +dynamic ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List my_list) { + int x = new Math.Random().nextInt(my_list.length); + return my_list.removeAt(x); +} +`); const code = functionName + '(' + list + ')'; return [code, Dart.ORDER_UNARY_POSTFIX]; } @@ -344,18 +347,25 @@ Dart['lists_getSublist'] = function(block) { } else { const at1 = Dart.getAdjusted(block, 'AT1'); const at2 = Dart.getAdjusted(block, 'AT2'); - const functionName = Dart.provideFunction_('lists_get_sublist', [ - 'List ' + Dart.FUNCTION_NAME_PLACEHOLDER_ + - '(List list, String where1, num at1, String where2, num at2) {', - ' int getAt(String where, num at) {', ' if (where == \'FROM_END\') {', - ' at = list.length - 1 - at;', - ' } else if (where == \'FIRST\') {', ' at = 0;', - ' } else if (where == \'LAST\') {', ' at = list.length - 1;', - ' } else if (where != \'FROM_START\') {', - ' throw \'Unhandled option (lists_getSublist).\';', ' }', - ' return at;', ' }', ' at1 = getAt(where1, at1);', - ' at2 = getAt(where2, at2) + 1;', ' return list.sublist(at1, at2);', '}' - ]); + const functionName = Dart.provideFunction_('lists_get_sublist', ` +List ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List list, String where1, num at1, String where2, num at2) { + int getAt(String where, num at) { + if (where == 'FROM_END') { + at = list.length - 1 - at; + } else if (where == 'FIRST') { + at = 0; + } else if (where == 'LAST') { + at = list.length - 1; + } else if (where != 'FROM_START') { + throw 'Unhandled option (lists_getSublist).'; + } + return at; + } + at1 = getAt(where1, at1); + at2 = getAt(where2, at2) + 1; + return list.sublist(at1, at2); +} +`); code = functionName + '(' + list + ', \'' + where1 + '\', ' + at1 + ', \'' + where2 + '\', ' + at2 + ')'; } @@ -367,20 +377,21 @@ Dart['lists_sort'] = function(block) { const list = Dart.valueToCode(block, 'LIST', Dart.ORDER_NONE) || '[]'; const direction = block.getFieldValue('DIRECTION') === '1' ? 1 : -1; const type = block.getFieldValue('TYPE'); - const sortFunctionName = Dart.provideFunction_('lists_sort', [ - 'List ' + Dart.FUNCTION_NAME_PLACEHOLDER_ + - '(List list, String type, int direction) {', - ' var compareFuncs = {', - ' "NUMERIC": (a, b) => (direction * a.compareTo(b)).toInt(),', - ' "TEXT": (a, b) => direction * ' + - 'a.toString().compareTo(b.toString()),', - ' "IGNORE_CASE": ', ' (a, b) => direction * ', - ' a.toString().toLowerCase().compareTo(b.toString().toLowerCase())', - ' };', - ' list = new List.from(list);', // Clone the list. - ' var compare = compareFuncs[type];', ' list.sort(compare);', - ' return list;', '}' - ]); + const sortFunctionName = Dart.provideFunction_('lists_sort', ` +List ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List list, String type, int direction) { + var compareFuncs = { + 'NUMERIC': (a, b) => (direction * a.compareTo(b)).toInt(), + 'TEXT': (a, b) => direction * a.toString().compareTo(b.toString()), + 'IGNORE_CASE': + (a, b) => direction * + a.toString().toLowerCase().compareTo(b.toString().toLowerCase()) + }; + list = new List.from(list); + var compare = compareFuncs[type]; + list.sort(compare); + return list; +} +`); return [ sortFunctionName + '(' + list + ', ' + '"' + type + '", ' + direction + ')', @@ -391,12 +402,12 @@ Dart['lists_sort'] = function(block) { Dart['lists_split'] = function(block) { // Block for splitting text into a list, or joining a list into text. let input = Dart.valueToCode(block, 'INPUT', Dart.ORDER_UNARY_POSTFIX); - const delimiter = Dart.valueToCode(block, 'DELIM', Dart.ORDER_NONE) || '\'\''; + const delimiter = Dart.valueToCode(block, 'DELIM', Dart.ORDER_NONE) || "''"; const mode = block.getFieldValue('MODE'); let functionName; if (mode === 'SPLIT') { if (!input) { - input = '\'\''; + input = "''"; } functionName = 'split'; } else if (mode === 'JOIN') { diff --git a/generators/dart/math.js b/generators/dart/math.js index 24b7e268e..24c42c2ae 100644 --- a/generators/dart/math.js +++ b/generators/dart/math.js @@ -42,7 +42,7 @@ Dart['math_arithmetic'] = function(block) { 'MINUS': [' - ', Dart.ORDER_ADDITIVE], 'MULTIPLY': [' * ', Dart.ORDER_MULTIPLICATIVE], 'DIVIDE': [' / ', Dart.ORDER_MULTIPLICATIVE], - 'POWER': [null, Dart.ORDER_NONE] // Handle power separately. + 'POWER': [null, Dart.ORDER_NONE], // Handle power separately. }; const tuple = OPERATORS[block.getFieldValue('OP')]; const operator = tuple[0]; @@ -152,7 +152,7 @@ Dart['math_constant'] = function(block) { 'GOLDEN_RATIO': ['(1 + Math.sqrt(5)) / 2', Dart.ORDER_MULTIPLICATIVE], 'SQRT2': ['Math.sqrt2', Dart.ORDER_UNARY_POSTFIX], 'SQRT1_2': ['Math.sqrt1_2', Dart.ORDER_UNARY_POSTFIX], - 'INFINITY': ['double.infinity', Dart.ORDER_ATOMIC] + 'INFINITY': ['double.infinity', Dart.ORDER_ATOMIC], }; const constant = block.getFieldValue('CONSTANT'); if (constant !== 'INFINITY') { @@ -164,59 +164,56 @@ Dart['math_constant'] = function(block) { Dart['math_number_property'] = function(block) { // Check if a number is even, odd, prime, whole, positive, or negative // or if it is divisible by certain number. Returns true or false. - const number_to_check = - Dart.valueToCode(block, 'NUMBER_TO_CHECK', Dart.ORDER_MULTIPLICATIVE); - if (!number_to_check) { - return ['false', Dart.ORDER_ATOMIC]; - } - const dropdown_property = block.getFieldValue('PROPERTY'); + const PROPERTIES = { + 'EVEN': [' % 2 == 0', Dart.ORDER_MULTIPLICATIVE, Dart.ORDER_EQUALITY], + 'ODD': [' % 2 == 1', Dart.ORDER_MULTIPLICATIVE, Dart.ORDER_EQUALITY], + 'WHOLE': [' % 1 == 0', Dart.ORDER_MULTIPLICATIVE, Dart.ORDER_EQUALITY], + 'POSITIVE': [' > 0', Dart.ORDER_RELATIONAL, Dart.ORDER_RELATIONAL], + 'NEGATIVE': [' < 0', Dart.ORDER_RELATIONAL, Dart.ORDER_RELATIONAL], + 'DIVISIBLE_BY': [null, Dart.ORDER_MULTIPLICATIVE, Dart.ORDER_EQUALITY], + 'PRIME': [null, Dart.ORDER_NONE, Dart.ORDER_UNARY_POSTFIX], + }; + const dropdownProperty = block.getFieldValue('PROPERTY'); + const [suffix, inputOrder, outputOrder] = PROPERTIES[dropdownProperty]; + const numberToCheck = Dart.valueToCode(block, 'NUMBER_TO_CHECK', + inputOrder) || '0'; let code; - if (dropdown_property === 'PRIME') { + if (dropdownProperty === 'PRIME') { // Prime is a special case as it is not a one-liner test. - Dart.definitions_['import_dart_math'] = 'import \'dart:math\' as Math;'; - const functionName = Dart.provideFunction_('math_isPrime', [ - 'bool ' + Dart.FUNCTION_NAME_PLACEHOLDER_ + '(n) {', - ' // https://en.wikipedia.org/wiki/Primality_test#Naive_methods', - ' if (n == 2 || n == 3) {', ' return true;', ' }', - ' // False if n is null, negative, is 1, or not whole.', - ' // And false if n is divisible by 2 or 3.', - ' if (n == null || n <= 1 || n % 1 != 0 || n % 2 == 0 ||' + - ' n % 3 == 0) {', - ' return false;', ' }', - ' // Check all the numbers of form 6k +/- 1, up to sqrt(n).', - ' for (var x = 6; x <= Math.sqrt(n) + 1; x += 6) {', - ' if (n % (x - 1) == 0 || n % (x + 1) == 0) {', ' return false;', - ' }', ' }', ' return true;', '}' - ]); - code = functionName + '(' + number_to_check + ')'; - return [code, Dart.ORDER_UNARY_POSTFIX]; + Dart.definitions_['import_dart_math'] = + 'import \'dart:math\' as Math;'; + const functionName = Dart.provideFunction_('math_isPrime', ` +bool ${Dart.FUNCTION_NAME_PLACEHOLDER_}(n) { + // https://en.wikipedia.org/wiki/Primality_test#Naive_methods + if (n == 2 || n == 3) { + return true; } - switch (dropdown_property) { - case 'EVEN': - code = number_to_check + ' % 2 == 0'; - break; - case 'ODD': - code = number_to_check + ' % 2 == 1'; - break; - case 'WHOLE': - code = number_to_check + ' % 1 == 0'; - break; - case 'POSITIVE': - code = number_to_check + ' > 0'; - break; - case 'NEGATIVE': - code = number_to_check + ' < 0'; - break; - case 'DIVISIBLE_BY': - const divisor = - Dart.valueToCode(block, 'DIVISOR', Dart.ORDER_MULTIPLICATIVE); - if (!divisor) { - return ['false', Dart.ORDER_ATOMIC]; - } - code = number_to_check + ' % ' + divisor + ' == 0'; - break; + // False if n is null, negative, is 1, or not whole. + // And false if n is divisible by 2 or 3. + if (n == null || n <= 1 || n % 1 != 0 || n % 2 == 0 || n % 3 == 0) { + return false; } - return [code, Dart.ORDER_EQUALITY]; + // Check all the numbers of form 6k +/- 1, up to sqrt(n). + for (var x = 6; x <= Math.sqrt(n) + 1; x += 6) { + if (n % (x - 1) == 0 || n % (x + 1) == 0) { + return false; + } + } + return true; +} +`); + code = functionName + '(' + numberToCheck + ')'; + } else if (dropdownProperty === 'DIVISIBLE_BY') { + const divisor = Dart.valueToCode(block, 'DIVISOR', + Dart.ORDER_MULTIPLICATIVE) || '0'; + if (divisor === '0') { + return ['false', Dart.ORDER_ATOMIC]; + } + code = numberToCheck + ' % ' + divisor + ' == 0'; + } else { + code = numberToCheck + suffix; + } + return [code, outputOrder]; }; Dart['math_change'] = function(block) { @@ -241,70 +238,76 @@ Dart['math_on_list'] = function(block) { let code; switch (func) { case 'SUM': { - const functionName = Dart.provideFunction_('math_sum', [ - 'num ' + Dart.FUNCTION_NAME_PLACEHOLDER_ + '(List myList) {', - ' num sumVal = 0;', - ' myList.forEach((num entry) {sumVal += entry;});', ' return sumVal;', - '}' - ]); + const functionName = Dart.provideFunction_('math_sum', ` +num ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List myList) { + num sumVal = 0; + myList.forEach((num entry) {sumVal += entry;}); + return sumVal; +} +`); code = functionName + '(' + list + ')'; break; } case 'MIN': { Dart.definitions_['import_dart_math'] = 'import \'dart:math\' as Math;'; - const functionName = Dart.provideFunction_('math_min', [ - 'num ' + Dart.FUNCTION_NAME_PLACEHOLDER_ + '(List myList) {', - ' if (myList.isEmpty) return null;', ' num minVal = myList[0];', - ' myList.forEach((num entry) ' + - '{minVal = Math.min(minVal, entry);});', - ' return minVal;', '}' - ]); + const functionName = Dart.provideFunction_('math_min', ` +num ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List myList) { + if (myList.isEmpty) return null; + num minVal = myList[0]; + myList.forEach((num entry) {minVal = Math.min(minVal, entry);}); + return minVal; +} +`); code = functionName + '(' + list + ')'; break; } case 'MAX': { Dart.definitions_['import_dart_math'] = 'import \'dart:math\' as Math;'; - const functionName = Dart.provideFunction_('math_max', [ - 'num ' + Dart.FUNCTION_NAME_PLACEHOLDER_ + '(List myList) {', - ' if (myList.isEmpty) return null;', ' num maxVal = myList[0];', - ' myList.forEach((num entry) ' + - '{maxVal = Math.max(maxVal, entry);});', - ' return maxVal;', '}' - ]); + const functionName = Dart.provideFunction_('math_max', ` +num ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List myList) { + if (myList.isEmpty) return null; + num maxVal = myList[0]; + myList.forEach((num entry) {maxVal = Math.max(maxVal, entry);}); + return maxVal; +} +`); code = functionName + '(' + list + ')'; break; } case 'AVERAGE': { // This operation exclude null and values that are not int or float: // math_mean([null,null,"aString",1,9]) -> 5.0 - const functionName = Dart.provideFunction_('math_mean', [ - 'num ' + Dart.FUNCTION_NAME_PLACEHOLDER_ + '(List myList) {', - ' // First filter list for numbers only.', - ' List localList = new List.from(myList);', - ' localList.removeWhere((a) => a is! num);', - ' if (localList.isEmpty) return null;', ' num sumVal = 0;', - ' localList.forEach((var entry) {sumVal += entry;});', - ' return sumVal / localList.length;', '}' - ]); + const functionName = Dart.provideFunction_('math_mean', ` +num ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List myList) { + // First filter list for numbers only. + List localList = new List.from(myList); + localList.removeWhere((a) => a is! num); + if (localList.isEmpty) return null; + num sumVal = 0; + localList.forEach((var entry) {sumVal += entry;}); + return sumVal / localList.length; +} +`); code = functionName + '(' + list + ')'; break; } case 'MEDIAN': { - const functionName = Dart.provideFunction_('math_median', [ - 'num ' + Dart.FUNCTION_NAME_PLACEHOLDER_ + '(List myList) {', - ' // First filter list for numbers only, then sort, ' + - 'then return middle value', - ' // or the average of two middle values if list has an ' + - 'even number of elements.', - ' List localList = new List.from(myList);', - ' localList.removeWhere((a) => a is! num);', - ' if (localList.isEmpty) return null;', - ' localList.sort((a, b) => (a - b));', - ' int index = localList.length ~/ 2;', - ' if (localList.length % 2 == 1) {', ' return localList[index];', - ' } else {', - ' return (localList[index - 1] + localList[index]) / 2;', ' }', '}' - ]); + const functionName = Dart.provideFunction_('math_median', ` +num ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List myList) { + // First filter list for numbers only, then sort, then return middle value + // or the average of two middle values if list has an even number of elements. + List localList = new List.from(myList); + localList.removeWhere((a) => a is! num); + if (localList.isEmpty) return null; + localList.sort((a, b) => (a - b)); + int index = localList.length ~/ 2; + if (localList.length % 2 == 1) { + return localList[index]; + } else { + return (localList[index - 1] + localList[index]) / 2; + } +} +`); code = functionName + '(' + list + ')'; break; } @@ -313,63 +316,67 @@ Dart['math_on_list'] = function(block) { // As a list of numbers can contain more than one mode, // the returned result is provided as an array. // Mode of [3, 'x', 'x', 1, 1, 2, '3'] -> ['x', 1] - const functionName = Dart.provideFunction_('math_modes', [ - 'List ' + Dart.FUNCTION_NAME_PLACEHOLDER_ + '(List values) {', - ' List modes = [];', - ' List counts = [];', - ' int maxCount = 0;', - ' for (int i = 0; i < values.length; i++) {', - ' var value = values[i];', - ' bool found = false;', - ' int thisCount;', - ' for (int j = 0; j < counts.length; j++) {', - ' if (counts[j][0] == value) {', - ' thisCount = ++counts[j][1];', - ' found = true;', - ' break;', - ' }', - ' }', - ' if (!found) {', - ' counts.add([value, 1]);', - ' thisCount = 1;', - ' }', - ' maxCount = Math.max(thisCount, maxCount);', - ' }', - ' for (int j = 0; j < counts.length; j++) {', - ' if (counts[j][1] == maxCount) {', - ' modes.add(counts[j][0]);', - ' }', - ' }', - ' return modes;', - '}' - ]); + const functionName = Dart.provideFunction_('math_modes', ` +List ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List values) { + List modes = []; + List counts = []; + int maxCount = 0; + for (int i = 0; i < values.length; i++) { + var value = values[i]; + bool found = false; + int thisCount; + for (int j = 0; j < counts.length; j++) { + if (counts[j][0] == value) { + thisCount = ++counts[j][1]; + found = true; + break; + } + } + if (!found) { + counts.add([value, 1]); + thisCount = 1; + } + maxCount = Math.max(thisCount, maxCount); + } + for (int j = 0; j < counts.length; j++) { + if (counts[j][1] == maxCount) { + modes.add(counts[j][0]); + } + } + return modes; +} +`); code = functionName + '(' + list + ')'; break; } case 'STD_DEV': { Dart.definitions_['import_dart_math'] = 'import \'dart:math\' as Math;'; - const functionName = Dart.provideFunction_('math_standard_deviation', [ - 'num ' + Dart.FUNCTION_NAME_PLACEHOLDER_ + '(List myList) {', - ' // First filter list for numbers only.', - ' List numbers = new List.from(myList);', - ' numbers.removeWhere((a) => a is! num);', - ' if (numbers.isEmpty) return null;', ' num n = numbers.length;', - ' num sum = 0;', ' numbers.forEach((x) => sum += x);', - ' num mean = sum / n;', ' num sumSquare = 0;', - ' numbers.forEach((x) => sumSquare += ' + - 'Math.pow(x - mean, 2));', - ' return Math.sqrt(sumSquare / n);', '}' - ]); + const functionName = Dart.provideFunction_('math_standard_deviation', ` +num ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List myList) { + // First filter list for numbers only. + List numbers = new List.from(myList); + numbers.removeWhere((a) => a is! num); + if (numbers.isEmpty) return null; + num n = numbers.length; + num sum = 0; + numbers.forEach((x) => sum += x); + num mean = sum / n; + num sumSquare = 0; + numbers.forEach((x) => sumSquare += Math.pow(x - mean, 2)); + return Math.sqrt(sumSquare / n); +} +`); code = functionName + '(' + list + ')'; break; } case 'RANDOM': { Dart.definitions_['import_dart_math'] = 'import \'dart:math\' as Math;'; - const functionName = Dart.provideFunction_('math_random_item', [ - 'dynamic ' + Dart.FUNCTION_NAME_PLACEHOLDER_ + '(List myList) {', - ' int x = new Math.Random().nextInt(myList.length);', - ' return myList[x];', '}' - ]); + const functionName = Dart.provideFunction_('math_random_item', ` +dynamic ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List myList) { + int x = new Math.Random().nextInt(myList.length); + return myList[x]; +} +`); code = functionName + '(' + list + ')'; break; } @@ -406,12 +413,17 @@ Dart['math_random_int'] = function(block) { Dart.definitions_['import_dart_math'] = 'import \'dart:math\' as Math;'; const argument0 = Dart.valueToCode(block, 'FROM', Dart.ORDER_NONE) || '0'; const argument1 = Dart.valueToCode(block, 'TO', Dart.ORDER_NONE) || '0'; - const functionName = Dart.provideFunction_('math_random_int', [ - 'int ' + Dart.FUNCTION_NAME_PLACEHOLDER_ + '(num a, num b) {', - ' if (a > b) {', ' // Swap a and b to ensure a is smaller.', - ' num c = a;', ' a = b;', ' b = c;', ' }', - ' return new Math.Random().nextInt(b - a + 1) + a;', '}' - ]); + const functionName = Dart.provideFunction_('math_random_int', ` +int ${Dart.FUNCTION_NAME_PLACEHOLDER_}(num a, num b) { + if (a > b) { + // Swap a and b to ensure a is smaller. + num c = a; + a = b; + b = c; + } + return new Math.Random().nextInt(b - a + 1) + a; +} +`); const code = functionName + '(' + argument0 + ', ' + argument1 + ')'; return [code, Dart.ORDER_UNARY_POSTFIX]; }; diff --git a/generators/dart/text.js b/generators/dart/text.js index 7078815ec..5dfb372b0 100644 --- a/generators/dart/text.js +++ b/generators/dart/text.js @@ -35,10 +35,10 @@ Dart['text_join'] = function(block) { // Create a string made up of any number of elements of any type. switch (block.itemCount_) { case 0: - return ['\'\'', Dart.ORDER_ATOMIC]; + return ["''", Dart.ORDER_ATOMIC]; case 1: { const element = - Dart.valueToCode(block, 'ADD0', Dart.ORDER_UNARY_POSTFIX) || '\'\''; + Dart.valueToCode(block, 'ADD0', Dart.ORDER_UNARY_POSTFIX) || "''"; const code = element + '.toString()'; return [code, Dart.ORDER_UNARY_POSTFIX]; } @@ -46,7 +46,7 @@ Dart['text_join'] = function(block) { const elements = new Array(block.itemCount_); for (let i = 0; i < block.itemCount_; i++) { elements[i] = - Dart.valueToCode(block, 'ADD' + i, Dart.ORDER_NONE) || '\'\''; + Dart.valueToCode(block, 'ADD' + i, Dart.ORDER_NONE) || "''"; } const code = '[' + elements.join(',') + '].join()'; return [code, Dart.ORDER_UNARY_POSTFIX]; @@ -58,21 +58,21 @@ Dart['text_append'] = function(block) { // Append to a variable in place. const varName = Dart.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE); - const value = Dart.valueToCode(block, 'TEXT', Dart.ORDER_NONE) || '\'\''; + const value = Dart.valueToCode(block, 'TEXT', Dart.ORDER_NONE) || "''"; return varName + ' = [' + varName + ', ' + value + '].join();\n'; }; Dart['text_length'] = function(block) { // String or array length. const text = - Dart.valueToCode(block, 'VALUE', Dart.ORDER_UNARY_POSTFIX) || '\'\''; + Dart.valueToCode(block, 'VALUE', Dart.ORDER_UNARY_POSTFIX) || "''"; return [text + '.length', Dart.ORDER_UNARY_POSTFIX]; }; Dart['text_isEmpty'] = function(block) { // Is the string null or array empty? const text = - Dart.valueToCode(block, 'VALUE', Dart.ORDER_UNARY_POSTFIX) || '\'\''; + Dart.valueToCode(block, 'VALUE', Dart.ORDER_UNARY_POSTFIX) || "''"; return [text + '.isEmpty', Dart.ORDER_UNARY_POSTFIX]; }; @@ -80,9 +80,9 @@ Dart['text_indexOf'] = function(block) { // Search the text for a substring. const operator = block.getFieldValue('END') === 'FIRST' ? 'indexOf' : 'lastIndexOf'; - const substring = Dart.valueToCode(block, 'FIND', Dart.ORDER_NONE) || '\'\''; + const substring = Dart.valueToCode(block, 'FIND', Dart.ORDER_NONE) || "''"; const text = - Dart.valueToCode(block, 'VALUE', Dart.ORDER_UNARY_POSTFIX) || '\'\''; + Dart.valueToCode(block, 'VALUE', Dart.ORDER_UNARY_POSTFIX) || "''"; const code = text + '.' + operator + '(' + substring + ')'; if (block.workspace.options.oneBasedIndex) { return [code + ' + 1', Dart.ORDER_ADDITIVE]; @@ -97,7 +97,7 @@ Dart['text_charAt'] = function(block) { const textOrder = (where === 'FIRST' || where === 'FROM_START') ? Dart.ORDER_UNARY_POSTFIX : Dart.ORDER_NONE; - const text = Dart.valueToCode(block, 'VALUE', textOrder) || '\'\''; + const text = Dart.valueToCode(block, 'VALUE', textOrder) || "''"; let at; switch (where) { case 'FIRST': { @@ -114,20 +114,22 @@ Dart['text_charAt'] = function(block) { // Fall through. case 'FROM_END': { at = Dart.getAdjusted(block, 'AT', 1); - const functionName = Dart.provideFunction_('text_get_from_end', [ - 'String ' + Dart.FUNCTION_NAME_PLACEHOLDER_ + '(String text, num x) {', - ' return text[text.length - x];', '}' - ]); + const functionName = Dart.provideFunction_('text_get_from_end', ` +String ${Dart.FUNCTION_NAME_PLACEHOLDER_}(String text, num x) { + return text[text.length - x]; +} +`); const code = functionName + '(' + text + ', ' + at + ')'; return [code, Dart.ORDER_UNARY_POSTFIX]; } case 'RANDOM': { Dart.definitions_['import_dart_math'] = 'import \'dart:math\' as Math;'; - const functionName = Dart.provideFunction_('text_random_letter', [ - 'String ' + Dart.FUNCTION_NAME_PLACEHOLDER_ + '(String text) {', - ' int x = new Math.Random().nextInt(text.length);', - ' return text[x];', '}' - ]); + const functionName = Dart.provideFunction_('text_random_letter', ` +String ${Dart.FUNCTION_NAME_PLACEHOLDER_}(String text) { + int x = new Math.Random().nextInt(text.length); + return text[x]; +} +`); const code = functionName + '(' + text + ')'; return [code, Dart.ORDER_UNARY_POSTFIX]; } @@ -142,7 +144,7 @@ Dart['text_getSubstring'] = function(block) { const requiresLengthCall = (where1 !== 'FROM_END' && where2 === 'FROM_START'); const textOrder = requiresLengthCall ? Dart.ORDER_UNARY_POSTFIX : Dart.ORDER_NONE; - const text = Dart.valueToCode(block, 'STRING', textOrder) || '\'\''; + const text = Dart.valueToCode(block, 'STRING', textOrder) || "''"; let code; if (where1 === 'FIRST' && where2 === 'LAST') { code = text; @@ -188,19 +190,25 @@ Dart['text_getSubstring'] = function(block) { } else { const at1 = Dart.getAdjusted(block, 'AT1'); const at2 = Dart.getAdjusted(block, 'AT2'); - const functionName = Dart.provideFunction_('text_get_substring', [ - 'String ' + Dart.FUNCTION_NAME_PLACEHOLDER_ + - '(String text, String where1, num at1, String where2, num at2) {', - ' int getAt(String where, num at) {', ' if (where == \'FROM_END\') {', - ' at = text.length - 1 - at;', - ' } else if (where == \'FIRST\') {', ' at = 0;', - ' } else if (where == \'LAST\') {', ' at = text.length - 1;', - ' } else if (where != \'FROM_START\') {', - ' throw \'Unhandled option (text_getSubstring).\';', ' }', - ' return at;', ' }', ' at1 = getAt(where1, at1);', - ' at2 = getAt(where2, at2) + 1;', ' return text.substring(at1, at2);', - '}' - ]); + const functionName = Dart.provideFunction_('text_get_substring', ` +String ${Dart.FUNCTION_NAME_PLACEHOLDER_}(String text, String where1, num at1, String where2, num at2) { + int getAt(String where, num at) { + if (where == 'FROM_END') { + at = text.length - 1 - at; + } else if (where == 'FIRST') { + at = 0; + } else if (where == 'LAST') { + at = text.length - 1; + } else if (where != 'FROM_START') { + throw 'Unhandled option (text_getSubstring).'; + } + return at; + } + at1 = getAt(where1, at1); + at2 = getAt(where2, at2) + 1; + return text.substring(at1, at2); +} +`); code = functionName + '(' + text + ', \'' + where1 + '\', ' + at1 + ', \'' + where2 + '\', ' + at2 + ')'; } @@ -216,23 +224,29 @@ Dart['text_changeCase'] = function(block) { }; const operator = OPERATORS[block.getFieldValue('CASE')]; const textOrder = operator ? Dart.ORDER_UNARY_POSTFIX : Dart.ORDER_NONE; - const text = Dart.valueToCode(block, 'TEXT', textOrder) || '\'\''; + const text = Dart.valueToCode(block, 'TEXT', textOrder) || "''"; let code; if (operator) { // Upper and lower case are functions built into Dart. code = text + operator; } else { // Title case is not a native Dart function. Define one. - const functionName = Dart.provideFunction_('text_toTitleCase', [ - 'String ' + Dart.FUNCTION_NAME_PLACEHOLDER_ + '(String str) {', - ' RegExp exp = new RegExp(r\'\\b\');', - ' List list = str.split(exp);', - ' final title = new StringBuffer();', ' for (String part in list) {', - ' if (part.length > 0) {', ' title.write(part[0].toUpperCase());', - ' if (part.length > 0) {', - ' title.write(part.substring(1).toLowerCase());', ' }', - ' }', ' }', ' return title.toString();', '}' - ]); + const functionName = Dart.provideFunction_('text_toTitleCase', ` +String ${Dart.FUNCTION_NAME_PLACEHOLDER_}(String str) { + RegExp exp = new RegExp(r'\\b'); + List list = str.split(exp); + final title = new StringBuffer(); + for (String part in list) { + if (part.length > 0) { + title.write(part[0].toUpperCase()); + if (part.length > 0) { + title.write(part.substring(1).toLowerCase()); + } + } + } + return title.toString(); +} +`); code = functionName + '(' + text + ')'; } return [code, Dart.ORDER_UNARY_POSTFIX]; @@ -247,13 +261,13 @@ Dart['text_trim'] = function(block) { }; const operator = OPERATORS[block.getFieldValue('MODE')]; const text = - Dart.valueToCode(block, 'TEXT', Dart.ORDER_UNARY_POSTFIX) || '\'\''; + Dart.valueToCode(block, 'TEXT', Dart.ORDER_UNARY_POSTFIX) || "''"; return [text + operator, Dart.ORDER_UNARY_POSTFIX]; }; Dart['text_print'] = function(block) { // Print statement. - const msg = Dart.valueToCode(block, 'TEXT', Dart.ORDER_NONE) || '\'\''; + const msg = Dart.valueToCode(block, 'TEXT', Dart.ORDER_NONE) || "''"; return 'print(' + msg + ');\n'; }; @@ -266,7 +280,7 @@ Dart['text_prompt_ext'] = function(block) { msg = Dart.quote_(block.getFieldValue('TEXT')); } else { // External message. - msg = Dart.valueToCode(block, 'TEXT', Dart.ORDER_NONE) || '\'\''; + msg = Dart.valueToCode(block, 'TEXT', Dart.ORDER_NONE) || "''"; } let code = 'Html.window.prompt(' + msg + ', \'\')'; const toNumber = block.getFieldValue('TYPE') === 'NUMBER'; @@ -280,27 +294,35 @@ Dart['text_prompt_ext'] = function(block) { Dart['text_prompt'] = Dart['text_prompt_ext']; Dart['text_count'] = function(block) { - const text = Dart.valueToCode(block, 'TEXT', Dart.ORDER_NONE) || '\'\''; - const sub = Dart.valueToCode(block, 'SUB', Dart.ORDER_NONE) || '\'\''; + const text = Dart.valueToCode(block, 'TEXT', Dart.ORDER_NONE) || "''"; + const sub = Dart.valueToCode(block, 'SUB', Dart.ORDER_NONE) || "''"; // Substring count is not a native Dart function. Define one. - const functionName = Dart.provideFunction_('text_count', [ - 'int ' + Dart.FUNCTION_NAME_PLACEHOLDER_ + - '(String haystack, String needle) {', - ' if (needle.length == 0) {', ' return haystack.length + 1;', ' }', - ' int index = 0;', ' int count = 0;', ' while (index != -1) {', - ' index = haystack.indexOf(needle, index);', ' if (index != -1) {', - ' count++;', ' index += needle.length;', ' }', ' }', - ' return count;', '}' - ]); + const functionName = Dart.provideFunction_('text_count', ` +int ${Dart.FUNCTION_NAME_PLACEHOLDER_}(String haystack, String needle) { + if (needle.length == 0) { + return haystack.length + 1; + } + int index = 0; + int count = 0; + while (index != -1) { + index = haystack.indexOf(needle, index); + if (index != -1) { + count++; + index += needle.length; + } + } + return count; +} +`); const code = functionName + '(' + text + ', ' + sub + ')'; return [code, Dart.ORDER_UNARY_POSTFIX]; }; Dart['text_replace'] = function(block) { const text = - Dart.valueToCode(block, 'TEXT', Dart.ORDER_UNARY_POSTFIX) || '\'\''; - const from = Dart.valueToCode(block, 'FROM', Dart.ORDER_NONE) || '\'\''; - const to = Dart.valueToCode(block, 'TO', Dart.ORDER_NONE) || '\'\''; + Dart.valueToCode(block, 'TEXT', Dart.ORDER_UNARY_POSTFIX) || "''"; + const from = Dart.valueToCode(block, 'FROM', Dart.ORDER_NONE) || "''"; + const to = Dart.valueToCode(block, 'TO', Dart.ORDER_NONE) || "''"; const code = text + '.replaceAll(' + from + ', ' + to + ')'; return [code, Dart.ORDER_UNARY_POSTFIX]; }; @@ -310,7 +332,7 @@ Dart['text_reverse'] = function(block) { // http://stackoverflow.com/a/21613700/3529104 // Implementing something is possibly better than not implementing anything? const text = - Dart.valueToCode(block, 'TEXT', Dart.ORDER_UNARY_POSTFIX) || '\'\''; + Dart.valueToCode(block, 'TEXT', Dart.ORDER_UNARY_POSTFIX) || "''"; const code = 'new String.fromCharCodes(' + text + '.runes.toList().reversed)'; return [code, Dart.ORDER_UNARY_PREFIX]; }; diff --git a/generators/javascript/colour.js b/generators/javascript/colour.js index 3b6632575..a2cc64a5a 100644 --- a/generators/javascript/colour.js +++ b/generators/javascript/colour.js @@ -22,11 +22,12 @@ JavaScript['colour_picker'] = function(block) { JavaScript['colour_random'] = function(block) { // Generate a random colour. - const functionName = JavaScript.provideFunction_('colourRandom', [ - 'function ' + JavaScript.FUNCTION_NAME_PLACEHOLDER_ + '() {', - ' var num = Math.floor(Math.random() * Math.pow(2, 24));', - ' return \'#\' + (\'00000\' + num.toString(16)).substr(-6);', '}' - ]); + const functionName = JavaScript.provideFunction_('colourRandom', ` +function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}() { + var num = Math.floor(Math.random() * Math.pow(2, 24)); + return '#' + ('00000' + num.toString(16)).substr(-6); +} +`); const code = functionName + '()'; return [code, JavaScript.ORDER_FUNCTION_CALL]; }; @@ -38,16 +39,17 @@ JavaScript['colour_rgb'] = function(block) { JavaScript.valueToCode(block, 'GREEN', JavaScript.ORDER_NONE) || 0; const blue = JavaScript.valueToCode(block, 'BLUE', JavaScript.ORDER_NONE) || 0; - const functionName = JavaScript.provideFunction_('colourRgb', [ - 'function ' + JavaScript.FUNCTION_NAME_PLACEHOLDER_ + '(r, g, b) {', - ' r = Math.max(Math.min(Number(r), 100), 0) * 2.55;', - ' g = Math.max(Math.min(Number(g), 100), 0) * 2.55;', - ' b = Math.max(Math.min(Number(b), 100), 0) * 2.55;', - ' r = (\'0\' + (Math.round(r) || 0).toString(16)).slice(-2);', - ' g = (\'0\' + (Math.round(g) || 0).toString(16)).slice(-2);', - ' b = (\'0\' + (Math.round(b) || 0).toString(16)).slice(-2);', - ' return \'#\' + r + g + b;', '}' - ]); + const functionName = JavaScript.provideFunction_('colourRgb', ` +function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(r, g, b) { + r = Math.max(Math.min(Number(r), 100), 0) * 2.55; + g = Math.max(Math.min(Number(g), 100), 0) * 2.55; + b = Math.max(Math.min(Number(b), 100), 0) * 2.55; + r = ('0' + (Math.round(r) || 0).toString(16)).slice(-2); + g = ('0' + (Math.round(g) || 0).toString(16)).slice(-2); + b = ('0' + (Math.round(b) || 0).toString(16)).slice(-2); + return '#' + r + g + b; +} +`); const code = functionName + '(' + red + ', ' + green + ', ' + blue + ')'; return [code, JavaScript.ORDER_FUNCTION_CALL]; }; @@ -55,28 +57,29 @@ JavaScript['colour_rgb'] = function(block) { JavaScript['colour_blend'] = function(block) { // Blend two colours together. const c1 = JavaScript.valueToCode(block, 'COLOUR1', JavaScript.ORDER_NONE) || - '\'#000000\''; + "'#000000'"; const c2 = JavaScript.valueToCode(block, 'COLOUR2', JavaScript.ORDER_NONE) || - '\'#000000\''; + "'#000000'"; const ratio = JavaScript.valueToCode(block, 'RATIO', JavaScript.ORDER_NONE) || 0.5; - const functionName = JavaScript.provideFunction_('colourBlend', [ - 'function ' + JavaScript.FUNCTION_NAME_PLACEHOLDER_ + '(c1, c2, ratio) {', - ' ratio = Math.max(Math.min(Number(ratio), 1), 0);', - ' var r1 = parseInt(c1.substring(1, 3), 16);', - ' var g1 = parseInt(c1.substring(3, 5), 16);', - ' var b1 = parseInt(c1.substring(5, 7), 16);', - ' var r2 = parseInt(c2.substring(1, 3), 16);', - ' var g2 = parseInt(c2.substring(3, 5), 16);', - ' var b2 = parseInt(c2.substring(5, 7), 16);', - ' var r = Math.round(r1 * (1 - ratio) + r2 * ratio);', - ' var g = Math.round(g1 * (1 - ratio) + g2 * ratio);', - ' var b = Math.round(b1 * (1 - ratio) + b2 * ratio);', - ' r = (\'0\' + (r || 0).toString(16)).slice(-2);', - ' g = (\'0\' + (g || 0).toString(16)).slice(-2);', - ' b = (\'0\' + (b || 0).toString(16)).slice(-2);', - ' return \'#\' + r + g + b;', '}' - ]); + const functionName = JavaScript.provideFunction_('colourBlend', ` +function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(c1, c2, ratio) { + ratio = Math.max(Math.min(Number(ratio), 1), 0); + var r1 = parseInt(c1.substring(1, 3), 16); + var g1 = parseInt(c1.substring(3, 5), 16); + var b1 = parseInt(c1.substring(5, 7), 16); + var r2 = parseInt(c2.substring(1, 3), 16); + var g2 = parseInt(c2.substring(3, 5), 16); + var b2 = parseInt(c2.substring(5, 7), 16); + var r = Math.round(r1 * (1 - ratio) + r2 * ratio); + var g = Math.round(g1 * (1 - ratio) + g2 * ratio); + var b = Math.round(b1 * (1 - ratio) + b2 * ratio); + r = ('0' + (r || 0).toString(16)).slice(-2); + g = ('0' + (g || 0).toString(16)).slice(-2); + b = ('0' + (b || 0).toString(16)).slice(-2); + return '#' + r + g + b; +} +`); const code = functionName + '(' + c1 + ', ' + c2 + ', ' + ratio + ')'; return [code, JavaScript.ORDER_FUNCTION_CALL]; }; diff --git a/generators/javascript/lists.js b/generators/javascript/lists.js index a033710b8..e9fbc3598 100644 --- a/generators/javascript/lists.js +++ b/generators/javascript/lists.js @@ -35,11 +35,15 @@ JavaScript['lists_create_with'] = function(block) { JavaScript['lists_repeat'] = function(block) { // Create a list with one element repeated. - const functionName = JavaScript.provideFunction_('listsRepeat', [ - 'function ' + JavaScript.FUNCTION_NAME_PLACEHOLDER_ + '(value, n) {', - ' var array = [];', ' for (var i = 0; i < n; i++) {', - ' array[i] = value;', ' }', ' return array;', '}' - ]); + const functionName = JavaScript.provideFunction_('listsRepeat', ` +function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(value, n) { + var array = []; + for (var i = 0; i < n; i++) { + array[i] = value; + } + return array; +} +`); const element = JavaScript.valueToCode(block, 'ITEM', JavaScript.ORDER_NONE) || 'null'; const repeatCount = @@ -67,7 +71,7 @@ JavaScript['lists_indexOf'] = function(block) { const operator = block.getFieldValue('END') === 'FIRST' ? 'indexOf' : 'lastIndexOf'; const item = - JavaScript.valueToCode(block, 'FIND', JavaScript.ORDER_NONE) || '\'\''; + JavaScript.valueToCode(block, 'FIND', JavaScript.ORDER_NONE) || "''"; const list = JavaScript.valueToCode(block, 'VALUE', JavaScript.ORDER_MEMBER) || '[]'; const code = list + '.' + operator + '(' + item + ')'; @@ -136,13 +140,16 @@ JavaScript['lists_getIndex'] = function(block) { break; } case ('RANDOM'): { - const functionName = JavaScript.provideFunction_('listsGetRandomItem', [ - 'function ' + JavaScript.FUNCTION_NAME_PLACEHOLDER_ + - '(list, remove) {', - ' var x = Math.floor(Math.random() * list.length);', ' if (remove) {', - ' return list.splice(x, 1)[0];', ' } else {', ' return list[x];', - ' }', '}' - ]); + const functionName = JavaScript.provideFunction_('listsGetRandomItem', ` +function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(list, remove) { + var x = Math.floor(Math.random() * list.length); + if (remove) { + return list.splice(x, 1)[0]; + } else { + return list[x]; + } +} +`); const code = functionName + '(' + list + ', ' + (mode !== 'GET') + ')'; if (mode === 'GET' || mode === 'GET_REMOVE') { return [code, JavaScript.ORDER_FUNCTION_CALL]; @@ -309,23 +316,22 @@ JavaScript['lists_getSublist'] = function(block) { 'FIRST': 'First', 'LAST': 'Last', 'FROM_START': 'FromStart', - 'FROM_END': 'FromEnd' + 'FROM_END': 'FromEnd', }; + // The value for 'FROM_END' and'FROM_START' depends on `at` so + // we add it as a parameter. + const at1Param = + (where1 === 'FROM_END' || where1 === 'FROM_START') ? ', at1' : ''; + const at2Param = + (where2 === 'FROM_END' || where2 === 'FROM_START') ? ', at2' : ''; const functionName = JavaScript.provideFunction_( - 'subsequence' + wherePascalCase[where1] + wherePascalCase[where2], [ - 'function ' + JavaScript.FUNCTION_NAME_PLACEHOLDER_ + '(sequence' + - // The value for 'FROM_END' and'FROM_START' depends on `at` so - // we add it as a parameter. - ((where1 === 'FROM_END' || where1 === 'FROM_START') ? ', at1' : - '') + - ((where2 === 'FROM_END' || where2 === 'FROM_START') ? ', at2' : - '') + - ') {', - getSubstringIndex('sequence', where1, 'at1') + ';', - ' var end = ' + getSubstringIndex('sequence', where2, 'at2') + - ' + 1;', - ' return sequence.slice(start, end);', '}' - ]); + 'subsequence' + wherePascalCase[where1] + wherePascalCase[where2], ` +function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(sequence${at1Param}${at2Param}) { + var start = ${getSubstringIndex('sequence', where1, 'at1')}; + var end = ${getSubstringIndex('sequence', where2, 'at2')} + 1; + return sequence.slice(start, end); +} +`); code = functionName + '(' + list + // The value for 'FROM_END' and 'FROM_START' depends on `at` so we // pass it. @@ -344,19 +350,20 @@ JavaScript['lists_sort'] = function(block) { const direction = block.getFieldValue('DIRECTION') === '1' ? 1 : -1; const type = block.getFieldValue('TYPE'); const getCompareFunctionName = - JavaScript.provideFunction_('listsGetSortCompare', [ - 'function ' + JavaScript.FUNCTION_NAME_PLACEHOLDER_ + - '(type, direction) {', - ' var compareFuncs = {', ' "NUMERIC": function(a, b) {', - ' return Number(a) - Number(b); },', - ' "TEXT": function(a, b) {', - ' return a.toString() > b.toString() ? 1 : -1; },', - ' "IGNORE_CASE": function(a, b) {', - ' return a.toString().toLowerCase() > ' + - 'b.toString().toLowerCase() ? 1 : -1; },', - ' };', ' var compare = compareFuncs[type];', - ' return function(a, b) { return compare(a, b) * direction; }', '}' - ]); + JavaScript.provideFunction_('listsGetSortCompare', ` +function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(type, direction) { + var compareFuncs = { + 'NUMERIC': function(a, b) { + return Number(a) - Number(b); }, + 'TEXT': function(a, b) { + return a.toString() > b.toString() ? 1 : -1; }, + 'IGNORE_CASE': function(a, b) { + return a.toString().toLowerCase() > b.toString().toLowerCase() ? 1 : -1; }, + }; + var compare = compareFuncs[type]; + return function(a, b) { return compare(a, b) * direction; }; +} + `); return [ list + '.slice().sort(' + getCompareFunctionName + '("' + type + '", ' + direction + '))', @@ -368,12 +375,12 @@ JavaScript['lists_split'] = function(block) { // Block for splitting text into a list, or joining a list into text. let input = JavaScript.valueToCode(block, 'INPUT', JavaScript.ORDER_MEMBER); const delimiter = - JavaScript.valueToCode(block, 'DELIM', JavaScript.ORDER_NONE) || '\'\''; + JavaScript.valueToCode(block, 'DELIM', JavaScript.ORDER_NONE) || "''"; const mode = block.getFieldValue('MODE'); let functionName; if (mode === 'SPLIT') { if (!input) { - input = '\'\''; + input = "''"; } functionName = 'split'; } else if (mode === 'JOIN') { diff --git a/generators/javascript/math.js b/generators/javascript/math.js index 103d36a25..f370ba559 100644 --- a/generators/javascript/math.js +++ b/generators/javascript/math.js @@ -31,7 +31,7 @@ JavaScript['math_arithmetic'] = function(block) { 'MINUS': [' - ', JavaScript.ORDER_SUBTRACTION], 'MULTIPLY': [' * ', JavaScript.ORDER_MULTIPLICATION], 'DIVIDE': [' / ', JavaScript.ORDER_DIVISION], - 'POWER': [null, JavaScript.ORDER_NONE] // Handle power separately. + 'POWER': [null, JavaScript.ORDER_NONE], // Handle power separately. }; const tuple = OPERATORS[block.getFieldValue('OP')]; const operator = tuple[0]; @@ -137,11 +137,10 @@ JavaScript['math_constant'] = function(block) { const CONSTANTS = { 'PI': ['Math.PI', JavaScript.ORDER_MEMBER], 'E': ['Math.E', JavaScript.ORDER_MEMBER], - 'GOLDEN_RATIO': - ['(1 + Math.sqrt(5)) / 2', JavaScript.ORDER_DIVISION], + 'GOLDEN_RATIO': ['(1 + Math.sqrt(5)) / 2', JavaScript.ORDER_DIVISION], 'SQRT2': ['Math.SQRT2', JavaScript.ORDER_MEMBER], 'SQRT1_2': ['Math.SQRT1_2', JavaScript.ORDER_MEMBER], - 'INFINITY': ['Infinity', JavaScript.ORDER_ATOMIC] + 'INFINITY': ['Infinity', JavaScript.ORDER_ATOMIC], }; return CONSTANTS[block.getFieldValue('CONSTANT')]; }; @@ -149,60 +148,54 @@ JavaScript['math_constant'] = function(block) { JavaScript['math_number_property'] = function(block) { // Check if a number is even, odd, prime, whole, positive, or negative // or if it is divisible by certain number. Returns true or false. - const number_to_check = JavaScript.valueToCode(block, 'NUMBER_TO_CHECK', - JavaScript.ORDER_MODULUS) || '0'; - const dropdown_property = block.getFieldValue('PROPERTY'); + const PROPERTIES = { + 'EVEN': [' % 2 === 0', JavaScript.ORDER_MODULUS, JavaScript.ORDER_EQUALITY], + 'ODD': [' % 2 === 1', JavaScript.ORDER_MODULUS, JavaScript.ORDER_EQUALITY], + 'WHOLE': [' % 1 === 0', JavaScript.ORDER_MODULUS, + JavaScript.ORDER_EQUALITY], + 'POSITIVE': [' > 0', JavaScript.ORDER_RELATIONAL, + JavaScript.ORDER_RELATIONAL], + 'NEGATIVE': [' < 0', JavaScript.ORDER_RELATIONAL, + JavaScript.ORDER_RELATIONAL], + 'DIVISIBLE_BY': [null, JavaScript.ORDER_MODULUS, JavaScript.ORDER_EQUALITY], + 'PRIME': [null, JavaScript.ORDER_NONE, JavaScript.ORDER_FUNCTION_CALL], + }; + const dropdownProperty = block.getFieldValue('PROPERTY'); + const [suffix, inputOrder, outputOrder] = PROPERTIES[dropdownProperty]; + const numberToCheck = JavaScript.valueToCode(block, 'NUMBER_TO_CHECK', + inputOrder) || '0'; let code; - if (dropdown_property === 'PRIME') { + if (dropdownProperty === 'PRIME') { // Prime is a special case as it is not a one-liner test. - const functionName = JavaScript.provideFunction_( - 'mathIsPrime', - ['function ' + JavaScript.FUNCTION_NAME_PLACEHOLDER_ + '(n) {', - ' // https://en.wikipedia.org/wiki/Primality_test#Naive_methods', - ' if (n == 2 || n == 3) {', - ' return true;', - ' }', - ' // False if n is NaN, negative, is 1, or not whole.', - ' // And false if n is divisible by 2 or 3.', - ' if (isNaN(n) || n <= 1 || n % 1 !== 0 || n % 2 === 0 ||' + - ' n % 3 === 0) {', - ' return false;', - ' }', - ' // Check all the numbers of form 6k +/- 1, up to sqrt(n).', - ' for (var x = 6; x <= Math.sqrt(n) + 1; x += 6) {', - ' if (n % (x - 1) === 0 || n % (x + 1) === 0) {', - ' return false;', - ' }', - ' }', - ' return true;', - '}']); - code = functionName + '(' + number_to_check + ')'; - return [code, JavaScript.ORDER_FUNCTION_CALL]; + const functionName = JavaScript.provideFunction_('mathIsPrime', ` +function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(n) { + // https://en.wikipedia.org/wiki/Primality_test#Naive_methods + if (n == 2 || n == 3) { + return true; } - switch (dropdown_property) { - case 'EVEN': - code = number_to_check + ' % 2 === 0'; - break; - case 'ODD': - code = number_to_check + ' % 2 === 1'; - break; - case 'WHOLE': - code = number_to_check + ' % 1 === 0'; - break; - case 'POSITIVE': - code = number_to_check + ' > 0'; - break; - case 'NEGATIVE': - code = number_to_check + ' < 0'; - break; - case 'DIVISIBLE_BY': { - const divisor = JavaScript.valueToCode(block, 'DIVISOR', - JavaScript.ORDER_MODULUS) || '0'; - code = number_to_check + ' % ' + divisor + ' === 0'; - break; + // False if n is NaN, negative, is 1, or not whole. + // And false if n is divisible by 2 or 3. + if (isNaN(n) || n <= 1 || n % 1 !== 0 || n % 2 === 0 || n % 3 === 0) { + return false; + } + // Check all the numbers of form 6k +/- 1, up to sqrt(n). + for (var x = 6; x <= Math.sqrt(n) + 1; x += 6) { + if (n % (x - 1) === 0 || n % (x + 1) === 0) { + return false; } } - return [code, JavaScript.ORDER_EQUALITY]; + return true; +} +`); + code = functionName + '(' + numberToCheck + ')'; + } else if (dropdownProperty === 'DIVISIBLE_BY') { + const divisor = JavaScript.valueToCode(block, 'DIVISOR', + JavaScript.ORDER_MODULUS) || '0'; + code = numberToCheck + ' % ' + divisor + ' === 0'; + } else { + code = numberToCheck + suffix; + } + return [code, outputOrder]; }; JavaScript['math_change'] = function(block) { @@ -243,13 +236,11 @@ JavaScript['math_on_list'] = function(block) { break; case 'AVERAGE': { // mathMean([null,null,1,3]) === 2.0. - const functionName = JavaScript.provideFunction_( - 'mathMean', - ['function ' + JavaScript.FUNCTION_NAME_PLACEHOLDER_ + - '(myList) {', - ' return myList.reduce(function(x, y) {return x + y;}) / ' + - 'myList.length;', - '}']); + const functionName = JavaScript.provideFunction_('mathMean', ` +function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(myList) { + return myList.reduce(function(x, y) {return x + y;}) / myList.length; +} +`); list = JavaScript.valueToCode(block, 'LIST', JavaScript.ORDER_NONE) || '[]'; code = functionName + '(' + list + ')'; @@ -257,21 +248,18 @@ JavaScript['math_on_list'] = function(block) { } case 'MEDIAN': { // mathMedian([null,null,1,3]) === 2.0. - const functionName = JavaScript.provideFunction_( - 'mathMedian', - ['function ' + JavaScript.FUNCTION_NAME_PLACEHOLDER_ + - '(myList) {', - ' var localList = myList.filter(function (x) ' + - '{return typeof x === \'number\';});', - ' if (!localList.length) return null;', - ' localList.sort(function(a, b) {return b - a;});', - ' if (localList.length % 2 === 0) {', - ' return (localList[localList.length / 2 - 1] + ' + - 'localList[localList.length / 2]) / 2;', - ' } else {', - ' return localList[(localList.length - 1) / 2];', - ' }', - '}']); + const functionName = JavaScript.provideFunction_('mathMedian', ` +function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(myList) { + var localList = myList.filter(function (x) {return typeof x === \'number\';}); + if (!localList.length) return null; + localList.sort(function(a, b) {return b - a;}); + if (localList.length % 2 === 0) { + return (localList[localList.length / 2 - 1] + localList[localList.length / 2]) / 2; + } else { + return localList[(localList.length - 1) / 2]; + } +} +`); list = JavaScript.valueToCode(block, 'LIST', JavaScript.ORDER_NONE) || '[]'; code = functionName + '(' + list + ')'; @@ -281,70 +269,67 @@ JavaScript['math_on_list'] = function(block) { // As a list of numbers can contain more than one mode, // the returned result is provided as an array. // Mode of [3, 'x', 'x', 1, 1, 2, '3'] -> ['x', 1]. - const functionName = JavaScript.provideFunction_( - 'mathModes', - ['function ' + JavaScript.FUNCTION_NAME_PLACEHOLDER_ + - '(values) {', - ' var modes = [];', - ' var counts = [];', - ' var maxCount = 0;', - ' for (var i = 0; i < values.length; i++) {', - ' var value = values[i];', - ' var found = false;', - ' var thisCount;', - ' for (var j = 0; j < counts.length; j++) {', - ' if (counts[j][0] === value) {', - ' thisCount = ++counts[j][1];', - ' found = true;', - ' break;', - ' }', - ' }', - ' if (!found) {', - ' counts.push([value, 1]);', - ' thisCount = 1;', - ' }', - ' maxCount = Math.max(thisCount, maxCount);', - ' }', - ' for (var j = 0; j < counts.length; j++) {', - ' if (counts[j][1] === maxCount) {', - ' modes.push(counts[j][0]);', - ' }', - ' }', - ' return modes;', - '}']); + const functionName = JavaScript.provideFunction_('mathModes', ` +function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(values) { + var modes = []; + var counts = []; + var maxCount = 0; + for (var i = 0; i < values.length; i++) { + var value = values[i]; + var found = false; + var thisCount; + for (var j = 0; j < counts.length; j++) { + if (counts[j][0] === value) { + thisCount = ++counts[j][1]; + found = true; + break; + } + } + if (!found) { + counts.push([value, 1]); + thisCount = 1; + } + maxCount = Math.max(thisCount, maxCount); + } + for (var j = 0; j < counts.length; j++) { + if (counts[j][1] === maxCount) { + modes.push(counts[j][0]); + } + } + return modes; +} +`); list = JavaScript.valueToCode(block, 'LIST', JavaScript.ORDER_NONE) || '[]'; code = functionName + '(' + list + ')'; break; } case 'STD_DEV': { - const functionName = JavaScript.provideFunction_( - 'mathStandardDeviation', - ['function ' + JavaScript.FUNCTION_NAME_PLACEHOLDER_ + - '(numbers) {', - ' var n = numbers.length;', - ' if (!n) return null;', - ' var mean = numbers.reduce(function(x, y) {return x + y;}) / n;', - ' var variance = 0;', - ' for (var j = 0; j < n; j++) {', - ' variance += Math.pow(numbers[j] - mean, 2);', - ' }', - ' variance = variance / n;', - ' return Math.sqrt(variance);', - '}']); + const functionName = JavaScript.provideFunction_('mathStandardDeviation', ` +function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(numbers) { + var n = numbers.length; + if (!n) return null; + var mean = numbers.reduce(function(x, y) {return x + y;}) / n; + var variance = 0; + for (var j = 0; j < n; j++) { + variance += Math.pow(numbers[j] - mean, 2); + } + variance = variance / n; + return Math.sqrt(variance); +} +`); list = JavaScript.valueToCode(block, 'LIST', JavaScript.ORDER_NONE) || '[]'; code = functionName + '(' + list + ')'; break; } case 'RANDOM': { - const functionName = JavaScript.provideFunction_( - 'mathRandomList', - ['function ' + JavaScript.FUNCTION_NAME_PLACEHOLDER_ + - '(list) {', - ' var x = Math.floor(Math.random() * list.length);', - ' return list[x];', - '}']); + const functionName = JavaScript.provideFunction_('mathRandomList', ` +function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(list) { + var x = Math.floor(Math.random() * list.length); + return list[x]; +} +`); list = JavaScript.valueToCode(block, 'LIST', JavaScript.ORDER_NONE) || '[]'; code = functionName + '(' + list + ')'; @@ -385,18 +370,17 @@ JavaScript['math_random_int'] = function(block) { JavaScript.ORDER_NONE) || '0'; const argument1 = JavaScript.valueToCode(block, 'TO', JavaScript.ORDER_NONE) || '0'; - const functionName = JavaScript.provideFunction_( - 'mathRandomInt', - ['function ' + JavaScript.FUNCTION_NAME_PLACEHOLDER_ + - '(a, b) {', - ' if (a > b) {', - ' // Swap a and b to ensure a is smaller.', - ' var c = a;', - ' a = b;', - ' b = c;', - ' }', - ' return Math.floor(Math.random() * (b - a + 1) + a);', - '}']); + const functionName = JavaScript.provideFunction_('mathRandomInt', ` +function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(a, b) { + if (a > b) { + // Swap a and b to ensure a is smaller. + var c = a; + a = b; + b = c; + } + return Math.floor(Math.random() * (b - a + 1) + a); +} +`); const code = functionName + '(' + argument0 + ', ' + argument1 + ')'; return [code, JavaScript.ORDER_FUNCTION_CALL]; }; diff --git a/generators/javascript/text.js b/generators/javascript/text.js index 419ffbfec..9c60bfa6c 100644 --- a/generators/javascript/text.js +++ b/generators/javascript/text.js @@ -71,18 +71,18 @@ JavaScript['text_join'] = function(block) { // Create a string made up of any number of elements of any type. switch (block.itemCount_) { case 0: - return ['\'\'', JavaScript.ORDER_ATOMIC]; + return ["''", JavaScript.ORDER_ATOMIC]; case 1: { const element = JavaScript.valueToCode(block, 'ADD0', - JavaScript.ORDER_NONE) || '\'\''; + JavaScript.ORDER_NONE) || "''"; const codeAndOrder = forceString(element); return codeAndOrder; } case 2: { const element0 = JavaScript.valueToCode(block, 'ADD0', - JavaScript.ORDER_NONE) || '\'\''; + JavaScript.ORDER_NONE) || "''"; const element1 = JavaScript.valueToCode(block, 'ADD1', - JavaScript.ORDER_NONE) || '\'\''; + JavaScript.ORDER_NONE) || "''"; const code = forceString(element0)[0] + ' + ' + forceString(element1)[0]; return [code, JavaScript.ORDER_ADDITION]; @@ -91,7 +91,7 @@ JavaScript['text_join'] = function(block) { const elements = new Array(block.itemCount_); for (let i = 0; i < block.itemCount_; i++) { elements[i] = JavaScript.valueToCode(block, 'ADD' + i, - JavaScript.ORDER_NONE) || '\'\''; + JavaScript.ORDER_NONE) || "''"; } const code = '[' + elements.join(',') + '].join(\'\')'; return [code, JavaScript.ORDER_FUNCTION_CALL]; @@ -104,7 +104,7 @@ JavaScript['text_append'] = function(block) { const varName = JavaScript.nameDB_.getName( block.getFieldValue('VAR'), NameType.VARIABLE); const value = JavaScript.valueToCode(block, 'TEXT', - JavaScript.ORDER_NONE) || '\'\''; + JavaScript.ORDER_NONE) || "''"; const code = varName + ' += ' + forceString(value)[0] + ';\n'; return code; @@ -113,14 +113,14 @@ JavaScript['text_append'] = function(block) { JavaScript['text_length'] = function(block) { // String or array length. const text = JavaScript.valueToCode(block, 'VALUE', - JavaScript.ORDER_MEMBER) || '\'\''; + JavaScript.ORDER_MEMBER) || "''"; return [text + '.length', JavaScript.ORDER_MEMBER]; }; JavaScript['text_isEmpty'] = function(block) { // Is the string null or array empty? const text = JavaScript.valueToCode(block, 'VALUE', - JavaScript.ORDER_MEMBER) || '\'\''; + JavaScript.ORDER_MEMBER) || "''"; return ['!' + text + '.length', JavaScript.ORDER_LOGICAL_NOT]; }; @@ -129,9 +129,9 @@ JavaScript['text_indexOf'] = function(block) { const operator = block.getFieldValue('END') === 'FIRST' ? 'indexOf' : 'lastIndexOf'; const substring = JavaScript.valueToCode(block, 'FIND', - JavaScript.ORDER_NONE) || '\'\''; + JavaScript.ORDER_NONE) || "''"; const text = JavaScript.valueToCode(block, 'VALUE', - JavaScript.ORDER_MEMBER) || '\'\''; + JavaScript.ORDER_MEMBER) || "''"; const code = text + '.' + operator + '(' + substring + ')'; // Adjust index if using one-based indices. if (block.workspace.options.oneBasedIndex) { @@ -146,8 +146,7 @@ JavaScript['text_charAt'] = function(block) { const where = block.getFieldValue('WHERE') || 'FROM_START'; const textOrder = (where === 'RANDOM') ? JavaScript.ORDER_NONE : JavaScript.ORDER_MEMBER; - const text = JavaScript.valueToCode(block, 'VALUE', - textOrder) || '\'\''; + const text = JavaScript.valueToCode(block, 'VALUE', textOrder) || "''"; switch (where) { case 'FIRST': { const code = text + '.charAt(0)'; @@ -169,13 +168,12 @@ JavaScript['text_charAt'] = function(block) { return [code, JavaScript.ORDER_FUNCTION_CALL]; } case 'RANDOM': { - const functionName = JavaScript.provideFunction_( - 'textRandomLetter', - ['function ' + JavaScript.FUNCTION_NAME_PLACEHOLDER_ + - '(text) {', - ' var x = Math.floor(Math.random() * text.length);', - ' return text[x];', - '}']); + const functionName = JavaScript.provideFunction_('textRandomLetter', ` +function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(text) { + var x = Math.floor(Math.random() * text.length); + return text[x]; +} +`); const code = functionName + '(' + text + ')'; return [code, JavaScript.ORDER_FUNCTION_CALL]; } @@ -191,8 +189,7 @@ JavaScript['text_getSubstring'] = function(block) { where2 !== 'FROM_END' && where2 !== 'LAST'); const textOrder = requiresLengthCall ? JavaScript.ORDER_MEMBER : JavaScript.ORDER_NONE; - const text = JavaScript.valueToCode(block, 'STRING', - textOrder) || '\'\''; + const text = JavaScript.valueToCode(block, 'STRING', textOrder) || "''"; let code; if (where1 === 'FIRST' && where2 === 'LAST') { code = text; @@ -238,22 +235,20 @@ JavaScript['text_getSubstring'] = function(block) { const at2 = JavaScript.getAdjusted(block, 'AT2'); const wherePascalCase = {'FIRST': 'First', 'LAST': 'Last', 'FROM_START': 'FromStart', 'FROM_END': 'FromEnd'}; + // The value for 'FROM_END' and'FROM_START' depends on `at` so + // we add it as a parameter. + const at1Param = + (where1 === 'FROM_END' || where1 === 'FROM_START') ? ', at1' : ''; + const at2Param = + (where2 === 'FROM_END' || where2 === 'FROM_START') ? ', at2' : ''; const functionName = JavaScript.provideFunction_( - 'subsequence' + wherePascalCase[where1] + wherePascalCase[where2], [ - 'function ' + JavaScript.FUNCTION_NAME_PLACEHOLDER_ + - '(sequence' + - // The value for 'FROM_END' and'FROM_START' depends on `at` so - // we add it as a parameter. - ((where1 === 'FROM_END' || where1 === 'FROM_START') ? ', at1' : - '') + - ((where2 === 'FROM_END' || where2 === 'FROM_START') ? ', at2' : - '') + - ') {', - ' var start = ' + getSubstringIndex('sequence', where1, 'at1') + ';', - ' var end = ' + getSubstringIndex('sequence', where2, 'at2') + - ' + 1;', - ' return sequence.slice(start, end);', '}' - ]); + 'subsequence' + wherePascalCase[where1] + wherePascalCase[where2], ` +function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(sequence${at1Param}${at2Param}) { + var start = ${getSubstringIndex('sequence', where1, 'at1')}; + var end = ${getSubstringIndex('sequence', where2, 'at2')} + 1; + return sequence.slice(start, end); +} +`); code = functionName + '(' + text + // The value for 'FROM_END' and 'FROM_START' depends on `at` so we // pass it. @@ -269,27 +264,23 @@ JavaScript['text_changeCase'] = function(block) { const OPERATORS = { 'UPPERCASE': '.toUpperCase()', 'LOWERCASE': '.toLowerCase()', - 'TITLECASE': null + 'TITLECASE': null, }; const operator = OPERATORS[block.getFieldValue('CASE')]; - const textOrder = operator ? JavaScript.ORDER_MEMBER : - JavaScript.ORDER_NONE; - const text = JavaScript.valueToCode(block, 'TEXT', - textOrder) || '\'\''; + const textOrder = operator ? JavaScript.ORDER_MEMBER : JavaScript.ORDER_NONE; + const text = JavaScript.valueToCode(block, 'TEXT', textOrder) || "''"; let code; if (operator) { // Upper and lower case are functions built into JavaScript. code = text + operator; } else { // Title case is not a native JavaScript function. Define one. - const functionName = JavaScript.provideFunction_( - 'textToTitleCase', - ['function ' + JavaScript.FUNCTION_NAME_PLACEHOLDER_ + - '(str) {', - ' return str.replace(/\\S+/g,', - ' function(txt) {return txt[0].toUpperCase() + ' + - 'txt.substring(1).toLowerCase();});', - '}']); + const functionName = JavaScript.provideFunction_('textToTitleCase', ` +function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(str) { + return str.replace(/\\S+/g, + function(txt) {return txt[0].toUpperCase() + txt.substring(1).toLowerCase();}); +} +`); code = functionName + '(' + text + ')'; } return [code, JavaScript.ORDER_FUNCTION_CALL]; @@ -300,18 +291,18 @@ JavaScript['text_trim'] = function(block) { const OPERATORS = { 'LEFT': ".replace(/^[\\s\\xa0]+/, '')", 'RIGHT': ".replace(/[\\s\\xa0]+$/, '')", - 'BOTH': '.trim()' + 'BOTH': '.trim()', }; const operator = OPERATORS[block.getFieldValue('MODE')]; const text = JavaScript.valueToCode(block, 'TEXT', - JavaScript.ORDER_MEMBER) || '\'\''; + JavaScript.ORDER_MEMBER) || "''"; return [text + operator, JavaScript.ORDER_FUNCTION_CALL]; }; JavaScript['text_print'] = function(block) { // Print statement. const msg = JavaScript.valueToCode(block, 'TEXT', - JavaScript.ORDER_NONE) || '\'\''; + JavaScript.ORDER_NONE) || "''"; return 'window.alert(' + msg + ');\n'; }; @@ -323,8 +314,7 @@ JavaScript['text_prompt_ext'] = function(block) { msg = JavaScript.quote_(block.getFieldValue('TEXT')); } else { // External message. - msg = JavaScript.valueToCode(block, 'TEXT', - JavaScript.ORDER_NONE) || '\'\''; + msg = JavaScript.valueToCode(block, 'TEXT', JavaScript.ORDER_NONE) || "''"; } let code = 'window.prompt(' + msg + ')'; const toNumber = block.getFieldValue('TYPE') === 'NUMBER'; @@ -338,48 +328,44 @@ JavaScript['text_prompt'] = JavaScript['text_prompt_ext']; JavaScript['text_count'] = function(block) { const text = JavaScript.valueToCode(block, 'TEXT', - JavaScript.ORDER_NONE) || '\'\''; + JavaScript.ORDER_NONE) || "''"; const sub = JavaScript.valueToCode(block, 'SUB', - JavaScript.ORDER_NONE) || '\'\''; - const functionName = JavaScript.provideFunction_( - 'textCount', - ['function ' + JavaScript.FUNCTION_NAME_PLACEHOLDER_ + - '(haystack, needle) {', - ' if (needle.length === 0) {', - ' return haystack.length + 1;', - ' } else {', - ' return haystack.split(needle).length - 1;', - ' }', - '}']); + JavaScript.ORDER_NONE) || "''"; + const functionName = JavaScript.provideFunction_('textCount', ` +function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(haystack, needle) { + if (needle.length === 0) { + return haystack.length + 1; + } else { + return haystack.split(needle).length - 1; + } +} +`); const code = functionName + '(' + text + ', ' + sub + ')'; return [code, JavaScript.ORDER_FUNCTION_CALL]; }; JavaScript['text_replace'] = function(block) { const text = JavaScript.valueToCode(block, 'TEXT', - JavaScript.ORDER_NONE) || '\'\''; + JavaScript.ORDER_NONE) || "''"; const from = JavaScript.valueToCode(block, 'FROM', - JavaScript.ORDER_NONE) || '\'\''; - const to = JavaScript.valueToCode(block, 'TO', - JavaScript.ORDER_NONE) || '\'\''; + JavaScript.ORDER_NONE) || "''"; + const to = JavaScript.valueToCode(block, 'TO', JavaScript.ORDER_NONE) || "''"; // The regex escaping code below is taken from the implementation of // goog.string.regExpEscape. - const functionName = JavaScript.provideFunction_( - 'textReplace', - ['function ' + JavaScript.FUNCTION_NAME_PLACEHOLDER_ + - '(haystack, needle, replacement) {', - ' needle = ' + - 'needle.replace(/([-()\\[\\]{}+?*.$\\^|,:# 0 ? 'else' : '') + 'if ' + conditionCode + ' then\n' + branchCode; diff --git a/generators/lua/math.js b/generators/lua/math.js index 7fdb50aa8..b17821a33 100644 --- a/generators/lua/math.js +++ b/generators/lua/math.js @@ -25,11 +25,11 @@ Lua['math_number'] = function(block) { Lua['math_arithmetic'] = function(block) { // Basic arithmetic operators, and power. const OPERATORS = { - ADD: [' + ', Lua.ORDER_ADDITIVE], - MINUS: [' - ', Lua.ORDER_ADDITIVE], - MULTIPLY: [' * ', Lua.ORDER_MULTIPLICATIVE], - DIVIDE: [' / ', Lua.ORDER_MULTIPLICATIVE], - POWER: [' ^ ', Lua.ORDER_EXPONENTIATION] + 'ADD': [' + ', Lua.ORDER_ADDITIVE], + 'MINUS': [' - ', Lua.ORDER_ADDITIVE], + 'MULTIPLY': [' * ', Lua.ORDER_MULTIPLICATIVE], + 'DIVIDE': [' / ', Lua.ORDER_MULTIPLICATIVE], + 'POWER': [' ^ ', Lua.ORDER_EXPONENTIATION], }; const tuple = OPERATORS[block.getFieldValue('OP')]; const operator = tuple[0]; @@ -113,12 +113,12 @@ Lua['math_single'] = function(block) { Lua['math_constant'] = function(block) { // Constants: PI, E, the Golden Ratio, sqrt(2), 1/sqrt(2), INFINITY. const CONSTANTS = { - PI: ['math.pi', Lua.ORDER_HIGH], - E: ['math.exp(1)', Lua.ORDER_HIGH], - GOLDEN_RATIO: ['(1 + math.sqrt(5)) / 2', Lua.ORDER_MULTIPLICATIVE], - SQRT2: ['math.sqrt(2)', Lua.ORDER_HIGH], - SQRT1_2: ['math.sqrt(1 / 2)', Lua.ORDER_HIGH], - INFINITY: ['math.huge', Lua.ORDER_HIGH] + 'PI': ['math.pi', Lua.ORDER_HIGH], + 'E': ['math.exp(1)', Lua.ORDER_HIGH], + 'GOLDEN_RATIO': ['(1 + math.sqrt(5)) / 2', Lua.ORDER_MULTIPLICATIVE], + 'SQRT2': ['math.sqrt(2)', Lua.ORDER_HIGH], + 'SQRT1_2': ['math.sqrt(1 / 2)', Lua.ORDER_HIGH], + 'INFINITY': ['math.huge', Lua.ORDER_HIGH], }; return CONSTANTS[block.getFieldValue('CONSTANT')]; }; @@ -126,61 +126,59 @@ Lua['math_constant'] = function(block) { Lua['math_number_property'] = function(block) { // Check if a number is even, odd, prime, whole, positive, or negative // or if it is divisible by certain number. Returns true or false. - const number_to_check = - Lua.valueToCode(block, 'NUMBER_TO_CHECK', Lua.ORDER_MULTIPLICATIVE) || - '0'; - const dropdown_property = block.getFieldValue('PROPERTY'); + const PROPERTIES = { + 'EVEN': [' % 2 == 0', Lua.ORDER_MULTIPLICATIVE, Lua.ORDER_RELATIONAL], + 'ODD': [' % 2 == 1', Lua.ORDER_MULTIPLICATIVE, Lua.ORDER_RELATIONAL], + 'WHOLE': [' % 1 == 0', Lua.ORDER_MULTIPLICATIVE, Lua.ORDER_RELATIONAL], + 'POSITIVE': [' > 0', Lua.ORDER_RELATIONAL, Lua.ORDER_RELATIONAL], + 'NEGATIVE': [' < 0', Lua.ORDER_RELATIONAL, Lua.ORDER_RELATIONAL], + 'DIVISIBLE_BY': [null, Lua.ORDER_MULTIPLICATIVE, Lua.ORDER_RELATIONAL], + 'PRIME': [null, Lua.ORDER_NONE, Lua.ORDER_HIGH], + }; + const dropdownProperty = block.getFieldValue('PROPERTY'); + const [suffix, inputOrder, outputOrder] = PROPERTIES[dropdownProperty]; + const numberToCheck = Lua.valueToCode(block, 'NUMBER_TO_CHECK', + inputOrder) || '0'; let code; - if (dropdown_property === 'PRIME') { + if (dropdownProperty === 'PRIME') { // Prime is a special case as it is not a one-liner test. - const functionName = Lua.provideFunction_('math_isPrime', [ - 'function ' + Lua.FUNCTION_NAME_PLACEHOLDER_ + '(n)', - ' -- https://en.wikipedia.org/wiki/Primality_test#Naive_methods', - ' if n == 2 or n == 3 then', ' return true', ' end', - ' -- False if n is NaN, negative, is 1, or not whole.', - ' -- And false if n is divisible by 2 or 3.', - ' if not(n > 1) or n % 1 ~= 0 or n % 2 == 0 or n % 3 == 0 then', - ' return false', ' end', - ' -- Check all the numbers of form 6k +/- 1, up to sqrt(n).', - ' for x = 6, math.sqrt(n) + 1.5, 6 do', - ' if n % (x - 1) == 0 or n % (x + 1) == 0 then', ' return false', - ' end', ' end', ' return true', 'end' - ]); - code = functionName + '(' + number_to_check + ')'; - return [code, Lua.ORDER_HIGH]; - } - switch (dropdown_property) { - case 'EVEN': - code = number_to_check + ' % 2 == 0'; - break; - case 'ODD': - code = number_to_check + ' % 2 == 1'; - break; - case 'WHOLE': - code = number_to_check + ' % 1 == 0'; - break; - case 'POSITIVE': - code = number_to_check + ' > 0'; - break; - case 'NEGATIVE': - code = number_to_check + ' < 0'; - break; - case 'DIVISIBLE_BY': { - const divisor = - Lua.valueToCode(block, 'DIVISOR', Lua.ORDER_MULTIPLICATIVE); - // If 'divisor' is some code that evals to 0, Lua will produce a nan. - // Let's produce nil if we can determine this at compile-time. - if (!divisor || divisor === '0') { - return ['nil', Lua.ORDER_ATOMIC]; - } - // The normal trick to implement ?: with and/or doesn't work here: - // divisor == 0 and nil or number_to_check % divisor == 0 - // because nil is false, so allow a runtime failure. :-( - code = number_to_check + ' % ' + divisor + ' == 0'; - break; + const functionName = Lua.provideFunction_('math_isPrime', ` +function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(n) + -- https://en.wikipedia.org/wiki/Primality_test#Naive_methods + if n == 2 or n == 3 then + return true + end + -- False if n is NaN, negative, is 1, or not whole. + -- And false if n is divisible by 2 or 3. + if not(n > 1) or n % 1 ~= 0 or n % 2 == 0 or n % 3 == 0 then + return false + end + -- Check all the numbers of form 6k +/- 1, up to sqrt(n). + for x = 6, math.sqrt(n) + 1.5, 6 do + if n % (x - 1) == 0 or n % (x + 1) == 0 then + return false + end + end + return true +end +`); + code = functionName + '(' + numberToCheck + ')'; + } else if (dropdownProperty === 'DIVISIBLE_BY') { + const divisor = Lua.valueToCode(block, 'DIVISOR', + Lua.ORDER_MULTIPLICATIVE) || '0'; + // If 'divisor' is some code that evals to 0, Lua will produce a nan. + // Let's produce nil if we can determine this at compile-time. + if (divisor === '0') { + return ['nil', Lua.ORDER_ATOMIC]; } + // The normal trick to implement ?: with and/or doesn't work here: + // divisor == 0 and nil or number_to_check % divisor == 0 + // because nil is false, so allow a runtime failure. :-( + code = numberToCheck + ' % ' + divisor + ' == 0'; + } else { + code = numberToCheck + suffix; } - return [code, Lua.ORDER_RELATIONAL]; + return [code, outputOrder]; }; Lua['math_change'] = function(block) { @@ -204,11 +202,15 @@ Lua['math_on_list'] = function(block) { // Functions needed in more than one case. function provideSum() { - return Lua.provideFunction_('math_sum', [ - 'function ' + Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t)', - ' local result = 0', ' for _, v in ipairs(t) do', - ' result = result + v', ' end', ' return result', 'end' - ]); + return Lua.provideFunction_('math_sum', ` +function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(t) + local result = 0 + for _, v in ipairs(t) do + result = result + v + end + return result +end +`); } switch (func) { @@ -218,103 +220,139 @@ Lua['math_on_list'] = function(block) { case 'MIN': // Returns 0 for the empty list. - functionName = Lua.provideFunction_('math_min', [ - 'function ' + Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t)', - ' if #t == 0 then', ' return 0', ' end', - ' local result = math.huge', ' for _, v in ipairs(t) do', - ' if v < result then', ' result = v', ' end', ' end', - ' return result', 'end' - ]); + functionName = Lua.provideFunction_('math_min', ` +function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(t) + if #t == 0 then + return 0 + end + local result = math.huge + for _, v in ipairs(t) do + if v < result then + result = v + end + end + return result +end +`); break; case 'AVERAGE': // Returns 0 for the empty list. - functionName = Lua.provideFunction_('math_average', [ - 'function ' + Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t)', - ' if #t == 0 then', ' return 0', ' end', - ' return ' + provideSum() + '(t) / #t', 'end' - ]); + functionName = Lua.provideFunction_('math_average', ` +function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(t) + if #t == 0 then + return 0 + end + return ${provideSum()}(t) / #t +end +`); break; case 'MAX': // Returns 0 for the empty list. - functionName = Lua.provideFunction_('math_max', [ - 'function ' + Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t)', - ' if #t == 0 then', ' return 0', ' end', - ' local result = -math.huge', ' for _, v in ipairs(t) do', - ' if v > result then', ' result = v', ' end', ' end', - ' return result', 'end' - ]); + functionName = Lua.provideFunction_('math_max', ` +function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(t) + if #t == 0 then + return 0 + end + local result = -math.huge + for _, v in ipairs(t) do + if v > result then + result = v + end + end + return result +end +`); break; case 'MEDIAN': - functionName = Lua.provideFunction_( - 'math_median', - // This operation excludes non-numbers. - [ - 'function ' + Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t)', - ' -- Source: http://lua-users.org/wiki/SimpleStats', - ' if #t == 0 then', ' return 0', ' end', ' local temp={}', - ' for _, v in ipairs(t) do', ' if type(v) == "number" then', - ' table.insert(temp, v)', ' end', ' end', - ' table.sort(temp)', ' if #temp % 2 == 0 then', - ' return (temp[#temp/2] + temp[(#temp/2)+1]) / 2', ' else', - ' return temp[math.ceil(#temp/2)]', ' end', 'end' - ]); + // This operation excludes non-numbers. + functionName = Lua.provideFunction_('math_median', ` +function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(t) + -- Source: http://lua-users.org/wiki/SimpleStats + if #t == 0 then + return 0 + end + local temp = {} + for _, v in ipairs(t) do + if type(v) == 'number' then + table.insert(temp, v) + end + end + table.sort(temp) + if #temp % 2 == 0 then + return (temp[#temp / 2] + temp[(#temp / 2) + 1]) / 2 + else + return temp[math.ceil(#temp / 2)] + end +end +`); break; case 'MODE': - functionName = Lua.provideFunction_( - 'math_modes', - // As a list of numbers can contain more than one mode, - // the returned result is provided as an array. - // The Lua version includes non-numbers. - [ - 'function ' + Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t)', - ' -- Source: http://lua-users.org/wiki/SimpleStats', - ' local counts={}', - ' for _, v in ipairs(t) do', - ' if counts[v] == nil then', - ' counts[v] = 1', - ' else', - ' counts[v] = counts[v] + 1', - ' end', - ' end', - ' local biggestCount = 0', - ' for _, v in pairs(counts) do', - ' if v > biggestCount then', - ' biggestCount = v', - ' end', - ' end', - ' local temp={}', - ' for k, v in pairs(counts) do', - ' if v == biggestCount then', - ' table.insert(temp, k)', - ' end', - ' end', - ' return temp', - 'end' - ]); + // As a list of numbers can contain more than one mode, + // the returned result is provided as an array. + // The Lua version includes non-numbers. + functionName = Lua.provideFunction_('math_modes', ` +function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(t) + -- Source: http://lua-users.org/wiki/SimpleStats + local counts = {} + for _, v in ipairs(t) do + if counts[v] == nil then + counts[v] = 1 + else + counts[v] = counts[v] + 1 + end + end + local biggestCount = 0 + for _, v in pairs(counts) do + if v > biggestCount then + biggestCount = v + end + end + local temp = {} + for k, v in pairs(counts) do + if v == biggestCount then + table.insert(temp, k) + end + end + return temp +end +`); break; case 'STD_DEV': - functionName = Lua.provideFunction_('math_standard_deviation', [ - 'function ' + Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t)', ' local m', - ' local vm', ' local total = 0', ' local count = 0', - ' local result', ' m = #t == 0 and 0 or ' + provideSum() + '(t) / #t', - ' for _, v in ipairs(t) do', ' if type(v) == \'number\' then', - ' vm = v - m', ' total = total + (vm * vm)', - ' count = count + 1', ' end', ' end', - ' result = math.sqrt(total / (count-1))', ' return result', 'end' - ]); + functionName = Lua.provideFunction_('math_standard_deviation', ` +function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(t) + local m + local vm + local total = 0 + local count = 0 + local result + m = #t == 0 and 0 or ${provideSum()}(t) / #t + for _, v in ipairs(t) do + if type(v) == 'number' then + vm = v - m + total = total + (vm * vm) + count = count + 1 + end + end + result = math.sqrt(total / (count-1)) + return result +end +`); break; case 'RANDOM': - functionName = Lua.provideFunction_('math_random_list', [ - 'function ' + Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t)', - ' if #t == 0 then', ' return nil', ' end', - ' return t[math.random(#t)]', 'end' - ]); + functionName = Lua.provideFunction_('math_random_list', ` +function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(t) + if #t == 0 then + return nil + end + return t[math.random(#t)] +end +`); break; default: diff --git a/generators/lua/text.js b/generators/lua/text.js index a8dae86c9..74fe842d8 100644 --- a/generators/lua/text.js +++ b/generators/lua/text.js @@ -32,22 +32,22 @@ Lua['text_multiline'] = function(block) { Lua['text_join'] = function(block) { // Create a string made up of any number of elements of any type. if (block.itemCount_ === 0) { - return ['\'\'', Lua.ORDER_ATOMIC]; + return ["''", Lua.ORDER_ATOMIC]; } else if (block.itemCount_ === 1) { - const element = Lua.valueToCode(block, 'ADD0', Lua.ORDER_NONE) || '\'\''; + const element = Lua.valueToCode(block, 'ADD0', Lua.ORDER_NONE) || "''"; const code = 'tostring(' + element + ')'; return [code, Lua.ORDER_HIGH]; } else if (block.itemCount_ === 2) { const element0 = - Lua.valueToCode(block, 'ADD0', Lua.ORDER_CONCATENATION) || '\'\''; + Lua.valueToCode(block, 'ADD0', Lua.ORDER_CONCATENATION) || "''"; const element1 = - Lua.valueToCode(block, 'ADD1', Lua.ORDER_CONCATENATION) || '\'\''; + Lua.valueToCode(block, 'ADD1', Lua.ORDER_CONCATENATION) || "''"; const code = element0 + ' .. ' + element1; return [code, Lua.ORDER_CONCATENATION]; } else { const elements = []; for (let i = 0; i < block.itemCount_; i++) { - elements[i] = Lua.valueToCode(block, 'ADD' + i, Lua.ORDER_NONE) || '\'\''; + elements[i] = Lua.valueToCode(block, 'ADD' + i, Lua.ORDER_NONE) || "''"; } const code = 'table.concat({' + elements.join(', ') + '})'; return [code, Lua.ORDER_HIGH]; @@ -59,41 +59,47 @@ Lua['text_append'] = function(block) { const varName = Lua.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE); const value = - Lua.valueToCode(block, 'TEXT', Lua.ORDER_CONCATENATION) || '\'\''; + Lua.valueToCode(block, 'TEXT', Lua.ORDER_CONCATENATION) || "''"; return varName + ' = ' + varName + ' .. ' + value + '\n'; }; Lua['text_length'] = function(block) { // String or array length. - const text = Lua.valueToCode(block, 'VALUE', Lua.ORDER_UNARY) || '\'\''; + const text = Lua.valueToCode(block, 'VALUE', Lua.ORDER_UNARY) || "''"; return ['#' + text, Lua.ORDER_UNARY]; }; Lua['text_isEmpty'] = function(block) { // Is the string null or array empty? - const text = Lua.valueToCode(block, 'VALUE', Lua.ORDER_UNARY) || '\'\''; + const text = Lua.valueToCode(block, 'VALUE', Lua.ORDER_UNARY) || "''"; return ['#' + text + ' == 0', Lua.ORDER_RELATIONAL]; }; Lua['text_indexOf'] = function(block) { // Search the text for a substring. - const substring = Lua.valueToCode(block, 'FIND', Lua.ORDER_NONE) || '\'\''; - const text = Lua.valueToCode(block, 'VALUE', Lua.ORDER_NONE) || '\'\''; + const substring = Lua.valueToCode(block, 'FIND', Lua.ORDER_NONE) || "''"; + const text = Lua.valueToCode(block, 'VALUE', Lua.ORDER_NONE) || "''"; let functionName; if (block.getFieldValue('END') === 'FIRST') { - functionName = Lua.provideFunction_('firstIndexOf', [ - 'function ' + Lua.FUNCTION_NAME_PLACEHOLDER_ + '(str, substr) ', - ' local i = string.find(str, substr, 1, true)', ' if i == nil then', - ' return 0', ' else', ' return i', ' end', 'end' - ]); + functionName = Lua.provideFunction_('firstIndexOf', ` +function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(str, substr) + local i = string.find(str, substr, 1, true) + if i == nil then + return 0 + end + return i +end +`); } else { - functionName = Lua.provideFunction_('lastIndexOf', [ - 'function ' + Lua.FUNCTION_NAME_PLACEHOLDER_ + '(str, substr)', - ' local i = string.find(string.reverse(str), ' + - 'string.reverse(substr), 1, true)', - ' if i then', ' return #str + 2 - i - #substr', ' end', ' return 0', - 'end' - ]); + functionName = Lua.provideFunction_('lastIndexOf', ` +function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(str, substr) + local i = string.find(string.reverse(str), string.reverse(substr), 1, true) + if i then + return #str + 2 - i - #substr + end + return 0 +end +`); } const code = functionName + '(' + text + ', ' + substring + ')'; return [code, Lua.ORDER_HIGH]; @@ -105,14 +111,15 @@ Lua['text_charAt'] = function(block) { const where = block.getFieldValue('WHERE') || 'FROM_START'; const atOrder = (where === 'FROM_END') ? Lua.ORDER_UNARY : Lua.ORDER_NONE; const at = Lua.valueToCode(block, 'AT', atOrder) || '1'; - const text = Lua.valueToCode(block, 'VALUE', Lua.ORDER_NONE) || '\'\''; + const text = Lua.valueToCode(block, 'VALUE', Lua.ORDER_NONE) || "''"; let code; if (where === 'RANDOM') { - const functionName = Lua.provideFunction_('text_random_letter', [ - 'function ' + Lua.FUNCTION_NAME_PLACEHOLDER_ + '(str)', - ' local index = math.random(string.len(str))', - ' return string.sub(str, index, index)', 'end' - ]); + const functionName = Lua.provideFunction_('text_random_letter', ` +function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(str) + local index = math.random(string.len(str)) + return string.sub(str, index, index) +end +`); code = functionName + '(' + text + ')'; } else { let start; @@ -133,10 +140,11 @@ Lua['text_charAt'] = function(block) { code = 'string.sub(' + text + ', ' + start + ', ' + start + ')'; } else { // use function to avoid reevaluation - const functionName = Lua.provideFunction_('text_char_at', [ - 'function ' + Lua.FUNCTION_NAME_PLACEHOLDER_ + '(str, index)', - ' return string.sub(str, index, index)', 'end' - ]); + const functionName = Lua.provideFunction_('text_char_at', ` +function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(str, index) + return string.sub(str, index, index) +end +`); code = functionName + '(' + text + ', ' + start + ')'; } } @@ -145,7 +153,7 @@ Lua['text_charAt'] = function(block) { Lua['text_getSubstring'] = function(block) { // Get substring. - const text = Lua.valueToCode(block, 'STRING', Lua.ORDER_NONE) || '\'\''; + const text = Lua.valueToCode(block, 'STRING', Lua.ORDER_NONE) || "''"; // Get start index. const where1 = block.getFieldValue('WHERE1'); @@ -183,28 +191,35 @@ Lua['text_getSubstring'] = function(block) { Lua['text_changeCase'] = function(block) { // Change capitalization. const operator = block.getFieldValue('CASE'); - const text = Lua.valueToCode(block, 'TEXT', Lua.ORDER_NONE) || '\'\''; + const text = Lua.valueToCode(block, 'TEXT', Lua.ORDER_NONE) || "''"; let functionName; if (operator === 'UPPERCASE') { functionName = 'string.upper'; } else if (operator === 'LOWERCASE') { functionName = 'string.lower'; } else if (operator === 'TITLECASE') { - functionName = Lua.provideFunction_( - 'text_titlecase', - // There are shorter versions at - // http://lua-users.org/wiki/SciteTitleCase - // that do not preserve whitespace. - [ - 'function ' + Lua.FUNCTION_NAME_PLACEHOLDER_ + '(str)', - ' local buf = {}', ' local inWord = false', ' for i = 1, #str do', - ' local c = string.sub(str, i, i)', ' if inWord then', - ' table.insert(buf, string.lower(c))', - ' if string.find(c, "%s") then', ' inWord = false', - ' end', ' else', ' table.insert(buf, string.upper(c))', - ' inWord = true', ' end', ' end', - ' return table.concat(buf)', 'end' - ]); + // There are shorter versions at + // http://lua-users.org/wiki/SciteTitleCase + // that do not preserve whitespace. + functionName = Lua.provideFunction_('text_titlecase', ` +function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(str) + local buf = {} + local inWord = false + for i = 1, #str do + local c = string.sub(str, i, i) + if inWord then + table.insert(buf, string.lower(c)) + if string.find(c, "%s") then + inWord = false + end + else + table.insert(buf, string.upper(c)) + inWord = true + end + end + return table.concat(buf) +end +`); } const code = functionName + '(' + text + ')'; return [code, Lua.ORDER_HIGH]; @@ -214,14 +229,14 @@ Lua['text_trim'] = function(block) { // Trim spaces. const OPERATORS = {LEFT: '^%s*(,-)', RIGHT: '(.-)%s*$', BOTH: '^%s*(.-)%s*$'}; const operator = OPERATORS[block.getFieldValue('MODE')]; - const text = Lua.valueToCode(block, 'TEXT', Lua.ORDER_NONE) || '\'\''; + const text = Lua.valueToCode(block, 'TEXT', Lua.ORDER_NONE) || "''"; const code = 'string.gsub(' + text + ', "' + operator + '", "%1")'; return [code, Lua.ORDER_HIGH]; }; Lua['text_print'] = function(block) { // Print statement. - const msg = Lua.valueToCode(block, 'TEXT', Lua.ORDER_NONE) || '\'\''; + const msg = Lua.valueToCode(block, 'TEXT', Lua.ORDER_NONE) || "''"; return 'print(' + msg + ')\n'; }; @@ -233,13 +248,16 @@ Lua['text_prompt_ext'] = function(block) { msg = Lua.quote_(block.getFieldValue('TEXT')); } else { // External message. - msg = Lua.valueToCode(block, 'TEXT', Lua.ORDER_NONE) || '\'\''; + msg = Lua.valueToCode(block, 'TEXT', Lua.ORDER_NONE) || "''"; } - const functionName = Lua.provideFunction_('text_prompt', [ - 'function ' + Lua.FUNCTION_NAME_PLACEHOLDER_ + '(msg)', ' io.write(msg)', - ' io.flush()', ' return io.read()', 'end' - ]); + const functionName = Lua.provideFunction_('text_prompt', ` +function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(msg) + io.write(msg) + io.flush() + return io.read() +end +`); let code = functionName + '(' + msg + ')'; const toNumber = block.getFieldValue('TYPE') === 'NUMBER'; @@ -252,59 +270,58 @@ Lua['text_prompt_ext'] = function(block) { Lua['text_prompt'] = Lua['text_prompt_ext']; Lua['text_count'] = function(block) { - const text = Lua.valueToCode(block, 'TEXT', Lua.ORDER_NONE) || '\'\''; - const sub = Lua.valueToCode(block, 'SUB', Lua.ORDER_NONE) || '\'\''; - const functionName = Lua.provideFunction_('text_count', [ - 'function ' + Lua.FUNCTION_NAME_PLACEHOLDER_ + '(haystack, needle)', - ' if #needle == 0 then', - ' return #haystack + 1', - ' end', - ' local i = 1', - ' local count = 0', - ' while true do', - ' i = string.find(haystack, needle, i, true)', - ' if i == nil then', - ' break', - ' end', - ' count = count + 1', - ' i = i + #needle', - ' end', - ' return count', - 'end', - ]); + const text = Lua.valueToCode(block, 'TEXT', Lua.ORDER_NONE) || "''"; + const sub = Lua.valueToCode(block, 'SUB', Lua.ORDER_NONE) || "''"; + const functionName = Lua.provideFunction_('text_count', ` +function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(haystack, needle) + if #needle == 0 then + return #haystack + 1 + end + local i = 1 + local count = 0 + while true do + i = string.find(haystack, needle, i, true) + if i == nil then + break + end + count = count + 1 + i = i + #needle + end + return count +end +`); const code = functionName + '(' + text + ', ' + sub + ')'; return [code, Lua.ORDER_HIGH]; }; Lua['text_replace'] = function(block) { - const text = Lua.valueToCode(block, 'TEXT', Lua.ORDER_NONE) || '\'\''; - const from = Lua.valueToCode(block, 'FROM', Lua.ORDER_NONE) || '\'\''; - const to = Lua.valueToCode(block, 'TO', Lua.ORDER_NONE) || '\'\''; - const functionName = Lua.provideFunction_('text_replace', [ - 'function ' + Lua.FUNCTION_NAME_PLACEHOLDER_ + - '(haystack, needle, replacement)', - ' local buf = {}', - ' local i = 1', - ' while i <= #haystack do', - ' if string.sub(haystack, i, i + #needle - 1) == needle then', - ' for j = 1, #replacement do', - ' table.insert(buf, string.sub(replacement, j, j))', - ' end', - ' i = i + #needle', - ' else', - ' table.insert(buf, string.sub(haystack, i, i))', - ' i = i + 1', - ' end', - ' end', - ' return table.concat(buf)', - 'end', - ]); + const text = Lua.valueToCode(block, 'TEXT', Lua.ORDER_NONE) || "''"; + const from = Lua.valueToCode(block, 'FROM', Lua.ORDER_NONE) || "''"; + const to = Lua.valueToCode(block, 'TO', Lua.ORDER_NONE) || "''"; + const functionName = Lua.provideFunction_('text_replace', ` +function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(haystack, needle, replacement) + local buf = {} + local i = 1 + while i <= #haystack do + if string.sub(haystack, i, i + #needle - 1) == needle then + for j = 1, #replacement do + table.insert(buf, string.sub(replacement, j, j)) + end + i = i + #needle + else + table.insert(buf, string.sub(haystack, i, i)) + i = i + 1 + end + end + return table.concat(buf) +end +`); const code = functionName + '(' + text + ', ' + from + ', ' + to + ')'; return [code, Lua.ORDER_HIGH]; }; Lua['text_reverse'] = function(block) { - const text = Lua.valueToCode(block, 'TEXT', Lua.ORDER_NONE) || '\'\''; + const text = Lua.valueToCode(block, 'TEXT', Lua.ORDER_NONE) || "''"; const code = 'string.reverse(' + text + ')'; return [code, Lua.ORDER_HIGH]; }; diff --git a/generators/php/colour.js b/generators/php/colour.js index d50cbfb63..bbd2fd9d7 100644 --- a/generators/php/colour.js +++ b/generators/php/colour.js @@ -22,12 +22,11 @@ PHP['colour_picker'] = function(block) { PHP['colour_random'] = function(block) { // Generate a random colour. - const functionName = PHP.provideFunction_('colour_random', [ - 'function ' + PHP.FUNCTION_NAME_PLACEHOLDER_ + '() {', - ' return \'#\' . str_pad(dechex(mt_rand(0, 0xFFFFFF)), ' + - '6, \'0\', STR_PAD_LEFT);', - '}' - ]); + const functionName = PHP.provideFunction_('colour_random', ` +function ${PHP.FUNCTION_NAME_PLACEHOLDER_}() { + return '#' . str_pad(dechex(mt_rand(0, 0xFFFFFF)), 6, '0', STR_PAD_LEFT); +} +`); const code = functionName + '()'; return [code, PHP.ORDER_FUNCTION_CALL]; }; @@ -37,39 +36,46 @@ PHP['colour_rgb'] = function(block) { const red = PHP.valueToCode(block, 'RED', PHP.ORDER_NONE) || 0; const green = PHP.valueToCode(block, 'GREEN', PHP.ORDER_NONE) || 0; const blue = PHP.valueToCode(block, 'BLUE', PHP.ORDER_NONE) || 0; - const functionName = PHP.provideFunction_('colour_rgb', [ - 'function ' + 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;', - '}' - ]); + const functionName = PHP.provideFunction_('colour_rgb', ` +function ${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; +} +`); const code = functionName + '(' + red + ', ' + green + ', ' + blue + ')'; return [code, PHP.ORDER_FUNCTION_CALL]; }; PHP['colour_blend'] = function(block) { // Blend two colours together. - const c1 = PHP.valueToCode(block, 'COLOUR1', PHP.ORDER_NONE) || '\'#000000\''; - const c2 = PHP.valueToCode(block, 'COLOUR2', PHP.ORDER_NONE) || '\'#000000\''; + const c1 = PHP.valueToCode(block, 'COLOUR1', PHP.ORDER_NONE) || "'#000000'"; + const c2 = PHP.valueToCode(block, 'COLOUR2', PHP.ORDER_NONE) || "'#000000'"; const ratio = PHP.valueToCode(block, 'RATIO', PHP.ORDER_NONE) || 0.5; - const functionName = PHP.provideFunction_('colour_blend', [ - 'function ' + 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));', - ' $r2 = hexdec(substr($c2, 1, 2));', ' $g2 = hexdec(substr($c2, 3, 2));', - ' $b2 = hexdec(substr($c2, 5, 2));', - ' $r = round($r1 * (1 - $ratio) + $r2 * $ratio);', - ' $g = round($g1 * (1 - $ratio) + $g2 * $ratio);', - ' $b = round($b1 * (1 - $ratio) + $b2 * $ratio);', ' $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;', - '}' - ]); + const functionName = PHP.provideFunction_('colour_blend', ` +function ${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)); + $r2 = hexdec(substr($c2, 1, 2)); + $g2 = hexdec(substr($c2, 3, 2)); + $b2 = hexdec(substr($c2, 5, 2)); + $r = round($r1 * (1 - $ratio) + $r2 * $ratio); + $g = round($g1 * (1 - $ratio) + $g2 * $ratio); + $b = round($b1 * (1 - $ratio) + $b2 * $ratio); + $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; +} +`); const code = functionName + '(' + c1 + ', ' + c2 + ', ' + ratio + ')'; return [code, PHP.ORDER_FUNCTION_CALL]; }; diff --git a/generators/php/lists.js b/generators/php/lists.js index efc237887..1c39a1ab6 100644 --- a/generators/php/lists.js +++ b/generators/php/lists.js @@ -44,11 +44,15 @@ PHP['lists_create_with'] = function(block) { PHP['lists_repeat'] = function(block) { // Create a list with one element repeated. - const functionName = PHP.provideFunction_('lists_repeat', [ - 'function ' + PHP.FUNCTION_NAME_PLACEHOLDER_ + '($value, $count) {', - ' $array = array();', ' for ($index = 0; $index < $count; $index++) {', - ' $array[] = $value;', ' }', ' return $array;', '}' - ]); + const functionName = PHP.provideFunction_('lists_repeat', ` +function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($value, $count) { + $array = array(); + for ($index = 0; $index < $count; $index++) { + $array[] = $value; + } + return $array; +} +`); const element = PHP.valueToCode(block, 'ITEM', PHP.ORDER_NONE) || 'null'; const repeatCount = PHP.valueToCode(block, 'NUM', PHP.ORDER_NONE) || '0'; const code = functionName + '(' + element + ', ' + repeatCount + ')'; @@ -57,12 +61,16 @@ PHP['lists_repeat'] = function(block) { PHP['lists_length'] = function(block) { // String or array length. - const functionName = PHP.provideFunction_('length', [ - 'function ' + PHP.FUNCTION_NAME_PLACEHOLDER_ + '($value) {', - ' if (is_string($value)) {', ' return strlen($value);', ' } else {', - ' return count($value);', ' }', '}' - ]); - const list = PHP.valueToCode(block, 'VALUE', PHP.ORDER_NONE) || '\'\''; + const functionName = PHP.provideFunction_('length', ` +function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($value) { + if (is_string($value)) { + return strlen($value); + } else { + return count($value); + } +} +`); + const list = PHP.valueToCode(block, 'VALUE', PHP.ORDER_NONE) || "''"; return [functionName + '(' + list + ')', PHP.ORDER_FUNCTION_CALL]; }; @@ -75,7 +83,7 @@ PHP['lists_isEmpty'] = function(block) { PHP['lists_indexOf'] = function(block) { // Find an item in the list. - const argument0 = PHP.valueToCode(block, 'FIND', PHP.ORDER_NONE) || '\'\''; + const argument0 = PHP.valueToCode(block, 'FIND', PHP.ORDER_NONE) || "''"; const argument1 = PHP.valueToCode(block, 'VALUE', PHP.ORDER_MEMBER) || '[]'; let errorIndex = ' -1'; let indexAdjustment = ''; @@ -86,23 +94,25 @@ PHP['lists_indexOf'] = function(block) { let functionName; if (block.getFieldValue('END') === 'FIRST') { // indexOf - functionName = PHP.provideFunction_('indexOf', [ - 'function ' + PHP.FUNCTION_NAME_PLACEHOLDER_ + '($haystack, $needle) {', - ' for ($index = 0; $index < count($haystack); $index++) {', - ' if ($haystack[$index] == $needle) return $index' + indexAdjustment + - ';', - ' }', ' return ' + errorIndex + ';', '}' - ]); + functionName = PHP.provideFunction_('indexOf', ` +function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($haystack, $needle) { + for ($index = 0; $index < count($haystack); $index++) { + if ($haystack[$index] == $needle) return $index${indexAdjustment}; + } + return ${errorIndex}; +} +`); } else { // lastIndexOf - functionName = PHP.provideFunction_('lastIndexOf', [ - 'function ' + PHP.FUNCTION_NAME_PLACEHOLDER_ + '($haystack, $needle) {', - ' $last = ' + errorIndex + ';', - ' for ($index = 0; $index < count($haystack); $index++) {', - ' if ($haystack[$index] == $needle) $last = $index' + indexAdjustment + - ';', - ' }', ' return $last;', '}' - ]); + functionName = PHP.provideFunction_('lastIndexOf', ` +function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($haystack, $needle) { + $last = ${errorIndex}; + for ($index = 0; $index < count($haystack); $index++) { + if ($haystack[$index] == $needle) $last = $index${indexAdjustment}; + } + return $last; +} +`); } const code = functionName + '(' + argument1 + ', ' + argument0 + ')'; @@ -191,26 +201,30 @@ PHP['lists_getIndex'] = function(block) { case 'RANDOM': { const list = PHP.valueToCode(block, 'VALUE', PHP.ORDER_NONE) || 'array()'; if (mode === 'GET') { - const functionName = PHP.provideFunction_('lists_get_random_item', [ - 'function ' + PHP.FUNCTION_NAME_PLACEHOLDER_ + '($list) {', - ' return $list[rand(0,count($list)-1)];', '}' - ]); + const functionName = PHP.provideFunction_('lists_get_random_item', ` +function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($list) { + return $list[rand(0,count($list)-1)]; +} +`); const code = functionName + '(' + list + ')'; return [code, PHP.ORDER_FUNCTION_CALL]; } else if (mode === 'GET_REMOVE') { const functionName = - PHP.provideFunction_('lists_get_remove_random_item', [ - 'function ' + PHP.FUNCTION_NAME_PLACEHOLDER_ + '(&$list) {', - ' $x = rand(0,count($list)-1);', ' unset($list[$x]);', - ' return array_values($list);', '}' - ]); + PHP.provideFunction_('lists_get_remove_random_item', ` +function ${PHP.FUNCTION_NAME_PLACEHOLDER_}(&$list) { + $x = rand(0,count($list)-1); + unset($list[$x]); + return array_values($list); +} +`); const code = functionName + '(' + list + ')'; return [code, PHP.ORDER_FUNCTION_CALL]; } else if (mode === 'REMOVE') { - const functionName = PHP.provideFunction_('lists_remove_random_item', [ - 'function ' + PHP.FUNCTION_NAME_PLACEHOLDER_ + '(&$list) {', - ' unset($list[rand(0,count($list)-1)]);', '}' - ]); + const functionName = PHP.provideFunction_('lists_remove_random_item', ` +function ${PHP.FUNCTION_NAME_PLACEHOLDER_}(&$list) { + unset($list[rand(0,count($list)-1)]); +} +`); return functionName + '(' + list + ');\n'; } break; @@ -252,10 +266,11 @@ PHP['lists_setIndex'] = function(block) { case 'LAST': { const list = PHP.valueToCode(block, 'LIST', PHP.ORDER_NONE) || 'array()'; if (mode === 'SET') { - const functionName = PHP.provideFunction_('lists_set_last_item', [ - 'function ' + PHP.FUNCTION_NAME_PLACEHOLDER_ + '(&$list, $value) {', - ' $list[count($list) - 1] = $value;', '}' - ]); + const functionName = PHP.provideFunction_('lists_set_last_item', ` +function ${PHP.FUNCTION_NAME_PLACEHOLDER_}(&$list, $value) { + $list[count($list) - 1] = $value; +} +`); return functionName + '(' + list + ', ' + value + ');\n'; } else if (mode === 'INSERT') { return 'array_push(' + list + ', ' + value + ');\n'; @@ -279,18 +294,18 @@ PHP['lists_setIndex'] = function(block) { const list = PHP.valueToCode(block, 'LIST', PHP.ORDER_NONE) || 'array()'; const at = PHP.getAdjusted(block, 'AT', 1); if (mode === 'SET') { - const functionName = PHP.provideFunction_('lists_set_from_end', [ - 'function ' + PHP.FUNCTION_NAME_PLACEHOLDER_ + - '(&$list, $at, $value) {', - ' $list[count($list) - $at] = $value;', '}' - ]); + const functionName = PHP.provideFunction_('lists_set_from_end', ` +function ${PHP.FUNCTION_NAME_PLACEHOLDER_}(&$list, $at, $value) { + $list[count($list) - $at] = $value; +} +`); return functionName + '(' + list + ', ' + at + ', ' + value + ');\n'; } else if (mode === 'INSERT') { - const functionName = PHP.provideFunction_('lists_insert_from_end', [ - 'function ' + PHP.FUNCTION_NAME_PLACEHOLDER_ + - '(&$list, $at, $value) {', - ' return array_splice($list, count($list) - $at, 0, $value);', '}' - ]); + const functionName = PHP.provideFunction_('lists_insert_from_end', ` +function ${PHP.FUNCTION_NAME_PLACEHOLDER_}(&$list, $at, $value) { + return array_splice($list, count($list) - $at, 0, $value); +} +`); return functionName + '(' + list + ', ' + at + ', ' + value + ');\n'; } break; @@ -382,29 +397,28 @@ PHP['lists_getSublist'] = function(block) { } else { const at1 = PHP.getAdjusted(block, 'AT1'); const at2 = PHP.getAdjusted(block, 'AT2'); - const functionName = PHP.provideFunction_('lists_get_sublist', [ - 'function ' + PHP.FUNCTION_NAME_PLACEHOLDER_ + - '($list, $where1, $at1, $where2, $at2) {', - ' if ($where1 == \'FROM_END\') {', - ' $at1 = count($list) - 1 - $at1;', - ' } else if ($where1 == \'FIRST\') {', - ' $at1 = 0;', - ' } else if ($where1 != \'FROM_START\') {', - ' throw new Exception(\'Unhandled option (lists_get_sublist).\');', - ' }', - ' $length = 0;', - ' if ($where2 == \'FROM_START\') {', - ' $length = $at2 - $at1 + 1;', - ' } else if ($where2 == \'FROM_END\') {', - ' $length = count($list) - $at1 - $at2;', - ' } else if ($where2 == \'LAST\') {', - ' $length = count($list) - $at1;', - ' } else {', - ' throw new Exception(\'Unhandled option (lists_get_sublist).\');', - ' }', - ' return array_slice($list, $at1, $length);', - '}' - ]); + const functionName = PHP.provideFunction_('lists_get_sublist', ` +function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($list, $where1, $at1, $where2, $at2) { + if ($where1 == 'FROM_END') { + $at1 = count($list) - 1 - $at1; + } else if ($where1 == 'FIRST') { + $at1 = 0; + } else if ($where1 != 'FROM_START') { + throw new Exception('Unhandled option (lists_get_sublist).'); + } + $length = 0; + if ($where2 == 'FROM_START') { + $length = $at2 - $at1 + 1; + } else if ($where2 == 'FROM_END') { + $length = count($list) - $at1 - $at2; + } else if ($where2 == 'LAST') { + $length = count($list) - $at1; + } else { + throw new Exception('Unhandled option (lists_get_sublist).'); + } + return array_slice($list, $at1, $length); +} +`); code = functionName + '(' + list + ', \'' + where1 + '\', ' + at1 + ', \'' + where2 + '\', ' + at2 + ')'; } @@ -416,16 +430,22 @@ PHP['lists_sort'] = function(block) { const listCode = PHP.valueToCode(block, 'LIST', PHP.ORDER_NONE) || 'array()'; const direction = block.getFieldValue('DIRECTION') === '1' ? 1 : -1; const type = block.getFieldValue('TYPE'); - const functionName = PHP.provideFunction_('lists_sort', [ - 'function ' + PHP.FUNCTION_NAME_PLACEHOLDER_ + - '($list, $type, $direction) {', - ' $sortCmpFuncs = array(', ' "NUMERIC" => "strnatcasecmp",', - ' "TEXT" => "strcmp",', ' "IGNORE_CASE" => "strcasecmp"', ' );', - ' $sortCmp = $sortCmpFuncs[$type];', - ' $list2 = $list;', // Clone list. - ' usort($list2, $sortCmp);', ' if ($direction == -1) {', - ' $list2 = array_reverse($list2);', ' }', ' return $list2;', '}' - ]); + const functionName = PHP.provideFunction_('lists_sort', ` +function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($list, $type, $direction) { + $sortCmpFuncs = array( + 'NUMERIC' => 'strnatcasecmp', + 'TEXT' => 'strcmp', + 'IGNORE_CASE' => 'strcasecmp' + ); + $sortCmp = $sortCmpFuncs[$type]; + $list2 = $list; + usort($list2, $sortCmp); + if ($direction == -1) { + $list2 = array_reverse($list2); + } + return $list2; +} +`); const sortCode = functionName + '(' + listCode + ', "' + type + '", ' + direction + ')'; return [sortCode, PHP.ORDER_FUNCTION_CALL]; @@ -434,12 +454,12 @@ PHP['lists_sort'] = function(block) { PHP['lists_split'] = function(block) { // Block for splitting text into a list, or joining a list into text. let value_input = PHP.valueToCode(block, 'INPUT', PHP.ORDER_NONE); - const value_delim = PHP.valueToCode(block, 'DELIM', PHP.ORDER_NONE) || '\'\''; + const value_delim = PHP.valueToCode(block, 'DELIM', PHP.ORDER_NONE) || "''"; const mode = block.getFieldValue('MODE'); let functionName; if (mode === 'SPLIT') { if (!value_input) { - value_input = '\'\''; + value_input = "''"; } functionName = 'explode'; } else if (mode === 'JOIN') { diff --git a/generators/php/math.js b/generators/php/math.js index d77f47806..6c8c5ffb4 100644 --- a/generators/php/math.js +++ b/generators/php/math.js @@ -34,7 +34,7 @@ PHP['math_arithmetic'] = function(block) { 'MINUS': [' - ', PHP.ORDER_SUBTRACTION], 'MULTIPLY': [' * ', PHP.ORDER_MULTIPLICATION], 'DIVIDE': [' / ', PHP.ORDER_DIVISION], - 'POWER': [' ** ', PHP.ORDER_POWER] + 'POWER': [' ** ', PHP.ORDER_POWER], }; const tuple = OPERATORS[block.getFieldValue('OP')]; const operator = tuple[0]; @@ -134,7 +134,7 @@ PHP['math_constant'] = function(block) { 'GOLDEN_RATIO': ['(1 + sqrt(5)) / 2', PHP.ORDER_DIVISION], 'SQRT2': ['M_SQRT2', PHP.ORDER_ATOMIC], 'SQRT1_2': ['M_SQRT1_2', PHP.ORDER_ATOMIC], - 'INFINITY': ['INF', PHP.ORDER_ATOMIC] + 'INFINITY': ['INF', PHP.ORDER_ATOMIC], }; return CONSTANTS[block.getFieldValue('CONSTANT')]; }; @@ -142,53 +142,55 @@ PHP['math_constant'] = function(block) { PHP['math_number_property'] = function(block) { // Check if a number is even, odd, prime, whole, positive, or negative // or if it is divisible by certain number. Returns true or false. - const number_to_check = - PHP.valueToCode(block, 'NUMBER_TO_CHECK', PHP.ORDER_MODULUS) || '0'; - const dropdown_property = block.getFieldValue('PROPERTY'); + const PROPERTIES = { + 'EVEN': ['', ' % 2 == 0', PHP.ORDER_MODULUS, PHP.ORDER_EQUALITY], + 'ODD': ['', ' % 2 == 1', PHP.ORDER_MODULUS, PHP.ORDER_EQUALITY], + 'WHOLE': ['is_int(', ')', PHP.ORDER_NONE, PHP.ORDER_FUNCTION_CALL], + 'POSITIVE': ['', ' > 0', PHP.ORDER_RELATIONAL, PHP.ORDER_RELATIONAL], + 'NEGATIVE': ['', ' < 0', PHP.ORDER_RELATIONAL, PHP.ORDER_RELATIONAL], + 'DIVISIBLE_BY': [null, null, PHP.ORDER_MODULUS, PHP.ORDER_EQUALITY], + 'PRIME': [null, null, PHP.ORDER_NONE, PHP.ORDER_FUNCTION_CALL], + }; + const dropdownProperty = block.getFieldValue('PROPERTY'); + const [prefix, suffix, inputOrder, outputOrder] = PROPERTIES[dropdownProperty]; + const numberToCheck = PHP.valueToCode(block, 'NUMBER_TO_CHECK', + inputOrder) || '0'; let code; - if (dropdown_property === 'PRIME') { + if (dropdownProperty === 'PRIME') { // Prime is a special case as it is not a one-liner test. - const functionName = PHP.provideFunction_('math_isPrime', [ - 'function ' + PHP.FUNCTION_NAME_PLACEHOLDER_ + '($n) {', - ' // https://en.wikipedia.org/wiki/Primality_test#Naive_methods', - ' if ($n == 2 || $n == 3) {', ' return true;', ' }', - ' // False if n is NaN, negative, is 1, or not whole.', - ' // And false if n is divisible by 2 or 3.', - ' if (!is_numeric($n) || $n <= 1 || $n % 1 != 0 || $n % 2 == 0 ||' + - ' $n % 3 == 0) {', - ' return false;', ' }', - ' // Check all the numbers of form 6k +/- 1, up to sqrt(n).', - ' for ($x = 6; $x <= sqrt($n) + 1; $x += 6) {', - ' if ($n % ($x - 1) == 0 || $n % ($x + 1) == 0) {', - ' return false;', ' }', ' }', ' return true;', '}' - ]); - code = functionName + '(' + number_to_check + ')'; - return [code, PHP.ORDER_FUNCTION_CALL]; + const functionName = PHP.provideFunction_('math_isPrime', ` +function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($n) { + // https://en.wikipedia.org/wiki/Primality_test#Naive_methods + if ($n == 2 || $n == 3) { + return true; } - switch (dropdown_property) { - case 'EVEN': - code = number_to_check + ' % 2 == 0'; - break; - case 'ODD': - code = number_to_check + ' % 2 == 1'; - break; - case 'WHOLE': - code = 'is_int(' + number_to_check + ')'; - break; - case 'POSITIVE': - code = number_to_check + ' > 0'; - break; - case 'NEGATIVE': - code = number_to_check + ' < 0'; - break; - case 'DIVISIBLE_BY': { - const divisor = - PHP.valueToCode(block, 'DIVISOR', PHP.ORDER_MODULUS) || '0'; - code = number_to_check + ' % ' + divisor + ' == 0'; - break; + // False if n is NaN, negative, is 1, or not whole. + // And false if n is divisible by 2 or 3. + if (!is_numeric($n) || $n <= 1 || $n % 1 != 0 || $n % 2 == 0 || $n % 3 == 0) { + return false; + } + // Check all the numbers of form 6k +/- 1, up to sqrt(n). + for ($x = 6; $x <= sqrt($n) + 1; $x += 6) { + if ($n % ($x - 1) == 0 || $n % ($x + 1) == 0) { + return false; } } - return [code, PHP.ORDER_EQUALITY]; + return true; +} +`); + code = functionName + '(' + numberToCheck + ')'; + } else if (dropdownProperty === 'DIVISIBLE_BY') { + const divisor = PHP.valueToCode(block, 'DIVISOR', + PHP.ORDER_MODULUS) || '0'; + if (divisor === '0') { + return ['false', PHP.ORDER_ATOMIC]; + + } + code = numberToCheck + ' % ' + divisor + ' == 0'; + } else { + code = prefix + numberToCheck + suffix; + } + return [code, outputOrder]; }; PHP['math_change'] = function(block) { @@ -226,23 +228,23 @@ PHP['math_on_list'] = function(block) { code = 'max(' + list + ')'; break; case 'AVERAGE': { - const functionName = PHP.provideFunction_('math_mean', [ - 'function ' + PHP.FUNCTION_NAME_PLACEHOLDER_ + '($myList) {', - ' return array_sum($myList) / count($myList);', '}' - ]); + const functionName = PHP.provideFunction_('math_mean', ` +function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($myList) { + return array_sum($myList) / count($myList); +} +`); list = PHP.valueToCode(block, 'LIST', PHP.ORDER_NONE) || 'array()'; code = functionName + '(' + list + ')'; break; } case 'MEDIAN': { - const functionName = PHP.provideFunction_('math_median', [ - 'function ' + PHP.FUNCTION_NAME_PLACEHOLDER_ + '($arr) {', - ' sort($arr,SORT_NUMERIC);', - ' return (count($arr) % 2) ? $arr[floor(count($arr)/2)] : ', - ' ($arr[floor(count($arr)/2)] + $arr[floor(count($arr)/2)' + - ' - 1]) / 2;', - '}' - ]); + const functionName = PHP.provideFunction_('math_median', ` +function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($arr) { + sort($arr,SORT_NUMERIC); + return (count($arr) % 2) ? $arr[floor(count($arr) / 2)] : + ($arr[floor(count($arr) / 2)] + $arr[floor(count($arr) / 2) - 1]) / 2; +} +`); list = PHP.valueToCode(block, 'LIST', PHP.ORDER_NONE) || '[]'; code = functionName + '(' + list + ')'; break; @@ -251,36 +253,40 @@ PHP['math_on_list'] = function(block) { // As a list of numbers can contain more than one mode, // the returned result is provided as an array. // Mode of [3, 'x', 'x', 1, 1, 2, '3'] -> ['x', 1]. - const functionName = PHP.provideFunction_('math_modes', [ - 'function ' + PHP.FUNCTION_NAME_PLACEHOLDER_ + '($values) {', - ' if (empty($values)) return array();', - ' $counts = array_count_values($values);', - ' arsort($counts); // Sort counts in descending order', - ' $modes = array_keys($counts, current($counts), true);', - ' return $modes;', '}' - ]); + const functionName = PHP.provideFunction_('math_modes', ` +function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($values) { + if (empty($values)) return array(); + $counts = array_count_values($values); + arsort($counts); // Sort counts in descending order + $modes = array_keys($counts, current($counts), true); + return $modes; +} +`); list = PHP.valueToCode(block, 'LIST', PHP.ORDER_NONE) || '[]'; code = functionName + '(' + list + ')'; break; } case 'STD_DEV': { - const functionName = PHP.provideFunction_('math_standard_deviation', [ - 'function ' + PHP.FUNCTION_NAME_PLACEHOLDER_ + '($numbers) {', - ' $n = count($numbers);', ' if (!$n) return null;', - ' $mean = array_sum($numbers) / count($numbers);', - ' foreach($numbers as $key => $num) $devs[$key] = ' + - 'pow($num - $mean, 2);', - ' return sqrt(array_sum($devs) / (count($devs) - 1));', '}' - ]); + const functionName = PHP.provideFunction_('math_standard_deviation', ` +function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($numbers) { + $n = count($numbers); + if (!$n) return null; + $mean = array_sum($numbers) / count($numbers); + foreach($numbers as $key => $num) $devs[$key] = pow($num - $mean, 2); + return sqrt(array_sum($devs) / (count($devs) - 1)); +} +`); list = PHP.valueToCode(block, 'LIST', PHP.ORDER_NONE) || '[]'; code = functionName + '(' + list + ')'; break; } case 'RANDOM': { - const functionName = PHP.provideFunction_('math_random_list', [ - 'function ' + PHP.FUNCTION_NAME_PLACEHOLDER_ + '($list) {', - ' $x = rand(0, count($list)-1);', ' return $list[$x];', '}' - ]); + const functionName = PHP.provideFunction_('math_random_list', ` +function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($list) { + $x = rand(0, count($list)-1); + return $list[$x]; +} +`); list = PHP.valueToCode(block, 'LIST', PHP.ORDER_NONE) || '[]'; code = functionName + '(' + list + ')'; break; @@ -315,11 +321,14 @@ PHP['math_random_int'] = function(block) { // Random integer between [X] and [Y]. const argument0 = PHP.valueToCode(block, 'FROM', PHP.ORDER_NONE) || '0'; const argument1 = PHP.valueToCode(block, 'TO', PHP.ORDER_NONE) || '0'; - const functionName = PHP.provideFunction_('math_random_int', [ - 'function ' + PHP.FUNCTION_NAME_PLACEHOLDER_ + '($a, $b) {', - ' if ($a > $b) {', ' return rand($b, $a);', ' }', - ' return rand($a, $b);', '}' - ]); + const functionName = PHP.provideFunction_('math_random_int', ` +function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($a, $b) { + if ($a > $b) { + return rand($b, $a); + } + return rand($a, $b); +} +`); const code = functionName + '(' + argument0 + ', ' + argument1 + ')'; return [code, PHP.ORDER_FUNCTION_CALL]; }; diff --git a/generators/php/text.js b/generators/php/text.js index eadf4cf5b..bc62a95cf 100644 --- a/generators/php/text.js +++ b/generators/php/text.js @@ -32,22 +32,22 @@ PHP['text_multiline'] = function(block) { PHP['text_join'] = function(block) { // Create a string made up of any number of elements of any type. if (block.itemCount_ === 0) { - return ['\'\'', PHP.ORDER_ATOMIC]; + return ["''", PHP.ORDER_ATOMIC]; } else if (block.itemCount_ === 1) { - const element = PHP.valueToCode(block, 'ADD0', PHP.ORDER_NONE) || '\'\''; + const element = PHP.valueToCode(block, 'ADD0', PHP.ORDER_NONE) || "''"; const code = element; return [code, PHP.ORDER_NONE]; } else if (block.itemCount_ === 2) { const element0 = - PHP.valueToCode(block, 'ADD0', PHP.ORDER_STRING_CONCAT) || '\'\''; + PHP.valueToCode(block, 'ADD0', PHP.ORDER_STRING_CONCAT) || "''"; const element1 = - PHP.valueToCode(block, 'ADD1', PHP.ORDER_STRING_CONCAT) || '\'\''; + PHP.valueToCode(block, 'ADD1', PHP.ORDER_STRING_CONCAT) || "''"; const code = element0 + ' . ' + element1; return [code, PHP.ORDER_STRING_CONCAT]; } else { const elements = new Array(block.itemCount_); for (let i = 0; i < block.itemCount_; i++) { - elements[i] = PHP.valueToCode(block, 'ADD' + i, PHP.ORDER_NONE) || '\'\''; + elements[i] = PHP.valueToCode(block, 'ADD' + i, PHP.ORDER_NONE) || "''"; } const code = 'implode(\'\', array(' + elements.join(',') + '))'; return [code, PHP.ORDER_FUNCTION_CALL]; @@ -58,24 +58,27 @@ PHP['text_append'] = function(block) { // Append to a variable in place. const varName = PHP.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE); - const value = PHP.valueToCode(block, 'TEXT', PHP.ORDER_ASSIGNMENT) || '\'\''; + const value = PHP.valueToCode(block, 'TEXT', PHP.ORDER_ASSIGNMENT) || "''"; return varName + ' .= ' + value + ';\n'; }; PHP['text_length'] = function(block) { // String or array length. - const functionName = PHP.provideFunction_('length', [ - 'function ' + PHP.FUNCTION_NAME_PLACEHOLDER_ + '($value) {', - ' if (is_string($value)) {', ' return strlen($value);', ' } else {', - ' return count($value);', ' }', '}' - ]); - const text = PHP.valueToCode(block, 'VALUE', PHP.ORDER_NONE) || '\'\''; + const functionName = PHP.provideFunction_('length', ` +function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($value) { + if (is_string($value)) { + return strlen($value); + } + return count($value); +} +`); + const text = PHP.valueToCode(block, 'VALUE', PHP.ORDER_NONE) || "''"; return [functionName + '(' + text + ')', PHP.ORDER_FUNCTION_CALL]; }; PHP['text_isEmpty'] = function(block) { // Is the string null or array empty? - const text = PHP.valueToCode(block, 'VALUE', PHP.ORDER_NONE) || '\'\''; + const text = PHP.valueToCode(block, 'VALUE', PHP.ORDER_NONE) || "''"; return ['empty(' + text + ')', PHP.ORDER_FUNCTION_CALL]; }; @@ -83,8 +86,8 @@ PHP['text_indexOf'] = function(block) { // Search the text for a substring. const operator = block.getFieldValue('END') === 'FIRST' ? 'strpos' : 'strrpos'; - const substring = PHP.valueToCode(block, 'FIND', PHP.ORDER_NONE) || '\'\''; - const text = PHP.valueToCode(block, 'VALUE', PHP.ORDER_NONE) || '\'\''; + const substring = PHP.valueToCode(block, 'FIND', PHP.ORDER_NONE) || "''"; + const text = PHP.valueToCode(block, 'VALUE', PHP.ORDER_NONE) || "''"; let errorIndex = ' -1'; let indexAdjustment = ''; if (block.workspace.options.oneBasedIndex) { @@ -94,13 +97,12 @@ PHP['text_indexOf'] = function(block) { const functionName = PHP.provideFunction_( block.getFieldValue('END') === 'FIRST' ? 'text_indexOf' : 'text_lastIndexOf', - [ - 'function ' + PHP.FUNCTION_NAME_PLACEHOLDER_ + '($text, $search) {', - ' $pos = ' + operator + '($text, $search);', - ' return $pos === false ? ' + errorIndex + ' : $pos' + - indexAdjustment + ';', - '}' - ]); + ` +function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($text, $search) { + $pos = ${operator}($text, $search); + return $pos === false ? ${errorIndex} : $pos${indexAdjustment}; +} +`); const code = functionName + '(' + text + ', ' + substring + ')'; return [code, PHP.ORDER_FUNCTION_CALL]; }; @@ -109,7 +111,7 @@ PHP['text_charAt'] = function(block) { // Get letter at index. const where = block.getFieldValue('WHERE') || 'FROM_START'; const textOrder = (where === 'RANDOM') ? PHP.ORDER_NONE : PHP.ORDER_NONE; - const text = PHP.valueToCode(block, 'VALUE', textOrder) || '\'\''; + const text = PHP.valueToCode(block, 'VALUE', textOrder) || "''"; switch (where) { case 'FIRST': { const code = 'substr(' + text + ', 0, 1)'; @@ -130,10 +132,11 @@ PHP['text_charAt'] = function(block) { return [code, PHP.ORDER_FUNCTION_CALL]; } case 'RANDOM': { - const functionName = PHP.provideFunction_('text_random_letter', [ - 'function ' + PHP.FUNCTION_NAME_PLACEHOLDER_ + '($text) {', - ' return $text[rand(0, strlen($text) - 1)];', '}' - ]); + const functionName = PHP.provideFunction_('text_random_letter', ` +function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($text) { + return $text[rand(0, strlen($text) - 1)]; +} +`); const code = functionName + '(' + text + ')'; return [code, PHP.ORDER_FUNCTION_CALL]; } @@ -145,36 +148,35 @@ PHP['text_getSubstring'] = function(block) { // Get substring. const where1 = block.getFieldValue('WHERE1'); const where2 = block.getFieldValue('WHERE2'); - const text = PHP.valueToCode(block, 'STRING', PHP.ORDER_NONE) || '\'\''; + const text = PHP.valueToCode(block, 'STRING', PHP.ORDER_NONE) || "''"; if (where1 === 'FIRST' && where2 === 'LAST') { const code = text; return [code, PHP.ORDER_NONE]; } else { const at1 = PHP.getAdjusted(block, 'AT1'); const at2 = PHP.getAdjusted(block, 'AT2'); - const functionName = PHP.provideFunction_('text_get_substring', [ - 'function ' + PHP.FUNCTION_NAME_PLACEHOLDER_ + - '($text, $where1, $at1, $where2, $at2) {', - ' if ($where1 == \'FROM_END\') {', - ' $at1 = strlen($text) - 1 - $at1;', - ' } else if ($where1 == \'FIRST\') {', - ' $at1 = 0;', - ' } else if ($where1 != \'FROM_START\') {', - ' throw new Exception(\'Unhandled option (text_get_substring).\');', - ' }', - ' $length = 0;', - ' if ($where2 == \'FROM_START\') {', - ' $length = $at2 - $at1 + 1;', - ' } else if ($where2 == \'FROM_END\') {', - ' $length = strlen($text) - $at1 - $at2;', - ' } else if ($where2 == \'LAST\') {', - ' $length = strlen($text) - $at1;', - ' } else {', - ' throw new Exception(\'Unhandled option (text_get_substring).\');', - ' }', - ' return substr($text, $at1, $length);', - '}' - ]); + const functionName = PHP.provideFunction_('text_get_substring', ` +function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($text, $where1, $at1, $where2, $at2) { + if ($where1 == 'FROM_END') { + $at1 = strlen($text) - 1 - $at1; + } else if ($where1 == 'FIRST') { + $at1 = 0; + } else if ($where1 != 'FROM_START') { + throw new Exception('Unhandled option (text_get_substring).'); + } + $length = 0; + if ($where2 == 'FROM_START') { + $length = $at2 - $at1 + 1; + } else if ($where2 == 'FROM_END') { + $length = strlen($text) - $at1 - $at2; + } else if ($where2 == 'LAST') { + $length = strlen($text) - $at1; + } else { + throw new Exception('Unhandled option (text_get_substring).'); + } + return substr($text, $at1, $length); +} +`); const code = functionName + '(' + text + ', \'' + where1 + '\', ' + at1 + ', \'' + where2 + '\', ' + at2 + ')'; return [code, PHP.ORDER_FUNCTION_CALL]; @@ -183,7 +185,7 @@ PHP['text_getSubstring'] = function(block) { PHP['text_changeCase'] = function(block) { // Change capitalization. - const text = PHP.valueToCode(block, 'TEXT', PHP.ORDER_NONE) || '\'\''; + const text = PHP.valueToCode(block, 'TEXT', PHP.ORDER_NONE) || "''"; let code; if (block.getFieldValue('CASE') === 'UPPERCASE') { code = 'strtoupper(' + text + ')'; @@ -199,13 +201,13 @@ PHP['text_trim'] = function(block) { // Trim spaces. const OPERATORS = {'LEFT': 'ltrim', 'RIGHT': 'rtrim', 'BOTH': 'trim'}; const operator = OPERATORS[block.getFieldValue('MODE')]; - const text = PHP.valueToCode(block, 'TEXT', PHP.ORDER_NONE) || '\'\''; + const text = PHP.valueToCode(block, 'TEXT', PHP.ORDER_NONE) || "''"; return [operator + '(' + text + ')', PHP.ORDER_FUNCTION_CALL]; }; PHP['text_print'] = function(block) { // Print statement. - const msg = PHP.valueToCode(block, 'TEXT', PHP.ORDER_NONE) || '\'\''; + const msg = PHP.valueToCode(block, 'TEXT', PHP.ORDER_NONE) || "''"; return 'print(' + msg + ');\n'; }; @@ -217,7 +219,7 @@ PHP['text_prompt_ext'] = function(block) { msg = PHP.quote_(block.getFieldValue('TEXT')); } else { // External message. - msg = PHP.valueToCode(block, 'TEXT', PHP.ORDER_NONE) || '\'\''; + msg = PHP.valueToCode(block, 'TEXT', PHP.ORDER_NONE) || "''"; } let code = 'readline(' + msg + ')'; const toNumber = block.getFieldValue('TYPE') === 'NUMBER'; @@ -230,8 +232,8 @@ PHP['text_prompt_ext'] = function(block) { PHP['text_prompt'] = PHP['text_prompt_ext']; PHP['text_count'] = function(block) { - const text = PHP.valueToCode(block, 'TEXT', PHP.ORDER_NONE) || '\'\''; - const sub = PHP.valueToCode(block, 'SUB', PHP.ORDER_NONE) || '\'\''; + const text = PHP.valueToCode(block, 'TEXT', PHP.ORDER_NONE) || "''"; + const sub = PHP.valueToCode(block, 'SUB', PHP.ORDER_NONE) || "''"; const code = 'strlen(' + sub + ') === 0' + ' ? strlen(' + text + ') + 1' + ' : substr_count(' + text + ', ' + sub + ')'; @@ -239,15 +241,15 @@ PHP['text_count'] = function(block) { }; PHP['text_replace'] = function(block) { - const text = PHP.valueToCode(block, 'TEXT', PHP.ORDER_NONE) || '\'\''; - const from = PHP.valueToCode(block, 'FROM', PHP.ORDER_NONE) || '\'\''; - const to = PHP.valueToCode(block, 'TO', PHP.ORDER_NONE) || '\'\''; + const text = PHP.valueToCode(block, 'TEXT', PHP.ORDER_NONE) || "''"; + const from = PHP.valueToCode(block, 'FROM', PHP.ORDER_NONE) || "''"; + const to = PHP.valueToCode(block, 'TO', PHP.ORDER_NONE) || "''"; const code = 'str_replace(' + from + ', ' + to + ', ' + text + ')'; return [code, PHP.ORDER_FUNCTION_CALL]; }; PHP['text_reverse'] = function(block) { - const text = PHP.valueToCode(block, 'TEXT', PHP.ORDER_NONE) || '\'\''; + const text = PHP.valueToCode(block, 'TEXT', PHP.ORDER_NONE) || "''"; const code = 'strrev(' + text + ')'; return [code, PHP.ORDER_FUNCTION_CALL]; }; diff --git a/generators/python/colour.js b/generators/python/colour.js index 30cb6f463..1a24c2780 100644 --- a/generators/python/colour.js +++ b/generators/python/colour.js @@ -29,13 +29,13 @@ Python['colour_random'] = function(block) { Python['colour_rgb'] = function(block) { // Compose a colour from RGB components expressed as percentages. - const functionName = Python.provideFunction_('colour_rgb', [ - 'def ' + Python.FUNCTION_NAME_PLACEHOLDER_ + '(r, g, b):', - ' r = round(min(100, max(0, r)) * 2.55)', - ' g = round(min(100, max(0, g)) * 2.55)', - ' b = round(min(100, max(0, b)) * 2.55)', - ' return \'#%02x%02x%02x\' % (r, g, b)' - ]); + const functionName = Python.provideFunction_('colour_rgb', ` +def ${Python.FUNCTION_NAME_PLACEHOLDER_}(r, g, b): + r = round(min(100, max(0, r)) * 2.55) + g = round(min(100, max(0, g)) * 2.55) + b = round(min(100, max(0, b)) * 2.55) + return \'#%02x%02x%02x\' % (r, g, b) +`); const r = Python.valueToCode(block, 'RED', Python.ORDER_NONE) || 0; const g = Python.valueToCode(block, 'GREEN', Python.ORDER_NONE) || 0; const b = Python.valueToCode(block, 'BLUE', Python.ORDER_NONE) || 0; @@ -45,17 +45,17 @@ Python['colour_rgb'] = function(block) { Python['colour_blend'] = function(block) { // Blend two colours together. - const functionName = Python.provideFunction_('colour_blend', [ - 'def ' + Python.FUNCTION_NAME_PLACEHOLDER_ + '(colour1, colour2, ratio):', - ' r1, r2 = int(colour1[1:3], 16), int(colour2[1:3], 16)', - ' g1, g2 = int(colour1[3:5], 16), int(colour2[3:5], 16)', - ' b1, b2 = int(colour1[5:7], 16), int(colour2[5:7], 16)', - ' ratio = min(1, max(0, ratio))', - ' r = round(r1 * (1 - ratio) + r2 * ratio)', - ' g = round(g1 * (1 - ratio) + g2 * ratio)', - ' b = round(b1 * (1 - ratio) + b2 * ratio)', - ' return \'#%02x%02x%02x\' % (r, g, b)' - ]); + const functionName = Python.provideFunction_('colour_blend', ` +def ${Python.FUNCTION_NAME_PLACEHOLDER_}(colour1, colour2, ratio): + r1, r2 = int(colour1[1:3], 16), int(colour2[1:3], 16) + g1, g2 = int(colour1[3:5], 16), int(colour2[3:5], 16) + b1, b2 = int(colour1[5:7], 16), int(colour2[5:7], 16) + ratio = min(1, max(0, ratio)) + r = round(r1 * (1 - ratio) + r2 * ratio) + g = round(g1 * (1 - ratio) + g2 * ratio) + b = round(b1 * (1 - ratio) + b2 * ratio) + return \'#%02x%02x%02x\' % (r, g, b) +`); const colour1 = Python.valueToCode(block, 'COLOUR1', Python.ORDER_NONE) || '\'#000000\''; const colour2 = diff --git a/generators/python/lists.js b/generators/python/lists.js index 607f0671b..7026162ae 100644 --- a/generators/python/lists.js +++ b/generators/python/lists.js @@ -57,7 +57,7 @@ Python['lists_isEmpty'] = function(block) { Python['lists_indexOf'] = function(block) { // Find an item in the list. const item = Python.valueToCode(block, 'FIND', Python.ORDER_NONE) || '[]'; - const list = Python.valueToCode(block, 'VALUE', Python.ORDER_NONE) || '\'\''; + const list = Python.valueToCode(block, 'VALUE', Python.ORDER_NONE) || "''"; let errorIndex = ' -1'; let firstIndexAdjustment = ''; let lastIndexAdjustment = ' - 1'; @@ -68,21 +68,22 @@ Python['lists_indexOf'] = function(block) { lastIndexAdjustment = ''; } + let functionName; if (block.getFieldValue('END') === 'FIRST') { - const functionName = Python.provideFunction_('first_index', [ - 'def ' + Python.FUNCTION_NAME_PLACEHOLDER_ + '(my_list, elem):', - ' try: index = my_list.index(elem)' + firstIndexAdjustment, - ' except: index =' + errorIndex, ' return index' - ]); - const code = functionName + '(' + list + ', ' + item + ')'; - return [code, Python.ORDER_FUNCTION_CALL]; + functionName = Python.provideFunction_('first_index', ` +def ${Python.FUNCTION_NAME_PLACEHOLDER_}(my_list, elem): + try: index = my_list.index(elem)${firstIndexAdjustment} + except: index =${errorIndex} + return index +`); + } else { + functionName = Python.provideFunction_('last_index', ` +def ${Python.FUNCTION_NAME_PLACEHOLDER_}(my_list, elem): + try: index = len(my_list) - my_list[::-1].index(elem)${lastIndexAdjustment} + except: index =${errorIndex} + return index +`); } - const functionName = Python.provideFunction_('last_index', [ - 'def ' + Python.FUNCTION_NAME_PLACEHOLDER_ + '(my_list, elem):', - ' try: index = len(my_list) - my_list[::-1].index(elem)' + - lastIndexAdjustment, - ' except: index =' + errorIndex, ' return index' - ]); const code = functionName + '(' + list + ', ' + item + ')'; return [code, Python.ORDER_FUNCTION_CALL]; }; @@ -152,11 +153,11 @@ Python['lists_getIndex'] = function(block) { return [code, Python.ORDER_FUNCTION_CALL]; } else { const functionName = - Python.provideFunction_('lists_remove_random_item', [ - 'def ' + Python.FUNCTION_NAME_PLACEHOLDER_ + '(myList):', - ' x = int(random.random() * len(myList))', - ' return myList.pop(x)' - ]); + Python.provideFunction_('lists_remove_random_item', ` +def ${Python.FUNCTION_NAME_PLACEHOLDER_}(myList): + x = int(random.random() * len(myList)) + return myList.pop(x) +`); const code = functionName + '(' + list + ')'; if (mode === 'GET_REMOVE') { return [code, Python.ORDER_FUNCTION_CALL]; @@ -294,15 +295,22 @@ Python['lists_sort'] = function(block) { const list = (Python.valueToCode(block, 'LIST', Python.ORDER_NONE) || '[]'); const type = block.getFieldValue('TYPE'); const reverse = block.getFieldValue('DIRECTION') === '1' ? 'False' : 'True'; - const sortFunctionName = Python.provideFunction_('lists_sort', [ - 'def ' + Python.FUNCTION_NAME_PLACEHOLDER_ + '(my_list, type, reverse):', - ' def try_float(s):', ' try:', ' return float(s)', ' except:', - ' return 0', ' key_funcs = {', ' "NUMERIC": try_float,', - ' "TEXT": str,', ' "IGNORE_CASE": lambda s: str(s).lower()', ' }', - ' key_func = key_funcs[type]', - ' list_cpy = list(my_list)', // Clone the list. - ' return sorted(list_cpy, key=key_func, reverse=reverse)' - ]); + const sortFunctionName = Python.provideFunction_('lists_sort', ` +def ${Python.FUNCTION_NAME_PLACEHOLDER_}(my_list, type, reverse): + def try_float(s): + try: + return float(s) + except: + return 0 + key_funcs = { + "NUMERIC": try_float, + "TEXT": str, + "IGNORE_CASE": lambda s: str(s).lower() + } + key_func = key_funcs[type] + list_cpy = list(my_list) + return sorted(list_cpy, key=key_func, reverse=reverse) +`); const code = sortFunctionName + '(' + list + ', "' + type + '", ' + reverse + ')'; @@ -315,14 +323,14 @@ Python['lists_split'] = function(block) { let code; if (mode === 'SPLIT') { const value_input = - Python.valueToCode(block, 'INPUT', Python.ORDER_MEMBER) || '\'\''; + Python.valueToCode(block, 'INPUT', Python.ORDER_MEMBER) || "''"; const value_delim = Python.valueToCode(block, 'DELIM', Python.ORDER_NONE); code = value_input + '.split(' + value_delim + ')'; } else if (mode === 'JOIN') { const value_input = Python.valueToCode(block, 'INPUT', Python.ORDER_NONE) || '[]'; const value_delim = - Python.valueToCode(block, 'DELIM', Python.ORDER_MEMBER) || '\'\''; + Python.valueToCode(block, 'DELIM', Python.ORDER_MEMBER) || "''"; code = value_delim + '.join(' + value_input + ')'; } else { throw Error('Unknown mode: ' + mode); diff --git a/generators/python/loops.js b/generators/python/loops.js index aff791d21..b3c267f86 100644 --- a/generators/python/loops.js +++ b/generators/python/loops.js @@ -70,16 +70,20 @@ Python['controls_for'] = function(block) { // Helper functions. const defineUpRange = function() { - return Python.provideFunction_('upRange', [ - 'def ' + Python.FUNCTION_NAME_PLACEHOLDER_ + '(start, stop, step):', - ' while start <= stop:', ' yield start', ' start += abs(step)' - ]); + return Python.provideFunction_('upRange', ` +def ${Python.FUNCTION_NAME_PLACEHOLDER_}(start, stop, step): + while start <= stop: + yield start + start += abs(step) +`); }; const defineDownRange = function() { - return Python.provideFunction_('downRange', [ - 'def ' + Python.FUNCTION_NAME_PLACEHOLDER_ + '(start, stop, step):', - ' while start >= stop:', ' yield start', ' start -= abs(step)' - ]); + return Python.provideFunction_('downRange', ` +def ${Python.FUNCTION_NAME_PLACEHOLDER_}(start, stop, step): + while start >= stop: + yield start + start -= abs(step) +`); }; // Arguments are legal Python code (numbers or strings returned by scrub()). const generateUpDownRange = function(start, end, inc) { diff --git a/generators/python/math.js b/generators/python/math.js index 9f4fdc983..ca23c1770 100644 --- a/generators/python/math.js +++ b/generators/python/math.js @@ -41,7 +41,7 @@ Python['math_arithmetic'] = function(block) { 'MINUS': [' - ', Python.ORDER_ADDITIVE], 'MULTIPLY': [' * ', Python.ORDER_MULTIPLICATIVE], 'DIVIDE': [' / ', Python.ORDER_MULTIPLICATIVE], - 'POWER': [' ** ', Python.ORDER_EXPONENTIATION] + 'POWER': [' ** ', Python.ORDER_EXPONENTIATION], }; const tuple = OPERATORS[block.getFieldValue('OP')]; const operator = tuple[0]; @@ -142,7 +142,7 @@ Python['math_constant'] = function(block) { 'GOLDEN_RATIO': ['(1 + math.sqrt(5)) / 2', Python.ORDER_MULTIPLICATIVE], 'SQRT2': ['math.sqrt(2)', Python.ORDER_MEMBER], 'SQRT1_2': ['math.sqrt(1.0 / 2)', Python.ORDER_MEMBER], - 'INFINITY': ['float(\'inf\')', Python.ORDER_ATOMIC] + 'INFINITY': ['float(\'inf\')', Python.ORDER_ATOMIC], }; const constant = block.getFieldValue('CONSTANT'); if (constant !== 'INFINITY') { @@ -152,65 +152,62 @@ Python['math_constant'] = function(block) { }; Python['math_number_property'] = function(block) { - // Check if a number is even, odd, prime, whole, positive, or negative - // or if it is divisible by certain number. Returns true or false. - const number_to_check = - Python.valueToCode( - block, 'NUMBER_TO_CHECK', Python.ORDER_MULTIPLICATIVE) || - '0'; - const dropdown_property = block.getFieldValue('PROPERTY'); + // Check if a number is even, odd, prime, whole, positive, or negative + // or if it is divisible by certain number. Returns true or false. + const PROPERTIES = { + 'EVEN': [' % 2 == 0', Python.ORDER_MULTIPLICATIVE, Python.ORDER_RELATIONAL], + 'ODD': [' % 2 == 1', Python.ORDER_MULTIPLICATIVE, Python.ORDER_RELATIONAL], + 'WHOLE': [' % 1 == 0', Python.ORDER_MULTIPLICATIVE, + Python.ORDER_RELATIONAL], + 'POSITIVE': [' > 0', Python.ORDER_RELATIONAL, Python.ORDER_RELATIONAL], + 'NEGATIVE': [' < 0', Python.ORDER_RELATIONAL, Python.ORDER_RELATIONAL], + 'DIVISIBLE_BY': [null, Python.ORDER_MULTIPLICATIVE, + Python.ORDER_RELATIONAL], + 'PRIME': [null, Python.ORDER_NONE, Python.ORDER_FUNCTION_CALL], + } + const dropdownProperty = block.getFieldValue('PROPERTY'); + const [suffix, inputOrder, outputOrder] = PROPERTIES[dropdownProperty]; + const numberToCheck = Python.valueToCode(block, 'NUMBER_TO_CHECK', + inputOrder) || '0'; let code; - if (dropdown_property === 'PRIME') { + if (dropdownProperty === 'PRIME') { + // Prime is a special case as it is not a one-liner test. Python.definitions_['import_math'] = 'import math'; Python.definitions_['from_numbers_import_Number'] = 'from numbers import Number'; - const functionName = Python.provideFunction_('math_isPrime', [ - 'def ' + Python.FUNCTION_NAME_PLACEHOLDER_ + '(n):', - ' # https://en.wikipedia.org/wiki/Primality_test#Naive_methods', - ' # If n is not a number but a string, try parsing it.', - ' if not isinstance(n, Number):', ' try:', ' n = float(n)', - ' except:', ' return False', - ' if n == 2 or n == 3:', ' return True', - ' # False if n is negative, is 1, or not whole,' + - ' or if n is divisible by 2 or 3.', - ' if n <= 1 or n % 1 != 0 or n % 2 == 0 or n % 3 == 0:', - ' return False', - ' # Check all the numbers of form 6k +/- 1, up to sqrt(n).', - ' for x in range(6, int(math.sqrt(n)) + 2, 6):', - ' if n % (x - 1) == 0 or n % (x + 1) == 0:', ' return False', - ' return True' - ]); - code = functionName + '(' + number_to_check + ')'; - return [code, Python.ORDER_FUNCTION_CALL]; - } - switch (dropdown_property) { - case 'EVEN': - code = number_to_check + ' % 2 == 0'; - break; - case 'ODD': - code = number_to_check + ' % 2 == 1'; - break; - case 'WHOLE': - code = number_to_check + ' % 1 == 0'; - break; - case 'POSITIVE': - code = number_to_check + ' > 0'; - break; - case 'NEGATIVE': - code = number_to_check + ' < 0'; - break; - case 'DIVISIBLE_BY': { - const divisor = - Python.valueToCode(block, 'DIVISOR', Python.ORDER_MULTIPLICATIVE); - // If 'divisor' is some code that evals to 0, Python will raise an error. - if (!divisor || divisor === '0') { - return ['False', Python.ORDER_ATOMIC]; - } - code = number_to_check + ' % ' + divisor + ' == 0'; - break; + const functionName = Python.provideFunction_('math_isPrime', ` +def ${Python.FUNCTION_NAME_PLACEHOLDER_}(n): + # https://en.wikipedia.org/wiki/Primality_test#Naive_methods + # If n is not a number but a string, try parsing it. + if not isinstance(n, Number): + try: + n = float(n) + except: + return False + if n == 2 or n == 3: + return True + # False if n is negative, is 1, or not whole, or if n is divisible by 2 or 3. + if n <= 1 or n % 1 != 0 or n % 2 == 0 or n % 3 == 0: + return False + # Check all the numbers of form 6k +/- 1, up to sqrt(n). + for x in range(6, int(math.sqrt(n)) + 2, 6): + if n % (x - 1) == 0 or n % (x + 1) == 0: + return False + return True +`); + code = functionName + '(' + numberToCheck + ')'; + } else if (dropdownProperty === 'DIVISIBLE_BY') { + const divisor = Python.valueToCode(block, 'DIVISOR', + Python.ORDER_MULTIPLICATIVE) || '0'; + // If 'divisor' is some code that evals to 0, Python will raise an error. + if (divisor === '0') { + return ['False', Python.ORDER_ATOMIC]; } - } - return [code, Python.ORDER_RELATIONAL]; + code = numberToCheck + ' % ' + divisor + ' == 0'; + } else { + code = numberToCheck + suffix; + }; + return [code, outputOrder]; }; Python['math_change'] = function(block) { @@ -248,71 +245,72 @@ Python['math_on_list'] = function(block) { case 'AVERAGE': { Python.definitions_['from_numbers_import_Number'] = 'from numbers import Number'; - const functionName = Python.provideFunction_( - 'math_mean', - // This operation excludes null and values that aren't int or float: - // math_mean([null, null, "aString", 1, 9]) -> 5.0 - [ - 'def ' + Python.FUNCTION_NAME_PLACEHOLDER_ + '(myList):', - ' localList = [e for e in myList if isinstance(e, Number)]', - ' if not localList: return', - ' return float(sum(localList)) / len(localList)' - ]); + // This operation excludes null and values that aren't int or float: + // math_mean([null, null, "aString", 1, 9]) -> 5.0 + const functionName = Python.provideFunction_('math_mean', ` +def ${Python.FUNCTION_NAME_PLACEHOLDER_}(myList): + localList = [e for e in myList if isinstance(e, Number)] + if not localList: return + return float(sum(localList)) / len(localList) +`); code = functionName + '(' + list + ')'; break; } case 'MEDIAN': { Python.definitions_['from_numbers_import_Number'] = 'from numbers import Number'; - const functionName = Python.provideFunction_( - 'math_median', - // This operation excludes null values: - // math_median([null, null, 1, 3]) -> 2.0 - [ - 'def ' + Python.FUNCTION_NAME_PLACEHOLDER_ + '(myList):', - ' localList = sorted([e for e in myList if isinstance(e, Number)])', - ' if not localList: return', ' if len(localList) % 2 == 0:', - ' return (localList[len(localList) // 2 - 1] + ' + - 'localList[len(localList) // 2]) / 2.0', - ' else:', ' return localList[(len(localList) - 1) // 2]' - ]); + // This operation excludes null values: + // math_median([null, null, 1, 3]) -> 2.0 + const functionName = Python.provideFunction_( 'math_median', ` +def ${Python.FUNCTION_NAME_PLACEHOLDER_}(myList): + localList = sorted([e for e in myList if isinstance(e, Number)]) + if not localList: return + if len(localList) % 2 == 0: + return (localList[len(localList) // 2 - 1] + localList[len(localList) // 2]) / 2.0 + else: + return localList[(len(localList) - 1) // 2] +`); code = functionName + '(' + list + ')'; break; } case 'MODE': { - const functionName = Python.provideFunction_( - 'math_modes', - // As a list of numbers can contain more than one mode, - // the returned result is provided as an array. - // Mode of [3, 'x', 'x', 1, 1, 2, '3'] -> ['x', 1] - [ - 'def ' + Python.FUNCTION_NAME_PLACEHOLDER_ + '(some_list):', - ' modes = []', - ' # Using a lists of [item, count] to keep count rather than dict', - ' # to avoid "unhashable" errors when the counted item is ' + - 'itself a list or dict.', - ' counts = []', ' maxCount = 1', ' for item in some_list:', - ' found = False', ' for count in counts:', - ' if count[0] == item:', ' count[1] += 1', - ' maxCount = max(maxCount, count[1])', - ' found = True', - ' if not found:', ' counts.append([item, 1])', - ' for counted_item, item_count in counts:', - ' if item_count == maxCount:', - ' modes.append(counted_item)', ' return modes' - ]); + // As a list of numbers can contain more than one mode, + // the returned result is provided as an array. + // Mode of [3, 'x', 'x', 1, 1, 2, '3'] -> ['x', 1] + const functionName = Python.provideFunction_('math_modes', ` +def ${Python.FUNCTION_NAME_PLACEHOLDER_}(some_list): + modes = [] + # Using a lists of [item, count] to keep count rather than dict + # to avoid "unhashable" errors when the counted item is itself a list or dict. + counts = [] + maxCount = 1 + for item in some_list: + found = False + for count in counts: + if count[0] == item: + count[1] += 1 + maxCount = max(maxCount, count[1]) + found = True + if not found: + counts.append([item, 1]) + for counted_item, item_count in counts: + if item_count == maxCount: + modes.append(counted_item) + return modes +`); code = functionName + '(' + list + ')'; break; } case 'STD_DEV': { Python.definitions_['import_math'] = 'import math'; - const functionName = Python.provideFunction_('math_standard_deviation', [ - 'def ' + Python.FUNCTION_NAME_PLACEHOLDER_ + '(numbers):', - ' n = len(numbers)', ' if n == 0: return', - ' mean = float(sum(numbers)) / n', - ' variance = sum((x - mean) ** 2 for x in numbers) / n', - ' return math.sqrt(variance)' - ]); + const functionName = Python.provideFunction_('math_standard_deviation', ` +def ${Python.FUNCTION_NAME_PLACEHOLDER_}(numbers): + n = len(numbers) + if n == 0: return + mean = float(sum(numbers)) / n + variance = sum((x - mean) ** 2 for x in numbers) / n + return math.sqrt(variance) +`); code = functionName + '(' + list + ')'; break; } diff --git a/generators/python/text.js b/generators/python/text.js index 4edb5d3b2..a097cbee2 100644 --- a/generators/python/text.js +++ b/generators/python/text.js @@ -55,18 +55,18 @@ Python['text_join'] = function(block) { // Should we allow joining by '-' or ',' or any other characters? switch (block.itemCount_) { case 0: - return ['\'\'', Python.ORDER_ATOMIC]; + return ["''", Python.ORDER_ATOMIC]; case 1: { const element = - Python.valueToCode(block, 'ADD0', Python.ORDER_NONE) || '\'\''; + Python.valueToCode(block, 'ADD0', Python.ORDER_NONE) || "''"; const codeAndOrder = forceString(element); return codeAndOrder; } case 2: { const element0 = - Python.valueToCode(block, 'ADD0', Python.ORDER_NONE) || '\'\''; + Python.valueToCode(block, 'ADD0', Python.ORDER_NONE) || "''"; const element1 = - Python.valueToCode(block, 'ADD1', Python.ORDER_NONE) || '\'\''; + Python.valueToCode(block, 'ADD1', Python.ORDER_NONE) || "''"; const code = forceString(element0)[0] + ' + ' + forceString(element1)[0]; return [code, Python.ORDER_ADDITIVE]; } @@ -74,7 +74,7 @@ Python['text_join'] = function(block) { const elements = []; for (let i = 0; i < block.itemCount_; i++) { elements[i] = - Python.valueToCode(block, 'ADD' + i, Python.ORDER_NONE) || '\'\''; + Python.valueToCode(block, 'ADD' + i, Python.ORDER_NONE) || "''"; } const tempVar = Python.nameDB_.getDistinctName('x', NameType.VARIABLE); const code = '\'\'.join([str(' + tempVar + ') for ' + tempVar + ' in [' + @@ -88,19 +88,19 @@ Python['text_append'] = function(block) { // Append to a variable in place. const varName = Python.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE); - const value = Python.valueToCode(block, 'TEXT', Python.ORDER_NONE) || '\'\''; + const value = Python.valueToCode(block, 'TEXT', Python.ORDER_NONE) || "''"; return varName + ' = str(' + varName + ') + ' + forceString(value)[0] + '\n'; }; Python['text_length'] = function(block) { // Is the string null or array empty? - const text = Python.valueToCode(block, 'VALUE', Python.ORDER_NONE) || '\'\''; + const text = Python.valueToCode(block, 'VALUE', Python.ORDER_NONE) || "''"; return ['len(' + text + ')', Python.ORDER_FUNCTION_CALL]; }; Python['text_isEmpty'] = function(block) { // Is the string null or array empty? - const text = Python.valueToCode(block, 'VALUE', Python.ORDER_NONE) || '\'\''; + const text = Python.valueToCode(block, 'VALUE', Python.ORDER_NONE) || "''"; const code = 'not len(' + text + ')'; return [code, Python.ORDER_LOGICAL_NOT]; }; @@ -110,9 +110,9 @@ Python['text_indexOf'] = function(block) { // Should we allow for non-case sensitive??? const operator = block.getFieldValue('END') === 'FIRST' ? 'find' : 'rfind'; const substring = - Python.valueToCode(block, 'FIND', Python.ORDER_NONE) || '\'\''; + Python.valueToCode(block, 'FIND', Python.ORDER_NONE) || "''"; const text = - Python.valueToCode(block, 'VALUE', Python.ORDER_MEMBER) || '\'\''; + Python.valueToCode(block, 'VALUE', Python.ORDER_MEMBER) || "''"; const code = text + '.' + operator + '(' + substring + ')'; if (block.workspace.options.oneBasedIndex) { return [code + ' + 1', Python.ORDER_ADDITIVE]; @@ -126,7 +126,7 @@ Python['text_charAt'] = function(block) { const where = block.getFieldValue('WHERE') || 'FROM_START'; const textOrder = (where === 'RANDOM') ? Python.ORDER_NONE : Python.ORDER_MEMBER; - const text = Python.valueToCode(block, 'VALUE', textOrder) || '\'\''; + const text = Python.valueToCode(block, 'VALUE', textOrder) || "''"; switch (where) { case 'FIRST': { const code = text + '[0]'; @@ -148,10 +148,11 @@ Python['text_charAt'] = function(block) { } case 'RANDOM': { Python.definitions_['import_random'] = 'import random'; - const functionName = Python.provideFunction_('text_random_letter', [ - 'def ' + Python.FUNCTION_NAME_PLACEHOLDER_ + '(text):', - ' x = int(random.random() * len(text))', ' return text[x];' - ]); + const functionName = Python.provideFunction_('text_random_letter', ` +def ${Python.FUNCTION_NAME_PLACEHOLDER_}(text): + x = int(random.random() * len(text)) + return text[x] +`); const code = functionName + '(' + text + ')'; return [code, Python.ORDER_FUNCTION_CALL]; } @@ -164,7 +165,7 @@ Python['text_getSubstring'] = function(block) { const where1 = block.getFieldValue('WHERE1'); const where2 = block.getFieldValue('WHERE2'); const text = - Python.valueToCode(block, 'STRING', Python.ORDER_MEMBER) || '\'\''; + Python.valueToCode(block, 'STRING', Python.ORDER_MEMBER) || "''"; let at1; switch (where1) { case 'FROM_START': @@ -217,7 +218,7 @@ Python['text_changeCase'] = function(block) { 'TITLECASE': '.title()' }; const operator = OPERATORS[block.getFieldValue('CASE')]; - const text = Python.valueToCode(block, 'TEXT', Python.ORDER_MEMBER) || '\'\''; + const text = Python.valueToCode(block, 'TEXT', Python.ORDER_MEMBER) || "''"; const code = text + operator; return [code, Python.ORDER_FUNCTION_CALL]; }; @@ -230,30 +231,33 @@ Python['text_trim'] = function(block) { 'BOTH': '.strip()' }; const operator = OPERATORS[block.getFieldValue('MODE')]; - const text = Python.valueToCode(block, 'TEXT', Python.ORDER_MEMBER) || '\'\''; + const text = Python.valueToCode(block, 'TEXT', Python.ORDER_MEMBER) || "''"; const code = text + operator; return [code, Python.ORDER_FUNCTION_CALL]; }; Python['text_print'] = function(block) { // Print statement. - const msg = Python.valueToCode(block, 'TEXT', Python.ORDER_NONE) || '\'\''; + const msg = Python.valueToCode(block, 'TEXT', Python.ORDER_NONE) || "''"; return 'print(' + msg + ')\n'; }; Python['text_prompt_ext'] = function(block) { // Prompt function. - const functionName = Python.provideFunction_('text_prompt', [ - 'def ' + Python.FUNCTION_NAME_PLACEHOLDER_ + '(msg):', ' try:', - ' return raw_input(msg)', ' except NameError:', ' return input(msg)' - ]); + const functionName = Python.provideFunction_('text_prompt', ` +def ${Python.FUNCTION_NAME_PLACEHOLDER_}(msg): + try: + return raw_input(msg) + except NameError: + return input(msg) +`); let msg; if (block.getField('TEXT')) { // Internal message. msg = Python.quote_(block.getFieldValue('TEXT')); } else { // External message. - msg = Python.valueToCode(block, 'TEXT', Python.ORDER_NONE) || '\'\''; + msg = Python.valueToCode(block, 'TEXT', Python.ORDER_NONE) || "''"; } let code = functionName + '(' + msg + ')'; const toNumber = block.getFieldValue('TYPE') === 'NUMBER'; @@ -266,22 +270,22 @@ Python['text_prompt_ext'] = function(block) { Python['text_prompt'] = Python['text_prompt_ext']; Python['text_count'] = function(block) { - const text = Python.valueToCode(block, 'TEXT', Python.ORDER_MEMBER) || '\'\''; - const sub = Python.valueToCode(block, 'SUB', Python.ORDER_NONE) || '\'\''; + const text = Python.valueToCode(block, 'TEXT', Python.ORDER_MEMBER) || "''"; + const sub = Python.valueToCode(block, 'SUB', Python.ORDER_NONE) || "''"; const code = text + '.count(' + sub + ')'; return [code, Python.ORDER_FUNCTION_CALL]; }; Python['text_replace'] = function(block) { - const text = Python.valueToCode(block, 'TEXT', Python.ORDER_MEMBER) || '\'\''; - const from = Python.valueToCode(block, 'FROM', Python.ORDER_NONE) || '\'\''; - const to = Python.valueToCode(block, 'TO', Python.ORDER_NONE) || '\'\''; + const text = Python.valueToCode(block, 'TEXT', Python.ORDER_MEMBER) || "''"; + const from = Python.valueToCode(block, 'FROM', Python.ORDER_NONE) || "''"; + const to = Python.valueToCode(block, 'TO', Python.ORDER_NONE) || "''"; const code = text + '.replace(' + from + ', ' + to + ')'; return [code, Python.ORDER_MEMBER]; }; Python['text_reverse'] = function(block) { - const text = Python.valueToCode(block, 'TEXT', Python.ORDER_MEMBER) || '\'\''; + const text = Python.valueToCode(block, 'TEXT', Python.ORDER_MEMBER) || "''"; const code = text + '[::-1]'; return [code, Python.ORDER_MEMBER]; }; diff --git a/gulpfile.js b/gulpfile.js index 6cf2da365..c78ac405a 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -8,16 +8,17 @@ * @fileoverview Gulp script to build Blockly for Node & NPM. * Run this script by calling "npm install" in this directory. */ +/* eslint-env node */ -var gulp = require('gulp'); +const gulp = require('gulp'); -var buildTasks = require('./scripts/gulpfiles/build_tasks'); -var packageTasks = require('./scripts/gulpfiles/package_tasks'); -var gitTasks = require('./scripts/gulpfiles/git_tasks'); -var licenseTasks = require('./scripts/gulpfiles/license_tasks'); -var appengineTasks = require('./scripts/gulpfiles/appengine_tasks'); -var releaseTasks = require('./scripts/gulpfiles/release_tasks'); -var cleanupTasks = require('./scripts/gulpfiles/cleanup_tasks'); +const buildTasks = require('./scripts/gulpfiles/build_tasks'); +const packageTasks = require('./scripts/gulpfiles/package_tasks'); +const gitTasks = require('./scripts/gulpfiles/git_tasks'); +const licenseTasks = require('./scripts/gulpfiles/license_tasks'); +const appengineTasks = require('./scripts/gulpfiles/appengine_tasks'); +const releaseTasks = require('./scripts/gulpfiles/release_tasks'); +const cleanupTasks = require('./scripts/gulpfiles/cleanup_tasks'); module.exports = { deployDemos: appengineTasks.deployDemos, @@ -29,6 +30,7 @@ module.exports = { buildLangfiles: buildTasks.langfiles, buildCompiled: buildTasks.compiled, buildAdvancedCompilationTest: buildTasks.advancedCompilationTest, + buildTs: buildTasks.buildTypescript, // TODO(5621): Re-enable once typings generation is fixed. // checkin: gulp.parallel(buildTasks.checkinBuilt, typings.checkinTypings), checkin: gulp.parallel(buildTasks.checkinBuilt), diff --git a/javascript_compressed.js b/javascript_compressed.js index 6d8480401..1bb40e813 100644 --- a/javascript_compressed.js +++ b/javascript_compressed.js @@ -7,10 +7,11 @@ } else if (typeof exports === 'object') { // Node.js module.exports = factory(require("./blockly_compressed.js")); } else { // Browser - root.Blockly.JavaScript = factory(root.Blockly); + var factoryExports = factory(root.Blockly); + root.Blockly.JavaScript = factoryExports; } -}(this, function(Blockly) { -const $=Blockly.internal_; +}(this, function(__parent__) { +var $=__parent__.__namespace__; var module$contents$Blockly$JavaScript_JavaScript=new $.module$exports$Blockly$Generator.Generator("JavaScript");module$contents$Blockly$JavaScript_JavaScript.addReservedWords("break,case,catch,class,const,continue,debugger,default,delete,do,else,export,extends,finally,for,function,if,import,in,instanceof,new,return,super,switch,this,throw,try,typeof,var,void,while,with,yield,enum,implements,interface,let,package,private,protected,public,static,await,null,true,false,arguments,"+Object.getOwnPropertyNames($.module$exports$Blockly$utils$global.globalThis).join(",")); module$contents$Blockly$JavaScript_JavaScript.ORDER_ATOMIC=0;module$contents$Blockly$JavaScript_JavaScript.ORDER_NEW=1.1;module$contents$Blockly$JavaScript_JavaScript.ORDER_MEMBER=1.2;module$contents$Blockly$JavaScript_JavaScript.ORDER_FUNCTION_CALL=2;module$contents$Blockly$JavaScript_JavaScript.ORDER_INCREMENT=3;module$contents$Blockly$JavaScript_JavaScript.ORDER_DECREMENT=3;module$contents$Blockly$JavaScript_JavaScript.ORDER_BITWISE_NOT=4.1; module$contents$Blockly$JavaScript_JavaScript.ORDER_UNARY_PLUS=4.2;module$contents$Blockly$JavaScript_JavaScript.ORDER_UNARY_NEGATION=4.3;module$contents$Blockly$JavaScript_JavaScript.ORDER_LOGICAL_NOT=4.4;module$contents$Blockly$JavaScript_JavaScript.ORDER_TYPEOF=4.5;module$contents$Blockly$JavaScript_JavaScript.ORDER_VOID=4.6;module$contents$Blockly$JavaScript_JavaScript.ORDER_DELETE=4.7;module$contents$Blockly$JavaScript_JavaScript.ORDER_AWAIT=4.8; @@ -36,19 +37,19 @@ $.Blockly.JavaScript.text_append=function(a){var b=$.Blockly.JavaScript.nameDB_. $.Blockly.JavaScript.text_isEmpty=function(a){return["!"+($.Blockly.JavaScript.valueToCode(a,"VALUE",$.Blockly.JavaScript.ORDER_MEMBER)||"''")+".length",$.Blockly.JavaScript.ORDER_LOGICAL_NOT]}; $.Blockly.JavaScript.text_indexOf=function(a){var b="FIRST"===a.getFieldValue("END")?"indexOf":"lastIndexOf",c=$.Blockly.JavaScript.valueToCode(a,"FIND",$.Blockly.JavaScript.ORDER_NONE)||"''";b=($.Blockly.JavaScript.valueToCode(a,"VALUE",$.Blockly.JavaScript.ORDER_MEMBER)||"''")+"."+b+"("+c+")";return a.workspace.options.oneBasedIndex?[b+" + 1",$.Blockly.JavaScript.ORDER_ADDITION]:[b,$.Blockly.JavaScript.ORDER_FUNCTION_CALL]}; $.Blockly.JavaScript.text_charAt=function(a){var b=a.getFieldValue("WHERE")||"FROM_START",c=$.Blockly.JavaScript.valueToCode(a,"VALUE","RANDOM"===b?$.Blockly.JavaScript.ORDER_NONE:$.Blockly.JavaScript.ORDER_MEMBER)||"''";switch(b){case "FIRST":return[c+".charAt(0)",$.Blockly.JavaScript.ORDER_FUNCTION_CALL];case "LAST":return[c+".slice(-1)",$.Blockly.JavaScript.ORDER_FUNCTION_CALL];case "FROM_START":return a=$.Blockly.JavaScript.getAdjusted(a,"AT"),[c+".charAt("+a+")",$.Blockly.JavaScript.ORDER_FUNCTION_CALL]; -case "FROM_END":return a=$.Blockly.JavaScript.getAdjusted(a,"AT",1,!0),[c+".slice("+a+").charAt(0)",$.Blockly.JavaScript.ORDER_FUNCTION_CALL];case "RANDOM":return[$.Blockly.JavaScript.provideFunction_("textRandomLetter",["function "+$.Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_+"(text) {"," var x = Math.floor(Math.random() * text.length);"," return text[x];","}"])+"("+c+")",$.Blockly.JavaScript.ORDER_FUNCTION_CALL]}throw Error("Unhandled option (text_charAt).");}; +case "FROM_END":return a=$.Blockly.JavaScript.getAdjusted(a,"AT",1,!0),[c+".slice("+a+").charAt(0)",$.Blockly.JavaScript.ORDER_FUNCTION_CALL];case "RANDOM":return[$.Blockly.JavaScript.provideFunction_("textRandomLetter","\nfunction "+$.Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_+"(text) {\n var x = Math.floor(Math.random() * text.length);\n return text[x];\n}\n")+"("+c+")",$.Blockly.JavaScript.ORDER_FUNCTION_CALL]}throw Error("Unhandled option (text_charAt).");}; $.Blockly.JavaScript.text_getSubstring=function(a){var b=a.getFieldValue("WHERE1"),c=a.getFieldValue("WHERE2"),d="FROM_END"!==b&&"LAST"!==b&&"FROM_END"!==c&&"LAST"!==c,e=$.Blockly.JavaScript.valueToCode(a,"STRING",d?$.Blockly.JavaScript.ORDER_MEMBER:$.Blockly.JavaScript.ORDER_NONE)||"''";if("FIRST"===b&&"LAST"===c)return[e,$.Blockly.JavaScript.ORDER_NONE];if(e.match(/^'?\w+'?$/)||d){switch(b){case "FROM_START":b=$.Blockly.JavaScript.getAdjusted(a,"AT1");break;case "FROM_END":b=$.Blockly.JavaScript.getAdjusted(a, "AT1",1,!1,$.Blockly.JavaScript.ORDER_SUBTRACTION);b=e+".length - "+b;break;case "FIRST":b="0";break;default:throw Error("Unhandled option (text_getSubstring).");}switch(c){case "FROM_START":c=$.Blockly.JavaScript.getAdjusted(a,"AT2",1);break;case "FROM_END":c=$.Blockly.JavaScript.getAdjusted(a,"AT2",0,!1,$.Blockly.JavaScript.ORDER_SUBTRACTION);c=e+".length - "+c;break;case "LAST":c=e+".length";break;default:throw Error("Unhandled option (text_getSubstring).");}e=e+".slice("+b+", "+c+")"}else{d=$.Blockly.JavaScript.getAdjusted(a, -"AT1");a=$.Blockly.JavaScript.getAdjusted(a,"AT2");var f={FIRST:"First",LAST:"Last",FROM_START:"FromStart",FROM_END:"FromEnd"};e=$.Blockly.JavaScript.provideFunction_("subsequence"+f[b]+f[c],["function "+$.Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_+"(sequence"+("FROM_END"===b||"FROM_START"===b?", at1":"")+("FROM_END"===c||"FROM_START"===c?", at2":"")+") {"," var start = "+module$contents$Blockly$JavaScript$texts_getSubstringIndex("sequence",b,"at1")+";"," var end = "+module$contents$Blockly$JavaScript$texts_getSubstringIndex("sequence", -c,"at2")+" + 1;"," return sequence.slice(start, end);","}"])+"("+e+("FROM_END"===b||"FROM_START"===b?", "+d:"")+("FROM_END"===c||"FROM_START"===c?", "+a:"")+")"}return[e,$.Blockly.JavaScript.ORDER_FUNCTION_CALL]}; -$.Blockly.JavaScript.text_changeCase=function(a){var b={UPPERCASE:".toUpperCase()",LOWERCASE:".toLowerCase()",TITLECASE:null}[a.getFieldValue("CASE")];a=$.Blockly.JavaScript.valueToCode(a,"TEXT",b?$.Blockly.JavaScript.ORDER_MEMBER:$.Blockly.JavaScript.ORDER_NONE)||"''";return[b?a+b:$.Blockly.JavaScript.provideFunction_("textToTitleCase",["function "+$.Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_+"(str) {"," return str.replace(/\\S+/g,"," function(txt) {return txt[0].toUpperCase() + txt.substring(1).toLowerCase();});", -"}"])+"("+a+")",$.Blockly.JavaScript.ORDER_FUNCTION_CALL]};$.Blockly.JavaScript.text_trim=function(a){var b={LEFT:".replace(/^[\\s\\xa0]+/, '')",RIGHT:".replace(/[\\s\\xa0]+$/, '')",BOTH:".trim()"}[a.getFieldValue("MODE")];return[($.Blockly.JavaScript.valueToCode(a,"TEXT",$.Blockly.JavaScript.ORDER_MEMBER)||"''")+b,$.Blockly.JavaScript.ORDER_FUNCTION_CALL]}; +"AT1");a=$.Blockly.JavaScript.getAdjusted(a,"AT2");var f={FIRST:"First",LAST:"Last",FROM_START:"FromStart",FROM_END:"FromEnd"};e=$.Blockly.JavaScript.provideFunction_("subsequence"+f[b]+f[c],"\nfunction "+$.Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_+"(sequence"+("FROM_END"===b||"FROM_START"===b?", at1":"")+("FROM_END"===c||"FROM_START"===c?", at2":"")+") {\n var start = "+module$contents$Blockly$JavaScript$texts_getSubstringIndex("sequence",b,"at1")+";\n var end = "+module$contents$Blockly$JavaScript$texts_getSubstringIndex("sequence", +c,"at2")+" + 1;\n return sequence.slice(start, end);\n}\n")+"("+e+("FROM_END"===b||"FROM_START"===b?", "+d:"")+("FROM_END"===c||"FROM_START"===c?", "+a:"")+")"}return[e,$.Blockly.JavaScript.ORDER_FUNCTION_CALL]}; +$.Blockly.JavaScript.text_changeCase=function(a){var b={UPPERCASE:".toUpperCase()",LOWERCASE:".toLowerCase()",TITLECASE:null}[a.getFieldValue("CASE")];a=$.Blockly.JavaScript.valueToCode(a,"TEXT",b?$.Blockly.JavaScript.ORDER_MEMBER:$.Blockly.JavaScript.ORDER_NONE)||"''";return[b?a+b:$.Blockly.JavaScript.provideFunction_("textToTitleCase","\nfunction "+$.Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_+"(str) {\n return str.replace(/\\S+/g,\n function(txt) {return txt[0].toUpperCase() + txt.substring(1).toLowerCase();});\n}\n")+ +"("+a+")",$.Blockly.JavaScript.ORDER_FUNCTION_CALL]};$.Blockly.JavaScript.text_trim=function(a){var b={LEFT:".replace(/^[\\s\\xa0]+/, '')",RIGHT:".replace(/[\\s\\xa0]+$/, '')",BOTH:".trim()"}[a.getFieldValue("MODE")];return[($.Blockly.JavaScript.valueToCode(a,"TEXT",$.Blockly.JavaScript.ORDER_MEMBER)||"''")+b,$.Blockly.JavaScript.ORDER_FUNCTION_CALL]}; $.Blockly.JavaScript.text_print=function(a){return"window.alert("+($.Blockly.JavaScript.valueToCode(a,"TEXT",$.Blockly.JavaScript.ORDER_NONE)||"''")+");\n"};$.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="Number("+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_NONE)||"''";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_FUNCTION_CALL]}; -$.Blockly.JavaScript.text_replace=function(a){var b=$.Blockly.JavaScript.valueToCode(a,"TEXT",$.Blockly.JavaScript.ORDER_NONE)||"''",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(/([-()\\[\\]{}+?*.$\\^|,:# 0";break;case "NEGATIVE":d=b+" < 0";break;case "DIVISIBLE_BY":a=$.Blockly.JavaScript.valueToCode(a,"DIVISOR",$.Blockly.JavaScript.ORDER_MODULUS)||"0",d=b+" % "+a+" === 0"}return[d,$.Blockly.JavaScript.ORDER_EQUALITY]}; -$.Blockly.JavaScript.math_change=function(a){var b=$.Blockly.JavaScript.valueToCode(a,"DELTA",$.Blockly.JavaScript.ORDER_ADDITION)||"0";a=$.Blockly.JavaScript.nameDB_.getName(a.getFieldValue("VAR"),$.module$exports$Blockly$Names.NameType.VARIABLE);return a+" = (typeof "+a+" === 'number' ? "+a+" : 0) + "+b+";\n"};$.Blockly.JavaScript.math_round=$.Blockly.JavaScript.math_single;$.Blockly.JavaScript.math_trig=$.Blockly.JavaScript.math_single; +$.Blockly.JavaScript.math_number_property=function(a){var b={EVEN:[" % 2 === 0",$.Blockly.JavaScript.ORDER_MODULUS,$.Blockly.JavaScript.ORDER_EQUALITY],ODD:[" % 2 === 1",$.Blockly.JavaScript.ORDER_MODULUS,$.Blockly.JavaScript.ORDER_EQUALITY],WHOLE:[" % 1 === 0",$.Blockly.JavaScript.ORDER_MODULUS,$.Blockly.JavaScript.ORDER_EQUALITY],POSITIVE:[" > 0",$.Blockly.JavaScript.ORDER_RELATIONAL,$.Blockly.JavaScript.ORDER_RELATIONAL],NEGATIVE:[" < 0",$.Blockly.JavaScript.ORDER_RELATIONAL,$.Blockly.JavaScript.ORDER_RELATIONAL], +DIVISIBLE_BY:[null,$.Blockly.JavaScript.ORDER_MODULUS,$.Blockly.JavaScript.ORDER_EQUALITY],PRIME:[null,$.Blockly.JavaScript.ORDER_NONE,$.Blockly.JavaScript.ORDER_FUNCTION_CALL]},c=a.getFieldValue("PROPERTY");b=$.$jscomp.makeIterator(b[c]);var d=b.next().value,e=b.next().value;b=b.next().value;e=$.Blockly.JavaScript.valueToCode(a,"NUMBER_TO_CHECK",e)||"0";"PRIME"===c?a=$.Blockly.JavaScript.provideFunction_("mathIsPrime","\nfunction "+$.Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_+"(n) {\n // https://en.wikipedia.org/wiki/Primality_test#Naive_methods\n if (n == 2 || n == 3) {\n return true;\n }\n // False if n is NaN, negative, is 1, or not whole.\n // And false if n is divisible by 2 or 3.\n if (isNaN(n) || n <= 1 || n % 1 !== 0 || n % 2 === 0 || n % 3 === 0) {\n return false;\n }\n // Check all the numbers of form 6k +/- 1, up to sqrt(n).\n for (var x = 6; x <= Math.sqrt(n) + 1; x += 6) {\n if (n % (x - 1) === 0 || n % (x + 1) === 0) {\n return false;\n }\n }\n return true;\n}\n")+ +"("+e+")":"DIVISIBLE_BY"===c?(a=$.Blockly.JavaScript.valueToCode(a,"DIVISOR",$.Blockly.JavaScript.ORDER_MODULUS)||"0",a=e+" % "+a+" === 0"):a=e+d;return[a,b]};$.Blockly.JavaScript.math_change=function(a){var b=$.Blockly.JavaScript.valueToCode(a,"DELTA",$.Blockly.JavaScript.ORDER_ADDITION)||"0";a=$.Blockly.JavaScript.nameDB_.getName(a.getFieldValue("VAR"),$.module$exports$Blockly$Names.NameType.VARIABLE);return a+" = (typeof "+a+" === 'number' ? "+a+" : 0) + "+b+";\n"}; +$.Blockly.JavaScript.math_round=$.Blockly.JavaScript.math_single;$.Blockly.JavaScript.math_trig=$.Blockly.JavaScript.math_single; $.Blockly.JavaScript.math_on_list=function(a){var b=a.getFieldValue("OP");switch(b){case "SUM":a=$.Blockly.JavaScript.valueToCode(a,"LIST",$.Blockly.JavaScript.ORDER_MEMBER)||"[]";a+=".reduce(function(x, y) {return x + y;})";break;case "MIN":a=$.Blockly.JavaScript.valueToCode(a,"LIST",$.Blockly.JavaScript.ORDER_NONE)||"[]";a="Math.min.apply(null, "+a+")";break;case "MAX":a=$.Blockly.JavaScript.valueToCode(a,"LIST",$.Blockly.JavaScript.ORDER_NONE)||"[]";a="Math.max.apply(null, "+a+")";break;case "AVERAGE":b= -$.Blockly.JavaScript.provideFunction_("mathMean",["function "+$.Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_+"(myList) {"," return myList.reduce(function(x, y) {return x + y;}) / myList.length;","}"]);a=$.Blockly.JavaScript.valueToCode(a,"LIST",$.Blockly.JavaScript.ORDER_NONE)||"[]";a=b+"("+a+")";break;case "MEDIAN":b=$.Blockly.JavaScript.provideFunction_("mathMedian",["function "+$.Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_+"(myList) {"," var localList = myList.filter(function (x) {return typeof x === 'number';});", -" if (!localList.length) return null;"," localList.sort(function(a, b) {return b - a;});"," if (localList.length % 2 === 0) {"," return (localList[localList.length / 2 - 1] + localList[localList.length / 2]) / 2;"," } else {"," return localList[(localList.length - 1) / 2];"," }","}"]);a=$.Blockly.JavaScript.valueToCode(a,"LIST",$.Blockly.JavaScript.ORDER_NONE)||"[]";a=b+"("+a+")";break;case "MODE":b=$.Blockly.JavaScript.provideFunction_("mathModes",["function "+$.Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_+ -"(values) {"," var modes = [];"," var counts = [];"," var maxCount = 0;"," for (var i = 0; i < values.length; i++) {"," var value = values[i];"," var found = false;"," var thisCount;"," for (var j = 0; j < counts.length; j++) {"," if (counts[j][0] === value) {"," thisCount = ++counts[j][1];"," found = true;"," break;"," }"," }"," if (!found) {"," counts.push([value, 1]);"," thisCount = 1;"," }"," maxCount = Math.max(thisCount, maxCount);", -" }"," for (var j = 0; j < counts.length; j++) {"," if (counts[j][1] === maxCount) {"," modes.push(counts[j][0]);"," }"," }"," return modes;","}"]);a=$.Blockly.JavaScript.valueToCode(a,"LIST",$.Blockly.JavaScript.ORDER_NONE)||"[]";a=b+"("+a+")";break;case "STD_DEV":b=$.Blockly.JavaScript.provideFunction_("mathStandardDeviation",["function "+$.Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_+"(numbers) {"," var n = numbers.length;"," if (!n) return null;"," var mean = numbers.reduce(function(x, y) {return x + y;}) / n;", -" var variance = 0;"," for (var j = 0; j < n; j++) {"," variance += Math.pow(numbers[j] - mean, 2);"," }"," variance = variance / n;"," return Math.sqrt(variance);","}"]);a=$.Blockly.JavaScript.valueToCode(a,"LIST",$.Blockly.JavaScript.ORDER_NONE)||"[]";a=b+"("+a+")";break;case "RANDOM":b=$.Blockly.JavaScript.provideFunction_("mathRandomList",["function "+$.Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_+"(list) {"," var x = Math.floor(Math.random() * list.length);"," return list[x];","}"]); -a=$.Blockly.JavaScript.valueToCode(a,"LIST",$.Blockly.JavaScript.ORDER_NONE)||"[]";a=b+"("+a+")";break;default:throw Error("Unknown operator: "+b);}return[a,$.Blockly.JavaScript.ORDER_FUNCTION_CALL]};$.Blockly.JavaScript.math_modulo=function(a){var b=$.Blockly.JavaScript.valueToCode(a,"DIVIDEND",$.Blockly.JavaScript.ORDER_MODULUS)||"0";a=$.Blockly.JavaScript.valueToCode(a,"DIVISOR",$.Blockly.JavaScript.ORDER_MODULUS)||"0";return[b+" % "+a,$.Blockly.JavaScript.ORDER_MODULUS]}; +$.Blockly.JavaScript.provideFunction_("mathMean","\nfunction "+$.Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_+"(myList) {\n return myList.reduce(function(x, y) {return x + y;}) / myList.length;\n}\n");a=$.Blockly.JavaScript.valueToCode(a,"LIST",$.Blockly.JavaScript.ORDER_NONE)||"[]";a=b+"("+a+")";break;case "MEDIAN":b=$.Blockly.JavaScript.provideFunction_("mathMedian","\nfunction "+$.Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_+"(myList) {\n var localList = myList.filter(function (x) {return typeof x === 'number';});\n if (!localList.length) return null;\n localList.sort(function(a, b) {return b - a;});\n if (localList.length % 2 === 0) {\n return (localList[localList.length / 2 - 1] + localList[localList.length / 2]) / 2;\n } else {\n return localList[(localList.length - 1) / 2];\n }\n}\n"); +a=$.Blockly.JavaScript.valueToCode(a,"LIST",$.Blockly.JavaScript.ORDER_NONE)||"[]";a=b+"("+a+")";break;case "MODE":b=$.Blockly.JavaScript.provideFunction_("mathModes","\nfunction "+$.Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_+"(values) {\n var modes = [];\n var counts = [];\n var maxCount = 0;\n for (var i = 0; i < values.length; i++) {\n var value = values[i];\n var found = false;\n var thisCount;\n for (var j = 0; j < counts.length; j++) {\n if (counts[j][0] === value) {\n thisCount = ++counts[j][1];\n found = true;\n break;\n }\n }\n if (!found) {\n counts.push([value, 1]);\n thisCount = 1;\n }\n maxCount = Math.max(thisCount, maxCount);\n }\n for (var j = 0; j < counts.length; j++) {\n if (counts[j][1] === maxCount) {\n modes.push(counts[j][0]);\n }\n }\n return modes;\n}\n"); +a=$.Blockly.JavaScript.valueToCode(a,"LIST",$.Blockly.JavaScript.ORDER_NONE)||"[]";a=b+"("+a+")";break;case "STD_DEV":b=$.Blockly.JavaScript.provideFunction_("mathStandardDeviation","\nfunction "+$.Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_+"(numbers) {\n var n = numbers.length;\n if (!n) return null;\n var mean = numbers.reduce(function(x, y) {return x + y;}) / n;\n var variance = 0;\n for (var j = 0; j < n; j++) {\n variance += Math.pow(numbers[j] - mean, 2);\n }\n variance = variance / n;\n return Math.sqrt(variance);\n}\n"); +a=$.Blockly.JavaScript.valueToCode(a,"LIST",$.Blockly.JavaScript.ORDER_NONE)||"[]";a=b+"("+a+")";break;case "RANDOM":b=$.Blockly.JavaScript.provideFunction_("mathRandomList","\nfunction "+$.Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_+"(list) {\n var x = Math.floor(Math.random() * list.length);\n return list[x];\n}\n");a=$.Blockly.JavaScript.valueToCode(a,"LIST",$.Blockly.JavaScript.ORDER_NONE)||"[]";a=b+"("+a+")";break;default:throw Error("Unknown operator: "+b);}return[a,$.Blockly.JavaScript.ORDER_FUNCTION_CALL]}; +$.Blockly.JavaScript.math_modulo=function(a){var b=$.Blockly.JavaScript.valueToCode(a,"DIVIDEND",$.Blockly.JavaScript.ORDER_MODULUS)||"0";a=$.Blockly.JavaScript.valueToCode(a,"DIVISOR",$.Blockly.JavaScript.ORDER_MODULUS)||"0";return[b+" % "+a,$.Blockly.JavaScript.ORDER_MODULUS]}; $.Blockly.JavaScript.math_constrain=function(a){var b=$.Blockly.JavaScript.valueToCode(a,"VALUE",$.Blockly.JavaScript.ORDER_NONE)||"0",c=$.Blockly.JavaScript.valueToCode(a,"LOW",$.Blockly.JavaScript.ORDER_NONE)||"0";a=$.Blockly.JavaScript.valueToCode(a,"HIGH",$.Blockly.JavaScript.ORDER_NONE)||"Infinity";return["Math.min(Math.max("+b+", "+c+"), "+a+")",$.Blockly.JavaScript.ORDER_FUNCTION_CALL]}; -$.Blockly.JavaScript.math_random_int=function(a){var b=$.Blockly.JavaScript.valueToCode(a,"FROM",$.Blockly.JavaScript.ORDER_NONE)||"0";a=$.Blockly.JavaScript.valueToCode(a,"TO",$.Blockly.JavaScript.ORDER_NONE)||"0";return[$.Blockly.JavaScript.provideFunction_("mathRandomInt",["function "+$.Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_+"(a, b) {"," if (a > b) {"," // Swap a and b to ensure a is smaller."," var c = a;"," a = b;"," b = c;"," }"," return Math.floor(Math.random() * (b - a + 1) + a);", -"}"])+"("+b+", "+a+")",$.Blockly.JavaScript.ORDER_FUNCTION_CALL]};$.Blockly.JavaScript.math_random_float=function(a){return["Math.random()",$.Blockly.JavaScript.ORDER_FUNCTION_CALL]};$.Blockly.JavaScript.math_atan2=function(a){var b=$.Blockly.JavaScript.valueToCode(a,"X",$.Blockly.JavaScript.ORDER_NONE)||"0";return["Math.atan2("+($.Blockly.JavaScript.valueToCode(a,"Y",$.Blockly.JavaScript.ORDER_NONE)||"0")+", "+b+") / Math.PI * 180",$.Blockly.JavaScript.ORDER_DIVISION]};var module$exports$Blockly$JavaScript$loops={}; +$.Blockly.JavaScript.math_random_int=function(a){var b=$.Blockly.JavaScript.valueToCode(a,"FROM",$.Blockly.JavaScript.ORDER_NONE)||"0";a=$.Blockly.JavaScript.valueToCode(a,"TO",$.Blockly.JavaScript.ORDER_NONE)||"0";return[$.Blockly.JavaScript.provideFunction_("mathRandomInt","\nfunction "+$.Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_+"(a, b) {\n if (a > b) {\n // Swap a and b to ensure a is smaller.\n var c = a;\n a = b;\n b = c;\n }\n return Math.floor(Math.random() * (b - a + 1) + a);\n}\n")+ +"("+b+", "+a+")",$.Blockly.JavaScript.ORDER_FUNCTION_CALL]};$.Blockly.JavaScript.math_random_float=function(a){return["Math.random()",$.Blockly.JavaScript.ORDER_FUNCTION_CALL]};$.Blockly.JavaScript.math_atan2=function(a){var b=$.Blockly.JavaScript.valueToCode(a,"X",$.Blockly.JavaScript.ORDER_NONE)||"0";return["Math.atan2("+($.Blockly.JavaScript.valueToCode(a,"Y",$.Blockly.JavaScript.ORDER_NONE)||"0")+", "+b+") / Math.PI * 180",$.Blockly.JavaScript.ORDER_DIVISION]};var module$exports$Blockly$JavaScript$loops={}; $.Blockly.JavaScript.controls_repeat_ext=function(a){var b=a.getField("TIMES")?String(Number(a.getFieldValue("TIMES"))):$.Blockly.JavaScript.valueToCode(a,"TIMES",$.Blockly.JavaScript.ORDER_ASSIGNMENT)||"0";var c=$.Blockly.JavaScript.statementToCode(a,"DO");c=$.Blockly.JavaScript.addLoopTrap(c,a);a="";var d=$.Blockly.JavaScript.nameDB_.getDistinctName("count",$.module$exports$Blockly$Names.NameType.VARIABLE),e=b;b.match(/^\w+$/)||(0,$.module$exports$Blockly$utils$string.isNumber)(b)||(e=$.Blockly.JavaScript.nameDB_.getDistinctName("repeat_end", $.module$exports$Blockly$Names.NameType.VARIABLE),a+="var "+e+" = "+b+";\n");return a+("for (var "+d+" = 0; "+d+" < "+e+"; "+d+"++) {\n"+c+"}\n")};$.Blockly.JavaScript.controls_repeat=$.Blockly.JavaScript.controls_repeat_ext; $.Blockly.JavaScript.controls_whileUntil=function(a){var b="UNTIL"===a.getFieldValue("MODE"),c=$.Blockly.JavaScript.valueToCode(a,"BOOL",b?$.Blockly.JavaScript.ORDER_LOGICAL_NOT:$.Blockly.JavaScript.ORDER_NONE)||"false",d=$.Blockly.JavaScript.statementToCode(a,"DO");d=$.Blockly.JavaScript.addLoopTrap(d,a);b&&(c="!"+c);return"while ("+c+") {\n"+d+"}\n"}; @@ -91,31 +91,29 @@ $.Blockly.JavaScript.logic_compare=function(a){var b={EQ:"==",NEQ:"!=",LT:"<",LT $.Blockly.JavaScript.logic_operation=function(a){var b="AND"===a.getFieldValue("OP")?"&&":"||",c="&&"===b?$.Blockly.JavaScript.ORDER_LOGICAL_AND:$.Blockly.JavaScript.ORDER_LOGICAL_OR,d=$.Blockly.JavaScript.valueToCode(a,"A",c);a=$.Blockly.JavaScript.valueToCode(a,"B",c);if(d||a){var e="&&"===b?"true":"false";d||(d=e);a||(a=e)}else a=d="false";return[d+" "+b+" "+a,c]}; $.Blockly.JavaScript.logic_negate=function(a){var b=$.Blockly.JavaScript.ORDER_LOGICAL_NOT;return["!"+($.Blockly.JavaScript.valueToCode(a,"BOOL",b)||"true"),b]};$.Blockly.JavaScript.logic_boolean=function(a){return["TRUE"===a.getFieldValue("BOOL")?"true":"false",$.Blockly.JavaScript.ORDER_ATOMIC]};$.Blockly.JavaScript.logic_null=function(a){return["null",$.Blockly.JavaScript.ORDER_ATOMIC]}; $.Blockly.JavaScript.logic_ternary=function(a){var b=$.Blockly.JavaScript.valueToCode(a,"IF",$.Blockly.JavaScript.ORDER_CONDITIONAL)||"false",c=$.Blockly.JavaScript.valueToCode(a,"THEN",$.Blockly.JavaScript.ORDER_CONDITIONAL)||"null";a=$.Blockly.JavaScript.valueToCode(a,"ELSE",$.Blockly.JavaScript.ORDER_CONDITIONAL)||"null";return[b+" ? "+c+" : "+a,$.Blockly.JavaScript.ORDER_CONDITIONAL]};var module$exports$Blockly$JavaScript$lists={};$.Blockly.JavaScript.lists_create_empty=function(a){return["[]",$.Blockly.JavaScript.ORDER_ATOMIC]};$.Blockly.JavaScript.lists_create_with=function(a){for(var b=Array(a.itemCount_),c=0;c b.toString() ? 1 : -1; },", -' "IGNORE_CASE": function(a, b) {'," return a.toString().toLowerCase() > b.toString().toLowerCase() ? 1 : -1; },"," };"," var compare = compareFuncs[type];"," return function(a, b) { return compare(a, b) * direction; }","}"]);return[b+".slice().sort("+d+'("'+a+'", '+c+"))",$.Blockly.JavaScript.ORDER_FUNCTION_CALL]}; -$.Blockly.JavaScript.lists_split=function(a){var b=$.Blockly.JavaScript.valueToCode(a,"INPUT",$.Blockly.JavaScript.ORDER_MEMBER),c=$.Blockly.JavaScript.valueToCode(a,"DELIM",$.Blockly.JavaScript.ORDER_NONE)||"''";a=a.getFieldValue("MODE");if("SPLIT"===a)b||(b="''"),a="split";else if("JOIN"===a)b||(b="[]"),a="join";else throw Error("Unknown mode: "+a);return[b+"."+a+"("+c+")",$.Blockly.JavaScript.ORDER_FUNCTION_CALL]}; -$.Blockly.JavaScript.lists_reverse=function(a){return[($.Blockly.JavaScript.valueToCode(a,"LIST",$.Blockly.JavaScript.ORDER_FUNCTION_CALL)||"[]")+".slice().reverse()",$.Blockly.JavaScript.ORDER_FUNCTION_CALL]};var module$exports$Blockly$JavaScript$colour={};$.Blockly.JavaScript.colour_picker=function(a){return[$.Blockly.JavaScript.quote_(a.getFieldValue("COLOUR")),$.Blockly.JavaScript.ORDER_ATOMIC]};$.Blockly.JavaScript.colour_random=function(a){return[$.Blockly.JavaScript.provideFunction_("colourRandom",["function "+$.Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_+"() {"," var num = Math.floor(Math.random() * Math.pow(2, 24));"," return '#' + ('00000' + num.toString(16)).substr(-6);","}"])+"()",$.Blockly.JavaScript.ORDER_FUNCTION_CALL]}; -$.Blockly.JavaScript.colour_rgb=function(a){var b=$.Blockly.JavaScript.valueToCode(a,"RED",$.Blockly.JavaScript.ORDER_NONE)||0,c=$.Blockly.JavaScript.valueToCode(a,"GREEN",$.Blockly.JavaScript.ORDER_NONE)||0;a=$.Blockly.JavaScript.valueToCode(a,"BLUE",$.Blockly.JavaScript.ORDER_NONE)||0;return[$.Blockly.JavaScript.provideFunction_("colourRgb",["function "+$.Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_+"(r, g, b) {"," r = Math.max(Math.min(Number(r), 100), 0) * 2.55;"," g = Math.max(Math.min(Number(g), 100), 0) * 2.55;", -" b = Math.max(Math.min(Number(b), 100), 0) * 2.55;"," r = ('0' + (Math.round(r) || 0).toString(16)).slice(-2);"," g = ('0' + (Math.round(g) || 0).toString(16)).slice(-2);"," b = ('0' + (Math.round(b) || 0).toString(16)).slice(-2);"," return '#' + r + g + b;","}"])+"("+b+", "+c+", "+a+")",$.Blockly.JavaScript.ORDER_FUNCTION_CALL]}; -$.Blockly.JavaScript.colour_blend=function(a){var b=$.Blockly.JavaScript.valueToCode(a,"COLOUR1",$.Blockly.JavaScript.ORDER_NONE)||"'#000000'",c=$.Blockly.JavaScript.valueToCode(a,"COLOUR2",$.Blockly.JavaScript.ORDER_NONE)||"'#000000'";a=$.Blockly.JavaScript.valueToCode(a,"RATIO",$.Blockly.JavaScript.ORDER_NONE)||.5;return[$.Blockly.JavaScript.provideFunction_("colourBlend",["function "+$.Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_+"(c1, c2, ratio) {"," ratio = Math.max(Math.min(Number(ratio), 1), 0);", -" var r1 = parseInt(c1.substring(1, 3), 16);"," var g1 = parseInt(c1.substring(3, 5), 16);"," var b1 = parseInt(c1.substring(5, 7), 16);"," var r2 = parseInt(c2.substring(1, 3), 16);"," var g2 = parseInt(c2.substring(3, 5), 16);"," var b2 = parseInt(c2.substring(5, 7), 16);"," var r = Math.round(r1 * (1 - ratio) + r2 * ratio);"," var g = Math.round(g1 * (1 - ratio) + g2 * ratio);"," var b = Math.round(b1 * (1 - ratio) + b2 * ratio);"," r = ('0' + (r || 0).toString(16)).slice(-2);"," g = ('0' + (g || 0).toString(16)).slice(-2);", -" b = ('0' + (b || 0).toString(16)).slice(-2);"," return '#' + r + g + b;","}"])+"("+b+", "+c+", "+a+")",$.Blockly.JavaScript.ORDER_FUNCTION_CALL]};var module$exports$Blockly$JavaScript$all={}; - +"AT2");var f={FIRST:"First",LAST:"Last",FROM_START:"FromStart",FROM_END:"FromEnd"};b=$.Blockly.JavaScript.provideFunction_("subsequence"+f[c]+f[d],"\nfunction "+$.Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_+"(sequence"+("FROM_END"===c||"FROM_START"===c?", at1":"")+("FROM_END"===d||"FROM_START"===d?", at2":"")+") {\n var start = "+module$contents$Blockly$JavaScript$lists_getSubstringIndex("sequence",c,"at1")+";\n var end = "+module$contents$Blockly$JavaScript$lists_getSubstringIndex("sequence", +d,"at2")+" + 1;\n return sequence.slice(start, end);\n}\n")+"("+b+("FROM_END"===c||"FROM_START"===c?", "+e:"")+("FROM_END"===d||"FROM_START"===d?", "+a:"")+")"}return[b,$.Blockly.JavaScript.ORDER_FUNCTION_CALL]}; +$.Blockly.JavaScript.lists_sort=function(a){var b=$.Blockly.JavaScript.valueToCode(a,"LIST",$.Blockly.JavaScript.ORDER_FUNCTION_CALL)||"[]",c="1"===a.getFieldValue("DIRECTION")?1:-1;a=a.getFieldValue("TYPE");var d=$.Blockly.JavaScript.provideFunction_("listsGetSortCompare","\nfunction "+$.Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_+"(type, direction) {\n var compareFuncs = {\n 'NUMERIC': function(a, b) {\n return Number(a) - Number(b); },\n 'TEXT': function(a, b) {\n return a.toString() > b.toString() ? 1 : -1; },\n 'IGNORE_CASE': function(a, b) {\n return a.toString().toLowerCase() > b.toString().toLowerCase() ? 1 : -1; },\n };\n var compare = compareFuncs[type];\n return function(a, b) { return compare(a, b) * direction; };\n}\n "); +return[b+".slice().sort("+d+'("'+a+'", '+c+"))",$.Blockly.JavaScript.ORDER_FUNCTION_CALL]};$.Blockly.JavaScript.lists_split=function(a){var b=$.Blockly.JavaScript.valueToCode(a,"INPUT",$.Blockly.JavaScript.ORDER_MEMBER),c=$.Blockly.JavaScript.valueToCode(a,"DELIM",$.Blockly.JavaScript.ORDER_NONE)||"''";a=a.getFieldValue("MODE");if("SPLIT"===a)b||(b="''"),a="split";else if("JOIN"===a)b||(b="[]"),a="join";else throw Error("Unknown mode: "+a);return[b+"."+a+"("+c+")",$.Blockly.JavaScript.ORDER_FUNCTION_CALL]}; +$.Blockly.JavaScript.lists_reverse=function(a){return[($.Blockly.JavaScript.valueToCode(a,"LIST",$.Blockly.JavaScript.ORDER_FUNCTION_CALL)||"[]")+".slice().reverse()",$.Blockly.JavaScript.ORDER_FUNCTION_CALL]};var module$exports$Blockly$JavaScript$colour={};$.Blockly.JavaScript.colour_picker=function(a){return[$.Blockly.JavaScript.quote_(a.getFieldValue("COLOUR")),$.Blockly.JavaScript.ORDER_ATOMIC]};$.Blockly.JavaScript.colour_random=function(a){return[$.Blockly.JavaScript.provideFunction_("colourRandom","\nfunction "+$.Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_+"() {\n var num = Math.floor(Math.random() * Math.pow(2, 24));\n return '#' + ('00000' + num.toString(16)).substr(-6);\n}\n")+"()",$.Blockly.JavaScript.ORDER_FUNCTION_CALL]}; +$.Blockly.JavaScript.colour_rgb=function(a){var b=$.Blockly.JavaScript.valueToCode(a,"RED",$.Blockly.JavaScript.ORDER_NONE)||0,c=$.Blockly.JavaScript.valueToCode(a,"GREEN",$.Blockly.JavaScript.ORDER_NONE)||0;a=$.Blockly.JavaScript.valueToCode(a,"BLUE",$.Blockly.JavaScript.ORDER_NONE)||0;return[$.Blockly.JavaScript.provideFunction_("colourRgb","\nfunction "+$.Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_+"(r, g, b) {\n r = Math.max(Math.min(Number(r), 100), 0) * 2.55;\n g = Math.max(Math.min(Number(g), 100), 0) * 2.55;\n b = Math.max(Math.min(Number(b), 100), 0) * 2.55;\n r = ('0' + (Math.round(r) || 0).toString(16)).slice(-2);\n g = ('0' + (Math.round(g) || 0).toString(16)).slice(-2);\n b = ('0' + (Math.round(b) || 0).toString(16)).slice(-2);\n return '#' + r + g + b;\n}\n")+ +"("+b+", "+c+", "+a+")",$.Blockly.JavaScript.ORDER_FUNCTION_CALL]}; +$.Blockly.JavaScript.colour_blend=function(a){var b=$.Blockly.JavaScript.valueToCode(a,"COLOUR1",$.Blockly.JavaScript.ORDER_NONE)||"'#000000'",c=$.Blockly.JavaScript.valueToCode(a,"COLOUR2",$.Blockly.JavaScript.ORDER_NONE)||"'#000000'";a=$.Blockly.JavaScript.valueToCode(a,"RATIO",$.Blockly.JavaScript.ORDER_NONE)||.5;return[$.Blockly.JavaScript.provideFunction_("colourBlend","\nfunction "+$.Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_+"(c1, c2, ratio) {\n ratio = Math.max(Math.min(Number(ratio), 1), 0);\n var r1 = parseInt(c1.substring(1, 3), 16);\n var g1 = parseInt(c1.substring(3, 5), 16);\n var b1 = parseInt(c1.substring(5, 7), 16);\n var r2 = parseInt(c2.substring(1, 3), 16);\n var g2 = parseInt(c2.substring(3, 5), 16);\n var b2 = parseInt(c2.substring(5, 7), 16);\n var r = Math.round(r1 * (1 - ratio) + r2 * ratio);\n var g = Math.round(g1 * (1 - ratio) + g2 * ratio);\n var b = Math.round(b1 * (1 - ratio) + b2 * ratio);\n r = ('0' + (r || 0).toString(16)).slice(-2);\n g = ('0' + (g || 0).toString(16)).slice(-2);\n b = ('0' + (b || 0).toString(16)).slice(-2);\n return '#' + r + g + b;\n}\n")+ +"("+b+", "+c+", "+a+")",$.Blockly.JavaScript.ORDER_FUNCTION_CALL]};var module$exports$Blockly$JavaScript$all={}; +$.Blockly.JavaScript.__namespace__=$; return $.Blockly.JavaScript; })); diff --git a/javascript_compressed.js.map b/javascript_compressed.js.map index 5a6e9d817..6a5f7bf0a 100644 --- a/javascript_compressed.js.map +++ b/javascript_compressed.js.map @@ -1 +1 @@ -{"version":3,"sources":["generators/javascript.js","generators/javascript/variables.js","generators/javascript/variables_dynamic.js","generators/javascript/text.js","generators/javascript/procedures.js","generators/javascript/math.js","generators/javascript/loops.js","generators/javascript/logic.js","generators/javascript/lists.js","generators/javascript/colour.js","generators/javascript/all.js"],"names":["JavaScript","Generator","addReservedWords","Object","getOwnPropertyNames","globalThis","join","ORDER_ATOMIC","ORDER_NEW","ORDER_MEMBER","ORDER_FUNCTION_CALL","ORDER_INCREMENT","ORDER_DECREMENT","ORDER_BITWISE_NOT","ORDER_UNARY_PLUS","ORDER_UNARY_NEGATION","ORDER_LOGICAL_NOT","ORDER_TYPEOF","ORDER_VOID","ORDER_DELETE","ORDER_AWAIT","ORDER_EXPONENTIATION","ORDER_MULTIPLICATION","ORDER_DIVISION","ORDER_MODULUS","ORDER_SUBTRACTION","ORDER_ADDITION","ORDER_BITWISE_SHIFT","ORDER_RELATIONAL","ORDER_IN","ORDER_INSTANCEOF","ORDER_EQUALITY","ORDER_BITWISE_AND","ORDER_BITWISE_XOR","ORDER_BITWISE_OR","ORDER_LOGICAL_AND","ORDER_LOGICAL_OR","ORDER_CONDITIONAL","ORDER_ASSIGNMENT","ORDER_YIELD","ORDER_COMMA","ORDER_NONE","ORDER_OVERRIDES","isInitialized","init","JavaScript.init","workspace","getPrototypeOf","call","nameDB_","reset","Names","RESERVED_WORDS_","setVariableMap","getVariableMap","populateVariables","populateProcedures","defvars","devVarList","Variables","allDeveloperVariables","i","length","push","getName","NameType","DEVELOPER_VARIABLE","variables","allUsedVarModels","getId","VARIABLE","definitions_","finish","JavaScript.finish","code","definitions","objectUtils","values","scrubNakedValue","JavaScript.scrubNakedValue","line","quote_","JavaScript.quote_","string","replace","multiline_quote_","JavaScript.multiline_quote_","split","map","lines","scrub_","JavaScript.scrub_","block","opt_thisOnly","commentCode","outputConnection","targetConnection","comment","getCommentText","stringUtils","wrap","COMMENT_WRAP","prefixLines","inputList","type","inputTypes","VALUE","childBlock","connection","targetBlock","allNestedComments","nextBlock","nextConnection","nextCode","blockToCode","getAdjusted","JavaScript.getAdjusted","atId","opt_delta","opt_negate","opt_order","delta","order","options","oneBasedIndex","defaultAtIndex","outerOrder","innerOrder","at","valueToCode","isNumber","Number","Math","floor","exports","getFieldValue","argument0","varName","strRegExp","forceString","value","test","getSubstringIndex","stringName","where","opt_at","indexOf","itemCount_","element","codeAndOrder","element0","element1","elements","Array","operator","substring","text","textOrder","provideFunction_","functionName","FUNCTION_NAME_PLACEHOLDER_","Error","where1","where2","requiresLengthCall","match","at1","at2","wherePascalCase","OPERATORS","getField","msg","sub","from","to","funcName","PROCEDURE","xfix1","STATEMENT_PREFIX","injectId","STATEMENT_SUFFIX","INDENT","loopTrap","INFINITE_LOOP_TRAP","branch","statementToCode","returnValue","xfix2","args","getVars","tuple","hasReturnValue_","argument1","arg","CONSTANTS","number_to_check","dropdown_property","divisor","func","list","argument2","repeats","String","addLoopTrap","loopVar","getDistinctName","endVar","until","variable0","increment","up","step","abs","startVar","incVar","listVar","indexVar","xfix","loop","getSurroundLoop","suppressPrefixSuffix","n","conditionCode","branchCode","getInput","defaultArgument","value_if","value_then","value_else","repeatCount","item","mode","listOrder","cacheList","xVar","listName","direction","getCompareFunctionName","input","delimiter","red","green","blue","c1","c2","ratio"],"mappings":"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","file":"javascript_compressed.js","sourceRoot":"./"} \ No newline at end of file +{"version":3,"sources":["generators/javascript.js","generators/javascript/variables.js","generators/javascript/variables_dynamic.js","generators/javascript/text.js","generators/javascript/procedures.js","generators/javascript/math.js","generators/javascript/loops.js","generators/javascript/logic.js","generators/javascript/lists.js","generators/javascript/colour.js","generators/javascript/all.js"],"names":["JavaScript","Generator","addReservedWords","Object","getOwnPropertyNames","globalThis","join","ORDER_ATOMIC","ORDER_NEW","ORDER_MEMBER","ORDER_FUNCTION_CALL","ORDER_INCREMENT","ORDER_DECREMENT","ORDER_BITWISE_NOT","ORDER_UNARY_PLUS","ORDER_UNARY_NEGATION","ORDER_LOGICAL_NOT","ORDER_TYPEOF","ORDER_VOID","ORDER_DELETE","ORDER_AWAIT","ORDER_EXPONENTIATION","ORDER_MULTIPLICATION","ORDER_DIVISION","ORDER_MODULUS","ORDER_SUBTRACTION","ORDER_ADDITION","ORDER_BITWISE_SHIFT","ORDER_RELATIONAL","ORDER_IN","ORDER_INSTANCEOF","ORDER_EQUALITY","ORDER_BITWISE_AND","ORDER_BITWISE_XOR","ORDER_BITWISE_OR","ORDER_LOGICAL_AND","ORDER_LOGICAL_OR","ORDER_CONDITIONAL","ORDER_ASSIGNMENT","ORDER_YIELD","ORDER_COMMA","ORDER_NONE","ORDER_OVERRIDES","isInitialized","init","JavaScript.init","workspace","getPrototypeOf","call","nameDB_","reset","Names","RESERVED_WORDS_","setVariableMap","getVariableMap","populateVariables","populateProcedures","defvars","devVarList","Variables","allDeveloperVariables","i","length","push","getName","NameType","DEVELOPER_VARIABLE","variables","allUsedVarModels","getId","VARIABLE","definitions_","finish","JavaScript.finish","code","definitions","objectUtils","values","scrubNakedValue","JavaScript.scrubNakedValue","line","quote_","JavaScript.quote_","string","replace","multiline_quote_","JavaScript.multiline_quote_","split","map","lines","scrub_","JavaScript.scrub_","block","opt_thisOnly","commentCode","outputConnection","targetConnection","comment","getCommentText","stringUtils","wrap","COMMENT_WRAP","prefixLines","inputList","type","inputTypes","VALUE","childBlock","connection","targetBlock","allNestedComments","nextBlock","nextConnection","nextCode","blockToCode","getAdjusted","JavaScript.getAdjusted","atId","opt_delta","opt_negate","opt_order","delta","order","options","oneBasedIndex","defaultAtIndex","outerOrder","innerOrder","at","valueToCode","isNumber","Number","Math","floor","exports","getFieldValue","argument0","varName","strRegExp","forceString","value","test","getSubstringIndex","stringName","where","opt_at","indexOf","itemCount_","element","codeAndOrder","element0","element1","elements","Array","operator","substring","text","textOrder","provideFunction_","functionName","FUNCTION_NAME_PLACEHOLDER_","Error","where1","where2","requiresLengthCall","match","at1","at2","wherePascalCase","at1Param","at2Param","OPERATORS","getField","msg","sub","from","to","funcName","PROCEDURE","xfix1","STATEMENT_PREFIX","injectId","STATEMENT_SUFFIX","INDENT","loopTrap","INFINITE_LOOP_TRAP","branch","statementToCode","returnValue","xfix2","args","getVars","tuple","hasReturnValue_","argument1","arg","CONSTANTS","PROPERTIES","dropdownProperty","suffix","inputOrder","outputOrder","numberToCheck","divisor","func","list","argument2","repeats","String","addLoopTrap","loopVar","getDistinctName","endVar","until","variable0","increment","up","step","abs","startVar","incVar","listVar","indexVar","xfix","loop","getSurroundLoop","suppressPrefixSuffix","n","conditionCode","branchCode","getInput","defaultArgument","value_if","value_then","value_else","repeatCount","item","mode","listOrder","cacheList","xVar","listName","direction","getCompareFunctionName","input","delimiter","red","green","blue","c1","c2","ratio"],"mappings":"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","file":"javascript_compressed.js","sourceRoot":"./"} \ No newline at end of file diff --git a/lua_compressed.js b/lua_compressed.js index e0b45413c..8295384e4 100644 --- a/lua_compressed.js +++ b/lua_compressed.js @@ -7,10 +7,11 @@ } else if (typeof exports === 'object') { // Node.js module.exports = factory(require("./blockly_compressed.js")); } else { // Browser - root.Blockly.Lua = factory(root.Blockly); + var factoryExports = factory(root.Blockly); + root.Blockly.Lua = factoryExports; } -}(this, function(Blockly) { -const $=Blockly.internal_; +}(this, function(__parent__) { +var $=__parent__.__namespace__; var module$contents$Blockly$Lua_Lua=new $.module$exports$Blockly$Generator.Generator("Lua");module$contents$Blockly$Lua_Lua.addReservedWords("_,__inext,assert,bit,colors,colours,coroutine,disk,dofile,error,fs,fetfenv,getmetatable,gps,help,io,ipairs,keys,loadfile,loadstring,math,native,next,os,paintutils,pairs,parallel,pcall,peripheral,print,printError,rawequal,rawget,rawset,read,rednet,redstone,rs,select,setfenv,setmetatable,sleep,string,table,term,textutils,tonumber,tostring,turtle,type,unpack,vector,write,xpcall,_VERSION,__indext,HTTP,and,break,do,else,elseif,end,false,for,function,if,in,local,nil,not,or,repeat,return,then,true,until,while,add,sub,mul,div,mod,pow,unm,concat,len,eq,lt,le,index,newindex,call,assert,collectgarbage,dofile,error,_G,getmetatable,inpairs,load,loadfile,next,pairs,pcall,print,rawequal,rawget,rawlen,rawset,select,setmetatable,tonumber,tostring,type,_VERSION,xpcall,require,package,string,table,math,bit32,io,file,os,debug"); module$contents$Blockly$Lua_Lua.ORDER_ATOMIC=0;module$contents$Blockly$Lua_Lua.ORDER_HIGH=1;module$contents$Blockly$Lua_Lua.ORDER_EXPONENTIATION=2;module$contents$Blockly$Lua_Lua.ORDER_UNARY=3;module$contents$Blockly$Lua_Lua.ORDER_MULTIPLICATIVE=4;module$contents$Blockly$Lua_Lua.ORDER_ADDITIVE=5;module$contents$Blockly$Lua_Lua.ORDER_CONCATENATION=6;module$contents$Blockly$Lua_Lua.ORDER_RELATIONAL=7;module$contents$Blockly$Lua_Lua.ORDER_AND=8;module$contents$Blockly$Lua_Lua.ORDER_OR=9; module$contents$Blockly$Lua_Lua.ORDER_NONE=99;module$contents$Blockly$Lua_Lua.isInitialized=!1;module$contents$Blockly$Lua_Lua.init=function(a){Object.getPrototypeOf(this).init.call(this);this.nameDB_?this.nameDB_.reset():this.nameDB_=new $.module$exports$Blockly$Names.Names(this.RESERVED_WORDS_);this.nameDB_.setVariableMap(a.getVariableMap());this.nameDB_.populateVariables(a);this.nameDB_.populateProcedures(a);this.isInitialized=!0}; @@ -21,20 +22,19 @@ a.nextConnection.targetBlock();c=c?"":this.blockToCode(a);return d+b+c};$.Blockl $.Blockly.Lua.text_join=function(a){if(0===a.itemCount_)return["''",$.Blockly.Lua.ORDER_ATOMIC];if(1===a.itemCount_)return["tostring("+($.Blockly.Lua.valueToCode(a,"ADD0",$.Blockly.Lua.ORDER_NONE)||"''")+")",$.Blockly.Lua.ORDER_HIGH];if(2===a.itemCount_){var b=$.Blockly.Lua.valueToCode(a,"ADD0",$.Blockly.Lua.ORDER_CONCATENATION)||"''";a=$.Blockly.Lua.valueToCode(a,"ADD1",$.Blockly.Lua.ORDER_CONCATENATION)||"''";return[b+" .. "+a,$.Blockly.Lua.ORDER_CONCATENATION]}b=[];for(var c=0;c 1) or n % 1 ~= 0 or n % 2 == 0 or n % 3 == 0 then"," return false"," end"," -- Check all the numbers of form 6k +/- 1, up to sqrt(n)."," for x = 6, math.sqrt(n) + 1.5, 6 do"," if n % (x - 1) == 0 or n % (x + 1) == 0 then"," return false"," end"," end"," return true","end"])+"("+b+")",$.Blockly.Lua.ORDER_HIGH];switch(c){case "EVEN":var d=b+" % 2 == 0";break;case "ODD":d=b+" % 2 == 1";break;case "WHOLE":d=b+" % 1 == 0";break;case "POSITIVE":d=b+" > 0";break;case "NEGATIVE":d= -b+" < 0";break;case "DIVISIBLE_BY":a=$.Blockly.Lua.valueToCode(a,"DIVISOR",$.Blockly.Lua.ORDER_MULTIPLICATIVE);if(!a||"0"===a)return["nil",$.Blockly.Lua.ORDER_ATOMIC];d=b+" % "+a+" == 0"}return[d,$.Blockly.Lua.ORDER_RELATIONAL]};$.Blockly.Lua.math_change=function(a){var b=$.Blockly.Lua.valueToCode(a,"DELTA",$.Blockly.Lua.ORDER_ADDITIVE)||"0";a=$.Blockly.Lua.nameDB_.getName(a.getFieldValue("VAR"),$.module$exports$Blockly$Names.NameType.VARIABLE);return a+" = "+a+" + "+b+"\n"}; -$.Blockly.Lua.math_round=$.Blockly.Lua.math_single;$.Blockly.Lua.math_trig=$.Blockly.Lua.math_single; -$.Blockly.Lua.math_on_list=function(a){function b(){return $.Blockly.Lua.provideFunction_("math_sum",["function "+$.Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_+"(t)"," local result = 0"," for _, v in ipairs(t) do"," result = result + v"," end"," return result","end"])}var c=a.getFieldValue("OP");a=$.Blockly.Lua.valueToCode(a,"LIST",$.Blockly.Lua.ORDER_NONE)||"{}";switch(c){case "SUM":c=b();break;case "MIN":c=$.Blockly.Lua.provideFunction_("math_min",["function "+$.Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_+ -"(t)"," if #t == 0 then"," return 0"," end"," local result = math.huge"," for _, v in ipairs(t) do"," if v < result then"," result = v"," end"," end"," return result","end"]);break;case "AVERAGE":c=$.Blockly.Lua.provideFunction_("math_average",["function "+$.Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_+"(t)"," if #t == 0 then"," return 0"," end"," return "+b()+"(t) / #t","end"]);break;case "MAX":c=$.Blockly.Lua.provideFunction_("math_max",["function "+$.Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_+ -"(t)"," if #t == 0 then"," return 0"," end"," local result = -math.huge"," for _, v in ipairs(t) do"," if v > result then"," result = v"," end"," end"," return result","end"]);break;case "MEDIAN":c=$.Blockly.Lua.provideFunction_("math_median",["function "+$.Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_+"(t)"," -- Source: http://lua-users.org/wiki/SimpleStats"," if #t == 0 then"," return 0"," end"," local temp={}"," for _, v in ipairs(t) do",' if type(v) == "number" then'," table.insert(temp, v)", -" end"," end"," table.sort(temp)"," if #temp % 2 == 0 then"," return (temp[#temp/2] + temp[(#temp/2)+1]) / 2"," else"," return temp[math.ceil(#temp/2)]"," end","end"]);break;case "MODE":c=$.Blockly.Lua.provideFunction_("math_modes",["function "+$.Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_+"(t)"," -- Source: http://lua-users.org/wiki/SimpleStats"," local counts={}"," for _, v in ipairs(t) do"," if counts[v] == nil then"," counts[v] = 1"," else"," counts[v] = counts[v] + 1", -" end"," end"," local biggestCount = 0"," for _, v in pairs(counts) do"," if v > biggestCount then"," biggestCount = v"," end"," end"," local temp={}"," for k, v in pairs(counts) do"," if v == biggestCount then"," table.insert(temp, k)"," end"," end"," return temp","end"]);break;case "STD_DEV":c=$.Blockly.Lua.provideFunction_("math_standard_deviation",["function "+$.Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_+"(t)"," local m"," local vm"," local total = 0"," local count = 0", -" local result"," m = #t == 0 and 0 or "+b()+"(t) / #t"," for _, v in ipairs(t) do"," if type(v) == 'number' then"," vm = v - m"," total = total + (vm * vm)"," count = count + 1"," end"," end"," result = math.sqrt(total / (count-1))"," return result","end"]);break;case "RANDOM":c=$.Blockly.Lua.provideFunction_("math_random_list",["function "+$.Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_+"(t)"," if #t == 0 then"," return nil"," end"," return t[math.random(#t)]","end"]); -break;default:throw Error("Unknown operator: "+c);}return[c+"("+a+")",$.Blockly.Lua.ORDER_HIGH]};$.Blockly.Lua.math_modulo=function(a){var b=$.Blockly.Lua.valueToCode(a,"DIVIDEND",$.Blockly.Lua.ORDER_MULTIPLICATIVE)||"0";a=$.Blockly.Lua.valueToCode(a,"DIVISOR",$.Blockly.Lua.ORDER_MULTIPLICATIVE)||"0";return[b+" % "+a,$.Blockly.Lua.ORDER_MULTIPLICATIVE]}; +$.Blockly.Lua.math_number_property=function(a){var b={EVEN:[" % 2 == 0",$.Blockly.Lua.ORDER_MULTIPLICATIVE,$.Blockly.Lua.ORDER_RELATIONAL],ODD:[" % 2 == 1",$.Blockly.Lua.ORDER_MULTIPLICATIVE,$.Blockly.Lua.ORDER_RELATIONAL],WHOLE:[" % 1 == 0",$.Blockly.Lua.ORDER_MULTIPLICATIVE,$.Blockly.Lua.ORDER_RELATIONAL],POSITIVE:[" > 0",$.Blockly.Lua.ORDER_RELATIONAL,$.Blockly.Lua.ORDER_RELATIONAL],NEGATIVE:[" < 0",$.Blockly.Lua.ORDER_RELATIONAL,$.Blockly.Lua.ORDER_RELATIONAL],DIVISIBLE_BY:[null,$.Blockly.Lua.ORDER_MULTIPLICATIVE, +$.Blockly.Lua.ORDER_RELATIONAL],PRIME:[null,$.Blockly.Lua.ORDER_NONE,$.Blockly.Lua.ORDER_HIGH]},c=a.getFieldValue("PROPERTY");b=$.$jscomp.makeIterator(b[c]);var d=b.next().value,e=b.next().value;b=b.next().value;e=$.Blockly.Lua.valueToCode(a,"NUMBER_TO_CHECK",e)||"0";if("PRIME"===c)a=$.Blockly.Lua.provideFunction_("math_isPrime","\nfunction "+$.Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_+"(n)\n -- https://en.wikipedia.org/wiki/Primality_test#Naive_methods\n if n == 2 or n == 3 then\n return true\n end\n -- False if n is NaN, negative, is 1, or not whole.\n -- And false if n is divisible by 2 or 3.\n if not(n > 1) or n % 1 ~= 0 or n % 2 == 0 or n % 3 == 0 then\n return false\n end\n -- Check all the numbers of form 6k +/- 1, up to sqrt(n).\n for x = 6, math.sqrt(n) + 1.5, 6 do\n if n % (x - 1) == 0 or n % (x + 1) == 0 then\n return false\n end\n end\n return true\nend\n")+ +"("+e+")";else if("DIVISIBLE_BY"===c){a=$.Blockly.Lua.valueToCode(a,"DIVISOR",$.Blockly.Lua.ORDER_MULTIPLICATIVE)||"0";if("0"===a)return["nil",$.Blockly.Lua.ORDER_ATOMIC];a=e+" % "+a+" == 0"}else a=e+d;return[a,b]};$.Blockly.Lua.math_change=function(a){var b=$.Blockly.Lua.valueToCode(a,"DELTA",$.Blockly.Lua.ORDER_ADDITIVE)||"0";a=$.Blockly.Lua.nameDB_.getName(a.getFieldValue("VAR"),$.module$exports$Blockly$Names.NameType.VARIABLE);return a+" = "+a+" + "+b+"\n"};$.Blockly.Lua.math_round=$.Blockly.Lua.math_single; +$.Blockly.Lua.math_trig=$.Blockly.Lua.math_single; +$.Blockly.Lua.math_on_list=function(a){function b(){return $.Blockly.Lua.provideFunction_("math_sum","\nfunction "+$.Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_+"(t)\n local result = 0\n for _, v in ipairs(t) do\n result = result + v\n end\n return result\nend\n")}var c=a.getFieldValue("OP");a=$.Blockly.Lua.valueToCode(a,"LIST",$.Blockly.Lua.ORDER_NONE)||"{}";switch(c){case "SUM":c=b();break;case "MIN":c=$.Blockly.Lua.provideFunction_("math_min","\nfunction "+$.Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_+ +"(t)\n if #t == 0 then\n return 0\n end\n local result = math.huge\n for _, v in ipairs(t) do\n if v < result then\n result = v\n end\n end\n return result\nend\n");break;case "AVERAGE":c=$.Blockly.Lua.provideFunction_("math_average","\nfunction "+$.Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_+"(t)\n if #t == 0 then\n return 0\n end\n return "+b()+"(t) / #t\nend\n");break;case "MAX":c=$.Blockly.Lua.provideFunction_("math_max","\nfunction "+$.Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_+ +"(t)\n if #t == 0 then\n return 0\n end\n local result = -math.huge\n for _, v in ipairs(t) do\n if v > result then\n result = v\n end\n end\n return result\nend\n");break;case "MEDIAN":c=$.Blockly.Lua.provideFunction_("math_median","\nfunction "+$.Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_+"(t)\n -- Source: http://lua-users.org/wiki/SimpleStats\n if #t == 0 then\n return 0\n end\n local temp = {}\n for _, v in ipairs(t) do\n if type(v) == 'number' then\n table.insert(temp, v)\n end\n end\n table.sort(temp)\n if #temp % 2 == 0 then\n return (temp[#temp / 2] + temp[(#temp / 2) + 1]) / 2\n else\n return temp[math.ceil(#temp / 2)]\n end\nend\n"); +break;case "MODE":c=$.Blockly.Lua.provideFunction_("math_modes","\nfunction "+$.Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_+"(t)\n -- Source: http://lua-users.org/wiki/SimpleStats\n local counts = {}\n for _, v in ipairs(t) do\n if counts[v] == nil then\n counts[v] = 1\n else\n counts[v] = counts[v] + 1\n end\n end\n local biggestCount = 0\n for _, v in pairs(counts) do\n if v > biggestCount then\n biggestCount = v\n end\n end\n local temp = {}\n for k, v in pairs(counts) do\n if v == biggestCount then\n table.insert(temp, k)\n end\n end\n return temp\nend\n"); +break;case "STD_DEV":c=$.Blockly.Lua.provideFunction_("math_standard_deviation","\nfunction "+$.Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_+"(t)\n local m\n local vm\n local total = 0\n local count = 0\n local result\n m = #t == 0 and 0 or "+b()+"(t) / #t\n for _, v in ipairs(t) do\n if type(v) == 'number' then\n vm = v - m\n total = total + (vm * vm)\n count = count + 1\n end\n end\n result = math.sqrt(total / (count-1))\n return result\nend\n");break;case "RANDOM":c=$.Blockly.Lua.provideFunction_("math_random_list", +"\nfunction "+$.Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_+"(t)\n if #t == 0 then\n return nil\n end\n return t[math.random(#t)]\nend\n");break;default:throw Error("Unknown operator: "+c);}return[c+"("+a+")",$.Blockly.Lua.ORDER_HIGH]};$.Blockly.Lua.math_modulo=function(a){var b=$.Blockly.Lua.valueToCode(a,"DIVIDEND",$.Blockly.Lua.ORDER_MULTIPLICATIVE)||"0";a=$.Blockly.Lua.valueToCode(a,"DIVISOR",$.Blockly.Lua.ORDER_MULTIPLICATIVE)||"0";return[b+" % "+a,$.Blockly.Lua.ORDER_MULTIPLICATIVE]}; $.Blockly.Lua.math_constrain=function(a){var b=$.Blockly.Lua.valueToCode(a,"VALUE",$.Blockly.Lua.ORDER_NONE)||"0",c=$.Blockly.Lua.valueToCode(a,"LOW",$.Blockly.Lua.ORDER_NONE)||"-math.huge";a=$.Blockly.Lua.valueToCode(a,"HIGH",$.Blockly.Lua.ORDER_NONE)||"math.huge";return["math.min(math.max("+b+", "+c+"), "+a+")",$.Blockly.Lua.ORDER_HIGH]}; $.Blockly.Lua.math_random_int=function(a){var b=$.Blockly.Lua.valueToCode(a,"FROM",$.Blockly.Lua.ORDER_NONE)||"0";a=$.Blockly.Lua.valueToCode(a,"TO",$.Blockly.Lua.ORDER_NONE)||"0";return["math.random("+b+", "+a+")",$.Blockly.Lua.ORDER_HIGH]};$.Blockly.Lua.math_random_float=function(a){return["math.random()",$.Blockly.Lua.ORDER_HIGH]}; $.Blockly.Lua.math_atan2=function(a){var b=$.Blockly.Lua.valueToCode(a,"X",$.Blockly.Lua.ORDER_NONE)||"0";return["math.deg(math.atan2("+($.Blockly.Lua.valueToCode(a,"Y",$.Blockly.Lua.ORDER_NONE)||"0")+", "+b+"))",$.Blockly.Lua.ORDER_HIGH]};var module$exports$Blockly$Lua$loops={},module$contents$Blockly$Lua$loops_CONTINUE_STATEMENT="goto continue\n",module$contents$Blockly$Lua$loops_addContinueLabel=function(a){return-1!==a.indexOf(module$contents$Blockly$Lua$loops_CONTINUE_STATEMENT)?a+$.Blockly.Lua.INDENT+"::continue::\n":a}; @@ -71,29 +70,27 @@ $.Blockly.Lua.STATEMENT_SUFFIX)b=$.Blockly.Lua.statementToCode(a,"ELSE"),$.Block $.Blockly.Lua.logic_compare=function(a){var b={EQ:"==",NEQ:"~=",LT:"<",LTE:"<=",GT:">",GTE:">="}[a.getFieldValue("OP")],c=$.Blockly.Lua.valueToCode(a,"A",$.Blockly.Lua.ORDER_RELATIONAL)||"0";a=$.Blockly.Lua.valueToCode(a,"B",$.Blockly.Lua.ORDER_RELATIONAL)||"0";return[c+" "+b+" "+a,$.Blockly.Lua.ORDER_RELATIONAL]}; $.Blockly.Lua.logic_operation=function(a){var b="AND"===a.getFieldValue("OP")?"and":"or",c="and"===b?$.Blockly.Lua.ORDER_AND:$.Blockly.Lua.ORDER_OR,d=$.Blockly.Lua.valueToCode(a,"A",c);a=$.Blockly.Lua.valueToCode(a,"B",c);if(d||a){var e="and"===b?"true":"false";d||(d=e);a||(a=e)}else a=d="false";return[d+" "+b+" "+a,c]};$.Blockly.Lua.logic_negate=function(a){return["not "+($.Blockly.Lua.valueToCode(a,"BOOL",$.Blockly.Lua.ORDER_UNARY)||"true"),$.Blockly.Lua.ORDER_UNARY]}; $.Blockly.Lua.logic_boolean=function(a){return["TRUE"===a.getFieldValue("BOOL")?"true":"false",$.Blockly.Lua.ORDER_ATOMIC]};$.Blockly.Lua.logic_null=function(a){return["nil",$.Blockly.Lua.ORDER_ATOMIC]};$.Blockly.Lua.logic_ternary=function(a){var b=$.Blockly.Lua.valueToCode(a,"IF",$.Blockly.Lua.ORDER_AND)||"false",c=$.Blockly.Lua.valueToCode(a,"THEN",$.Blockly.Lua.ORDER_AND)||"nil";a=$.Blockly.Lua.valueToCode(a,"ELSE",$.Blockly.Lua.ORDER_OR)||"nil";return[b+" and "+c+" or "+a,$.Blockly.Lua.ORDER_OR]};var module$exports$Blockly$Lua$lists={};$.Blockly.Lua.lists_create_empty=function(a){return["{}",$.Blockly.Lua.ORDER_HIGH]};$.Blockly.Lua.lists_create_with=function(a){for(var b=Array(a.itemCount_),c=0;c=6.9.0" } }, - "node_modules/@babel/generator": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.0.tgz", - "integrity": "sha512-RR8hUCfRQn9j9RPKEVXo9LiwoxLPYn6hNZlvUOR8tSnaxlD0p0+la00ZP9/SnRt6HchKr+X0fO2r8vrETiJGew==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz", - "integrity": "sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog==", - "dev": true, - "dependencies": { - "@babel/helper-get-function-arity": "^7.16.0", - "@babel/template": "^7.16.0", - "@babel/types": "^7.16.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-get-function-arity": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz", - "integrity": "sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz", - "integrity": "sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz", - "integrity": "sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-validator-identifier": { "version": "7.15.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", @@ -217,69 +154,10 @@ "node": ">=4" } }, - "node_modules/@babel/parser": { - "version": "7.16.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.4.tgz", - "integrity": "sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/template": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.0.tgz", - "integrity": "sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.16.0", - "@babel/parser": "^7.16.0", - "@babel/types": "^7.16.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.16.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.3.tgz", - "integrity": "sha512-eolumr1vVMjqevCpwVO99yN/LoGL0EyHiLO5I043aYQvwOJ9eR5UsZSClHVCzfhBduMAsSzgA/6AyqPjNayJag==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.16.0", - "@babel/generator": "^7.16.0", - "@babel/helper-function-name": "^7.16.0", - "@babel/helper-hoist-variables": "^7.16.0", - "@babel/helper-split-export-declaration": "^7.16.0", - "@babel/parser": "^7.16.3", - "@babel/types": "^7.16.0", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz", - "integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.15.7", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@blockly/block-test": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@blockly/block-test/-/block-test-2.0.1.tgz", - "integrity": "sha512-d8E109UEWTtFv1bfy5paQNk9HYdZDWH5S7qkCrlKTtSatPDt8f3SGCr/lu8JC086/LhjRafgX1RTa0uwJIDDdg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@blockly/block-test/-/block-test-2.0.4.tgz", + "integrity": "sha512-nECM+4kSaZLNVBhbfTnsKl+kfqxcBHbflo6TE2rmaP8GjbndtT9I7XHdmXDtHGomfPTFF1qJ7bl09fmFqcdeQQ==", "dev": true, "engines": { "node": ">=8.17.0" @@ -289,12 +167,12 @@ } }, "node_modules/@blockly/dev-tools": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@blockly/dev-tools/-/dev-tools-3.0.1.tgz", - "integrity": "sha512-afFCokPWHKeFrZRSoHa0oBBUWIwqI02/svYPRTjviEsxGcU89nG/bYBpfG9Fosm8hEKROvZf7/bIMn0x1KlCBA==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@blockly/dev-tools/-/dev-tools-3.0.7.tgz", + "integrity": "sha512-s55teQLYNKOtWZIngce2WFy052CrEGPMfhRzQgFKlrTfVafr+iZc+E4MTIY1J9R2Zw5uKnfxULV8LfNrwcH+fA==", "dev": true, "dependencies": { - "@blockly/block-test": "^2.0.1", + "@blockly/block-test": "^2.0.4", "@blockly/theme-dark": "^2.0.7", "@blockly/theme-deuteranopia": "^1.0.8", "@blockly/theme-highcontrast": "^1.0.8", @@ -350,15 +228,15 @@ } }, "node_modules/@blockly/theme-modern": { - "version": "2.1.24", - "resolved": "https://registry.npmjs.org/@blockly/theme-modern/-/theme-modern-2.1.24.tgz", - "integrity": "sha512-glTi5qhHr+S87S1skWG8Sre+DYeSIBG3j+YBbwwRX9k3zYtGO+W7sOdJ9DsBJj8NVukPpV1RhQhu5rt93Pp2rw==", + "version": "2.1.28", + "resolved": "https://registry.npmjs.org/@blockly/theme-modern/-/theme-modern-2.1.28.tgz", + "integrity": "sha512-SRPrQJOvTU8yC+NFnfFipaciCJEfmIG+imPwNcL1WZ/pPct6ojEDwHKAalWkHGLrTb0rmOaDVeHJJQx+HZmQsQ==", "dev": true, "engines": { "node": ">=8.17.0" }, "peerDependencies": { - "blockly": "3.20200123.0 - 6" + "blockly": "3.20200123.0 - 7" } }, "node_modules/@blockly/theme-tritanopia": { @@ -374,50 +252,23 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.1.0.tgz", + "integrity": "sha512-C1DfL7XX4nPqGd6jcP01W9pVM1HYCuUkFk1432D7F0v3JSlUIeOYn9oCoi3eoLZ+iwBSb29BMFxxny0YrrEZqg==", "dev": true, "dependencies": { "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", + "debug": "^4.3.2", + "espree": "^9.3.1", "globals": "^13.9.0", "ignore": "^4.0.6", "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", + "js-yaml": "^4.1.0", "minimatch": "^3.0.4", "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", - "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@gulp-sourcemaps/identity-map": { @@ -503,12 +354,12 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.2.tgz", + "integrity": "sha512-UXOuFCGcwciWckOpmfKDq/GyhlTf9pN/BzG//x8p8zTOFEcGuA68ANXheFS0AGvy3qgZqLBUkMs7hqzqCKOVwA==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^1.2.0", + "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", "minimatch": "^3.0.4" }, @@ -522,6 +373,68 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "node_modules/@hyperjump/json-pointer": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@hyperjump/json-pointer/-/json-pointer-0.9.2.tgz", + "integrity": "sha512-PGCyTWO+WTkNWhMdlgE7OiQYPVkme9/e6d7K2xiZxH1wMGxGgZEEDNCe8hox7rkuD1equ4eZM+K3eoPCexckmA==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "just-curry-it": "^3.2.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jdesrosiers" + } + }, + "node_modules/@hyperjump/json-schema": { + "version": "0.18.4", + "resolved": "https://registry.npmjs.org/@hyperjump/json-schema/-/json-schema-0.18.4.tgz", + "integrity": "sha512-FVdSlOrOio/sWCbVbAP3yH/gKKddvrIvKzLS/id6/CidWH0r0x5ZTPM1zBS0Su7gU6OOjFRxDYhrIhnNBI5ODg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@hyperjump/json-schema-core": "^0.23.4", + "fastest-stable-stringify": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jdesrosiers" + } + }, + "node_modules/@hyperjump/json-schema-core": { + "version": "0.23.6", + "resolved": "https://registry.npmjs.org/@hyperjump/json-schema-core/-/json-schema-core-0.23.6.tgz", + "integrity": "sha512-X0IzGRi5K4c91awB3xNt5bvbs34UyHwOpRKKFFJ2nWDWW7e22VNGvibqo/S2rdFyta3wqOHTICFNTQjjcVdIZg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@hyperjump/json-pointer": "^0.9.1", + "@hyperjump/pact": "^0.2.0", + "content-type": "^1.0.4", + "node-fetch": "^2.6.5", + "pubsub-js": "^1.9.1", + "url-resolve-browser": "^1.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jdesrosiers" + } + }, + "node_modules/@hyperjump/pact": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@hyperjump/pact/-/pact-0.2.1.tgz", + "integrity": "sha512-imzl9j1UiqM/HC3kgfS0/TdXcEFGFkq5EwjyaztLfdmia8KLBXGy3rC96K+nnyY+2fA69yA9HtnDappub5VSQQ==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "just-curry-it": "^3.1.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jdesrosiers" + } + }, "node_modules/@sindresorhus/is": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.2.0.tgz", @@ -820,11 +733,237 @@ "node": ">=12.0.0" } }, + "node_modules/@wdio/cli/node_modules/@wdio/repl": { + "version": "7.16.3", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-7.16.3.tgz", + "integrity": "sha512-aFpWyAIuPo6VVmkotZDWXMzd4qw3gD+xAhB6blNrMCZKWnz9+HqZnuGGc6pmiyuc5yFzb9wF22tnIxuyTyH7yA==", + "dev": true, + "peer": true, + "dependencies": { + "@wdio/utils": "7.16.3" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@wdio/cli/node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "peer": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@wdio/cli/node_modules/devtools": { + "version": "7.16.10", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-7.16.10.tgz", + "integrity": "sha512-43uB3t6uNjWsqiQKRLY7axFLuMdKqlQxq6N3FWCfBKl9We1oygkGkE7Scnushdbc4lk7QwGXBC1DQ83dCgA5Gw==", + "dev": true, + "peer": true, + "dependencies": { + "@types/node": "^16.11.1", + "@types/ua-parser-js": "^0.7.33", + "@wdio/config": "7.16.3", + "@wdio/logger": "7.16.0", + "@wdio/protocols": "7.16.7", + "@wdio/types": "7.16.3", + "@wdio/utils": "7.16.3", + "chrome-launcher": "^0.15.0", + "edge-paths": "^2.1.0", + "puppeteer-core": "^11.0.0", + "query-selector-shadow-dom": "^1.0.0", + "ua-parser-js": "^1.0.1", + "uuid": "^8.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@wdio/cli/node_modules/devtools-protocol": { + "version": "0.0.944179", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.944179.tgz", + "integrity": "sha512-oqBbLKuCAkEqqsWn0rsfkjy79F0/QTQR/rlijZzeHInJfDRPYwP0D04NiQX9MQmucrAyRWGseY0b/ff0yhQdXg==", + "dev": true, + "peer": true + }, + "node_modules/@wdio/cli/node_modules/node-fetch": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz", + "integrity": "sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==", + "dev": true, + "peer": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/@wdio/cli/node_modules/puppeteer-core": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-11.0.0.tgz", + "integrity": "sha512-hfQ39KNP0qKplQ86iaCNXHH9zpWlV01UFdggt2qffgWeCBF9KMavwP/k/iK/JidPPWfOnKZhDLSHZVSUr73DtA==", + "dev": true, + "peer": true, + "dependencies": { + "debug": "4.3.2", + "devtools-protocol": "0.0.901419", + "extract-zip": "2.0.1", + "https-proxy-agent": "5.0.0", + "node-fetch": "2.6.5", + "pkg-dir": "4.2.0", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "rimraf": "3.0.2", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "ws": "8.2.3" + }, + "engines": { + "node": ">=10.18.1" + } + }, + "node_modules/@wdio/cli/node_modules/puppeteer-core/node_modules/devtools-protocol": { + "version": "0.0.901419", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.901419.tgz", + "integrity": "sha512-4INMPwNm9XRpBukhNbF7OB6fNTTCaI8pzy/fXg0xQzAy5h3zL1P8xT3QazgKqBrb/hAYwIBizqDBZ7GtJE74QQ==", + "dev": true, + "peer": true + }, + "node_modules/@wdio/cli/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", + "dev": true, + "peer": true + }, + "node_modules/@wdio/cli/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "peer": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@wdio/cli/node_modules/webdriver": { + "version": "7.16.9", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.16.9.tgz", + "integrity": "sha512-6bpiyE3/1ncgyNM/RwzEWjpxu2NLYyeYNu/97OMEwFMDV8EqvlZh3wFnODi6tY0K5t4dEryIPiyjF3MDVySRAg==", + "dev": true, + "peer": true, + "dependencies": { + "@types/node": "^16.11.1", + "@wdio/config": "7.16.3", + "@wdio/logger": "7.16.0", + "@wdio/protocols": "7.16.7", + "@wdio/types": "7.16.3", + "@wdio/utils": "7.16.3", + "got": "^11.0.2", + "ky": "^0.28.5", + "lodash.merge": "^4.6.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@wdio/cli/node_modules/webdriverio": { + "version": "7.16.10", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.16.10.tgz", + "integrity": "sha512-Idsn0084HqcqHa5/BW/75dwFEitSDi/hhXk+GRA0wZkVU7woE8ZKACsMS270kOADgXYU9XJBT8jo6YM3R3Sa+Q==", + "dev": true, + "peer": true, + "dependencies": { + "@types/aria-query": "^5.0.0", + "@types/node": "^16.11.1", + "@wdio/config": "7.16.3", + "@wdio/logger": "7.16.0", + "@wdio/protocols": "7.16.7", + "@wdio/repl": "7.16.3", + "@wdio/types": "7.16.3", + "@wdio/utils": "7.16.3", + "archiver": "^5.0.0", + "aria-query": "^5.0.0", + "css-shorthand-properties": "^1.1.1", + "css-value": "^0.0.1", + "devtools": "7.16.10", + "devtools-protocol": "^0.0.944179", + "fs-extra": "^10.0.0", + "get-port": "^5.1.1", + "grapheme-splitter": "^1.0.2", + "lodash.clonedeep": "^4.5.0", + "lodash.isobject": "^3.0.2", + "lodash.isplainobject": "^4.0.6", + "lodash.zip": "^4.2.0", + "minimatch": "^3.0.4", + "puppeteer-core": "^11.0.0", + "query-selector-shadow-dom": "^1.0.0", + "resq": "^1.9.1", + "rgb2hex": "0.2.5", + "serialize-error": "^8.0.0", + "webdriver": "7.16.9" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@wdio/cli/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", + "dev": true, + "peer": true + }, + "node_modules/@wdio/cli/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dev": true, + "peer": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/@wdio/cli/node_modules/ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/@wdio/config": { "version": "7.16.3", "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.16.3.tgz", "integrity": "sha512-YbpeZAeEncyJrsKxfAwjhNbDUf/ZrMB2Io3PYnH3RQjEEo5lYlO15aUt9uJx09W5h8hBPcrj7CfUC5yNkFZJhw==", "dev": true, + "peer": true, "dependencies": { "@wdio/logger": "7.16.0", "@wdio/types": "7.16.3", @@ -840,6 +979,7 @@ "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.16.0.tgz", "integrity": "sha512-/6lOGb2Iow5eSsy7RJOl1kCwsP4eMlG+/QKro5zUJsuyNJSQXf2ejhpkzyKWLgQbHu83WX6cM1014AZuLkzoQg==", "dev": true, + "peer": true, "dependencies": { "chalk": "^4.0.0", "loglevel": "^1.6.0", @@ -855,34 +995,83 @@ "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.16.7.tgz", "integrity": "sha512-Wv40pNQcLiPzQ3o98Mv4A8T1EBQ6k4khglz/e2r16CTm+F3DDYh8eLMAsU5cgnmuwwDKX1EyOiFwieykBn5MCg==", "dev": true, + "peer": true, "engines": { "node": ">=12.0.0" } }, "node_modules/@wdio/repl": { - "version": "7.16.3", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-7.16.3.tgz", - "integrity": "sha512-aFpWyAIuPo6VVmkotZDWXMzd4qw3gD+xAhB6blNrMCZKWnz9+HqZnuGGc6pmiyuc5yFzb9wF22tnIxuyTyH7yA==", + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-7.17.3.tgz", + "integrity": "sha512-ZX4dYnoOb9NC3IQFhva4B7FCoVx9v7CIG7g5W4bX/un5Xfyz3Fne1vGP9Aku15nyIaXRSCzuV6vpT/5KR6q6Hg==", "dev": true, "dependencies": { - "@wdio/utils": "7.16.3" + "@wdio/utils": "7.17.3" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@wdio/repl/node_modules/@types/node": { + "version": "17.0.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", + "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", + "dev": true + }, + "node_modules/@wdio/repl/node_modules/@wdio/logger": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.17.3.tgz", + "integrity": "sha512-hpvJDsJMX8G/8gXHOEipxkQPjojjA+BRCZqCvZRLCVpWm2JB7tBoMzu9sUJXcpSkY03b94KAd4EwNA2uNAf9aQ==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@wdio/repl/node_modules/@wdio/types": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.17.3.tgz", + "integrity": "sha512-j8kYdaMl4NFRS8M1bFDuEa3GMbUZbLQY7i6XEnJSetyW0GyMDLlzwcfXI4DdX85+3JbO5624UGKxVsQcuA7T3A==", + "dev": true, + "dependencies": { + "@types/node": "^17.0.4", + "got": "^11.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@wdio/repl/node_modules/@wdio/utils": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.17.3.tgz", + "integrity": "sha512-20bGTCmgBNVKa2BJs3B5kxbsryjhfEOoKDnFjZ/rAVZYT1t1sg0e/W+vRfamd++NqTaIHOY/IKGEFiEnCw5nXw==", + "dev": true, + "dependencies": { + "@wdio/logger": "7.17.3", + "@wdio/types": "7.17.3", + "p-iteration": "^1.1.8" }, "engines": { "node": ">=12.0.0" } }, "node_modules/@wdio/selenium-standalone-service": { - "version": "7.16.6", - "resolved": "https://registry.npmjs.org/@wdio/selenium-standalone-service/-/selenium-standalone-service-7.16.6.tgz", - "integrity": "sha512-KWoVzoOkOSnXlCNQj13yniaSJLbVPQJkytXam/MKy6CKsnQqeLt+6E3DvcdUhP+fRfvh5AzZrMNLer8Rv4hXhg==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@wdio/selenium-standalone-service/-/selenium-standalone-service-7.18.0.tgz", + "integrity": "sha512-Tl+GB45oYsnL5zP5xQU245Uhcm3IFL7XP3x7nIsUj7VHzWPSvMB4Hib90CnNiTd2pY1AUC8q+2rlS1M1jZq3VQ==", "dev": true, "dependencies": { "@types/fs-extra": "^9.0.1", - "@types/node": "^16.11.1", + "@types/node": "^17.0.4", "@types/selenium-standalone": "^7.0.0", - "@wdio/config": "7.16.3", - "@wdio/logger": "7.16.0", - "@wdio/types": "7.16.3", + "@wdio/config": "7.18.0", + "@wdio/logger": "7.17.3", + "@wdio/types": "7.18.0", "fs-extra": "^10.0.0", "selenium-standalone": "^8.0.3" }, @@ -893,11 +1082,61 @@ "@wdio/cli": "^7.0.0" } }, + "node_modules/@wdio/selenium-standalone-service/node_modules/@types/node": { + "version": "17.0.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", + "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", + "dev": true + }, + "node_modules/@wdio/selenium-standalone-service/node_modules/@wdio/config": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.18.0.tgz", + "integrity": "sha512-pXv4MWfMFSOXU6eJRbJuHcP0hN9sOyRzEpHNgMk6hsDpzqSKFp2Rg4fYrlmvYJILrM6PxTfylVz9gwP4QWOsdA==", + "dev": true, + "dependencies": { + "@wdio/logger": "7.17.3", + "@wdio/types": "7.18.0", + "deepmerge": "^4.0.0", + "glob": "^7.1.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@wdio/selenium-standalone-service/node_modules/@wdio/logger": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.17.3.tgz", + "integrity": "sha512-hpvJDsJMX8G/8gXHOEipxkQPjojjA+BRCZqCvZRLCVpWm2JB7tBoMzu9sUJXcpSkY03b94KAd4EwNA2uNAf9aQ==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@wdio/selenium-standalone-service/node_modules/@wdio/types": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.18.0.tgz", + "integrity": "sha512-GZO5gmobbAOGln4+edAsyPbeLes4I6ZCa0iBn/KVM72y8cvHH+L2ojLsDvzd6Pn/g79zDjEUpBH7BgkDplj8lg==", + "dev": true, + "dependencies": { + "@types/node": "^17.0.4", + "got": "^11.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/@wdio/types": { "version": "7.16.3", "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.16.3.tgz", "integrity": "sha512-iJLtJrOJZSJrXR1zseCkVWUFs477FngjWz2HTMfGHR69LzfmxC0RNagemjZuLTfhTqWp/FBbqaA/F+7xJdNKag==", "dev": true, + "peer": true, "dependencies": { "@types/node": "^16.11.1", "got": "^11.8.1" @@ -911,6 +1150,7 @@ "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.16.3.tgz", "integrity": "sha512-/662h5Z7B5TejHN6GiW96PAKuTPi/xcAGmtjA9ozRBI2/0eHSccDfNEaBgTTjLqqEgGAXylHcOuxHOrKx2ddJw==", "dev": true, + "peer": true, "dependencies": { "@wdio/logger": "7.16.0", "@wdio/types": "7.16.3", @@ -938,9 +1178,9 @@ } }, "node_modules/acorn": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.6.0.tgz", - "integrity": "sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw==", + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -1183,13 +1423,10 @@ "dev": true }, "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true }, "node_modules/aria-query": { "version": "5.0.0", @@ -1410,15 +1647,6 @@ "node": ">=0.10.0" } }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/async": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", @@ -1498,27 +1726,6 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" }, - "node_modules/babel-eslint": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", - "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", - "deprecated": "babel-eslint is now @babel/eslint-parser. This package will no longer receive updates.", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.7.0", - "@babel/traverse": "^7.7.0", - "@babel/types": "^7.7.0", - "eslint-visitor-keys": "^1.0.0", - "resolve": "^1.12.0" - }, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "eslint": ">= 4.12.1" - } - }, "node_modules/bach": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", @@ -1681,9 +1888,9 @@ } }, "node_modules/blockly": { - "version": "6.20210701.0", - "resolved": "https://registry.npmjs.org/blockly/-/blockly-6.20210701.0.tgz", - "integrity": "sha512-cNrwFOAxXE5Pbs1FJAyLTlSRzpNW/C+0gPT2rGQDOJVVKcyF3vhFC1StgnxvQNsv//ueuksKWIXxDuSWh1VI4w==", + "version": "7.20211209.2", + "resolved": "https://registry.npmjs.org/blockly/-/blockly-7.20211209.2.tgz", + "integrity": "sha512-74HTPbnDOwVGKx6qRE/ZVVQwf+J9s/WkgDKv0vuXw/DtBLvLrew7Nf5jaZP0+DXRVJpP1u5sfu+qtHaom0i6Ug==", "dev": true, "peer": true, "dependencies": { @@ -2096,15 +2303,16 @@ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "node_modules/chai": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", - "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", + "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", "dev": true, "dependencies": { "assertion-error": "^1.1.0", "check-error": "^1.0.2", "deep-eql": "^3.0.1", "get-func-name": "^2.0.0", + "loupe": "^2.3.1", "pathval": "^1.1.1", "type-detect": "^4.0.5" }, @@ -2145,10 +2353,16 @@ } }, "node_modules/chokidar": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", - "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -2400,9 +2614,9 @@ } }, "node_modules/closure-calculate-chunks": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/closure-calculate-chunks/-/closure-calculate-chunks-3.0.2.tgz", - "integrity": "sha512-nCpUyKcCF+Izk1CzobGu2HdHrlXAVguiM2gpmwW2UX+JAAJyPKyyYJPnIIifZ2wVuxZBG4FlVWjV69y8TOa5Bg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/closure-calculate-chunks/-/closure-calculate-chunks-3.0.3.tgz", + "integrity": "sha512-xtDmQORvSXfgT+6Xkde1RYTHsowCwqyHL92WdG4ZJKJ4bpu+A9yWK32kr4gInZEKRSAS0QrCrkXQJq4bOD5cJA==", "dev": true, "dependencies": { "acorn": "8.x", @@ -2511,15 +2725,6 @@ "color-support": "bin.js" } }, - "node_modules/colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true, - "engines": { - "node": ">=0.1.90" - } - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2615,9 +2820,9 @@ } }, "node_modules/concurrently": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-6.4.0.tgz", - "integrity": "sha512-HZ3D0RTQMH3oS4gvtYj1P+NBc6PzE2McEra6yEFcQKrUQ9HvtTGU4Dbne083F034p+LRb7kWU0tPRNvSGs1UCQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-7.0.0.tgz", + "integrity": "sha512-WKM7PUsI8wyXpF80H+zjHP32fsgsHNQfPLw/e70Z5dYkV7hF+rf8q3D+ScWJIEr57CpkO3OWBko6hwhQLPR8Pw==", "dev": true, "dependencies": { "chalk": "^4.1.0", @@ -2630,10 +2835,10 @@ "yargs": "^16.2.0" }, "bin": { - "concurrently": "bin/concurrently.js" + "concurrently": "dist/bin/concurrently.js" }, "engines": { - "node": ">=10.0.0" + "node": "^12.20.0 || ^14.13.0 || >=16.0.0" } }, "node_modules/concurrently/node_modules/rxjs": { @@ -2690,6 +2895,15 @@ "node": ">=10" } }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/convert-source-map": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", @@ -2776,6 +2990,15 @@ "node": ">= 6" } }, + "node_modules/cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "dev": true, + "dependencies": { + "node-fetch": "2.6.7" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -3143,21 +3366,21 @@ } }, "node_modules/devtools": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-7.16.10.tgz", - "integrity": "sha512-43uB3t6uNjWsqiQKRLY7axFLuMdKqlQxq6N3FWCfBKl9We1oygkGkE7Scnushdbc4lk7QwGXBC1DQ83dCgA5Gw==", + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-7.17.3.tgz", + "integrity": "sha512-y5O+z+q7cUuAKMY9ZNGexbb62MUimKAJX7OkFecix2Fl9+YFSmAQUUtHWrTt9qFkw5NJNMdiXZhQvk+JdfRygw==", "dev": true, "dependencies": { - "@types/node": "^16.11.1", + "@types/node": "^17.0.4", "@types/ua-parser-js": "^0.7.33", - "@wdio/config": "7.16.3", - "@wdio/logger": "7.16.0", - "@wdio/protocols": "7.16.7", - "@wdio/types": "7.16.3", - "@wdio/utils": "7.16.3", + "@wdio/config": "7.17.3", + "@wdio/logger": "7.17.3", + "@wdio/protocols": "7.17.3", + "@wdio/types": "7.17.3", + "@wdio/utils": "7.17.3", "chrome-launcher": "^0.15.0", "edge-paths": "^2.1.0", - "puppeteer-core": "^11.0.0", + "puppeteer-core": "^13.1.3", "query-selector-shadow-dom": "^1.0.0", "ua-parser-js": "^1.0.1", "uuid": "^8.0.0" @@ -3167,11 +3390,83 @@ } }, "node_modules/devtools-protocol": { - "version": "0.0.944179", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.944179.tgz", - "integrity": "sha512-oqBbLKuCAkEqqsWn0rsfkjy79F0/QTQR/rlijZzeHInJfDRPYwP0D04NiQX9MQmucrAyRWGseY0b/ff0yhQdXg==", + "version": "0.0.979353", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.979353.tgz", + "integrity": "sha512-/A7o8FU5n4i2WN/RH6opBbteawPbNgyKmmyl6Ts4zpQ5FVq/cGe2K/qGr8t80BLVu8KynTckHbdpaLCwxzRyFA==", "dev": true }, + "node_modules/devtools/node_modules/@types/node": { + "version": "17.0.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", + "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", + "dev": true + }, + "node_modules/devtools/node_modules/@wdio/config": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.17.3.tgz", + "integrity": "sha512-MSWCsx0w1EbxbwOD8ykTxHqgx208CWoz9n4oWHx7Q1APfetqWFLM4O7K8cdZS1gV4IvH4EAV9807L91K8r0JNw==", + "dev": true, + "dependencies": { + "@wdio/logger": "7.17.3", + "@wdio/types": "7.17.3", + "deepmerge": "^4.0.0", + "glob": "^7.1.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/devtools/node_modules/@wdio/logger": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.17.3.tgz", + "integrity": "sha512-hpvJDsJMX8G/8gXHOEipxkQPjojjA+BRCZqCvZRLCVpWm2JB7tBoMzu9sUJXcpSkY03b94KAd4EwNA2uNAf9aQ==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/devtools/node_modules/@wdio/protocols": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.17.3.tgz", + "integrity": "sha512-DxVRil2uMDOshk0gMOrmemC9uEZuB5Dv4bJX/ozZwXPV9AHd6oJqUrsF/fs8bT9+4AWkE58yqsRBFc/pt7sFMw==", + "dev": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/devtools/node_modules/@wdio/types": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.17.3.tgz", + "integrity": "sha512-j8kYdaMl4NFRS8M1bFDuEa3GMbUZbLQY7i6XEnJSetyW0GyMDLlzwcfXI4DdX85+3JbO5624UGKxVsQcuA7T3A==", + "dev": true, + "dependencies": { + "@types/node": "^17.0.4", + "got": "^11.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/devtools/node_modules/@wdio/utils": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.17.3.tgz", + "integrity": "sha512-20bGTCmgBNVKa2BJs3B5kxbsryjhfEOoKDnFjZ/rAVZYT1t1sg0e/W+vRfamd++NqTaIHOY/IKGEFiEnCw5nXw==", + "dev": true, + "dependencies": { + "@wdio/logger": "7.17.3", + "@wdio/types": "7.17.3", + "p-iteration": "^1.1.8" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/devtools/node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -3339,18 +3634,6 @@ "once": "^1.4.0" } }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -3503,49 +3786,44 @@ } }, "node_modules/eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.9.0.tgz", + "integrity": "sha512-PB09IGwv4F4b0/atrbcMFboF/giawbBLVC7fyDamk5Wtey4Jh2K+rYaBhCAbUyEI4QzB1ly09Uglc9iCtFaG2Q==", "dev": true, "dependencies": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", + "@eslint/eslintrc": "^1.1.0", + "@humanwhocodes/config-array": "^0.9.2", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", - "debug": "^4.0.1", + "debug": "^4.3.2", "doctrine": "^3.0.0", - "enquirer": "^2.3.5", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1", "esquery": "^1.4.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", + "glob-parent": "^6.0.1", "globals": "^13.6.0", - "ignore": "^4.0.6", + "ignore": "^5.2.0", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", + "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.0.4", "natural-compare": "^1.4.0", "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", - "table": "^6.0.9", "text-table": "^0.2.0", "v8-compile-cache": "^2.0.3" }, @@ -3553,7 +3831,7 @@ "eslint": "bin/eslint.js" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -3572,52 +3850,46 @@ } }, "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "estraverse": "^5.2.0" }, "engines": { - "node": ">=8.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint-scope/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" } }, "node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", "dev": true, "dependencies": { - "eslint-visitor-keys": "^1.1.0" + "eslint-visitor-keys": "^2.0.0" }, "engines": { - "node": ">=6" + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" }, "funding": { "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" } }, - "node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint/node_modules/@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", @@ -3626,57 +3898,48 @@ "node": ">=10" } }, - "node_modules/eslint/node_modules/globals": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", - "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/eslint/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", "dev": true, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 4" } }, "node_modules/espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.1.tgz", + "integrity": "sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==", "dev": true, "dependencies": { - "acorn": "^7.4.0", + "acorn": "^8.7.0", "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" + "eslint-visitor-keys": "^3.3.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/espree/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/esprima": { @@ -4096,6 +4359,12 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, + "node_modules/fastest-stable-stringify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fastest-stable-stringify/-/fastest-stable-stringify-2.0.2.tgz", + "integrity": "sha512-bijHueCGd0LqqNK9b5oCMHc0MluJAx0cwqASgbWMvkO01lCYgIhacVRLcaDz3QnyYIRNJRDwMb41VuT6pHJ91Q==", + "dev": true + }, "node_modules/fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -4852,12 +5121,30 @@ } }, "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "version": "13.12.1", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz", + "integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globals/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/glogg": { @@ -4873,13 +5160,13 @@ } }, "node_modules/google-closure-compiler": { - "version": "20211107.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler/-/google-closure-compiler-20211107.0.0.tgz", - "integrity": "sha512-CVP18mjlTz+T8D0J2/vNGAkCRd9cZb06kRb0Fp/VXntFvcYdNWNs780vkiplNGtuxyI6Kn3NQPlZZutFuME4ww==", + "version": "20220301.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler/-/google-closure-compiler-20220301.0.0.tgz", + "integrity": "sha512-+yAqhufKIWddg587tnvRll92eLJQIlzINmgr1h5gLXZVioY3svrSYKH4TZiUuNj0UnVFoK0o1YuW122x+iFl2g==", "dev": true, "dependencies": { "chalk": "2.x", - "google-closure-compiler-java": "^20211107.0.0", + "google-closure-compiler-java": "^20220301.0.0", "minimist": "1.x", "vinyl": "2.x", "vinyl-sourcemaps-apply": "^0.2.0" @@ -4891,21 +5178,21 @@ "node": ">=10" }, "optionalDependencies": { - "google-closure-compiler-linux": "^20211107.0.0", - "google-closure-compiler-osx": "^20211107.0.0", - "google-closure-compiler-windows": "^20211107.0.0" + "google-closure-compiler-linux": "^20220301.0.0", + "google-closure-compiler-osx": "^20220301.0.0", + "google-closure-compiler-windows": "^20220301.0.0" } }, "node_modules/google-closure-compiler-java": { - "version": "20211107.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-java/-/google-closure-compiler-java-20211107.0.0.tgz", - "integrity": "sha512-WavJ1Rx+Fv98rNKClQD6b+GMCVqws7GkbI7sdxiIbrzeNiXwa5K8TKBaqyUx/G1myQ6+gml6GULgJi7nsBOXVQ==", + "version": "20220301.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-java/-/google-closure-compiler-java-20220301.0.0.tgz", + "integrity": "sha512-kv5oaUI4xn3qWYWtRHRqbm314kesfeFlCxiFRcvBIx13mKfR0qvbOkgajLpSM6nb3voNM/E9MB9mfvHJ9XIXSg==", "dev": true }, "node_modules/google-closure-compiler-linux": { - "version": "20211107.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-linux/-/google-closure-compiler-linux-20211107.0.0.tgz", - "integrity": "sha512-ULk90c4IiuTcHZ892eFusN/WYfY+gvopy38msrmUnYrmLjq5Ssef7NtDRUhYe9SIX7Dd5NM2jo7u7XjEu/4NCQ==", + "version": "20220301.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-linux/-/google-closure-compiler-linux-20220301.0.0.tgz", + "integrity": "sha512-N2D0SRnxZ7kqdoZ2WsmLIjmizR4Xr0HaUYDK2RCOtsV21RYV8OR2u0ATp7aXhYy8WfxvYH478Ehvmc9Uzy986A==", "cpu": [ "x64", "x86" @@ -4917,9 +5204,9 @@ ] }, "node_modules/google-closure-compiler-osx": { - "version": "20211107.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-osx/-/google-closure-compiler-osx-20211107.0.0.tgz", - "integrity": "sha512-nTDR6oAQ6GHT1UakZc4OdyZh2AqwKce7aZQS+XLWojh98SEq7JS5dyEkFBK6a1lqWyOMLH2Mrr3XhNobY/umbA==", + "version": "20220301.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-osx/-/google-closure-compiler-osx-20220301.0.0.tgz", + "integrity": "sha512-Xqf0m5takwfv43ML4aODJxmAsAZQMTMo683gyRs0APAecncs+YKxaDPMH+pQAdI3HPY2QsvkarlunAp0HSwU5A==", "cpu": [ "x64", "x86", @@ -4932,9 +5219,9 @@ ] }, "node_modules/google-closure-compiler-windows": { - "version": "20211107.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-windows/-/google-closure-compiler-windows-20211107.0.0.tgz", - "integrity": "sha512-0G7Dd/ZoSPS6pSsOdA6N4LJU0zJddHcS+PiNixQVN50kuHH3DjfI+gCyLx7CkJUZEuGiGHDVkc0ZmKBR5iq4QA==", + "version": "20220301.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-windows/-/google-closure-compiler-windows-20220301.0.0.tgz", + "integrity": "sha512-s+FU/vcpLTEgx8MCMgj0STCYkVk7syzF9KqiYPOTtbTD9ra99HPe/CEuQG7iJ3Fty9dhm9zEaetv4Dp4Wr6x+Q==", "cpu": [ "x64" ], @@ -5016,9 +5303,9 @@ } }, "node_modules/google-closure-deps": { - "version": "20211107.0.0", - "resolved": "https://registry.npmjs.org/google-closure-deps/-/google-closure-deps-20211107.0.0.tgz", - "integrity": "sha512-RkxMNY9U+AcVRc70oY95pz1TeAmS8yHwwfyN+LZ5Jrx+mBg9k+UYoE2rS8SiwcF/VjyOHlfw2sw6GIUO56hXCg==", + "version": "20220202.0.0", + "resolved": "https://registry.npmjs.org/google-closure-deps/-/google-closure-deps-20220202.0.0.tgz", + "integrity": "sha512-cPE4GbWRn4ix92UFE1nbjJpQw3eQ/1fGjjMDJG8mghhBEiBBXzBZUEpPALrpdEy9ZYgOdaRAr9UqAN35jfmy8g==", "dev": true, "dependencies": { "minimatch": "^3.0.4", @@ -5986,13 +6273,13 @@ } }, "node_modules/http-server": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.0.0.tgz", - "integrity": "sha512-XTePIXAo5x72bI8SlKFSqsg7UuSHwsOa4+RJIe56YeMUvfTvGDy7TxFkTEhfIRmM/Dnf6x29ut541ythSBZdkQ==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.0.tgz", + "integrity": "sha512-5lYsIcZtf6pdR8tCtzAHTWrAveo4liUlJdWc7YafwK/maPgYHs+VNP6KpCClmUnSorJrARVMXqtT055zBv11Yg==", "dev": true, "dependencies": { "basic-auth": "^2.0.1", - "colors": "^1.4.0", + "chalk": "^4.1.2", "corser": "^2.0.1", "he": "^1.2.0", "html-encoding-sniffer": "^3.0.0", @@ -6723,13 +7010,12 @@ "dev": true }, "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" @@ -6811,18 +7097,6 @@ "iconv-lite": "0.4.24" } }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -6856,6 +7130,21 @@ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, + "node_modules/json5": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -6882,6 +7171,12 @@ "node": ">=0.6.0" } }, + "node_modules/just-curry-it": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/just-curry-it/-/just-curry-it-3.2.1.tgz", + "integrity": "sha512-Q8206k8pTY7krW32cdmPsP+DqqLgWx/hYPSj9/+7SYqSqz7UuwPbfSe07lQtvuuaVyiSJveXk0E5RydOuWwsEg==", + "dev": true + }, "node_modules/just-debounce": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.1.0.tgz", @@ -6917,6 +7212,7 @@ "resolved": "https://registry.npmjs.org/ky/-/ky-0.28.7.tgz", "integrity": "sha512-a23i6qSr/ep15vdtw/zyEQIDLoUaKDg9Jf04CYl/0ns/wXNYna26zJpI+MeIFaPeDvkrjLPrKtKOiiI3IE53RQ==", "dev": true, + "peer": true, "engines": { "node": ">=12" }, @@ -7286,12 +7582,6 @@ "lodash._reinterpolate": "^3.0.0" } }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", - "dev": true - }, "node_modules/lodash.union": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", @@ -7339,6 +7629,15 @@ "integrity": "sha512-WpG9CcFAOjz/FtNht+QJeGpvVl/cdR6P0z6OcXSkr8wFJOsV2GRj2j10JLfjuA4aYkcKCNIEqRGCyTife9R8/g==", "dev": true }, + "node_modules/loupe": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.3.tgz", + "integrity": "sha512-krIV4Cf1BIGIx2t1e6tucThhrBemUnIUjMtD2vN4mrMxnxpBvrcosBSpooqunBqP/hOEEV1w/Cr1YskGtqw5Jg==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.0" + } + }, "node_modules/lowercase-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", @@ -7793,32 +8092,32 @@ "dev": true }, "node_modules/mocha": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.1.3.tgz", - "integrity": "sha512-Xcpl9FqXOAYqI3j79pEtHBBnQgVXIhpULjGQa7DVb0Po+VzmSIK9kanAiWLHoRR/dbZ2qpdPshuXr8l1VaHCzw==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.1.tgz", + "integrity": "sha512-T7uscqjJVS46Pq1XDXyo9Uvey9gd3huT/DD9cYBb4K2Xc/vbKRPUWK067bxDQRK0yIz6Jxk73IrnimvASzBNAQ==", "dev": true, "dependencies": { "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "chokidar": "3.5.2", - "debug": "4.3.2", + "chokidar": "3.5.3", + "debug": "4.3.3", "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", - "glob": "7.1.7", + "glob": "7.2.0", "growl": "1.10.5", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", "minimatch": "3.0.4", "ms": "2.1.3", - "nanoid": "3.1.25", + "nanoid": "3.2.0", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", "which": "2.0.2", - "workerpool": "6.1.5", + "workerpool": "6.2.0", "yargs": "16.2.0", "yargs-parser": "20.2.4", "yargs-unparser": "2.0.0" @@ -7835,35 +8134,6 @@ "url": "https://opencollective.com/mochajs" } }, - "node_modules/mocha/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/mocha/node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/mocha/node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/mocha/node_modules/diff": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", @@ -7873,38 +8143,6 @@ "node": ">=0.3.1" } }, - "node_modules/mocha/node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/mocha/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -7998,9 +8236,9 @@ "optional": true }, "node_modules/nanoid": { - "version": "3.1.25", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz", - "integrity": "sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", + "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", "dev": true, "bin": { "nanoid": "bin/nanoid.cjs" @@ -8066,15 +8304,23 @@ } }, "node_modules/node-fetch": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", - "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", "dev": true, "dependencies": { "whatwg-url": "^5.0.0" }, "engines": { "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, "node_modules/node-fetch/node_modules/tr46": { @@ -9212,6 +9458,12 @@ "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" }, + "node_modules/pubsub-js": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/pubsub-js/-/pubsub-js-1.9.4.tgz", + "integrity": "sha512-hJYpaDvPH4w8ZX/0Fdf9ma1AwRgU353GfbaVfPjfJQf1KxZ2iHaHl3fAUw1qlJIR5dr4F3RzjGaWohYUEyoh7A==", + "dev": true + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -9252,89 +9504,38 @@ } }, "node_modules/puppeteer-core": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-11.0.0.tgz", - "integrity": "sha512-hfQ39KNP0qKplQ86iaCNXHH9zpWlV01UFdggt2qffgWeCBF9KMavwP/k/iK/JidPPWfOnKZhDLSHZVSUr73DtA==", + "version": "13.5.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-13.5.1.tgz", + "integrity": "sha512-dobVqWjV34ilyfQHR3BBnCYaekBYTi5MgegEYBRYd3s3uFy8jUpZEEWbaFjG9ETm+LGzR5Lmr0aF6LLuHtiuCg==", "dev": true, "dependencies": { - "debug": "4.3.2", - "devtools-protocol": "0.0.901419", + "cross-fetch": "3.1.5", + "debug": "4.3.3", + "devtools-protocol": "0.0.969999", "extract-zip": "2.0.1", "https-proxy-agent": "5.0.0", - "node-fetch": "2.6.5", "pkg-dir": "4.2.0", "progress": "2.0.3", "proxy-from-env": "1.1.0", "rimraf": "3.0.2", "tar-fs": "2.1.1", "unbzip2-stream": "1.4.3", - "ws": "8.2.3" + "ws": "8.5.0" }, "engines": { "node": ">=10.18.1" } }, - "node_modules/puppeteer-core/node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/puppeteer-core/node_modules/devtools-protocol": { - "version": "0.0.901419", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.901419.tgz", - "integrity": "sha512-4INMPwNm9XRpBukhNbF7OB6fNTTCaI8pzy/fXg0xQzAy5h3zL1P8xT3QazgKqBrb/hAYwIBizqDBZ7GtJE74QQ==", + "version": "0.0.969999", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.969999.tgz", + "integrity": "sha512-6GfzuDWU0OFAuOvBokXpXPLxjOJ5DZ157Ue3sGQQM3LgAamb8m0R0ruSfN0DDu+XG5XJgT50i6zZ/0o8RglreQ==", "dev": true }, - "node_modules/puppeteer-core/node_modules/node-fetch": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz", - "integrity": "sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==", - "dev": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - } - }, - "node_modules/puppeteer-core/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", - "dev": true - }, - "node_modules/puppeteer-core/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", - "dev": true - }, - "node_modules/puppeteer-core/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "dev": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/puppeteer-core/node_modules/ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", + "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", "dev": true, "engines": { "node": ">=10.0.0" @@ -9871,15 +10072,6 @@ "node": ">=0.10.0" } }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/require-main-filename": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", @@ -10084,9 +10276,9 @@ "dev": true }, "node_modules/selenium-standalone": { - "version": "8.0.8", - "resolved": "https://registry.npmjs.org/selenium-standalone/-/selenium-standalone-8.0.8.tgz", - "integrity": "sha512-2NCHoK12dbLbtXPIdMe1ljiLAMflJDBezPhjJBwmnYz5/yGjCPv2R9ojHO6W1ctD9DOlqyi3AxoAvlT2nmB7Fw==", + "version": "8.0.9", + "resolved": "https://registry.npmjs.org/selenium-standalone/-/selenium-standalone-8.0.9.tgz", + "integrity": "sha512-Bl5Wbaeu5bKVmpLVATYeRcPsk9Bv3fr1aUvdC39UZjG6R/bem8j8Pj+dwB9F+PsS/owQnKh0BCg1KalZcAImbw==", "dev": true, "dependencies": { "commander": "^8.3.0", @@ -10290,23 +10482,6 @@ "node": ">=0.3.1" } }, - "node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, "node_modules/snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -10623,12 +10798,6 @@ "node": ">=0.10.0" } }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, "node_modules/sshpk": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", @@ -10960,44 +11129,6 @@ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" }, - "node_modules/table": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/table/-/table-6.7.5.tgz", - "integrity": "sha512-LFNeryOqiQHqCVKzhkymKwt6ozeRhlm8IL1mE8rNUurkir4heF6PzMyRgaTa4tlyPTGGgXuvVOF/OLWiH09Lqw==", - "dev": true, - "dependencies": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/table/node_modules/ajv": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz", - "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/table/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, "node_modules/tar-fs": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", @@ -11189,15 +11320,6 @@ "node": ">=0.10.0" } }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", @@ -11397,9 +11519,9 @@ "dev": true }, "node_modules/typescript": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz", - "integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==", + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -11629,6 +11751,12 @@ "node": ">=4" } }, + "node_modules/url-resolve-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/url-resolve-browser/-/url-resolve-browser-1.2.0.tgz", + "integrity": "sha512-L9PBPnlKNDFzt9ElK4br8I8Tufdm1xgv1GhMeiP7ZC87x0b7mr+4vSh13kmPq5km80JKX+UD2BeEFTCrFZ6xDA==", + "dev": true + }, "node_modules/use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", @@ -11849,45 +11977,129 @@ } }, "node_modules/webdriver": { - "version": "7.16.9", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.16.9.tgz", - "integrity": "sha512-6bpiyE3/1ncgyNM/RwzEWjpxu2NLYyeYNu/97OMEwFMDV8EqvlZh3wFnODi6tY0K5t4dEryIPiyjF3MDVySRAg==", + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.17.3.tgz", + "integrity": "sha512-E1V/IKYjJoVjK9zhHfSCWeqORhgNlDuYydykm0h+CchEhMSgTmtTH/LYfXSx4myXzobdlIg6xhE7Jv7XPjSkAA==", "dev": true, "dependencies": { - "@types/node": "^16.11.1", - "@wdio/config": "7.16.3", - "@wdio/logger": "7.16.0", - "@wdio/protocols": "7.16.7", - "@wdio/types": "7.16.3", - "@wdio/utils": "7.16.3", + "@types/node": "^17.0.4", + "@wdio/config": "7.17.3", + "@wdio/logger": "7.17.3", + "@wdio/protocols": "7.17.3", + "@wdio/types": "7.17.3", + "@wdio/utils": "7.17.3", "got": "^11.0.2", - "ky": "^0.28.5", + "ky": "^0.30.0", "lodash.merge": "^4.6.1" }, "engines": { "node": ">=12.0.0" } }, + "node_modules/webdriver/node_modules/@types/node": { + "version": "17.0.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", + "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", + "dev": true + }, + "node_modules/webdriver/node_modules/@wdio/config": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.17.3.tgz", + "integrity": "sha512-MSWCsx0w1EbxbwOD8ykTxHqgx208CWoz9n4oWHx7Q1APfetqWFLM4O7K8cdZS1gV4IvH4EAV9807L91K8r0JNw==", + "dev": true, + "dependencies": { + "@wdio/logger": "7.17.3", + "@wdio/types": "7.17.3", + "deepmerge": "^4.0.0", + "glob": "^7.1.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/webdriver/node_modules/@wdio/logger": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.17.3.tgz", + "integrity": "sha512-hpvJDsJMX8G/8gXHOEipxkQPjojjA+BRCZqCvZRLCVpWm2JB7tBoMzu9sUJXcpSkY03b94KAd4EwNA2uNAf9aQ==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/webdriver/node_modules/@wdio/protocols": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.17.3.tgz", + "integrity": "sha512-DxVRil2uMDOshk0gMOrmemC9uEZuB5Dv4bJX/ozZwXPV9AHd6oJqUrsF/fs8bT9+4AWkE58yqsRBFc/pt7sFMw==", + "dev": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/webdriver/node_modules/@wdio/types": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.17.3.tgz", + "integrity": "sha512-j8kYdaMl4NFRS8M1bFDuEa3GMbUZbLQY7i6XEnJSetyW0GyMDLlzwcfXI4DdX85+3JbO5624UGKxVsQcuA7T3A==", + "dev": true, + "dependencies": { + "@types/node": "^17.0.4", + "got": "^11.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/webdriver/node_modules/@wdio/utils": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.17.3.tgz", + "integrity": "sha512-20bGTCmgBNVKa2BJs3B5kxbsryjhfEOoKDnFjZ/rAVZYT1t1sg0e/W+vRfamd++NqTaIHOY/IKGEFiEnCw5nXw==", + "dev": true, + "dependencies": { + "@wdio/logger": "7.17.3", + "@wdio/types": "7.17.3", + "p-iteration": "^1.1.8" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/webdriver/node_modules/ky": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/ky/-/ky-0.30.0.tgz", + "integrity": "sha512-X/u76z4JtDVq10u1JA5UQfatPxgPaVDMYTrgHyiTpGN2z4TMEJkIHsoSBBSg9SWZEIXTKsi9kHgiQ9o3Y/4yog==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/ky?sponsor=1" + } + }, "node_modules/webdriverio": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.16.10.tgz", - "integrity": "sha512-Idsn0084HqcqHa5/BW/75dwFEitSDi/hhXk+GRA0wZkVU7woE8ZKACsMS270kOADgXYU9XJBT8jo6YM3R3Sa+Q==", + "version": "7.17.4", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.17.4.tgz", + "integrity": "sha512-p7u2q7NJL7Et8FdSroq/Ltoi3KkKxERE79Srh9lFr6yRNPFqb46dJf/g4nljLhburnGkbNdYN15JWgyWYnnj9g==", "dev": true, "dependencies": { "@types/aria-query": "^5.0.0", - "@types/node": "^16.11.1", - "@wdio/config": "7.16.3", - "@wdio/logger": "7.16.0", - "@wdio/protocols": "7.16.7", - "@wdio/repl": "7.16.3", - "@wdio/types": "7.16.3", - "@wdio/utils": "7.16.3", + "@types/node": "^17.0.4", + "@wdio/config": "7.17.3", + "@wdio/logger": "7.17.3", + "@wdio/protocols": "7.17.3", + "@wdio/repl": "7.17.3", + "@wdio/types": "7.17.3", + "@wdio/utils": "7.17.3", "archiver": "^5.0.0", "aria-query": "^5.0.0", "css-shorthand-properties": "^1.1.1", "css-value": "^0.0.1", - "devtools": "7.16.10", - "devtools-protocol": "^0.0.944179", + "devtools": "7.17.3", + "devtools-protocol": "^0.0.979353", "fs-extra": "^10.0.0", "get-port": "^5.1.1", "grapheme-splitter": "^1.0.2", @@ -11895,18 +12107,111 @@ "lodash.isobject": "^3.0.2", "lodash.isplainobject": "^4.0.6", "lodash.zip": "^4.2.0", - "minimatch": "^3.0.4", - "puppeteer-core": "^11.0.0", + "minimatch": "^5.0.0", + "puppeteer-core": "^13.1.3", "query-selector-shadow-dom": "^1.0.0", "resq": "^1.9.1", "rgb2hex": "0.2.5", "serialize-error": "^8.0.0", - "webdriver": "7.16.9" + "webdriver": "7.17.3" }, "engines": { "node": ">=12.0.0" } }, + "node_modules/webdriverio/node_modules/@types/node": { + "version": "17.0.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", + "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", + "dev": true + }, + "node_modules/webdriverio/node_modules/@wdio/config": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.17.3.tgz", + "integrity": "sha512-MSWCsx0w1EbxbwOD8ykTxHqgx208CWoz9n4oWHx7Q1APfetqWFLM4O7K8cdZS1gV4IvH4EAV9807L91K8r0JNw==", + "dev": true, + "dependencies": { + "@wdio/logger": "7.17.3", + "@wdio/types": "7.17.3", + "deepmerge": "^4.0.0", + "glob": "^7.1.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/webdriverio/node_modules/@wdio/logger": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.17.3.tgz", + "integrity": "sha512-hpvJDsJMX8G/8gXHOEipxkQPjojjA+BRCZqCvZRLCVpWm2JB7tBoMzu9sUJXcpSkY03b94KAd4EwNA2uNAf9aQ==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/webdriverio/node_modules/@wdio/protocols": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.17.3.tgz", + "integrity": "sha512-DxVRil2uMDOshk0gMOrmemC9uEZuB5Dv4bJX/ozZwXPV9AHd6oJqUrsF/fs8bT9+4AWkE58yqsRBFc/pt7sFMw==", + "dev": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/webdriverio/node_modules/@wdio/types": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.17.3.tgz", + "integrity": "sha512-j8kYdaMl4NFRS8M1bFDuEa3GMbUZbLQY7i6XEnJSetyW0GyMDLlzwcfXI4DdX85+3JbO5624UGKxVsQcuA7T3A==", + "dev": true, + "dependencies": { + "@types/node": "^17.0.4", + "got": "^11.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/webdriverio/node_modules/@wdio/utils": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.17.3.tgz", + "integrity": "sha512-20bGTCmgBNVKa2BJs3B5kxbsryjhfEOoKDnFjZ/rAVZYT1t1sg0e/W+vRfamd++NqTaIHOY/IKGEFiEnCw5nXw==", + "dev": true, + "dependencies": { + "@wdio/logger": "7.17.3", + "@wdio/types": "7.17.3", + "p-iteration": "^1.1.8" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/webdriverio/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/webdriverio/node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/webidl-conversions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", @@ -11981,9 +12286,9 @@ } }, "node_modules/workerpool": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.5.tgz", - "integrity": "sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", + "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", "dev": true }, "node_modules/wrap-ansi": { @@ -12064,9 +12369,9 @@ "dev": true }, "node_modules/yargs": { - "version": "17.3.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.0.tgz", - "integrity": "sha512-GQl1pWyDoGptFPJx9b9L6kmR33TGusZvXIZUT+BOz9f7X2L94oeAskFYLEg/FkhV06zZPBYLvLZRWeYId29lew==", + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz", + "integrity": "sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==", "dev": true, "dependencies": { "cliui": "^7.0.2", @@ -12320,55 +12625,6 @@ "@babel/highlight": "^7.16.0" } }, - "@babel/generator": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.0.tgz", - "integrity": "sha512-RR8hUCfRQn9j9RPKEVXo9LiwoxLPYn6hNZlvUOR8tSnaxlD0p0+la00ZP9/SnRt6HchKr+X0fO2r8vrETiJGew==", - "dev": true, - "requires": { - "@babel/types": "^7.16.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz", - "integrity": "sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.16.0", - "@babel/template": "^7.16.0", - "@babel/types": "^7.16.0" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz", - "integrity": "sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ==", - "dev": true, - "requires": { - "@babel/types": "^7.16.0" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz", - "integrity": "sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg==", - "dev": true, - "requires": { - "@babel/types": "^7.16.0" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz", - "integrity": "sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.0" - } - }, "@babel/helper-validator-identifier": { "version": "7.15.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", @@ -12444,64 +12700,20 @@ } } }, - "@babel/parser": { - "version": "7.16.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.4.tgz", - "integrity": "sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng==", - "dev": true - }, - "@babel/template": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.0.tgz", - "integrity": "sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.0", - "@babel/parser": "^7.16.0", - "@babel/types": "^7.16.0" - } - }, - "@babel/traverse": { - "version": "7.16.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.3.tgz", - "integrity": "sha512-eolumr1vVMjqevCpwVO99yN/LoGL0EyHiLO5I043aYQvwOJ9eR5UsZSClHVCzfhBduMAsSzgA/6AyqPjNayJag==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.0", - "@babel/generator": "^7.16.0", - "@babel/helper-function-name": "^7.16.0", - "@babel/helper-hoist-variables": "^7.16.0", - "@babel/helper-split-export-declaration": "^7.16.0", - "@babel/parser": "^7.16.3", - "@babel/types": "^7.16.0", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz", - "integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.15.7", - "to-fast-properties": "^2.0.0" - } - }, "@blockly/block-test": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@blockly/block-test/-/block-test-2.0.1.tgz", - "integrity": "sha512-d8E109UEWTtFv1bfy5paQNk9HYdZDWH5S7qkCrlKTtSatPDt8f3SGCr/lu8JC086/LhjRafgX1RTa0uwJIDDdg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@blockly/block-test/-/block-test-2.0.4.tgz", + "integrity": "sha512-nECM+4kSaZLNVBhbfTnsKl+kfqxcBHbflo6TE2rmaP8GjbndtT9I7XHdmXDtHGomfPTFF1qJ7bl09fmFqcdeQQ==", "dev": true, "requires": {} }, "@blockly/dev-tools": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@blockly/dev-tools/-/dev-tools-3.0.1.tgz", - "integrity": "sha512-afFCokPWHKeFrZRSoHa0oBBUWIwqI02/svYPRTjviEsxGcU89nG/bYBpfG9Fosm8hEKROvZf7/bIMn0x1KlCBA==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@blockly/dev-tools/-/dev-tools-3.0.7.tgz", + "integrity": "sha512-s55teQLYNKOtWZIngce2WFy052CrEGPMfhRzQgFKlrTfVafr+iZc+E4MTIY1J9R2Zw5uKnfxULV8LfNrwcH+fA==", "dev": true, "requires": { - "@blockly/block-test": "^2.0.1", + "@blockly/block-test": "^2.0.4", "@blockly/theme-dark": "^2.0.7", "@blockly/theme-deuteranopia": "^1.0.8", "@blockly/theme-highcontrast": "^1.0.8", @@ -12536,9 +12748,9 @@ "requires": {} }, "@blockly/theme-modern": { - "version": "2.1.24", - "resolved": "https://registry.npmjs.org/@blockly/theme-modern/-/theme-modern-2.1.24.tgz", - "integrity": "sha512-glTi5qhHr+S87S1skWG8Sre+DYeSIBG3j+YBbwwRX9k3zYtGO+W7sOdJ9DsBJj8NVukPpV1RhQhu5rt93Pp2rw==", + "version": "2.1.28", + "resolved": "https://registry.npmjs.org/@blockly/theme-modern/-/theme-modern-2.1.28.tgz", + "integrity": "sha512-SRPrQJOvTU8yC+NFnfFipaciCJEfmIG+imPwNcL1WZ/pPct6ojEDwHKAalWkHGLrTb0rmOaDVeHJJQx+HZmQsQ==", "dev": true, "requires": {} }, @@ -12550,37 +12762,20 @@ "requires": {} }, "@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.1.0.tgz", + "integrity": "sha512-C1DfL7XX4nPqGd6jcP01W9pVM1HYCuUkFk1432D7F0v3JSlUIeOYn9oCoi3eoLZ+iwBSb29BMFxxny0YrrEZqg==", "dev": true, "requires": { "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", + "debug": "^4.3.2", + "espree": "^9.3.1", "globals": "^13.9.0", "ignore": "^4.0.6", "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", + "js-yaml": "^4.1.0", "minimatch": "^3.0.4", "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "globals": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", - "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } } }, "@gulp-sourcemaps/identity-map": { @@ -12652,12 +12847,12 @@ } }, "@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.2.tgz", + "integrity": "sha512-UXOuFCGcwciWckOpmfKDq/GyhlTf9pN/BzG//x8p8zTOFEcGuA68ANXheFS0AGvy3qgZqLBUkMs7hqzqCKOVwA==", "dev": true, "requires": { - "@humanwhocodes/object-schema": "^1.2.0", + "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", "minimatch": "^3.0.4" } @@ -12668,6 +12863,48 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "@hyperjump/json-pointer": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@hyperjump/json-pointer/-/json-pointer-0.9.2.tgz", + "integrity": "sha512-PGCyTWO+WTkNWhMdlgE7OiQYPVkme9/e6d7K2xiZxH1wMGxGgZEEDNCe8hox7rkuD1equ4eZM+K3eoPCexckmA==", + "dev": true, + "requires": { + "just-curry-it": "^3.2.1" + } + }, + "@hyperjump/json-schema": { + "version": "0.18.4", + "resolved": "https://registry.npmjs.org/@hyperjump/json-schema/-/json-schema-0.18.4.tgz", + "integrity": "sha512-FVdSlOrOio/sWCbVbAP3yH/gKKddvrIvKzLS/id6/CidWH0r0x5ZTPM1zBS0Su7gU6OOjFRxDYhrIhnNBI5ODg==", + "dev": true, + "requires": { + "@hyperjump/json-schema-core": "^0.23.4", + "fastest-stable-stringify": "^2.0.2" + } + }, + "@hyperjump/json-schema-core": { + "version": "0.23.6", + "resolved": "https://registry.npmjs.org/@hyperjump/json-schema-core/-/json-schema-core-0.23.6.tgz", + "integrity": "sha512-X0IzGRi5K4c91awB3xNt5bvbs34UyHwOpRKKFFJ2nWDWW7e22VNGvibqo/S2rdFyta3wqOHTICFNTQjjcVdIZg==", + "dev": true, + "requires": { + "@hyperjump/json-pointer": "^0.9.1", + "@hyperjump/pact": "^0.2.0", + "content-type": "^1.0.4", + "node-fetch": "^2.6.5", + "pubsub-js": "^1.9.1", + "url-resolve-browser": "^1.2.0" + } + }, + "@hyperjump/pact": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@hyperjump/pact/-/pact-0.2.1.tgz", + "integrity": "sha512-imzl9j1UiqM/HC3kgfS0/TdXcEFGFkq5EwjyaztLfdmia8KLBXGy3rC96K+nnyY+2fA69yA9HtnDappub5VSQQ==", + "dev": true, + "requires": { + "just-curry-it": "^3.1.0" + } + }, "@sindresorhus/is": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.2.0.tgz", @@ -12949,6 +13186,192 @@ "webdriverio": "7.16.10", "yargs": "^17.0.0", "yarn-install": "^1.0.0" + }, + "dependencies": { + "@wdio/repl": { + "version": "7.16.3", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-7.16.3.tgz", + "integrity": "sha512-aFpWyAIuPo6VVmkotZDWXMzd4qw3gD+xAhB6blNrMCZKWnz9+HqZnuGGc6pmiyuc5yFzb9wF22tnIxuyTyH7yA==", + "dev": true, + "peer": true, + "requires": { + "@wdio/utils": "7.16.3" + } + }, + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "peer": true, + "requires": { + "ms": "2.1.2" + } + }, + "devtools": { + "version": "7.16.10", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-7.16.10.tgz", + "integrity": "sha512-43uB3t6uNjWsqiQKRLY7axFLuMdKqlQxq6N3FWCfBKl9We1oygkGkE7Scnushdbc4lk7QwGXBC1DQ83dCgA5Gw==", + "dev": true, + "peer": true, + "requires": { + "@types/node": "^16.11.1", + "@types/ua-parser-js": "^0.7.33", + "@wdio/config": "7.16.3", + "@wdio/logger": "7.16.0", + "@wdio/protocols": "7.16.7", + "@wdio/types": "7.16.3", + "@wdio/utils": "7.16.3", + "chrome-launcher": "^0.15.0", + "edge-paths": "^2.1.0", + "puppeteer-core": "^11.0.0", + "query-selector-shadow-dom": "^1.0.0", + "ua-parser-js": "^1.0.1", + "uuid": "^8.0.0" + } + }, + "devtools-protocol": { + "version": "0.0.944179", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.944179.tgz", + "integrity": "sha512-oqBbLKuCAkEqqsWn0rsfkjy79F0/QTQR/rlijZzeHInJfDRPYwP0D04NiQX9MQmucrAyRWGseY0b/ff0yhQdXg==", + "dev": true, + "peer": true + }, + "node-fetch": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz", + "integrity": "sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==", + "dev": true, + "peer": true, + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "puppeteer-core": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-11.0.0.tgz", + "integrity": "sha512-hfQ39KNP0qKplQ86iaCNXHH9zpWlV01UFdggt2qffgWeCBF9KMavwP/k/iK/JidPPWfOnKZhDLSHZVSUr73DtA==", + "dev": true, + "peer": true, + "requires": { + "debug": "4.3.2", + "devtools-protocol": "0.0.901419", + "extract-zip": "2.0.1", + "https-proxy-agent": "5.0.0", + "node-fetch": "2.6.5", + "pkg-dir": "4.2.0", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "rimraf": "3.0.2", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "ws": "8.2.3" + }, + "dependencies": { + "devtools-protocol": { + "version": "0.0.901419", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.901419.tgz", + "integrity": "sha512-4INMPwNm9XRpBukhNbF7OB6fNTTCaI8pzy/fXg0xQzAy5h3zL1P8xT3QazgKqBrb/hAYwIBizqDBZ7GtJE74QQ==", + "dev": true, + "peer": true + } + } + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", + "dev": true, + "peer": true + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "peer": true + }, + "webdriver": { + "version": "7.16.9", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.16.9.tgz", + "integrity": "sha512-6bpiyE3/1ncgyNM/RwzEWjpxu2NLYyeYNu/97OMEwFMDV8EqvlZh3wFnODi6tY0K5t4dEryIPiyjF3MDVySRAg==", + "dev": true, + "peer": true, + "requires": { + "@types/node": "^16.11.1", + "@wdio/config": "7.16.3", + "@wdio/logger": "7.16.0", + "@wdio/protocols": "7.16.7", + "@wdio/types": "7.16.3", + "@wdio/utils": "7.16.3", + "got": "^11.0.2", + "ky": "^0.28.5", + "lodash.merge": "^4.6.1" + } + }, + "webdriverio": { + "version": "7.16.10", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.16.10.tgz", + "integrity": "sha512-Idsn0084HqcqHa5/BW/75dwFEitSDi/hhXk+GRA0wZkVU7woE8ZKACsMS270kOADgXYU9XJBT8jo6YM3R3Sa+Q==", + "dev": true, + "peer": true, + "requires": { + "@types/aria-query": "^5.0.0", + "@types/node": "^16.11.1", + "@wdio/config": "7.16.3", + "@wdio/logger": "7.16.0", + "@wdio/protocols": "7.16.7", + "@wdio/repl": "7.16.3", + "@wdio/types": "7.16.3", + "@wdio/utils": "7.16.3", + "archiver": "^5.0.0", + "aria-query": "^5.0.0", + "css-shorthand-properties": "^1.1.1", + "css-value": "^0.0.1", + "devtools": "7.16.10", + "devtools-protocol": "^0.0.944179", + "fs-extra": "^10.0.0", + "get-port": "^5.1.1", + "grapheme-splitter": "^1.0.2", + "lodash.clonedeep": "^4.5.0", + "lodash.isobject": "^3.0.2", + "lodash.isplainobject": "^4.0.6", + "lodash.zip": "^4.2.0", + "minimatch": "^3.0.4", + "puppeteer-core": "^11.0.0", + "query-selector-shadow-dom": "^1.0.0", + "resq": "^1.9.1", + "rgb2hex": "0.2.5", + "serialize-error": "^8.0.0", + "webdriver": "7.16.9" + } + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", + "dev": true, + "peer": true + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dev": true, + "peer": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "dev": true, + "peer": true, + "requires": {} + } } }, "@wdio/config": { @@ -12956,6 +13379,7 @@ "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.16.3.tgz", "integrity": "sha512-YbpeZAeEncyJrsKxfAwjhNbDUf/ZrMB2Io3PYnH3RQjEEo5lYlO15aUt9uJx09W5h8hBPcrj7CfUC5yNkFZJhw==", "dev": true, + "peer": true, "requires": { "@wdio/logger": "7.16.0", "@wdio/types": "7.16.3", @@ -12968,6 +13392,7 @@ "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.16.0.tgz", "integrity": "sha512-/6lOGb2Iow5eSsy7RJOl1kCwsP4eMlG+/QKro5zUJsuyNJSQXf2ejhpkzyKWLgQbHu83WX6cM1014AZuLkzoQg==", "dev": true, + "peer": true, "requires": { "chalk": "^4.0.0", "loglevel": "^1.6.0", @@ -12979,31 +13404,115 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.16.7.tgz", "integrity": "sha512-Wv40pNQcLiPzQ3o98Mv4A8T1EBQ6k4khglz/e2r16CTm+F3DDYh8eLMAsU5cgnmuwwDKX1EyOiFwieykBn5MCg==", - "dev": true + "dev": true, + "peer": true }, "@wdio/repl": { - "version": "7.16.3", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-7.16.3.tgz", - "integrity": "sha512-aFpWyAIuPo6VVmkotZDWXMzd4qw3gD+xAhB6blNrMCZKWnz9+HqZnuGGc6pmiyuc5yFzb9wF22tnIxuyTyH7yA==", + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-7.17.3.tgz", + "integrity": "sha512-ZX4dYnoOb9NC3IQFhva4B7FCoVx9v7CIG7g5W4bX/un5Xfyz3Fne1vGP9Aku15nyIaXRSCzuV6vpT/5KR6q6Hg==", "dev": true, "requires": { - "@wdio/utils": "7.16.3" + "@wdio/utils": "7.17.3" + }, + "dependencies": { + "@types/node": { + "version": "17.0.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", + "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", + "dev": true + }, + "@wdio/logger": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.17.3.tgz", + "integrity": "sha512-hpvJDsJMX8G/8gXHOEipxkQPjojjA+BRCZqCvZRLCVpWm2JB7tBoMzu9sUJXcpSkY03b94KAd4EwNA2uNAf9aQ==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + } + }, + "@wdio/types": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.17.3.tgz", + "integrity": "sha512-j8kYdaMl4NFRS8M1bFDuEa3GMbUZbLQY7i6XEnJSetyW0GyMDLlzwcfXI4DdX85+3JbO5624UGKxVsQcuA7T3A==", + "dev": true, + "requires": { + "@types/node": "^17.0.4", + "got": "^11.8.1" + } + }, + "@wdio/utils": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.17.3.tgz", + "integrity": "sha512-20bGTCmgBNVKa2BJs3B5kxbsryjhfEOoKDnFjZ/rAVZYT1t1sg0e/W+vRfamd++NqTaIHOY/IKGEFiEnCw5nXw==", + "dev": true, + "requires": { + "@wdio/logger": "7.17.3", + "@wdio/types": "7.17.3", + "p-iteration": "^1.1.8" + } + } } }, "@wdio/selenium-standalone-service": { - "version": "7.16.6", - "resolved": "https://registry.npmjs.org/@wdio/selenium-standalone-service/-/selenium-standalone-service-7.16.6.tgz", - "integrity": "sha512-KWoVzoOkOSnXlCNQj13yniaSJLbVPQJkytXam/MKy6CKsnQqeLt+6E3DvcdUhP+fRfvh5AzZrMNLer8Rv4hXhg==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@wdio/selenium-standalone-service/-/selenium-standalone-service-7.18.0.tgz", + "integrity": "sha512-Tl+GB45oYsnL5zP5xQU245Uhcm3IFL7XP3x7nIsUj7VHzWPSvMB4Hib90CnNiTd2pY1AUC8q+2rlS1M1jZq3VQ==", "dev": true, "requires": { "@types/fs-extra": "^9.0.1", - "@types/node": "^16.11.1", + "@types/node": "^17.0.4", "@types/selenium-standalone": "^7.0.0", - "@wdio/config": "7.16.3", - "@wdio/logger": "7.16.0", - "@wdio/types": "7.16.3", + "@wdio/config": "7.18.0", + "@wdio/logger": "7.17.3", + "@wdio/types": "7.18.0", "fs-extra": "^10.0.0", "selenium-standalone": "^8.0.3" + }, + "dependencies": { + "@types/node": { + "version": "17.0.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", + "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", + "dev": true + }, + "@wdio/config": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.18.0.tgz", + "integrity": "sha512-pXv4MWfMFSOXU6eJRbJuHcP0hN9sOyRzEpHNgMk6hsDpzqSKFp2Rg4fYrlmvYJILrM6PxTfylVz9gwP4QWOsdA==", + "dev": true, + "requires": { + "@wdio/logger": "7.17.3", + "@wdio/types": "7.18.0", + "deepmerge": "^4.0.0", + "glob": "^7.1.2" + } + }, + "@wdio/logger": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.17.3.tgz", + "integrity": "sha512-hpvJDsJMX8G/8gXHOEipxkQPjojjA+BRCZqCvZRLCVpWm2JB7tBoMzu9sUJXcpSkY03b94KAd4EwNA2uNAf9aQ==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + } + }, + "@wdio/types": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.18.0.tgz", + "integrity": "sha512-GZO5gmobbAOGln4+edAsyPbeLes4I6ZCa0iBn/KVM72y8cvHH+L2ojLsDvzd6Pn/g79zDjEUpBH7BgkDplj8lg==", + "dev": true, + "requires": { + "@types/node": "^17.0.4", + "got": "^11.8.1" + } + } } }, "@wdio/types": { @@ -13011,6 +13520,7 @@ "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.16.3.tgz", "integrity": "sha512-iJLtJrOJZSJrXR1zseCkVWUFs477FngjWz2HTMfGHR69LzfmxC0RNagemjZuLTfhTqWp/FBbqaA/F+7xJdNKag==", "dev": true, + "peer": true, "requires": { "@types/node": "^16.11.1", "got": "^11.8.1" @@ -13021,6 +13531,7 @@ "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.16.3.tgz", "integrity": "sha512-/662h5Z7B5TejHN6GiW96PAKuTPi/xcAGmtjA9ozRBI2/0eHSccDfNEaBgTTjLqqEgGAXylHcOuxHOrKx2ddJw==", "dev": true, + "peer": true, "requires": { "@wdio/logger": "7.16.0", "@wdio/types": "7.16.3", @@ -13042,9 +13553,9 @@ } }, "acorn": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.6.0.tgz", - "integrity": "sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw==", + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", "dev": true }, "acorn-globals": { @@ -13225,13 +13736,10 @@ "dev": true }, "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true }, "aria-query": { "version": "5.0.0", @@ -13393,12 +13901,6 @@ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "dev": true }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, "async": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", @@ -13460,20 +13962,6 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" }, - "babel-eslint": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", - "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.7.0", - "@babel/traverse": "^7.7.0", - "@babel/types": "^7.7.0", - "eslint-visitor-keys": "^1.0.0", - "resolve": "^1.12.0" - } - }, "bach": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", @@ -13599,9 +14087,9 @@ } }, "blockly": { - "version": "6.20210701.0", - "resolved": "https://registry.npmjs.org/blockly/-/blockly-6.20210701.0.tgz", - "integrity": "sha512-cNrwFOAxXE5Pbs1FJAyLTlSRzpNW/C+0gPT2rGQDOJVVKcyF3vhFC1StgnxvQNsv//ueuksKWIXxDuSWh1VI4w==", + "version": "7.20211209.2", + "resolved": "https://registry.npmjs.org/blockly/-/blockly-7.20211209.2.tgz", + "integrity": "sha512-74HTPbnDOwVGKx6qRE/ZVVQwf+J9s/WkgDKv0vuXw/DtBLvLrew7Nf5jaZP0+DXRVJpP1u5sfu+qtHaom0i6Ug==", "dev": true, "peer": true, "requires": { @@ -13923,15 +14411,16 @@ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "chai": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", - "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", + "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", "dev": true, "requires": { "assertion-error": "^1.1.0", "check-error": "^1.0.2", "deep-eql": "^3.0.1", "get-func-name": "^2.0.0", + "loupe": "^2.3.1", "pathval": "^1.1.1", "type-detect": "^4.0.5" } @@ -13960,9 +14449,9 @@ "dev": true }, "chokidar": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", - "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", "dev": true, "requires": { "anymatch": "~3.1.2", @@ -14166,9 +14655,9 @@ } }, "closure-calculate-chunks": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/closure-calculate-chunks/-/closure-calculate-chunks-3.0.2.tgz", - "integrity": "sha512-nCpUyKcCF+Izk1CzobGu2HdHrlXAVguiM2gpmwW2UX+JAAJyPKyyYJPnIIifZ2wVuxZBG4FlVWjV69y8TOa5Bg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/closure-calculate-chunks/-/closure-calculate-chunks-3.0.3.tgz", + "integrity": "sha512-xtDmQORvSXfgT+6Xkde1RYTHsowCwqyHL92WdG4ZJKJ4bpu+A9yWK32kr4gInZEKRSAS0QrCrkXQJq4bOD5cJA==", "dev": true, "requires": { "acorn": "8.x", @@ -14252,12 +14741,6 @@ "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", "dev": true }, - "colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true - }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -14339,9 +14822,9 @@ } }, "concurrently": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-6.4.0.tgz", - "integrity": "sha512-HZ3D0RTQMH3oS4gvtYj1P+NBc6PzE2McEra6yEFcQKrUQ9HvtTGU4Dbne083F034p+LRb7kWU0tPRNvSGs1UCQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-7.0.0.tgz", + "integrity": "sha512-WKM7PUsI8wyXpF80H+zjHP32fsgsHNQfPLw/e70Z5dYkV7hF+rf8q3D+ScWJIEr57CpkO3OWBko6hwhQLPR8Pw==", "dev": true, "requires": { "chalk": "^4.1.0", @@ -14395,6 +14878,12 @@ } } }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, "convert-source-map": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", @@ -14465,6 +14954,15 @@ } } }, + "cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "dev": true, + "requires": { + "node-fetch": "2.6.7" + } + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -14754,26 +15252,83 @@ "dev": true }, "devtools": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-7.16.10.tgz", - "integrity": "sha512-43uB3t6uNjWsqiQKRLY7axFLuMdKqlQxq6N3FWCfBKl9We1oygkGkE7Scnushdbc4lk7QwGXBC1DQ83dCgA5Gw==", + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-7.17.3.tgz", + "integrity": "sha512-y5O+z+q7cUuAKMY9ZNGexbb62MUimKAJX7OkFecix2Fl9+YFSmAQUUtHWrTt9qFkw5NJNMdiXZhQvk+JdfRygw==", "dev": true, "requires": { - "@types/node": "^16.11.1", + "@types/node": "^17.0.4", "@types/ua-parser-js": "^0.7.33", - "@wdio/config": "7.16.3", - "@wdio/logger": "7.16.0", - "@wdio/protocols": "7.16.7", - "@wdio/types": "7.16.3", - "@wdio/utils": "7.16.3", + "@wdio/config": "7.17.3", + "@wdio/logger": "7.17.3", + "@wdio/protocols": "7.17.3", + "@wdio/types": "7.17.3", + "@wdio/utils": "7.17.3", "chrome-launcher": "^0.15.0", "edge-paths": "^2.1.0", - "puppeteer-core": "^11.0.0", + "puppeteer-core": "^13.1.3", "query-selector-shadow-dom": "^1.0.0", "ua-parser-js": "^1.0.1", "uuid": "^8.0.0" }, "dependencies": { + "@types/node": { + "version": "17.0.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", + "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", + "dev": true + }, + "@wdio/config": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.17.3.tgz", + "integrity": "sha512-MSWCsx0w1EbxbwOD8ykTxHqgx208CWoz9n4oWHx7Q1APfetqWFLM4O7K8cdZS1gV4IvH4EAV9807L91K8r0JNw==", + "dev": true, + "requires": { + "@wdio/logger": "7.17.3", + "@wdio/types": "7.17.3", + "deepmerge": "^4.0.0", + "glob": "^7.1.2" + } + }, + "@wdio/logger": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.17.3.tgz", + "integrity": "sha512-hpvJDsJMX8G/8gXHOEipxkQPjojjA+BRCZqCvZRLCVpWm2JB7tBoMzu9sUJXcpSkY03b94KAd4EwNA2uNAf9aQ==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + } + }, + "@wdio/protocols": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.17.3.tgz", + "integrity": "sha512-DxVRil2uMDOshk0gMOrmemC9uEZuB5Dv4bJX/ozZwXPV9AHd6oJqUrsF/fs8bT9+4AWkE58yqsRBFc/pt7sFMw==", + "dev": true + }, + "@wdio/types": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.17.3.tgz", + "integrity": "sha512-j8kYdaMl4NFRS8M1bFDuEa3GMbUZbLQY7i6XEnJSetyW0GyMDLlzwcfXI4DdX85+3JbO5624UGKxVsQcuA7T3A==", + "dev": true, + "requires": { + "@types/node": "^17.0.4", + "got": "^11.8.1" + } + }, + "@wdio/utils": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.17.3.tgz", + "integrity": "sha512-20bGTCmgBNVKa2BJs3B5kxbsryjhfEOoKDnFjZ/rAVZYT1t1sg0e/W+vRfamd++NqTaIHOY/IKGEFiEnCw5nXw==", + "dev": true, + "requires": { + "@wdio/logger": "7.17.3", + "@wdio/types": "7.17.3", + "p-iteration": "^1.1.8" + } + }, "uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -14783,9 +15338,9 @@ } }, "devtools-protocol": { - "version": "0.0.944179", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.944179.tgz", - "integrity": "sha512-oqBbLKuCAkEqqsWn0rsfkjy79F0/QTQR/rlijZzeHInJfDRPYwP0D04NiQX9MQmucrAyRWGseY0b/ff0yhQdXg==", + "version": "0.0.979353", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.979353.tgz", + "integrity": "sha512-/A7o8FU5n4i2WN/RH6opBbteawPbNgyKmmyl6Ts4zpQ5FVq/cGe2K/qGr8t80BLVu8KynTckHbdpaLCwxzRyFA==", "dev": true }, "diff": { @@ -14935,15 +15490,6 @@ "once": "^1.4.0" } }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1" - } - }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -15065,81 +15611,61 @@ } }, "eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.9.0.tgz", + "integrity": "sha512-PB09IGwv4F4b0/atrbcMFboF/giawbBLVC7fyDamk5Wtey4Jh2K+rYaBhCAbUyEI4QzB1ly09Uglc9iCtFaG2Q==", "dev": true, "requires": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", + "@eslint/eslintrc": "^1.1.0", + "@humanwhocodes/config-array": "^0.9.2", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", - "debug": "^4.0.1", + "debug": "^4.3.2", "doctrine": "^3.0.0", - "enquirer": "^2.3.5", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1", "esquery": "^1.4.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", + "glob-parent": "^6.0.1", "globals": "^13.6.0", - "ignore": "^4.0.6", + "ignore": "^5.2.0", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", + "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.0.4", "natural-compare": "^1.4.0", "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", - "table": "^6.0.9", "text-table": "^0.2.0", "v8-compile-cache": "^2.0.3" }, "dependencies": { - "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "requires": { - "@babel/highlight": "^7.10.4" + "is-glob": "^4.0.3" } }, - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - }, - "globals": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", - "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", "dev": true } } @@ -15152,47 +15678,55 @@ "requires": {} }, "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", "dev": true, "requires": { "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } } }, "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", "dev": true, "requires": { - "eslint-visitor-keys": "^1.1.0" + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } } }, "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", "dev": true }, "espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.1.tgz", + "integrity": "sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==", "dev": true, "requires": { - "acorn": "^7.4.0", + "acorn": "^8.7.0", "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "dependencies": { - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - } + "eslint-visitor-keys": "^3.3.0" } }, "esprima": { @@ -15533,6 +16067,12 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, + "fastest-stable-stringify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fastest-stable-stringify/-/fastest-stable-stringify-2.0.2.tgz", + "integrity": "sha512-bijHueCGd0LqqNK9b5oCMHc0MluJAx0cwqASgbWMvkO01lCYgIhacVRLcaDz3QnyYIRNJRDwMb41VuT6pHJ91Q==", + "dev": true + }, "fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -16129,10 +16669,21 @@ } }, "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true + "version": "13.12.1", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz", + "integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + }, + "dependencies": { + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } + } }, "glogg": { "version": "1.0.2", @@ -16144,16 +16695,16 @@ } }, "google-closure-compiler": { - "version": "20211107.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler/-/google-closure-compiler-20211107.0.0.tgz", - "integrity": "sha512-CVP18mjlTz+T8D0J2/vNGAkCRd9cZb06kRb0Fp/VXntFvcYdNWNs780vkiplNGtuxyI6Kn3NQPlZZutFuME4ww==", + "version": "20220301.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler/-/google-closure-compiler-20220301.0.0.tgz", + "integrity": "sha512-+yAqhufKIWddg587tnvRll92eLJQIlzINmgr1h5gLXZVioY3svrSYKH4TZiUuNj0UnVFoK0o1YuW122x+iFl2g==", "dev": true, "requires": { "chalk": "2.x", - "google-closure-compiler-java": "^20211107.0.0", - "google-closure-compiler-linux": "^20211107.0.0", - "google-closure-compiler-osx": "^20211107.0.0", - "google-closure-compiler-windows": "^20211107.0.0", + "google-closure-compiler-java": "^20220301.0.0", + "google-closure-compiler-linux": "^20220301.0.0", + "google-closure-compiler-osx": "^20220301.0.0", + "google-closure-compiler-windows": "^20220301.0.0", "minimist": "1.x", "vinyl": "2.x", "vinyl-sourcemaps-apply": "^0.2.0" @@ -16218,36 +16769,36 @@ } }, "google-closure-compiler-java": { - "version": "20211107.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-java/-/google-closure-compiler-java-20211107.0.0.tgz", - "integrity": "sha512-WavJ1Rx+Fv98rNKClQD6b+GMCVqws7GkbI7sdxiIbrzeNiXwa5K8TKBaqyUx/G1myQ6+gml6GULgJi7nsBOXVQ==", + "version": "20220301.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-java/-/google-closure-compiler-java-20220301.0.0.tgz", + "integrity": "sha512-kv5oaUI4xn3qWYWtRHRqbm314kesfeFlCxiFRcvBIx13mKfR0qvbOkgajLpSM6nb3voNM/E9MB9mfvHJ9XIXSg==", "dev": true }, "google-closure-compiler-linux": { - "version": "20211107.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-linux/-/google-closure-compiler-linux-20211107.0.0.tgz", - "integrity": "sha512-ULk90c4IiuTcHZ892eFusN/WYfY+gvopy38msrmUnYrmLjq5Ssef7NtDRUhYe9SIX7Dd5NM2jo7u7XjEu/4NCQ==", + "version": "20220301.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-linux/-/google-closure-compiler-linux-20220301.0.0.tgz", + "integrity": "sha512-N2D0SRnxZ7kqdoZ2WsmLIjmizR4Xr0HaUYDK2RCOtsV21RYV8OR2u0ATp7aXhYy8WfxvYH478Ehvmc9Uzy986A==", "dev": true, "optional": true }, "google-closure-compiler-osx": { - "version": "20211107.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-osx/-/google-closure-compiler-osx-20211107.0.0.tgz", - "integrity": "sha512-nTDR6oAQ6GHT1UakZc4OdyZh2AqwKce7aZQS+XLWojh98SEq7JS5dyEkFBK6a1lqWyOMLH2Mrr3XhNobY/umbA==", + "version": "20220301.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-osx/-/google-closure-compiler-osx-20220301.0.0.tgz", + "integrity": "sha512-Xqf0m5takwfv43ML4aODJxmAsAZQMTMo683gyRs0APAecncs+YKxaDPMH+pQAdI3HPY2QsvkarlunAp0HSwU5A==", "dev": true, "optional": true }, "google-closure-compiler-windows": { - "version": "20211107.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-windows/-/google-closure-compiler-windows-20211107.0.0.tgz", - "integrity": "sha512-0G7Dd/ZoSPS6pSsOdA6N4LJU0zJddHcS+PiNixQVN50kuHH3DjfI+gCyLx7CkJUZEuGiGHDVkc0ZmKBR5iq4QA==", + "version": "20220301.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-windows/-/google-closure-compiler-windows-20220301.0.0.tgz", + "integrity": "sha512-s+FU/vcpLTEgx8MCMgj0STCYkVk7syzF9KqiYPOTtbTD9ra99HPe/CEuQG7iJ3Fty9dhm9zEaetv4Dp4Wr6x+Q==", "dev": true, "optional": true }, "google-closure-deps": { - "version": "20211107.0.0", - "resolved": "https://registry.npmjs.org/google-closure-deps/-/google-closure-deps-20211107.0.0.tgz", - "integrity": "sha512-RkxMNY9U+AcVRc70oY95pz1TeAmS8yHwwfyN+LZ5Jrx+mBg9k+UYoE2rS8SiwcF/VjyOHlfw2sw6GIUO56hXCg==", + "version": "20220202.0.0", + "resolved": "https://registry.npmjs.org/google-closure-deps/-/google-closure-deps-20220202.0.0.tgz", + "integrity": "sha512-cPE4GbWRn4ix92UFE1nbjJpQw3eQ/1fGjjMDJG8mghhBEiBBXzBZUEpPALrpdEy9ZYgOdaRAr9UqAN35jfmy8g==", "dev": true, "requires": { "minimatch": "^3.0.4", @@ -17065,13 +17616,13 @@ } }, "http-server": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.0.0.tgz", - "integrity": "sha512-XTePIXAo5x72bI8SlKFSqsg7UuSHwsOa4+RJIe56YeMUvfTvGDy7TxFkTEhfIRmM/Dnf6x29ut541ythSBZdkQ==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.0.tgz", + "integrity": "sha512-5lYsIcZtf6pdR8tCtzAHTWrAveo4liUlJdWc7YafwK/maPgYHs+VNP6KpCClmUnSorJrARVMXqtT055zBv11Yg==", "dev": true, "requires": { "basic-auth": "^2.0.1", - "colors": "^1.4.0", + "chalk": "^4.1.2", "corser": "^2.0.1", "he": "^1.2.0", "html-encoding-sniffer": "^3.0.0", @@ -17619,13 +18170,12 @@ "dev": true }, "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" } }, "jsbn": { @@ -17689,12 +18239,6 @@ } } }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, "json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -17728,6 +18272,15 @@ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, + "json5": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, "jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -17749,6 +18302,12 @@ "verror": "1.10.0" } }, + "just-curry-it": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/just-curry-it/-/just-curry-it-3.2.1.tgz", + "integrity": "sha512-Q8206k8pTY7krW32cdmPsP+DqqLgWx/hYPSj9/+7SYqSqz7UuwPbfSe07lQtvuuaVyiSJveXk0E5RydOuWwsEg==", + "dev": true + }, "just-debounce": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.1.0.tgz", @@ -17780,7 +18339,8 @@ "version": "0.28.7", "resolved": "https://registry.npmjs.org/ky/-/ky-0.28.7.tgz", "integrity": "sha512-a23i6qSr/ep15vdtw/zyEQIDLoUaKDg9Jf04CYl/0ns/wXNYna26zJpI+MeIFaPeDvkrjLPrKtKOiiI3IE53RQ==", - "dev": true + "dev": true, + "peer": true }, "last-run": { "version": "1.1.1", @@ -18117,12 +18677,6 @@ "lodash._reinterpolate": "^3.0.0" } }, - "lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", - "dev": true - }, "lodash.union": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", @@ -18157,6 +18711,15 @@ "integrity": "sha512-WpG9CcFAOjz/FtNht+QJeGpvVl/cdR6P0z6OcXSkr8wFJOsV2GRj2j10JLfjuA4aYkcKCNIEqRGCyTife9R8/g==", "dev": true }, + "loupe": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.3.tgz", + "integrity": "sha512-krIV4Cf1BIGIx2t1e6tucThhrBemUnIUjMtD2vN4mrMxnxpBvrcosBSpooqunBqP/hOEEV1w/Cr1YskGtqw5Jg==", + "dev": true, + "requires": { + "get-func-name": "^2.0.0" + } + }, "lowercase-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", @@ -18516,89 +19079,43 @@ "dev": true }, "mocha": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.1.3.tgz", - "integrity": "sha512-Xcpl9FqXOAYqI3j79pEtHBBnQgVXIhpULjGQa7DVb0Po+VzmSIK9kanAiWLHoRR/dbZ2qpdPshuXr8l1VaHCzw==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.1.tgz", + "integrity": "sha512-T7uscqjJVS46Pq1XDXyo9Uvey9gd3huT/DD9cYBb4K2Xc/vbKRPUWK067bxDQRK0yIz6Jxk73IrnimvASzBNAQ==", "dev": true, "requires": { "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "chokidar": "3.5.2", - "debug": "4.3.2", + "chokidar": "3.5.3", + "debug": "4.3.3", "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", - "glob": "7.1.7", + "glob": "7.2.0", "growl": "1.10.5", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", "minimatch": "3.0.4", "ms": "2.1.3", - "nanoid": "3.1.25", + "nanoid": "3.2.0", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", "which": "2.0.2", - "workerpool": "6.1.5", + "workerpool": "6.2.0", "yargs": "16.2.0", "yargs-parser": "20.2.4", "yargs-unparser": "2.0.0" }, "dependencies": { - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "requires": { - "ms": "2.1.2" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, "diff": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", "dev": true }, - "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -18679,9 +19196,9 @@ "optional": true }, "nanoid": { - "version": "3.1.25", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz", - "integrity": "sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", + "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", "dev": true }, "nanomatch": { @@ -18737,9 +19254,9 @@ } }, "node-fetch": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", - "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", "dev": true, "requires": { "whatwg-url": "^5.0.0" @@ -19625,6 +20142,12 @@ "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" }, + "pubsub-js": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/pubsub-js/-/pubsub-js-1.9.4.tgz", + "integrity": "sha512-hJYpaDvPH4w8ZX/0Fdf9ma1AwRgU353GfbaVfPjfJQf1KxZ2iHaHl3fAUw1qlJIR5dr4F3RzjGaWohYUEyoh7A==", + "dev": true + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -19664,75 +20187,35 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "puppeteer-core": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-11.0.0.tgz", - "integrity": "sha512-hfQ39KNP0qKplQ86iaCNXHH9zpWlV01UFdggt2qffgWeCBF9KMavwP/k/iK/JidPPWfOnKZhDLSHZVSUr73DtA==", + "version": "13.5.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-13.5.1.tgz", + "integrity": "sha512-dobVqWjV34ilyfQHR3BBnCYaekBYTi5MgegEYBRYd3s3uFy8jUpZEEWbaFjG9ETm+LGzR5Lmr0aF6LLuHtiuCg==", "dev": true, "requires": { - "debug": "4.3.2", - "devtools-protocol": "0.0.901419", + "cross-fetch": "3.1.5", + "debug": "4.3.3", + "devtools-protocol": "0.0.969999", "extract-zip": "2.0.1", "https-proxy-agent": "5.0.0", - "node-fetch": "2.6.5", "pkg-dir": "4.2.0", "progress": "2.0.3", "proxy-from-env": "1.1.0", "rimraf": "3.0.2", "tar-fs": "2.1.1", "unbzip2-stream": "1.4.3", - "ws": "8.2.3" + "ws": "8.5.0" }, "dependencies": { - "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, "devtools-protocol": { - "version": "0.0.901419", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.901419.tgz", - "integrity": "sha512-4INMPwNm9XRpBukhNbF7OB6fNTTCaI8pzy/fXg0xQzAy5h3zL1P8xT3QazgKqBrb/hAYwIBizqDBZ7GtJE74QQ==", + "version": "0.0.969999", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.969999.tgz", + "integrity": "sha512-6GfzuDWU0OFAuOvBokXpXPLxjOJ5DZ157Ue3sGQQM3LgAamb8m0R0ruSfN0DDu+XG5XJgT50i6zZ/0o8RglreQ==", "dev": true }, - "node-fetch": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz", - "integrity": "sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==", - "dev": true, - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", - "dev": true - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", - "dev": true - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "dev": true, - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", + "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", "dev": true, "requires": {} } @@ -20146,12 +20629,6 @@ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true - }, "require-main-filename": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", @@ -20329,9 +20806,9 @@ "dev": true }, "selenium-standalone": { - "version": "8.0.8", - "resolved": "https://registry.npmjs.org/selenium-standalone/-/selenium-standalone-8.0.8.tgz", - "integrity": "sha512-2NCHoK12dbLbtXPIdMe1ljiLAMflJDBezPhjJBwmnYz5/yGjCPv2R9ojHO6W1ctD9DOlqyi3AxoAvlT2nmB7Fw==", + "version": "8.0.9", + "resolved": "https://registry.npmjs.org/selenium-standalone/-/selenium-standalone-8.0.9.tgz", + "integrity": "sha512-Bl5Wbaeu5bKVmpLVATYeRcPsk9Bv3fr1aUvdC39UZjG6R/bem8j8Pj+dwB9F+PsS/owQnKh0BCg1KalZcAImbw==", "dev": true, "requires": { "commander": "^8.3.0", @@ -20488,17 +20965,6 @@ } } }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - } - }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -20774,12 +21240,6 @@ "extend-shallow": "^3.0.0" } }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, "sshpk": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", @@ -21055,39 +21515,6 @@ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" }, - "table": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/table/-/table-6.7.5.tgz", - "integrity": "sha512-LFNeryOqiQHqCVKzhkymKwt6ozeRhlm8IL1mE8rNUurkir4heF6PzMyRgaTa4tlyPTGGgXuvVOF/OLWiH09Lqw==", - "dev": true, - "requires": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "dependencies": { - "ajv": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz", - "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - } - } - }, "tar-fs": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", @@ -21254,12 +21681,6 @@ "is-negated-glob": "^1.0.0" } }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - }, "to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", @@ -21420,9 +21841,9 @@ "dev": true }, "typescript": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz", - "integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==", + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", "dev": true }, "ua-parser-js": { @@ -21599,6 +22020,12 @@ "prepend-http": "^2.0.0" } }, + "url-resolve-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/url-resolve-browser/-/url-resolve-browser-1.2.0.tgz", + "integrity": "sha512-L9PBPnlKNDFzt9ElK4br8I8Tufdm1xgv1GhMeiP7ZC87x0b7mr+4vSh13kmPq5km80JKX+UD2BeEFTCrFZ6xDA==", + "dev": true + }, "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", @@ -21796,42 +22223,107 @@ } }, "webdriver": { - "version": "7.16.9", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.16.9.tgz", - "integrity": "sha512-6bpiyE3/1ncgyNM/RwzEWjpxu2NLYyeYNu/97OMEwFMDV8EqvlZh3wFnODi6tY0K5t4dEryIPiyjF3MDVySRAg==", + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.17.3.tgz", + "integrity": "sha512-E1V/IKYjJoVjK9zhHfSCWeqORhgNlDuYydykm0h+CchEhMSgTmtTH/LYfXSx4myXzobdlIg6xhE7Jv7XPjSkAA==", "dev": true, "requires": { - "@types/node": "^16.11.1", - "@wdio/config": "7.16.3", - "@wdio/logger": "7.16.0", - "@wdio/protocols": "7.16.7", - "@wdio/types": "7.16.3", - "@wdio/utils": "7.16.3", + "@types/node": "^17.0.4", + "@wdio/config": "7.17.3", + "@wdio/logger": "7.17.3", + "@wdio/protocols": "7.17.3", + "@wdio/types": "7.17.3", + "@wdio/utils": "7.17.3", "got": "^11.0.2", - "ky": "^0.28.5", + "ky": "^0.30.0", "lodash.merge": "^4.6.1" + }, + "dependencies": { + "@types/node": { + "version": "17.0.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", + "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", + "dev": true + }, + "@wdio/config": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.17.3.tgz", + "integrity": "sha512-MSWCsx0w1EbxbwOD8ykTxHqgx208CWoz9n4oWHx7Q1APfetqWFLM4O7K8cdZS1gV4IvH4EAV9807L91K8r0JNw==", + "dev": true, + "requires": { + "@wdio/logger": "7.17.3", + "@wdio/types": "7.17.3", + "deepmerge": "^4.0.0", + "glob": "^7.1.2" + } + }, + "@wdio/logger": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.17.3.tgz", + "integrity": "sha512-hpvJDsJMX8G/8gXHOEipxkQPjojjA+BRCZqCvZRLCVpWm2JB7tBoMzu9sUJXcpSkY03b94KAd4EwNA2uNAf9aQ==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + } + }, + "@wdio/protocols": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.17.3.tgz", + "integrity": "sha512-DxVRil2uMDOshk0gMOrmemC9uEZuB5Dv4bJX/ozZwXPV9AHd6oJqUrsF/fs8bT9+4AWkE58yqsRBFc/pt7sFMw==", + "dev": true + }, + "@wdio/types": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.17.3.tgz", + "integrity": "sha512-j8kYdaMl4NFRS8M1bFDuEa3GMbUZbLQY7i6XEnJSetyW0GyMDLlzwcfXI4DdX85+3JbO5624UGKxVsQcuA7T3A==", + "dev": true, + "requires": { + "@types/node": "^17.0.4", + "got": "^11.8.1" + } + }, + "@wdio/utils": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.17.3.tgz", + "integrity": "sha512-20bGTCmgBNVKa2BJs3B5kxbsryjhfEOoKDnFjZ/rAVZYT1t1sg0e/W+vRfamd++NqTaIHOY/IKGEFiEnCw5nXw==", + "dev": true, + "requires": { + "@wdio/logger": "7.17.3", + "@wdio/types": "7.17.3", + "p-iteration": "^1.1.8" + } + }, + "ky": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/ky/-/ky-0.30.0.tgz", + "integrity": "sha512-X/u76z4JtDVq10u1JA5UQfatPxgPaVDMYTrgHyiTpGN2z4TMEJkIHsoSBBSg9SWZEIXTKsi9kHgiQ9o3Y/4yog==", + "dev": true + } } }, "webdriverio": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.16.10.tgz", - "integrity": "sha512-Idsn0084HqcqHa5/BW/75dwFEitSDi/hhXk+GRA0wZkVU7woE8ZKACsMS270kOADgXYU9XJBT8jo6YM3R3Sa+Q==", + "version": "7.17.4", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.17.4.tgz", + "integrity": "sha512-p7u2q7NJL7Et8FdSroq/Ltoi3KkKxERE79Srh9lFr6yRNPFqb46dJf/g4nljLhburnGkbNdYN15JWgyWYnnj9g==", "dev": true, "requires": { "@types/aria-query": "^5.0.0", - "@types/node": "^16.11.1", - "@wdio/config": "7.16.3", - "@wdio/logger": "7.16.0", - "@wdio/protocols": "7.16.7", - "@wdio/repl": "7.16.3", - "@wdio/types": "7.16.3", - "@wdio/utils": "7.16.3", + "@types/node": "^17.0.4", + "@wdio/config": "7.17.3", + "@wdio/logger": "7.17.3", + "@wdio/protocols": "7.17.3", + "@wdio/repl": "7.17.3", + "@wdio/types": "7.17.3", + "@wdio/utils": "7.17.3", "archiver": "^5.0.0", "aria-query": "^5.0.0", "css-shorthand-properties": "^1.1.1", "css-value": "^0.0.1", - "devtools": "7.16.10", - "devtools-protocol": "^0.0.944179", + "devtools": "7.17.3", + "devtools-protocol": "^0.0.979353", "fs-extra": "^10.0.0", "get-port": "^5.1.1", "grapheme-splitter": "^1.0.2", @@ -21839,13 +22331,90 @@ "lodash.isobject": "^3.0.2", "lodash.isplainobject": "^4.0.6", "lodash.zip": "^4.2.0", - "minimatch": "^3.0.4", - "puppeteer-core": "^11.0.0", + "minimatch": "^5.0.0", + "puppeteer-core": "^13.1.3", "query-selector-shadow-dom": "^1.0.0", "resq": "^1.9.1", "rgb2hex": "0.2.5", "serialize-error": "^8.0.0", - "webdriver": "7.16.9" + "webdriver": "7.17.3" + }, + "dependencies": { + "@types/node": { + "version": "17.0.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", + "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", + "dev": true + }, + "@wdio/config": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.17.3.tgz", + "integrity": "sha512-MSWCsx0w1EbxbwOD8ykTxHqgx208CWoz9n4oWHx7Q1APfetqWFLM4O7K8cdZS1gV4IvH4EAV9807L91K8r0JNw==", + "dev": true, + "requires": { + "@wdio/logger": "7.17.3", + "@wdio/types": "7.17.3", + "deepmerge": "^4.0.0", + "glob": "^7.1.2" + } + }, + "@wdio/logger": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.17.3.tgz", + "integrity": "sha512-hpvJDsJMX8G/8gXHOEipxkQPjojjA+BRCZqCvZRLCVpWm2JB7tBoMzu9sUJXcpSkY03b94KAd4EwNA2uNAf9aQ==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + } + }, + "@wdio/protocols": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.17.3.tgz", + "integrity": "sha512-DxVRil2uMDOshk0gMOrmemC9uEZuB5Dv4bJX/ozZwXPV9AHd6oJqUrsF/fs8bT9+4AWkE58yqsRBFc/pt7sFMw==", + "dev": true + }, + "@wdio/types": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.17.3.tgz", + "integrity": "sha512-j8kYdaMl4NFRS8M1bFDuEa3GMbUZbLQY7i6XEnJSetyW0GyMDLlzwcfXI4DdX85+3JbO5624UGKxVsQcuA7T3A==", + "dev": true, + "requires": { + "@types/node": "^17.0.4", + "got": "^11.8.1" + } + }, + "@wdio/utils": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.17.3.tgz", + "integrity": "sha512-20bGTCmgBNVKa2BJs3B5kxbsryjhfEOoKDnFjZ/rAVZYT1t1sg0e/W+vRfamd++NqTaIHOY/IKGEFiEnCw5nXw==", + "dev": true, + "requires": { + "@wdio/logger": "7.17.3", + "@wdio/types": "7.17.3", + "p-iteration": "^1.1.8" + } + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } } }, "webidl-conversions": { @@ -21909,9 +22478,9 @@ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" }, "workerpool": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.5.tgz", - "integrity": "sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", + "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", "dev": true }, "wrap-ansi": { @@ -21966,9 +22535,9 @@ "dev": true }, "yargs": { - "version": "17.3.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.0.tgz", - "integrity": "sha512-GQl1pWyDoGptFPJx9b9L6kmR33TGusZvXIZUT+BOz9f7X2L94oeAskFYLEg/FkhV06zZPBYLvLZRWeYId29lew==", + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz", + "integrity": "sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==", "dev": true, "requires": { "cliui": "^7.0.2", diff --git a/package.json b/package.json index a26267e61..e91149524 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "blockly", - "version": "7.20211209.4", + "version": "8.0.0", "description": "Blockly is a library for building visual programming editors.", "keywords": [ "blockly" @@ -26,6 +26,7 @@ "build:compressed": "npm run build:compiled", "build:deps": "gulp buildDeps", "build:langfiles": "gulp buildLangfiles", + "build-ts": "gulp buildTs && gulp build --compileTs", "bump": "npm --no-git-tag-version version 4.$(date +'%Y%m%d').0", "clean": "gulp clean", "clean:build": "gulp cleanBuildDir", @@ -50,7 +51,7 @@ "test": "tests/run_all_tests.sh", "test:generators": "tests/scripts/run_generators.sh", "test:mocha:interactive": "http-server ./ -o /tests/mocha/index.html -c-1", - "test:compile:advanced": "gulp buildAdvancedCompilationTest", + "test:compile:advanced": "gulp buildAdvancedCompilationTest --debug", "typings": "gulp typings", "updateGithubPages": "gulp gitUpdateGithubPages" }, @@ -68,16 +69,16 @@ "@blockly/block-test": "^2.0.1", "@blockly/dev-tools": "^3.0.1", "@blockly/theme-modern": "^2.1.1", + "@hyperjump/json-schema": "^0.18.4", "@wdio/selenium-standalone-service": "^7.10.1", - "babel-eslint": "^10.1.0", "chai": "^4.2.0", "clang-format": "^1.6.0", "closure-calculate-chunks": "^3.0.2", - "concurrently": "^6.0.0", - "eslint": "^7.28.0", + "concurrently": "^7.0.0", + "eslint": "^8.4.1", "eslint-config-google": "^0.14.0", - "google-closure-compiler": "^20211107.0.0", - "google-closure-deps": "^20211107.0.0", + "google-closure-compiler": "^20220301.0.0", + "google-closure-deps": "^20220202.0.0", "gulp": "^4.0.2", "gulp-clang-format": "^1.0.27", "gulp-concat": "^2.6.1", @@ -90,6 +91,7 @@ "gulp-umd": "^2.0.0", "http-server": "^14.0.0", "js-green-licenses": "^3.0.0", + "json5": "^2.2.0", "mocha": "^9.1.1", "readline-sync": "^1.4.10", "rimraf": "^3.0.2", diff --git a/php_compressed.js b/php_compressed.js index be85f1b00..38c878cec 100644 --- a/php_compressed.js +++ b/php_compressed.js @@ -7,10 +7,11 @@ } else if (typeof exports === 'object') { // Node.js module.exports = factory(require("./blockly_compressed.js")); } else { // Browser - root.Blockly.PHP = factory(root.Blockly); + var factoryExports = factory(root.Blockly); + root.Blockly.PHP = factoryExports; } -}(this, function(Blockly) { -const $=Blockly.internal_; +}(this, function(__parent__) { +var $=__parent__.__namespace__; var module$contents$Blockly$PHP_PHP=new $.module$exports$Blockly$Generator.Generator("PHP");module$contents$Blockly$PHP_PHP.addReservedWords("__halt_compiler,abstract,and,array,as,break,callable,case,catch,class,clone,const,continue,declare,default,die,do,echo,else,elseif,empty,enddeclare,endfor,endforeach,endif,endswitch,endwhile,eval,exit,extends,final,for,foreach,function,global,goto,if,implements,include,include_once,instanceof,insteadof,interface,isset,list,namespace,new,or,print,private,protected,public,require,require_once,return,static,switch,throw,trait,try,unset,use,var,while,xor,PHP_VERSION,PHP_MAJOR_VERSION,PHP_MINOR_VERSION,PHP_RELEASE_VERSION,PHP_VERSION_ID,PHP_EXTRA_VERSION,PHP_ZTS,PHP_DEBUG,PHP_MAXPATHLEN,PHP_OS,PHP_SAPI,PHP_EOL,PHP_INT_MAX,PHP_INT_SIZE,DEFAULT_INCLUDE_PATH,PEAR_INSTALL_DIR,PEAR_EXTENSION_DIR,PHP_EXTENSION_DIR,PHP_PREFIX,PHP_BINDIR,PHP_BINARY,PHP_MANDIR,PHP_LIBDIR,PHP_DATADIR,PHP_SYSCONFDIR,PHP_LOCALSTATEDIR,PHP_CONFIG_FILE_PATH,PHP_CONFIG_FILE_SCAN_DIR,PHP_SHLIB_SUFFIX,E_ERROR,E_WARNING,E_PARSE,E_NOTICE,E_CORE_ERROR,E_CORE_WARNING,E_COMPILE_ERROR,E_COMPILE_WARNING,E_USER_ERROR,E_USER_WARNING,E_USER_NOTICE,E_DEPRECATED,E_USER_DEPRECATED,E_ALL,E_STRICT,__COMPILER_HALT_OFFSET__,TRUE,FALSE,NULL,__CLASS__,__DIR__,__FILE__,__FUNCTION__,__LINE__,__METHOD__,__NAMESPACE__,__TRAIT__"); module$contents$Blockly$PHP_PHP.ORDER_ATOMIC=0;module$contents$Blockly$PHP_PHP.ORDER_CLONE=1;module$contents$Blockly$PHP_PHP.ORDER_NEW=1;module$contents$Blockly$PHP_PHP.ORDER_MEMBER=2.1;module$contents$Blockly$PHP_PHP.ORDER_FUNCTION_CALL=2.2;module$contents$Blockly$PHP_PHP.ORDER_POWER=3;module$contents$Blockly$PHP_PHP.ORDER_INCREMENT=4;module$contents$Blockly$PHP_PHP.ORDER_DECREMENT=4;module$contents$Blockly$PHP_PHP.ORDER_BITWISE_NOT=4;module$contents$Blockly$PHP_PHP.ORDER_CAST=4; module$contents$Blockly$PHP_PHP.ORDER_SUPPRESS_ERROR=4;module$contents$Blockly$PHP_PHP.ORDER_INSTANCEOF=5;module$contents$Blockly$PHP_PHP.ORDER_LOGICAL_NOT=6;module$contents$Blockly$PHP_PHP.ORDER_UNARY_PLUS=7.1;module$contents$Blockly$PHP_PHP.ORDER_UNARY_NEGATION=7.2;module$contents$Blockly$PHP_PHP.ORDER_MULTIPLICATION=8.1;module$contents$Blockly$PHP_PHP.ORDER_DIVISION=8.2;module$contents$Blockly$PHP_PHP.ORDER_MODULUS=8.3;module$contents$Blockly$PHP_PHP.ORDER_ADDITION=9.1; @@ -27,15 +28,13 @@ module$contents$Blockly$PHP_PHP.getAdjusted=function(a,b,c,d,e){c=c||0;e=e||this (a="("+a+")"));return a};$.Blockly.PHP=module$contents$Blockly$PHP_PHP;var module$exports$Blockly$PHP$variables={};$.Blockly.PHP.variables_get=function(a){return[$.Blockly.PHP.nameDB_.getName(a.getFieldValue("VAR"),$.module$exports$Blockly$Names.NameType.VARIABLE),$.Blockly.PHP.ORDER_ATOMIC]};$.Blockly.PHP.variables_set=function(a){var b=$.Blockly.PHP.valueToCode(a,"VALUE",$.Blockly.PHP.ORDER_ASSIGNMENT)||"0";return $.Blockly.PHP.nameDB_.getName(a.getFieldValue("VAR"),$.module$exports$Blockly$Names.NameType.VARIABLE)+" = "+b+";\n"};var module$exports$Blockly$PHP$variablesDynamic={};$.Blockly.PHP.variables_get_dynamic=$.Blockly.PHP.variables_get;$.Blockly.PHP.variables_set_dynamic=$.Blockly.PHP.variables_set;var module$exports$Blockly$PHP$texts={};$.Blockly.PHP.text=function(a){return[$.Blockly.PHP.quote_(a.getFieldValue("TEXT")),$.Blockly.PHP.ORDER_ATOMIC]};$.Blockly.PHP.text_multiline=function(a){a=$.Blockly.PHP.multiline_quote_(a.getFieldValue("TEXT"));var b=-1!==a.indexOf(".")?$.Blockly.PHP.ORDER_STRING_CONCAT:$.Blockly.PHP.ORDER_ATOMIC;return[a,b]}; $.Blockly.PHP.text_join=function(a){if(0===a.itemCount_)return["''",$.Blockly.PHP.ORDER_ATOMIC];if(1===a.itemCount_)return[$.Blockly.PHP.valueToCode(a,"ADD0",$.Blockly.PHP.ORDER_NONE)||"''",$.Blockly.PHP.ORDER_NONE];if(2===a.itemCount_){var b=$.Blockly.PHP.valueToCode(a,"ADD0",$.Blockly.PHP.ORDER_STRING_CONCAT)||"''";a=$.Blockly.PHP.valueToCode(a,"ADD1",$.Blockly.PHP.ORDER_STRING_CONCAT)||"''";return[b+" . "+a,$.Blockly.PHP.ORDER_STRING_CONCAT]}b=Array(a.itemCount_);for(var c=0;c 0";break;case "NEGATIVE":d=b+" < 0";break;case "DIVISIBLE_BY":a=$.Blockly.PHP.valueToCode(a,"DIVISOR",$.Blockly.PHP.ORDER_MODULUS)||"0",d=b+" % "+a+" == 0"}return[d,$.Blockly.PHP.ORDER_EQUALITY]};$.Blockly.PHP.math_change=function(a){var b=$.Blockly.PHP.valueToCode(a,"DELTA",$.Blockly.PHP.ORDER_ADDITION)||"0";return $.Blockly.PHP.nameDB_.getName(a.getFieldValue("VAR"),$.module$exports$Blockly$Names.NameType.VARIABLE)+" += "+b+";\n"};$.Blockly.PHP.math_round=$.Blockly.PHP.math_single; +$.Blockly.PHP.math_number_property=function(a){var b={EVEN:[""," % 2 == 0",$.Blockly.PHP.ORDER_MODULUS,$.Blockly.PHP.ORDER_EQUALITY],ODD:[""," % 2 == 1",$.Blockly.PHP.ORDER_MODULUS,$.Blockly.PHP.ORDER_EQUALITY],WHOLE:["is_int(",")",$.Blockly.PHP.ORDER_NONE,$.Blockly.PHP.ORDER_FUNCTION_CALL],POSITIVE:[""," > 0",$.Blockly.PHP.ORDER_RELATIONAL,$.Blockly.PHP.ORDER_RELATIONAL],NEGATIVE:[""," < 0",$.Blockly.PHP.ORDER_RELATIONAL,$.Blockly.PHP.ORDER_RELATIONAL],DIVISIBLE_BY:[null,null,$.Blockly.PHP.ORDER_MODULUS, +$.Blockly.PHP.ORDER_EQUALITY],PRIME:[null,null,$.Blockly.PHP.ORDER_NONE,$.Blockly.PHP.ORDER_FUNCTION_CALL]},c=a.getFieldValue("PROPERTY");b=$.$jscomp.makeIterator(b[c]);var d=b.next().value,e=b.next().value,f=b.next().value;b=b.next().value;f=$.Blockly.PHP.valueToCode(a,"NUMBER_TO_CHECK",f)||"0";if("PRIME"===c)a=$.Blockly.PHP.provideFunction_("math_isPrime","\nfunction "+$.Blockly.PHP.FUNCTION_NAME_PLACEHOLDER_+"($n) {\n // https://en.wikipedia.org/wiki/Primality_test#Naive_methods\n if ($n == 2 || $n == 3) {\n return true;\n }\n // False if n is NaN, negative, is 1, or not whole.\n // And false if n is divisible by 2 or 3.\n if (!is_numeric($n) || $n <= 1 || $n % 1 != 0 || $n % 2 == 0 || $n % 3 == 0) {\n return false;\n }\n // Check all the numbers of form 6k +/- 1, up to sqrt(n).\n for ($x = 6; $x <= sqrt($n) + 1; $x += 6) {\n if ($n % ($x - 1) == 0 || $n % ($x + 1) == 0) {\n return false;\n }\n }\n return true;\n}\n")+ +"("+f+")";else if("DIVISIBLE_BY"===c){a=$.Blockly.PHP.valueToCode(a,"DIVISOR",$.Blockly.PHP.ORDER_MODULUS)||"0";if("0"===a)return["false",$.Blockly.PHP.ORDER_ATOMIC];a=f+" % "+a+" == 0"}else a=d+f+e;return[a,b]};$.Blockly.PHP.math_change=function(a){var b=$.Blockly.PHP.valueToCode(a,"DELTA",$.Blockly.PHP.ORDER_ADDITION)||"0";return $.Blockly.PHP.nameDB_.getName(a.getFieldValue("VAR"),$.module$exports$Blockly$Names.NameType.VARIABLE)+" += "+b+";\n"};$.Blockly.PHP.math_round=$.Blockly.PHP.math_single; $.Blockly.PHP.math_trig=$.Blockly.PHP.math_single; -$.Blockly.PHP.math_on_list=function(a){var b=a.getFieldValue("OP");switch(b){case "SUM":a=$.Blockly.PHP.valueToCode(a,"LIST",$.Blockly.PHP.ORDER_FUNCTION_CALL)||"array()";a="array_sum("+a+")";break;case "MIN":a=$.Blockly.PHP.valueToCode(a,"LIST",$.Blockly.PHP.ORDER_FUNCTION_CALL)||"array()";a="min("+a+")";break;case "MAX":a=$.Blockly.PHP.valueToCode(a,"LIST",$.Blockly.PHP.ORDER_FUNCTION_CALL)||"array()";a="max("+a+")";break;case "AVERAGE":b=$.Blockly.PHP.provideFunction_("math_mean",["function "+ -$.Blockly.PHP.FUNCTION_NAME_PLACEHOLDER_+"($myList) {"," return array_sum($myList) / count($myList);","}"]);a=$.Blockly.PHP.valueToCode(a,"LIST",$.Blockly.PHP.ORDER_NONE)||"array()";a=b+"("+a+")";break;case "MEDIAN":b=$.Blockly.PHP.provideFunction_("math_median",["function "+$.Blockly.PHP.FUNCTION_NAME_PLACEHOLDER_+"($arr) {"," sort($arr,SORT_NUMERIC);"," return (count($arr) % 2) ? $arr[floor(count($arr)/2)] : "," ($arr[floor(count($arr)/2)] + $arr[floor(count($arr)/2) - 1]) / 2;","}"]);a= -$.Blockly.PHP.valueToCode(a,"LIST",$.Blockly.PHP.ORDER_NONE)||"[]";a=b+"("+a+")";break;case "MODE":b=$.Blockly.PHP.provideFunction_("math_modes",["function "+$.Blockly.PHP.FUNCTION_NAME_PLACEHOLDER_+"($values) {"," if (empty($values)) return array();"," $counts = array_count_values($values);"," arsort($counts); // Sort counts in descending order"," $modes = array_keys($counts, current($counts), true);"," return $modes;","}"]);a=$.Blockly.PHP.valueToCode(a,"LIST",$.Blockly.PHP.ORDER_NONE)||"[]"; -a=b+"("+a+")";break;case "STD_DEV":b=$.Blockly.PHP.provideFunction_("math_standard_deviation",["function "+$.Blockly.PHP.FUNCTION_NAME_PLACEHOLDER_+"($numbers) {"," $n = count($numbers);"," if (!$n) return null;"," $mean = array_sum($numbers) / count($numbers);"," foreach($numbers as $key => $num) $devs[$key] = pow($num - $mean, 2);"," return sqrt(array_sum($devs) / (count($devs) - 1));","}"]);a=$.Blockly.PHP.valueToCode(a,"LIST",$.Blockly.PHP.ORDER_NONE)||"[]";a=b+"("+a+")";break;case "RANDOM":b= -$.Blockly.PHP.provideFunction_("math_random_list",["function "+$.Blockly.PHP.FUNCTION_NAME_PLACEHOLDER_+"($list) {"," $x = rand(0, count($list)-1);"," return $list[$x];","}"]);a=$.Blockly.PHP.valueToCode(a,"LIST",$.Blockly.PHP.ORDER_NONE)||"[]";a=b+"("+a+")";break;default:throw Error("Unknown operator: "+b);}return[a,$.Blockly.PHP.ORDER_FUNCTION_CALL]}; +$.Blockly.PHP.math_on_list=function(a){var b=a.getFieldValue("OP");switch(b){case "SUM":a=$.Blockly.PHP.valueToCode(a,"LIST",$.Blockly.PHP.ORDER_FUNCTION_CALL)||"array()";a="array_sum("+a+")";break;case "MIN":a=$.Blockly.PHP.valueToCode(a,"LIST",$.Blockly.PHP.ORDER_FUNCTION_CALL)||"array()";a="min("+a+")";break;case "MAX":a=$.Blockly.PHP.valueToCode(a,"LIST",$.Blockly.PHP.ORDER_FUNCTION_CALL)||"array()";a="max("+a+")";break;case "AVERAGE":b=$.Blockly.PHP.provideFunction_("math_mean","\nfunction "+ +$.Blockly.PHP.FUNCTION_NAME_PLACEHOLDER_+"($myList) {\n return array_sum($myList) / count($myList);\n}\n");a=$.Blockly.PHP.valueToCode(a,"LIST",$.Blockly.PHP.ORDER_NONE)||"array()";a=b+"("+a+")";break;case "MEDIAN":b=$.Blockly.PHP.provideFunction_("math_median","\nfunction "+$.Blockly.PHP.FUNCTION_NAME_PLACEHOLDER_+"($arr) {\n sort($arr,SORT_NUMERIC);\n return (count($arr) % 2) ? $arr[floor(count($arr) / 2)] :\n ($arr[floor(count($arr) / 2)] + $arr[floor(count($arr) / 2) - 1]) / 2;\n}\n"); +a=$.Blockly.PHP.valueToCode(a,"LIST",$.Blockly.PHP.ORDER_NONE)||"[]";a=b+"("+a+")";break;case "MODE":b=$.Blockly.PHP.provideFunction_("math_modes","\nfunction "+$.Blockly.PHP.FUNCTION_NAME_PLACEHOLDER_+"($values) {\n if (empty($values)) return array();\n $counts = array_count_values($values);\n arsort($counts); // Sort counts in descending order\n $modes = array_keys($counts, current($counts), true);\n return $modes;\n}\n");a=$.Blockly.PHP.valueToCode(a,"LIST",$.Blockly.PHP.ORDER_NONE)||"[]"; +a=b+"("+a+")";break;case "STD_DEV":b=$.Blockly.PHP.provideFunction_("math_standard_deviation","\nfunction "+$.Blockly.PHP.FUNCTION_NAME_PLACEHOLDER_+"($numbers) {\n $n = count($numbers);\n if (!$n) return null;\n $mean = array_sum($numbers) / count($numbers);\n foreach($numbers as $key => $num) $devs[$key] = pow($num - $mean, 2);\n return sqrt(array_sum($devs) / (count($devs) - 1));\n}\n");a=$.Blockly.PHP.valueToCode(a,"LIST",$.Blockly.PHP.ORDER_NONE)||"[]";a=b+"("+a+")";break;case "RANDOM":b= +$.Blockly.PHP.provideFunction_("math_random_list","\nfunction "+$.Blockly.PHP.FUNCTION_NAME_PLACEHOLDER_+"($list) {\n $x = rand(0, count($list)-1);\n return $list[$x];\n}\n");a=$.Blockly.PHP.valueToCode(a,"LIST",$.Blockly.PHP.ORDER_NONE)||"[]";a=b+"("+a+")";break;default:throw Error("Unknown operator: "+b);}return[a,$.Blockly.PHP.ORDER_FUNCTION_CALL]}; $.Blockly.PHP.math_modulo=function(a){var b=$.Blockly.PHP.valueToCode(a,"DIVIDEND",$.Blockly.PHP.ORDER_MODULUS)||"0";a=$.Blockly.PHP.valueToCode(a,"DIVISOR",$.Blockly.PHP.ORDER_MODULUS)||"0";return[b+" % "+a,$.Blockly.PHP.ORDER_MODULUS]}; $.Blockly.PHP.math_constrain=function(a){var b=$.Blockly.PHP.valueToCode(a,"VALUE",$.Blockly.PHP.ORDER_NONE)||"0",c=$.Blockly.PHP.valueToCode(a,"LOW",$.Blockly.PHP.ORDER_NONE)||"0";a=$.Blockly.PHP.valueToCode(a,"HIGH",$.Blockly.PHP.ORDER_NONE)||"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_NONE)||"0";a=$.Blockly.PHP.valueToCode(a,"TO",$.Blockly.PHP.ORDER_NONE)||"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_int=function(a){var b=$.Blockly.PHP.valueToCode(a,"FROM",$.Blockly.PHP.ORDER_NONE)||"0";a=$.Blockly.PHP.valueToCode(a,"TO",$.Blockly.PHP.ORDER_NONE)||"0";return[$.Blockly.PHP.provideFunction_("math_random_int","\nfunction "+$.Blockly.PHP.FUNCTION_NAME_PLACEHOLDER_+"($a, $b) {\n if ($a > $b) {\n return rand($b, $a);\n }\n return rand($a, $b);\n}\n")+"("+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.math_atan2=function(a){var b=$.Blockly.PHP.valueToCode(a,"X",$.Blockly.PHP.ORDER_NONE)||"0";return["atan2("+($.Blockly.PHP.valueToCode(a,"Y",$.Blockly.PHP.ORDER_NONE)||"0")+", "+b+") / pi() * 180",$.Blockly.PHP.ORDER_DIVISION]};var module$exports$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";var c=$.Blockly.PHP.statementToCode(a,"DO");c=$.Blockly.PHP.addLoopTrap(c,a);a="";var d=$.Blockly.PHP.nameDB_.getDistinctName("count",$.module$exports$Blockly$Names.NameType.VARIABLE),e=b;b.match(/^\w+$/)||(0,$.module$exports$Blockly$utils$string.isNumber)(b)||(e=$.Blockly.PHP.nameDB_.getDistinctName("repeat_end",$.module$exports$Blockly$Names.NameType.VARIABLE), 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);b&&(c="!"+c);return"while ("+c+") {\n"+d+"}\n"}; @@ -77,37 +76,34 @@ $.Blockly.PHP.logic_compare=function(a){var b={EQ:"==",NEQ:"!=",LT:"<",LTE:"<=", $.Blockly.PHP.logic_operation=function(a){var b="AND"===a.getFieldValue("OP")?"&&":"||",c="&&"===b?$.Blockly.PHP.ORDER_LOGICAL_AND:$.Blockly.PHP.ORDER_LOGICAL_OR,d=$.Blockly.PHP.valueToCode(a,"A",c);a=$.Blockly.PHP.valueToCode(a,"B",c);if(d||a){var e="&&"===b?"true":"false";d||(d=e);a||(a=e)}else a=d="false";return[d+" "+b+" "+a,c]};$.Blockly.PHP.logic_negate=function(a){var b=$.Blockly.PHP.ORDER_LOGICAL_NOT;return["!"+($.Blockly.PHP.valueToCode(a,"BOOL",b)||"true"),b]}; $.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]};var module$exports$Blockly$PHP$lists={};$.Blockly.PHP.lists_create_empty=function(a){return["array()",$.Blockly.PHP.ORDER_FUNCTION_CALL]};$.Blockly.PHP.lists_create_with=function(a){for(var b=Array(a.itemCount_),c=0;c "strnatcasecmp",',' "TEXT" => "strcmp",',' "IGNORE_CASE" => "strcasecmp"'," );"," $sortCmp = $sortCmpFuncs[$type];"," $list2 = $list;", -" usort($list2, $sortCmp);"," if ($direction == -1) {"," $list2 = array_reverse($list2);"," }"," return $list2;","}"])+"("+b+', "'+a+'", '+c+")",$.Blockly.PHP.ORDER_FUNCTION_CALL]}; -$.Blockly.PHP.lists_split=function(a){var b=$.Blockly.PHP.valueToCode(a,"INPUT",$.Blockly.PHP.ORDER_NONE),c=$.Blockly.PHP.valueToCode(a,"DELIM",$.Blockly.PHP.ORDER_NONE)||"''";a=a.getFieldValue("MODE");if("SPLIT"===a)b||(b="''"),a="explode";else if("JOIN"===a)b||(b="array()"),a="implode";else throw Error("Unknown mode: "+a);return[a+"("+c+", "+b+")",$.Blockly.PHP.ORDER_FUNCTION_CALL]}; -$.Blockly.PHP.lists_reverse=function(a){return["array_reverse("+($.Blockly.PHP.valueToCode(a,"LIST",$.Blockly.PHP.ORDER_NONE)||"[]")+")",$.Blockly.PHP.ORDER_FUNCTION_CALL]};var module$exports$Blockly$PHP$colour={};$.Blockly.PHP.colour_picker=function(a){return[$.Blockly.PHP.quote_(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_NONE)||0,c=$.Blockly.PHP.valueToCode(a,"GREEN",$.Blockly.PHP.ORDER_NONE)||0;a=$.Blockly.PHP.valueToCode(a,"BLUE",$.Blockly.PHP.ORDER_NONE)||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_NONE)||"'#000000'",c=$.Blockly.PHP.valueToCode(a,"COLOUR2",$.Blockly.PHP.ORDER_NONE)||"'#000000'";a=$.Blockly.PHP.valueToCode(a,"RATIO",$.Blockly.PHP.ORDER_NONE)||.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));"," $r2 = hexdec(substr($c2, 1, 2));"," $g2 = hexdec(substr($c2, 3, 2));"," $b2 = hexdec(substr($c2, 5, 2));"," $r = round($r1 * (1 - $ratio) + $r2 * $ratio);"," $g = round($g1 * (1 - $ratio) + $g2 * $ratio);"," $b = round($b1 * (1 - $ratio) + $b2 * $ratio);"," $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]};var module$exports$Blockly$PHP$all={}; - +d=(0,$.module$exports$Blockly$utils$string.isNumber)(String(c))||String(c).match(/^\(.+\)$/)?d+c:d+("("+c+")");break;default:throw Error("Unhandled option (lists_getSublist).");}b="array_slice("+b+", "+c+", "+d+")"}else{var e=$.Blockly.PHP.getAdjusted(a,"AT1");a=$.Blockly.PHP.getAdjusted(a,"AT2");b=$.Blockly.PHP.provideFunction_("lists_get_sublist","\nfunction "+$.Blockly.PHP.FUNCTION_NAME_PLACEHOLDER_+"($list, $where1, $at1, $where2, $at2) {\n if ($where1 == 'FROM_END') {\n $at1 = count($list) - 1 - $at1;\n } else if ($where1 == 'FIRST') {\n $at1 = 0;\n } else if ($where1 != 'FROM_START') {\n throw new Exception('Unhandled option (lists_get_sublist).');\n }\n $length = 0;\n if ($where2 == 'FROM_START') {\n $length = $at2 - $at1 + 1;\n } else if ($where2 == 'FROM_END') {\n $length = count($list) - $at1 - $at2;\n } else if ($where2 == 'LAST') {\n $length = count($list) - $at1;\n } else {\n throw new Exception('Unhandled option (lists_get_sublist).');\n }\n return array_slice($list, $at1, $length);\n}\n")+ +"("+b+", '"+c+"', "+e+", '"+d+"', "+a+")"}return[b,$.Blockly.PHP.ORDER_FUNCTION_CALL]}; +$.Blockly.PHP.lists_sort=function(a){var b=$.Blockly.PHP.valueToCode(a,"LIST",$.Blockly.PHP.ORDER_NONE)||"array()",c="1"===a.getFieldValue("DIRECTION")?1:-1;a=a.getFieldValue("TYPE");return[$.Blockly.PHP.provideFunction_("lists_sort","\nfunction "+$.Blockly.PHP.FUNCTION_NAME_PLACEHOLDER_+"($list, $type, $direction) {\n $sortCmpFuncs = array(\n 'NUMERIC' => 'strnatcasecmp',\n 'TEXT' => 'strcmp',\n 'IGNORE_CASE' => 'strcasecmp'\n );\n $sortCmp = $sortCmpFuncs[$type];\n $list2 = $list;\n usort($list2, $sortCmp);\n if ($direction == -1) {\n $list2 = array_reverse($list2);\n }\n return $list2;\n}\n")+"("+ +b+', "'+a+'", '+c+")",$.Blockly.PHP.ORDER_FUNCTION_CALL]};$.Blockly.PHP.lists_split=function(a){var b=$.Blockly.PHP.valueToCode(a,"INPUT",$.Blockly.PHP.ORDER_NONE),c=$.Blockly.PHP.valueToCode(a,"DELIM",$.Blockly.PHP.ORDER_NONE)||"''";a=a.getFieldValue("MODE");if("SPLIT"===a)b||(b="''"),a="explode";else if("JOIN"===a)b||(b="array()"),a="implode";else throw Error("Unknown mode: "+a);return[a+"("+c+", "+b+")",$.Blockly.PHP.ORDER_FUNCTION_CALL]}; +$.Blockly.PHP.lists_reverse=function(a){return["array_reverse("+($.Blockly.PHP.valueToCode(a,"LIST",$.Blockly.PHP.ORDER_NONE)||"[]")+")",$.Blockly.PHP.ORDER_FUNCTION_CALL]};var module$exports$Blockly$PHP$colour={};$.Blockly.PHP.colour_picker=function(a){return[$.Blockly.PHP.quote_(a.getFieldValue("COLOUR")),$.Blockly.PHP.ORDER_ATOMIC]};$.Blockly.PHP.colour_random=function(a){return[$.Blockly.PHP.provideFunction_("colour_random","\nfunction "+$.Blockly.PHP.FUNCTION_NAME_PLACEHOLDER_+"() {\n return '#' . str_pad(dechex(mt_rand(0, 0xFFFFFF)), 6, '0', STR_PAD_LEFT);\n}\n")+"()",$.Blockly.PHP.ORDER_FUNCTION_CALL]}; +$.Blockly.PHP.colour_rgb=function(a){var b=$.Blockly.PHP.valueToCode(a,"RED",$.Blockly.PHP.ORDER_NONE)||0,c=$.Blockly.PHP.valueToCode(a,"GREEN",$.Blockly.PHP.ORDER_NONE)||0;a=$.Blockly.PHP.valueToCode(a,"BLUE",$.Blockly.PHP.ORDER_NONE)||0;return[$.Blockly.PHP.provideFunction_("colour_rgb","\nfunction "+$.Blockly.PHP.FUNCTION_NAME_PLACEHOLDER_+"($r, $g, $b) {\n $r = round(max(min($r, 100), 0) * 2.55);\n $g = round(max(min($g, 100), 0) * 2.55);\n $b = round(max(min($b, 100), 0) * 2.55);\n $hex = '#';\n $hex .= str_pad(dechex($r), 2, '0', STR_PAD_LEFT);\n $hex .= str_pad(dechex($g), 2, '0', STR_PAD_LEFT);\n $hex .= str_pad(dechex($b), 2, '0', STR_PAD_LEFT);\n return $hex;\n}\n")+ +"("+b+", "+c+", "+a+")",$.Blockly.PHP.ORDER_FUNCTION_CALL]}; +$.Blockly.PHP.colour_blend=function(a){var b=$.Blockly.PHP.valueToCode(a,"COLOUR1",$.Blockly.PHP.ORDER_NONE)||"'#000000'",c=$.Blockly.PHP.valueToCode(a,"COLOUR2",$.Blockly.PHP.ORDER_NONE)||"'#000000'";a=$.Blockly.PHP.valueToCode(a,"RATIO",$.Blockly.PHP.ORDER_NONE)||.5;return[$.Blockly.PHP.provideFunction_("colour_blend","\nfunction "+$.Blockly.PHP.FUNCTION_NAME_PLACEHOLDER_+"($c1, $c2, $ratio) {\n $ratio = max(min($ratio, 1), 0);\n $r1 = hexdec(substr($c1, 1, 2));\n $g1 = hexdec(substr($c1, 3, 2));\n $b1 = hexdec(substr($c1, 5, 2));\n $r2 = hexdec(substr($c2, 1, 2));\n $g2 = hexdec(substr($c2, 3, 2));\n $b2 = hexdec(substr($c2, 5, 2));\n $r = round($r1 * (1 - $ratio) + $r2 * $ratio);\n $g = round($g1 * (1 - $ratio) + $g2 * $ratio);\n $b = round($b1 * (1 - $ratio) + $b2 * $ratio);\n $hex = '#';\n $hex .= str_pad(dechex($r), 2, '0', STR_PAD_LEFT);\n $hex .= str_pad(dechex($g), 2, '0', STR_PAD_LEFT);\n $hex .= str_pad(dechex($b), 2, '0', STR_PAD_LEFT);\n return $hex;\n}\n")+"("+ +b+", "+c+", "+a+")",$.Blockly.PHP.ORDER_FUNCTION_CALL]};var module$exports$Blockly$PHP$all={}; +$.Blockly.PHP.__namespace__=$; return $.Blockly.PHP; })); diff --git a/php_compressed.js.map b/php_compressed.js.map index 624e88dcb..1b27749f6 100644 --- a/php_compressed.js.map +++ b/php_compressed.js.map @@ -1 +1 @@ -{"version":3,"sources":["generators/php.js","generators/php/variables.js","generators/php/variables_dynamic.js","generators/php/text.js","generators/php/procedures.js","generators/php/math.js","generators/php/loops.js","generators/php/logic.js","generators/php/lists.js","generators/php/colour.js","generators/php/all.js"],"names":["PHP","Generator","addReservedWords","ORDER_ATOMIC","ORDER_CLONE","ORDER_NEW","ORDER_MEMBER","ORDER_FUNCTION_CALL","ORDER_POWER","ORDER_INCREMENT","ORDER_DECREMENT","ORDER_BITWISE_NOT","ORDER_CAST","ORDER_SUPPRESS_ERROR","ORDER_INSTANCEOF","ORDER_LOGICAL_NOT","ORDER_UNARY_PLUS","ORDER_UNARY_NEGATION","ORDER_MULTIPLICATION","ORDER_DIVISION","ORDER_MODULUS","ORDER_ADDITION","ORDER_SUBTRACTION","ORDER_STRING_CONCAT","ORDER_BITWISE_SHIFT","ORDER_RELATIONAL","ORDER_EQUALITY","ORDER_REFERENCE","ORDER_BITWISE_AND","ORDER_BITWISE_XOR","ORDER_BITWISE_OR","ORDER_LOGICAL_AND","ORDER_LOGICAL_OR","ORDER_IF_NULL","ORDER_CONDITIONAL","ORDER_ASSIGNMENT","ORDER_LOGICAL_AND_WEAK","ORDER_LOGICAL_XOR","ORDER_LOGICAL_OR_WEAK","ORDER_NONE","ORDER_OVERRIDES","isInitialized","init","PHP.init","workspace","Object","getPrototypeOf","call","nameDB_","reset","Names","RESERVED_WORDS_","setVariableMap","getVariableMap","populateVariables","populateProcedures","finish","PHP.finish","code","definitions","objectUtils","values","definitions_","join","scrubNakedValue","PHP.scrubNakedValue","line","quote_","PHP.quote_","string","replace","multiline_quote_","PHP.multiline_quote_","split","map","lines","scrub_","PHP.scrub_","block","opt_thisOnly","commentCode","outputConnection","targetConnection","comment","getCommentText","stringUtils","wrap","COMMENT_WRAP","prefixLines","i","inputList","length","type","inputTypes","VALUE","childBlock","connection","targetBlock","allNestedComments","nextBlock","nextConnection","nextCode","blockToCode","getAdjusted","PHP.getAdjusted","atId","opt_delta","opt_negate","opt_order","delta","order","options","oneBasedIndex","defaultAtIndex","outerOrder","innerOrder","at","valueToCode","isNumber","Number","Math","floor","exports","getName","getFieldValue","NameType","VARIABLE","argument0","varName","indexOf","itemCount_","element0","element1","elements","Array","value","functionName","provideFunction_","FUNCTION_NAME_PLACEHOLDER_","text","operator","substring","errorIndex","indexAdjustment","where","Error","where1","where2","at1","at2","OPERATORS","getField","msg","sub","from","to","globals","usedVariables","Variables","allUsedVarModels","variable","name","getVars","push","devVarList","allDeveloperVariables","DEVELOPER_VARIABLE","globalStr","INDENT","funcName","PROCEDURE","xfix1","STATEMENT_PREFIX","injectId","STATEMENT_SUFFIX","loopTrap","INFINITE_LOOP_TRAP","branch","statementToCode","returnValue","xfix2","args","variables","tuple","hasReturnValue_","Infinity","argument1","arg","CONSTANTS","number_to_check","dropdown_property","divisor","func","list","argument2","repeats","String","addLoopTrap","loopVar","getDistinctName","endVar","match","until","variable0","increment","up","step","abs","startVar","incVar","xfix","loop","getSurroundLoop","suppressPrefixSuffix","n","conditionCode","branchCode","getInput","defaultArgument","value_if","value_then","value_else","element","repeatCount","mode","cachedList","listVar","xVar","listCode","direction","value_input","value_delim","red","green","blue","c1","c2","ratio"],"mappings":"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","file":"php_compressed.js","sourceRoot":"./"} \ No newline at end of file +{"version":3,"sources":["generators/php.js","generators/php/variables.js","generators/php/variables_dynamic.js","generators/php/text.js","generators/php/procedures.js","generators/php/math.js","generators/php/loops.js","generators/php/logic.js","generators/php/lists.js","generators/php/colour.js","generators/php/all.js"],"names":["PHP","Generator","addReservedWords","ORDER_ATOMIC","ORDER_CLONE","ORDER_NEW","ORDER_MEMBER","ORDER_FUNCTION_CALL","ORDER_POWER","ORDER_INCREMENT","ORDER_DECREMENT","ORDER_BITWISE_NOT","ORDER_CAST","ORDER_SUPPRESS_ERROR","ORDER_INSTANCEOF","ORDER_LOGICAL_NOT","ORDER_UNARY_PLUS","ORDER_UNARY_NEGATION","ORDER_MULTIPLICATION","ORDER_DIVISION","ORDER_MODULUS","ORDER_ADDITION","ORDER_SUBTRACTION","ORDER_STRING_CONCAT","ORDER_BITWISE_SHIFT","ORDER_RELATIONAL","ORDER_EQUALITY","ORDER_REFERENCE","ORDER_BITWISE_AND","ORDER_BITWISE_XOR","ORDER_BITWISE_OR","ORDER_LOGICAL_AND","ORDER_LOGICAL_OR","ORDER_IF_NULL","ORDER_CONDITIONAL","ORDER_ASSIGNMENT","ORDER_LOGICAL_AND_WEAK","ORDER_LOGICAL_XOR","ORDER_LOGICAL_OR_WEAK","ORDER_NONE","ORDER_OVERRIDES","isInitialized","init","PHP.init","workspace","Object","getPrototypeOf","call","nameDB_","reset","Names","RESERVED_WORDS_","setVariableMap","getVariableMap","populateVariables","populateProcedures","finish","PHP.finish","code","definitions","objectUtils","values","definitions_","join","scrubNakedValue","PHP.scrubNakedValue","line","quote_","PHP.quote_","string","replace","multiline_quote_","PHP.multiline_quote_","split","map","lines","scrub_","PHP.scrub_","block","opt_thisOnly","commentCode","outputConnection","targetConnection","comment","getCommentText","stringUtils","wrap","COMMENT_WRAP","prefixLines","i","inputList","length","type","inputTypes","VALUE","childBlock","connection","targetBlock","allNestedComments","nextBlock","nextConnection","nextCode","blockToCode","getAdjusted","PHP.getAdjusted","atId","opt_delta","opt_negate","opt_order","delta","order","options","oneBasedIndex","defaultAtIndex","outerOrder","innerOrder","at","valueToCode","isNumber","Number","Math","floor","exports","getName","getFieldValue","NameType","VARIABLE","argument0","varName","indexOf","itemCount_","element0","element1","elements","Array","value","functionName","provideFunction_","FUNCTION_NAME_PLACEHOLDER_","text","operator","substring","errorIndex","indexAdjustment","where","Error","where1","where2","at1","at2","OPERATORS","getField","msg","sub","from","to","globals","usedVariables","Variables","allUsedVarModels","variable","name","getVars","push","devVarList","allDeveloperVariables","DEVELOPER_VARIABLE","globalStr","INDENT","funcName","PROCEDURE","xfix1","STATEMENT_PREFIX","injectId","STATEMENT_SUFFIX","loopTrap","INFINITE_LOOP_TRAP","branch","statementToCode","returnValue","xfix2","args","variables","tuple","hasReturnValue_","Infinity","argument1","arg","CONSTANTS","PROPERTIES","dropdownProperty","prefix","suffix","inputOrder","outputOrder","numberToCheck","divisor","func","list","argument2","repeats","String","addLoopTrap","loopVar","getDistinctName","endVar","match","until","variable0","increment","up","step","abs","startVar","incVar","xfix","loop","getSurroundLoop","suppressPrefixSuffix","n","conditionCode","branchCode","getInput","defaultArgument","value_if","value_then","value_else","element","repeatCount","mode","cachedList","listVar","xVar","listCode","direction","value_input","value_delim","red","green","blue","c1","c2","ratio"],"mappings":"A;;;;;;;;;;;;;;AA4BA,IAAMA,gCAAM,IAAIC,CAAAA,CAAAA,gCAAAA,CAAAA,SAAJ,CAAc,KAAd,CAQZD,gCAAIE,CAAAA,gBAAJ,CAEI,mqCAFJ,CA0BAF;+BAAIG,CAAAA,YAAJ,CAAmB,CACnBH,gCAAII,CAAAA,WAAJ,CAAkB,CAClBJ,gCAAIK,CAAAA,SAAJ,CAAgB,CAChBL,gCAAIM,CAAAA,YAAJ,CAAmB,GACnBN,gCAAIO,CAAAA,mBAAJ,CAA0B,GAC1BP,gCAAIQ,CAAAA,WAAJ,CAAkB,CAClBR,gCAAIS,CAAAA,eAAJ,CAAsB,CACtBT,gCAAIU,CAAAA,eAAJ,CAAsB,CACtBV,gCAAIW,CAAAA,iBAAJ,CAAwB,CACxBX,gCAAIY,CAAAA,UAAJ,CAAiB,CACjBZ;+BAAIa,CAAAA,oBAAJ,CAA2B,CAC3Bb,gCAAIc,CAAAA,gBAAJ,CAAuB,CACvBd,gCAAIe,CAAAA,iBAAJ,CAAwB,CACxBf,gCAAIgB,CAAAA,gBAAJ,CAAuB,GACvBhB,gCAAIiB,CAAAA,oBAAJ,CAA2B,GAC3BjB,gCAAIkB,CAAAA,oBAAJ,CAA2B,GAC3BlB,gCAAImB,CAAAA,cAAJ,CAAqB,GACrBnB,gCAAIoB,CAAAA,aAAJ,CAAoB,GACpBpB,gCAAIqB,CAAAA,cAAJ,CAAqB,GACrBrB;+BAAIsB,CAAAA,iBAAJ,CAAwB,GACxBtB,gCAAIuB,CAAAA,mBAAJ,CAA0B,GAC1BvB,gCAAIwB,CAAAA,mBAAJ,CAA0B,EAC1BxB,gCAAIyB,CAAAA,gBAAJ,CAAuB,EACvBzB,gCAAI0B,CAAAA,cAAJ,CAAqB,EACrB1B,gCAAI2B,CAAAA,eAAJ,CAAsB,EACtB3B,gCAAI4B,CAAAA,iBAAJ,CAAwB,EACxB5B,gCAAI6B,CAAAA,iBAAJ,CAAwB,EACxB7B,gCAAI8B,CAAAA,gBAAJ,CAAuB,EACvB9B;+BAAI+B,CAAAA,iBAAJ,CAAwB,EACxB/B,gCAAIgC,CAAAA,gBAAJ,CAAuB,EACvBhC,gCAAIiC,CAAAA,aAAJ,CAAoB,EACpBjC,gCAAIkC,CAAAA,iBAAJ,CAAwB,EACxBlC,gCAAImC,CAAAA,gBAAJ,CAAuB,EACvBnC,gCAAIoC,CAAAA,sBAAJ,CAA6B,EAC7BpC,gCAAIqC,CAAAA,iBAAJ,CAAwB,EACxBrC,gCAAIsC,CAAAA,qBAAJ,CAA4B,EAC5BtC,gCAAIuC,CAAAA,UAAJ,CAAiB,EAMjBvC;+BAAIwC,CAAAA,eAAJ,CAAsB,CAGpB,CAACxC,+BAAIM,CAAAA,YAAL,CAAmBN,+BAAIO,CAAAA,mBAAvB,CAHoB,CAMpB,CAACP,+BAAIM,CAAAA,YAAL,CAAmBN,+BAAIM,CAAAA,YAAvB,CANoB,CAQpB,CAACN,+BAAIe,CAAAA,iBAAL,CAAwBf,+BAAIe,CAAAA,iBAA5B,CARoB,CAUpB,CAACf,+BAAIkB,CAAAA,oBAAL,CAA2BlB,+BAAIkB,CAAAA,oBAA/B,CAVoB,CAYpB,CAAClB,+BAAIqB,CAAAA,cAAL,CAAqBrB,+BAAIqB,CAAAA,cAAzB,CAZoB;AAcpB,CAACrB,+BAAI+B,CAAAA,iBAAL,CAAwB/B,+BAAI+B,CAAAA,iBAA5B,CAdoB,CAgBpB,CAAC/B,+BAAIgC,CAAAA,gBAAL,CAAuBhC,+BAAIgC,CAAAA,gBAA3B,CAhBoB,CAuBtBhC,gCAAIyC,CAAAA,aAAJ,CAAoB,CAAA,CAMpBzC;+BAAI0C,CAAAA,IAAJ,CAAWC,QAAQ,CAACC,CAAD,CAAY,CAE7BC,MAAOC,CAAAA,cAAP,CAAsB,IAAtB,CAA4BJ,CAAAA,IAAKK,CAAAA,IAAjC,CAAsC,IAAtC,CAEK,KAAKC,CAAAA,OAAV,CAGE,IAAKA,CAAAA,OAAQC,CAAAA,KAAb,EAHF,CACE,IAAKD,CAAAA,OADP,CACiB,IAAIE,CAAAA,CAAAA,4BAAAA,CAAAA,KAAJ,CAAU,IAAKC,CAAAA,eAAf,CAAgC,GAAhC,CAKjB,KAAKH,CAAAA,OAAQI,CAAAA,cAAb,CAA4BR,CAAUS,CAAAA,cAAV,EAA5B,CACA,KAAKL,CAAAA,OAAQM,CAAAA,iBAAb,CAA+BV,CAA/B,CACA,KAAKI,CAAAA,OAAQO,CAAAA,kBAAb,CAAgCX,CAAhC,CAEA,KAAKH,CAAAA,aAAL,CAAqB,CAAA,CAdQ,CAsB/BzC;+BAAIwD,CAAAA,MAAJ,CAAaC,QAAQ,CAACC,CAAD,CAAO,CAE1B,IAAMC,EAAc,GAAAC,CAAAA,CAAAA,mCAAYC,CAAAA,MAAZ,EAAmB,IAAKC,CAAAA,YAAxB,CAEpBJ,EAAA,CAAOb,MAAOC,CAAAA,cAAP,CAAsB,IAAtB,CAA4BU,CAAAA,MAAOT,CAAAA,IAAnC,CAAwC,IAAxC,CAA8CW,CAA9C,CACP,KAAKjB,CAAAA,aAAL,CAAqB,CAAA,CAErB,KAAKO,CAAAA,OAAQC,CAAAA,KAAb,EACA,OAAOU,EAAYI,CAAAA,IAAZ,CAAiB,MAAjB,CAAP,CAAkC,QAAlC,CAA6CL,CARnB,CAiB5B1D,gCAAIgE,CAAAA,eAAJ,CAAsBC,QAAQ,CAACC,CAAD,CAAO,CACnC,MAAOA,EAAP,CAAc,KADqB,CAWrClE,gCAAImE,CAAAA,MAAJ,CAAaC,QAAQ,CAACC,CAAD,CAAS,CAC5BA,CAAA,CAASA,CAAOC,CAAAA,OAAP,CAAe,KAAf,CAAsB,MAAtB,CACKA,CAAAA,OADL,CACa,KADb,CACoB,MADpB,CAEKA,CAAAA,OAFL,CAEa,IAFb,CAEmB,KAFnB,CAGT,OAAO,GAAP,CAAcD,CAAd,CAAuB,GAJK,CAc9BrE;+BAAIuE,CAAAA,gBAAJ,CAAuBC,QAAQ,CAACH,CAAD,CAAS,CAKtC,MAJcA,EAAOI,CAAAA,KAAP,CAAa,KAAb,CAAoBC,CAAAA,GAApBC,CAAwB,IAAKR,CAAAA,MAA7BQ,CAIDZ,CAAAA,IAAN,CAAW,cAAX,CAL+B,CAkBxC/D;+BAAI4E,CAAAA,MAAJ,CAAaC,QAAQ,CAACC,CAAD,CAAQpB,CAAR,CAAcqB,CAAd,CAA4B,CAC/C,IAAIC,EAAc,EAElB,IAAI,CAACF,CAAMG,CAAAA,gBAAX,EAA+B,CAACH,CAAMG,CAAAA,gBAAiBC,CAAAA,gBAAvD,CAAyE,CAEvE,IAAIC,EAAUL,CAAMM,CAAAA,cAAN,EACVD,EAAJ,GACEA,CACA,CADU,GAAAE,CAAAA,CAAAA,mCAAYC,CAAAA,IAAZ,EAAiBH,CAAjB,CAA0B,IAAKI,CAAAA,YAA/B,CAA8C,CAA9C,CACV,CAAAP,CAAA,EAAe,IAAKQ,CAAAA,WAAL,CAAiBL,CAAjB,CAA0B,KAA1B,CAAf,CAAkD,IAFpD,CAMA,KAAK,IAAIM,EAAI,CAAb,CAAgBA,CAAhB,CAAoBX,CAAMY,CAAAA,SAAUC,CAAAA,MAApC,CAA4CF,CAAA,EAA5C,CACMX,CAAMY,CAAAA,SAAN,CAAgBD,CAAhB,CAAmBG,CAAAA,IAAvB,GAAgCC,CAAAA,CAAAA,iCAAAA,CAAAA,UAAWC,CAAAA,KAA3C,GACQC,CADR,CACqBjB,CAAMY,CAAAA,SAAN,CAAgBD,CAAhB,CAAmBO,CAAAA,UAAWC,CAAAA,WAA9B,EADrB,IAGId,CAHJ,CAGc,IAAKe,CAAAA,iBAAL,CAAuBH,CAAvB,CAHd,IAKMf,CALN,EAKqB,IAAKQ,CAAAA,WAAL,CAAiBL,CAAjB,CAA0B,KAA1B,CALrB,CAVqE,CAqBnEgB,CAAAA,CAAYrB,CAAMsB,CAAAA,cAAlBD;AAAoCrB,CAAMsB,CAAAA,cAAeH,CAAAA,WAArB,EACpCI,EAAAA,CAAWtB,CAAA,CAAe,EAAf,CAAoB,IAAKuB,CAAAA,WAAL,CAAiBH,CAAjB,CACrC,OAAOnB,EAAP,CAAqBtB,CAArB,CAA4B2C,CA1BmB,CAsCjDrG;+BAAIuG,CAAAA,WAAJ,CAAkBC,QAAQ,CAAC1B,CAAD,CAAQ2B,CAAR,CAAcC,CAAd,CAAyBC,CAAzB,CAAqCC,CAArC,CAAgD,CACpEC,CAAAA,CAAQH,CAARG,EAAqB,CACrBC,EAAAA,CAAQF,CAARE,EAAqB,IAAKvE,CAAAA,UAC1BuC,EAAMlC,CAAAA,SAAUmE,CAAAA,OAAQC,CAAAA,aAA5B,EACEH,CAAA,EAEF,KAAII,EAAiBnC,CAAMlC,CAAAA,SAAUmE,CAAAA,OAAQC,CAAAA,aAAxB,CAAwC,GAAxC,CAA8C,GAAnE,CACIE,EAAaJ,CAEjB,IAAY,CAAZ,CAAID,CAAJ,CAEE,IAAAM,EADAD,CACAC,CADa,IAAK9F,CAAAA,cADpB,KAGmB,EAAZ,CAAIwF,CAAJ,CAELM,CAFK,CACLD,CADK,CACQ,IAAK5F,CAAAA,iBADb,CAGIqF,CAHJ,GAKLQ,CALK,CAILD,CAJK,CAIQ,IAAKjG,CAAAA,oBAJb,CAOHmG,EAAAA,CAAK,IAAKC,CAAAA,WAAL,CAAiBvC,CAAjB,CAAwB2B,CAAxB,CAA8BS,CAA9B,CAALE,EAAkDH,CAElD,IAAA5B,CAAAA,CAAAA,mCAAYiC,CAAAA,QAAZ,EAAqBF,CAArB,CAAJ,EAEEA,CACA,CADKG,MAAA,CAAOH,CAAP,CACL,CADkBP,CAClB,CAAIF,CAAJ,GACES,CADF,CACO,CAACA,CADR,CAHF,GAQc,CAAZ,CAAIP,CAAJ,CACEO,CADF,CACOA,CADP,CACY,KADZ,CACoBP,CADpB,CAEmB,CAFnB,CAEWA,CAFX,GAGEO,CAHF,CAGOA,CAHP,CAGY,KAHZ,CAGoB,CAACP,CAHrB,CAcA,CATIF,CASJ,GAPIS,CAOJ,CARMP,CAAJ,CACO,IADP,CACcO,CADd,CACmB,GADnB,CAGO,GAHP,CAGaA,CAKf,EAFAD,CAEA,CAFaK,IAAKC,CAAAA,KAAL,CAAWN,CAAX,CAEb,CADAL,CACA,CADQU,IAAKC,CAAAA,KAAL,CAAWX,CAAX,CACR,CAAIK,CAAJ,EAAkBL,CAAlB,EAA2BK,CAA3B;CACEC,CADF,CACO,GADP,CACaA,CADb,CACkB,GADlB,CAtBF,CA0BA,OAAOA,EA/CiE,CAkD1EM,EAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAU1H,+B,CCpSV,IAAA,qCAAA,EAMAA,EAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,aAAA,CAAuB,QAAQ,CAAC8E,CAAD,CAAQ,CAIrC,MAAO,CADH9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIgD,CAAAA,OAAQ2E,CAAAA,OAAZjE,CAAoBoB,CAAM8C,CAAAA,aAAN,CAAoB,KAApB,CAApBlE,CAAgDmE,CAAAA,CAAAA,4BAAAA,CAAAA,QAASC,CAAAA,QAAzDpE,CACG,CAAO1D,CAAAA,CAAAA,OAAAA,CAAAA,GAAIG,CAAAA,YAAX,CAJ8B,CAOvCH,EAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,aAAA,CAAuB,QAAQ,CAAC8E,CAAD,CAAQ,CAErC,IAAMiD,EACF/H,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,OAAvB,CAAgC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAImC,CAAAA,gBAApC,CADE4F,EACuD,GAG7D,OADI/H,EAAAA,CAAAA,OAAAA,CAAAA,GAAIgD,CAAAA,OAAQ2E,CAAAA,OAAZK,CAAoBlD,CAAM8C,CAAAA,aAAN,CAAoB,KAApB,CAApBI,CAAgDH,CAAAA,CAAAA,4BAAAA,CAAAA,QAASC,CAAAA,QAAzDE,CACJ,CAAiB,KAAjB,CAAyBD,CAAzB,CAAqC,KANA,C,CCbvC,IAAA,4CAAA,EAQA/H,EAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,qBAAA,CAA+BA,CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,aAC/BA,EAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,qBAAA,CAA+BA,CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,a,CCT/B,IAAA,iCAAA,EAMAA,EAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,IAAA,CAAc,QAAQ,CAAC8E,CAAD,CAAQ,CAG5B,MAAO,CADM9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAImE,CAAAA,MAAJT,CAAWoB,CAAM8C,CAAAA,aAAN,CAAoB,MAApB,CAAXlE,CACN,CAAO1D,CAAAA,CAAAA,OAAAA,CAAAA,GAAIG,CAAAA,YAAX,CAHqB,CAM9BH,EAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,cAAA,CAAwB,QAAQ,CAAC8E,CAAD,CAAQ,CAEhCpB,CAAAA,CAAO1D,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuE,CAAAA,gBAAJ,CAAqBO,CAAM8C,CAAAA,aAAN,CAAoB,MAApB,CAArB,CACb,KAAMd,EACoB,CAAC,CAAvB,GAAApD,CAAKuE,CAAAA,OAAL,CAAa,GAAb,CAAA,CAA2BjI,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuB,CAAAA,mBAA/B,CAAqDvB,CAAAA,CAAAA,OAAAA,CAAAA,GAAIG,CAAAA,YAC7D,OAAO,CAACuD,CAAD,CAAOoD,CAAP,CAL+B,CAQxC9G;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,SAAA,CAAmB,QAAQ,CAAC8E,CAAD,CAAQ,CAEjC,GAAyB,CAAzB,GAAIA,CAAMoD,CAAAA,UAAV,CACE,MAAO,CAAC,IAAD,CAAOlI,CAAAA,CAAAA,OAAAA,CAAAA,GAAIG,CAAAA,YAAX,CACF,IAAyB,CAAzB,GAAI2E,CAAMoD,CAAAA,UAAV,CAGL,MAAO,CAFSlI,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAnC,CAET,EAF2D,IAE3D,CAAOvC,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAX,CACF,IAAyB,CAAzB,GAAIuC,CAAMoD,CAAAA,UAAV,CAA4B,CACjC,IAAMC,EACFnI,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuB,CAAAA,mBAAnC,CADE4G,EACyD,IACzDC,EAAAA,CACFpI,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuB,CAAAA,mBAAnC,CADE6G,EACyD,IAE/D,OAAO,CADMD,CACN,CADiB,KACjB,CADyBC,CACzB,CAAOpI,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuB,CAAAA,mBAAX,CAN0B,CAQ3B8G,CAAAA,CAAeC,KAAJ,CAAUxD,CAAMoD,CAAAA,UAAhB,CACjB,KAAK,IAAIzC,EAAI,CAAb,CAAgBA,CAAhB,CAAoBX,CAAMoD,CAAAA,UAA1B,CAAsCzC,CAAA,EAAtC,CACE4C,CAAA,CAAS5C,CAAT,CAAA;AAAczF,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,KAAvB,CAA+BW,CAA/B,CAAkCzF,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAtC,CAAd,EAAmE,IAGrE,OAAO,CADM,oBACN,CAD+B8F,CAAStE,CAAAA,IAAT,CAAc,GAAd,CAC/B,CADoD,IACpD,CAAO/D,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAX,CArBwB,CAyBnCP,EAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,WAAA,CAAqB,QAAQ,CAAC8E,CAAD,CAAQ,CAEnC,IAAMkD,EACFhI,CAAAA,CAAAA,OAAAA,CAAAA,GAAIgD,CAAAA,OAAQ2E,CAAAA,OAAZ,CAAoB7C,CAAM8C,CAAAA,aAAN,CAAoB,KAApB,CAApB,CAAgDC,CAAAA,CAAAA,4BAAAA,CAAAA,QAASC,CAAAA,QAAzD,CACES,EAAAA,CAAQvI,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAImC,CAAAA,gBAAnC,CAARoG,EAAgE,IACtE,OAAOP,EAAP,CAAiB,MAAjB,CAA0BO,CAA1B,CAAkC,KALC,CAQrCvI;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,WAAA,CAAqB,QAAQ,CAAC8E,CAAD,CAAQ,CAEnC,IAAM0D,EAAexI,CAAAA,CAAAA,OAAAA,CAAAA,GAAIyI,CAAAA,gBAAJ,CAAqB,QAArB,CAA+B,aAA/B,CACZzI,CAAAA,CAAAA,OAAAA,CAAAA,GAAI0I,CAAAA,0BADQ,CAA+B,uGAA/B,CAQfC,EAAAA,CAAO3I,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,OAAvB,CAAgC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAApC,CAAPoG,EAA0D,IAChE,OAAO,CAACH,CAAD,CAAgB,GAAhB,CAAsBG,CAAtB,CAA6B,GAA7B,CAAkC3I,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAtC,CAX4B,CAcrCP,EAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,YAAA,CAAsB,QAAQ,CAAC8E,CAAD,CAAQ,CAGpC,MAAO,CAAC,QAAD,EADM9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,OAAvB,CAAgC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAApC,CACN,EADyD,IACzD,EAAmB,GAAnB,CAAwBvC,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAA5B,CAH6B,CAMtCP;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,YAAA,CAAsB,QAAQ,CAAC8E,CAAD,CAAQ,CAEpC,IAAM8D,EAC6B,OAA/B,GAAA9D,CAAM8C,CAAAA,aAAN,CAAoB,KAApB,CAAA,CAAyC,QAAzC,CAAoD,SADxD,CAEMiB,EAAY7I,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAnC,CAAZsG,EAA8D,IAFpE,CAGMF,EAAO3I,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,OAAvB,CAAgC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAApC,CAAPoG,EAA0D,IAHhE,CAIIG,EAAa,KAJjB,CAKIC,EAAkB,EAClBjE,EAAMlC,CAAAA,SAAUmE,CAAAA,OAAQC,CAAAA,aAA5B,GACE8B,CACA,CADa,IACb,CAAAC,CAAA,CAAkB,MAFpB,CAcA,OAAO,CAVc/I,CAAAA,CAAAA,OAAAA,CAAAA,GAAIyI,CAAAA,gBAAJD,CACc,OAA/B,GAAA1D,CAAM8C,CAAAA,aAAN,CAAoB,KAApB,CAAA,CAAyC,cAAzC,CACyC,kBAFxBY,CAGjB,aAHiBA,CAIZxI,CAAAA,CAAAA,OAAAA,CAAAA,GAAI0I,CAAAA,0BAJQF,CAGjB,+BAHiBA,CAKZI,CALYJ,CAGjB,+CAHiBA;AAMKM,CANLN,CAGjB,SAHiBA,CAMyBO,CANzBP,CAGjB,QAHiBA,CAUd,CADqB,GACrB,CAD2BG,CAC3B,CADkC,IAClC,CADyCE,CACzC,CADqD,GACrD,CAAO7I,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAX,CAtB6B,CAyBtCP;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,WAAA,CAAqB,QAAQ,CAAC8E,CAAD,CAAQ,CAEnC,IAAMkE,EAAQlE,CAAM8C,CAAAA,aAAN,CAAoB,OAApB,CAARoB,EAAwC,YAA9C,CAEML,EAAO3I,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,OAAvB,CAD4B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAChC,CAAPoG,EAAqD,IAC3D,QAAQK,CAAR,EACE,KAAK,OAAL,CAEE,MAAO,CADM,SACN,CADkBL,CAClB,CADyB,SACzB,CAAO3I,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAX,CAET,MAAK,MAAL,CAEE,MAAO,CADM,SACN,CADkBoI,CAClB,CADyB,OACzB,CAAO3I,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAX,CAET,MAAK,YAAL,CAGE,MAFM6G,EAEC,CAFIpH,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuG,CAAAA,WAAJ,CAAgBzB,CAAhB,CAAuB,IAAvB,CAEJ,CAAA,CADM,SACN,CADkB6D,CAClB,CADyB,IACzB,CADgCvB,CAChC,CADqC,MACrC,CAAOpH,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAX,CAET,MAAK,UAAL,CAGE,MAFM6G,EAEC,CAFIpH,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuG,CAAAA,WAAJ,CAAgBzB,CAAhB,CAAuB,IAAvB,CAA6B,CAA7B,CAAgC,CAAA,CAAhC,CAEJ;AAAA,CADM,SACN,CADkB6D,CAClB,CADyB,IACzB,CADgCvB,CAChC,CADqC,MACrC,CAAOpH,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAX,CAET,MAAK,QAAL,CAOE,MAAO,CANcP,CAAAA,CAAAA,OAAAA,CAAAA,GAAIyI,CAAAA,gBAAJD,CAAqB,oBAArBA,CAA2C,aAA3CA,CAChBxI,CAAAA,CAAAA,OAAAA,CAAAA,GAAI0I,CAAAA,0BADYF,CAA2C,6DAA3CA,CAMd,CADqB,GACrB,CAD2BG,CAC3B,CADkC,GAClC,CAAO3I,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAX,CA1BX,CA6BA,KAAM0I,MAAA,CAAM,iCAAN,CAAN,CAlCmC,CAqCrCjJ;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,iBAAA,CAA2B,QAAQ,CAAC8E,CAAD,CAAQ,CAEzC,IAAMoE,EAASpE,CAAM8C,CAAAA,aAAN,CAAoB,QAApB,CAAf,CACMuB,EAASrE,CAAM8C,CAAAA,aAAN,CAAoB,QAApB,CADf,CAEMe,EAAO3I,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,QAAvB,CAAiC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAArC,CAAPoG,EAA2D,IACjE,IAAe,OAAf,GAAIO,CAAJ,EAAqC,MAArC,GAA0BC,CAA1B,CAEE,MAAO,CADMR,CACN,CAAO3I,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAX,CAEP,KAAM6G,EAAMpJ,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuG,CAAAA,WAAJ,CAAgBzB,CAAhB,CAAuB,KAAvB,CACNuE,EAAAA,CAAMrJ,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuG,CAAAA,WAAJ,CAAgBzB,CAAhB,CAAuB,KAAvB,CAyBZ,OAAO,CAxBc9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIyI,CAAAA,gBAAJD,CAAqB,oBAArBA,CAA2C,aAA3CA,CACdxI,CAAAA,CAAAA,OAAAA,CAAAA,GAAI0I,CAAAA,0BADUF,CAA2C,moBAA3CA,CAwBd;AAFqB,GAErB,CAF2BG,CAE3B,CAFkC,KAElC,CAF2CO,CAE3C,CAFoD,KAEpD,CAF6DE,CAE7D,CADH,KACG,CADMD,CACN,CADe,KACf,CADwBE,CACxB,CAD8B,GAC9B,CAAOrJ,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAX,CAnCgC,CAuC3CP,EAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,eAAA,CAAyB,QAAQ,CAAC8E,CAAD,CAAQ,CAEvC,IAAM6D,EAAO3I,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAnC,CAAPoG,EAAyD,IAA/D,CACIjF,CACgC,YAApC,GAAIoB,CAAM8C,CAAAA,aAAN,CAAoB,MAApB,CAAJ,CACElE,CADF,CACS,aADT,CACyBiF,CADzB,CACgC,GADhC,CAE2C,WAApC,GAAI7D,CAAM8C,CAAAA,aAAN,CAAoB,MAApB,CAAJ,CACLlE,CADK,CACE,aADF,CACkBiF,CADlB,CACyB,GADzB,CAEoC,WAFpC,GAEI7D,CAAM8C,CAAAA,aAAN,CAAoB,MAApB,CAFJ,GAGLlE,CAHK,CAGE,qBAHF,CAG0BiF,CAH1B,CAGiC,IAHjC,CAKP,OAAO,CAACjF,CAAD,CAAO1D,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAX,CAXgC,CAczCP;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,SAAA,CAAmB,QAAQ,CAAC8E,CAAD,CAAQ,CAGjC,IAAM8D,EADYU,CAAC,KAAQ,OAATA,CAAkB,MAAS,OAA3BA,CAAoC,KAAQ,MAA5CA,CACD,CAAUxE,CAAM8C,CAAAA,aAAN,CAAoB,MAApB,CAAV,CACXe,EAAAA,CAAO3I,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAnC,CAAPoG,EAAyD,IAC/D,OAAO,CAACC,CAAD,CAAY,GAAZ,CAAkBD,CAAlB,CAAyB,GAAzB,CAA8B3I,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAlC,CAL0B,CAQnCP,EAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,UAAA,CAAoB,QAAQ,CAAC8E,CAAD,CAAQ,CAGlC,MAAO,QAAP,EADY9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAnC,CACZ,EAD8D,IAC9D,EAAwB,MAHU,CAMpCvC;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,eAAA,CAAyB,QAAQ,CAAC8E,CAAD,CAAQ,CAUvC,IAAIpB,EAAO,WAAPA,EAPAoB,CAAMyE,CAAAA,QAAN,CAAe,MAAf,CAAJC,CAEQxJ,CAAAA,CAAAA,OAAAA,CAAAA,GAAImE,CAAAA,MAAJ,CAAWW,CAAM8C,CAAAA,aAAN,CAAoB,MAApB,CAAX,CAFR4B,CAKQxJ,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAnC,CALRiH,EAK0D,IAEtD9F,EAA2B,GACkB,SACjD,GADiBoB,CAAM8C,CAAAA,aAAN,CAAoB,MAApB,CACjB,GACElE,CADF,CACS,WADT,CACuBA,CADvB,CAC8B,GAD9B,CAGA,OAAO,CAACA,CAAD,CAAO1D,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAX,CAfgC,CAkBzCP,EAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,WAAA,CAAqBA,CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,eAErBA;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,UAAA,CAAoB,QAAQ,CAAC8E,CAAD,CAAQ,CAClC,IAAM6D,EAAO3I,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAnC,CAAPoG,EAAyD,IACzDc,EAAAA,CAAMzJ,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,KAAvB,CAA8B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAlC,CAANkH,EAAuD,IAI7D,OAAO,CAHM,SAGN,CAHkBA,CAGlB,CAHwB,mBAGxB,CAFYd,CAEZ,CAFmB,uBAEnB,CADkBA,CAClB,CADyB,IACzB,CADgCc,CAChC,CADsC,GACtC,CAAOzJ,CAAAA,CAAAA,OAAAA,CAAAA,GAAIkC,CAAAA,iBAAX,CAN2B,CASpClC;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,YAAA,CAAsB,QAAQ,CAAC8E,CAAD,CAAQ,CACpC,IAAM6D,EAAO3I,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAnC,CAAPoG,EAAyD,IAA/D,CACMe,EAAO1J,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAnC,CAAPmH,EAAyD,IACzDC,EAAAA,CAAK3J,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,IAAvB,CAA6B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAjC,CAALoH,EAAqD,IAE3D,OAAO,CADM,cACN,CADuBD,CACvB,CAD8B,IAC9B,CADqCC,CACrC,CAD0C,IAC1C,CADiDhB,CACjD,CADwD,GACxD,CAAO3I,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAX,CAL6B,CAQtCP,EAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,YAAA,CAAsB,QAAQ,CAAC8E,CAAD,CAAQ,CAGpC,MAAO,CADM,SACN,EAFM9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAnC,CAEN,EAFwD,IAExD,EADyB,GACzB,CAAOvC,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAX,CAH6B,C,CC/OtC,IAAA,sCAAA,EAOAP;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,oBAAA,CAA8B,QAAQ,CAAC8E,CAAD,CAAQ,CAO5C,IAHA,IAAM8E,EAAU,EAAhB,CACMhH,EAAYkC,CAAMlC,CAAAA,SADxB,CAEMiH,EAAgB,GAAAC,CAAAA,CAAAA,gCAAUC,CAAAA,gBAAV,EAA2BnH,CAA3B,CAAhBiH,EAAyD,EAF/D,CAGSpE,EAAI,CAHb,CAGgBuE,CAAhB,CAA0BA,CAA1B,CAAqCH,CAAA,CAAcpE,CAAd,CAArC,CAAuDA,CAAA,EAAvD,CACQuC,CACN,CADgBgC,CAASC,CAAAA,IACzB,CAAyC,CAAC,CAA1C,GAAInF,CAAMoF,CAAAA,OAAN,EAAgBjC,CAAAA,OAAhB,CAAwBD,CAAxB,CAAJ,EACE4B,CAAQO,CAAAA,IAAR,CAAanK,CAAAA,CAAAA,OAAAA,CAAAA,GAAIgD,CAAAA,OAAQ2E,CAAAA,OAAZ,CAAoBK,CAApB,CAA6BH,CAAAA,CAAAA,4BAAAA,CAAAA,QAASC,CAAAA,QAAtC,CAAb,CAIEsC,EAAAA,CAAa,GAAAN,CAAAA,CAAAA,gCAAUO,CAAAA,qBAAV,EAAgCzH,CAAhC,CACnB,KAAS6C,CAAT,CAAa,CAAb,CAAgBA,CAAhB,CAAoB2E,CAAWzE,CAAAA,MAA/B,CAAuCF,CAAA,EAAvC,CACEmE,CAAQO,CAAAA,IAAR,CACInK,CAAAA,CAAAA,OAAAA,CAAAA,GAAIgD,CAAAA,OAAQ2E,CAAAA,OAAZ,CAAoByC,CAAA,CAAW3E,CAAX,CAApB,CAAmCoC,CAAAA,CAAAA,4BAAAA,CAAAA,QAASyC,CAAAA,kBAA5C,CADJ,CAGIC,EAAAA,CACFX,CAAQjE,CAAAA,MAAR,CAAiB3F,CAAAA,CAAAA,OAAAA,CAAAA,GAAIwK,CAAAA,MAArB;AAA8B,SAA9B,CAA0CZ,CAAQ7F,CAAAA,IAAR,CAAa,IAAb,CAA1C,CAA+D,KAA/D,CAAuE,EAErE0G,EAAAA,CACFzK,CAAAA,CAAAA,OAAAA,CAAAA,GAAIgD,CAAAA,OAAQ2E,CAAAA,OAAZ,CAAoB7C,CAAM8C,CAAAA,aAAN,CAAoB,MAApB,CAApB,CAAiDC,CAAAA,CAAAA,4BAAAA,CAAAA,QAAS6C,CAAAA,SAA1D,CACAC,EAAAA,CAAQ,EACR3K,EAAAA,CAAAA,OAAAA,CAAAA,GAAI4K,CAAAA,gBAAR,GACED,CADF,EACW3K,CAAAA,CAAAA,OAAAA,CAAAA,GAAI6K,CAAAA,QAAJ,CAAa7K,CAAAA,CAAAA,OAAAA,CAAAA,GAAI4K,CAAAA,gBAAjB,CAAmC9F,CAAnC,CADX,CAGI9E,EAAAA,CAAAA,OAAAA,CAAAA,GAAI8K,CAAAA,gBAAR,GACEH,CADF,EACW3K,CAAAA,CAAAA,OAAAA,CAAAA,GAAI6K,CAAAA,QAAJ,CAAa7K,CAAAA,CAAAA,OAAAA,CAAAA,GAAI8K,CAAAA,gBAAjB,CAAmChG,CAAnC,CADX,CAGI6F,EAAJ,GACEA,CADF,CACU3K,CAAAA,CAAAA,OAAAA,CAAAA,GAAIwF,CAAAA,WAAJ,CAAgBmF,CAAhB,CAAuB3K,CAAAA,CAAAA,OAAAA,CAAAA,GAAIwK,CAAAA,MAA3B,CADV,CAGIO,EAAAA,CAAW,EACX/K,EAAAA,CAAAA,OAAAA,CAAAA,GAAIgL,CAAAA,kBAAR,GACED,CADF,CACa/K,CAAAA,CAAAA,OAAAA,CAAAA,GAAIwF,CAAAA,WAAJ,CACPxF,CAAAA,CAAAA,OAAAA,CAAAA,GAAI6K,CAAAA,QAAJ,CAAa7K,CAAAA,CAAAA,OAAAA,CAAAA,GAAIgL,CAAAA,kBAAjB;AAAqClG,CAArC,CADO,CACsC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIwK,CAAAA,MAD1C,CADb,CAIMS,EAAAA,CAASjL,CAAAA,CAAAA,OAAAA,CAAAA,GAAIkL,CAAAA,eAAJ,CAAoBpG,CAApB,CAA2B,OAA3B,CACf,KAAIqG,EAAcnL,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,QAAvB,CAAiC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAArC,CAAd4I,EAAkE,EAAtE,CACIC,EAAQ,EACRH,EAAJ,EAAcE,CAAd,GAEEC,CAFF,CAEUT,CAFV,CAIIQ,EAAJ,GACEA,CADF,CACgBnL,CAAAA,CAAAA,OAAAA,CAAAA,GAAIwK,CAAAA,MADpB,CAC6B,SAD7B,CACyCW,CADzC,CACuD,KADvD,CAKA,KAFA,IAAME,EAAO,EAAb,CACMC,EAAYxG,CAAMoF,CAAAA,OAAN,EADlB,CAESzE,EAAI,CAAb,CAAgBA,CAAhB,CAAoB6F,CAAU3F,CAAAA,MAA9B,CAAsCF,CAAA,EAAtC,CACE4F,CAAA,CAAK5F,CAAL,CAAA,CAAUzF,CAAAA,CAAAA,OAAAA,CAAAA,GAAIgD,CAAAA,OAAQ2E,CAAAA,OAAZ,CAAoB2D,CAAA,CAAU7F,CAAV,CAApB,CAAkCoC,CAAAA,CAAAA,4BAAAA,CAAAA,QAASC,CAAAA,QAA3C,CAERpE,EAAAA,CAAO,WAAPA,CAAqB+G,CAArB/G,CAAgC,GAAhCA,CAAsC2H,CAAKtH,CAAAA,IAAL,CAAU,IAAV,CAAtCL,CAAwD,OAAxDA,CACA6G,CADA7G,CACYiH,CADZjH,CACoBqH,CADpBrH,CAC+BuH,CAD/BvH,CACwC0H,CADxC1H,CACgDyH,CADhDzH,CAC8D,GAClEA,EAAA,CAAO1D,CAAAA,CAAAA,OAAAA,CAAAA,GAAI4E,CAAAA,MAAJ,CAAWE,CAAX,CAAkBpB,CAAlB,CAEP1D,EAAAA,CAAAA,OAAAA,CAAAA,GAAI8D,CAAAA,YAAJ,CAAiB,GAAjB,CAAuB2G,CAAvB,CAAA,CAAmC/G,CACnC,OAAO,KA3DqC,CAgE9C1D;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,sBAAA,CAAgCA,CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,oBAEhCA,EAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,qBAAA,CAA+B,QAAQ,CAAC8E,CAAD,CAAQ,CAM7C,IAJA,IAAM2F,EACFzK,CAAAA,CAAAA,OAAAA,CAAAA,GAAIgD,CAAAA,OAAQ2E,CAAAA,OAAZ,CAAoB7C,CAAM8C,CAAAA,aAAN,CAAoB,MAApB,CAApB,CAAiDC,CAAAA,CAAAA,4BAAAA,CAAAA,QAAS6C,CAAAA,SAA1D,CADJ,CAEMW,EAAO,EAFb,CAGMC,EAAYxG,CAAMoF,CAAAA,OAAN,EAHlB,CAISzE,EAAI,CAAb,CAAgBA,CAAhB,CAAoB6F,CAAU3F,CAAAA,MAA9B,CAAsCF,CAAA,EAAtC,CACE4F,CAAA,CAAK5F,CAAL,CAAA,CAAUzF,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,KAAvB,CAA+BW,CAA/B,CAAkCzF,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAtC,CAAV,EAA+D,MAGjE,OAAO,CADMkI,CACN,CADiB,GACjB,CADuBY,CAAKtH,CAAAA,IAAL,CAAU,IAAV,CACvB,CADyC,GACzC,CAAO/D,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAX,CAVsC,CAa/CP;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,uBAAA,CAAiC,QAAQ,CAAC8E,CAAD,CAAQ,CAK/C,MADc9E,EAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,qBAAAuL,CAA6BzG,CAA7ByG,CACP,CAAM,CAAN,CAAP,CAAkB,KAL6B,CAQjDvL;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,mBAAA,CAA6B,QAAQ,CAAC8E,CAAD,CAAQ,CAI3C,IAAIpB,EAAO,MAAPA,EADA1D,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,WAAvB,CAAoC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAxC,CACAmB,EADuD,OACvDA,EAA4B,OAC5B1D,EAAAA,CAAAA,OAAAA,CAAAA,GAAI8K,CAAAA,gBAAR,GAGEpH,CAHF,EAIM1D,CAAAA,CAAAA,OAAAA,CAAAA,GAAIwF,CAAAA,WAAJ,CAAgBxF,CAAAA,CAAAA,OAAAA,CAAAA,GAAI6K,CAAAA,QAAJ,CAAa7K,CAAAA,CAAAA,OAAAA,CAAAA,GAAI8K,CAAAA,gBAAjB,CAAmChG,CAAnC,CAAhB,CAA2D9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIwK,CAAAA,MAA/D,CAJN,CAMI1F,EAAM0G,CAAAA,eAAV,EACQjD,CACN,CADcvI,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,OAAvB,CAAgC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAApC,CACd,EADiE,MACjE,CAAAmB,CAAA,EAAQ1D,CAAAA,CAAAA,OAAAA,CAAAA,GAAIwK,CAAAA,MAAZ,CAAqB,SAArB,CAAiCjC,CAAjC,CAAyC,KAF3C,EAIE7E,CAJF,EAIU1D,CAAAA,CAAAA,OAAAA,CAAAA,GAAIwK,CAAAA,MAJd,CAIuB,WAGvB,OADA9G,EACA,CADQ,KAjBmC,C,CC9F7C,IAAA,gCAAA,EAMA1D,EAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,WAAA,CAAqB,QAAQ,CAAC8E,CAAD,CAAQ,CAE/BpB,CAAAA,CAAO6D,MAAA,CAAOzC,CAAM8C,CAAAA,aAAN,CAAoB,KAApB,CAAP,CACX,KAAMd,EAAgB,CAAR,EAAApD,CAAA,CAAY1D,CAAAA,CAAAA,OAAAA,CAAAA,GAAIG,CAAAA,YAAhB,CAA+BH,CAAAA,CAAAA,OAAAA,CAAAA,GAAIiB,CAAAA,oBACpCwK,SAAb,GAAI/H,CAAJ,CACEA,CADF,CACS,KADT,CAEoB,CAAC+H,QAFrB,GAEW/H,CAFX,GAGEA,CAHF,CAGS,MAHT,CAKA,OAAO,CAACA,CAAD,CAAOoD,CAAP,CAT4B,CAYrC9G;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,eAAA,CAAyB,QAAQ,CAAC8E,CAAD,CAAQ,CASvC,IAAMyG,EAPYjC,CAChB,IAAO,CAAC,KAAD,CAAQtJ,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqB,CAAAA,cAAZ,CADSiI,CAEhB,MAAS,CAAC,KAAD,CAAQtJ,CAAAA,CAAAA,OAAAA,CAAAA,GAAIsB,CAAAA,iBAAZ,CAFOgI,CAGhB,SAAY,CAAC,KAAD,CAAQtJ,CAAAA,CAAAA,OAAAA,CAAAA,GAAIkB,CAAAA,oBAAZ,CAHIoI,CAIhB,OAAU,CAAC,KAAD,CAAQtJ,CAAAA,CAAAA,OAAAA,CAAAA,GAAImB,CAAAA,cAAZ,CAJMmI,CAKhB,MAAS,CAAC,MAAD,CAAStJ,CAAAA,CAAAA,OAAAA,CAAAA,GAAIQ,CAAAA,WAAb,CALO8I,CAOJ,CAAUxE,CAAM8C,CAAAA,aAAN,CAAoB,IAApB,CAAV,CAAd,CACMgB,EAAW2C,CAAA,CAAM,CAAN,CACXzE,EAAAA,CAAQyE,CAAA,CAAM,CAAN,CACd,KAAMxD,EAAY/H,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,GAAvB,CAA4BgC,CAA5B,CAAZiB,EAAkD,GAClD2D,EAAAA,CAAY1L,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,GAAvB,CAA4BgC,CAA5B,CAAZ4E,EAAkD,GAExD,OAAO,CADM3D,CACN,CADkBa,CAClB,CAD6B8C,CAC7B,CAAO5E,CAAP,CAfgC,CAkBzC9G;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,WAAA,CAAqB,QAAQ,CAAC8E,CAAD,CAAQ,CAEnC,IAAM8D,EAAW9D,CAAM8C,CAAAA,aAAN,CAAoB,IAApB,CAGjB,IAAiB,KAAjB,GAAIgB,CAAJ,CAQE,MANA+C,EAMO,CAND3L,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,KAAvB,CAA8B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIiB,CAAAA,oBAAlC,CAMC,EAN0D,GAM1D,CALQ,GAKR,GALH0K,CAAA,CAAI,CAAJ,CAKG,GAHLA,CAGK,CAHC,GAGD,CAHOA,CAGP,EAAA,CADA,GACA,CADMA,CACN,CAAO3L,CAAAA,CAAAA,OAAAA,CAAAA,GAAIiB,CAAAA,oBAAX,CAGP0K,EAAA,CADe,KAAjB,GAAI/C,CAAJ,EAAuC,KAAvC,GAA0BA,CAA1B,EAA6D,KAA7D,GAAgDA,CAAhD,CACQ5I,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,KAAvB,CAA8B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAImB,CAAAA,cAAlC,CADR,EAC6D,GAD7D,CAGQnB,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,KAAvB,CAA8B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAlC,CAHR,EAGyD,GAIzD,QAAQqG,CAAR,EACE,KAAK,KAAL,CACE,IAAAlF,EAAO,MAAPA,CAAgBiI,CAAhBjI,CAAsB,GACtB,MACF,MAAK,MAAL,CACEA,CAAA,CAAO,OAAP,CAAiBiI,CAAjB,CAAuB,GACvB,MACF,MAAK,IAAL,CACEjI,CAAA,CAAO,MAAP,CAAgBiI,CAAhB,CAAsB,GACtB;KACF,MAAK,KAAL,CACEjI,CAAA,CAAO,MAAP,CAAgBiI,CAAhB,CAAsB,GACtB,MACF,MAAK,OAAL,CACEjI,CAAA,CAAO,SAAP,CAAmBiI,CAAnB,CAAyB,GACzB,MACF,MAAK,OAAL,CACEjI,CAAA,CAAO,QAAP,CAAkBiI,CAAlB,CAAwB,GACxB,MACF,MAAK,SAAL,CACEjI,CAAA,CAAO,OAAP,CAAiBiI,CAAjB,CAAuB,GACvB,MACF,MAAK,WAAL,CACEjI,CAAA,CAAO,QAAP,CAAkBiI,CAAlB,CAAwB,GACxB,MACF,MAAK,KAAL,CACEjI,CAAA,CAAO,MAAP,CAAgBiI,CAAhB,CAAsB,gBACtB,MACF,MAAK,KAAL,CACEjI,CAAA,CAAO,MAAP,CAAgBiI,CAAhB,CAAsB,gBACtB,MACF,MAAK,KAAL,CACEjI,CAAA,CAAO,MAAP,CAAgBiI,CAAhB,CAAsB,gBAhC1B,CAmCA,GAAIjI,CAAJ,CACE,MAAO,CAACA,CAAD,CAAO1D,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAX,CAIT,QAAQqI,CAAR,EACE,KAAK,OAAL,CACElF,CAAA,CAAO,MAAP,CAAgBiI,CAAhB,CAAsB,aACtB,MACF,MAAK,MAAL,CACEjI,CAAA,CAAO,OAAP,CAAiBiI,CAAjB,CAAuB,gBACvB,MACF,MAAK,MAAL,CACEjI,CAAA,CAAO,OAAP,CAAiBiI,CAAjB,CAAuB,gBACvB;KACF,MAAK,MAAL,CACEjI,CAAA,CAAO,OAAP,CAAiBiI,CAAjB,CAAuB,gBACvB,MACF,SACE,KAAM1C,MAAA,CAAM,yBAAN,CAAkCL,CAAlC,CAAN,CAdJ,CAgBA,MAAO,CAAClF,CAAD,CAAO1D,CAAAA,CAAAA,OAAAA,CAAAA,GAAImB,CAAAA,cAAX,CA9E4B,CAiFrCnB,EAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,aAAA,CAAuB,QAAQ,CAAC8E,CAAD,CAAQ,CAUrC,MARkB8G,CAChB,GAAM,CAAC,MAAD,CAAS5L,CAAAA,CAAAA,OAAAA,CAAAA,GAAIG,CAAAA,YAAb,CADUyL,CAEhB,EAAK,CAAC,KAAD,CAAQ5L,CAAAA,CAAAA,OAAAA,CAAAA,GAAIG,CAAAA,YAAZ,CAFWyL,CAGhB,aAAgB,CAAC,mBAAD,CAAsB5L,CAAAA,CAAAA,OAAAA,CAAAA,GAAImB,CAAAA,cAA1B,CAHAyK,CAIhB,MAAS,CAAC,SAAD,CAAY5L,CAAAA,CAAAA,OAAAA,CAAAA,GAAIG,CAAAA,YAAhB,CAJOyL,CAKhB,QAAW,CAAC,WAAD,CAAc5L,CAAAA,CAAAA,OAAAA,CAAAA,GAAIG,CAAAA,YAAlB,CALKyL,CAMhB,SAAY,CAAC,KAAD,CAAQ5L,CAAAA,CAAAA,OAAAA,CAAAA,GAAIG,CAAAA,YAAZ,CANIyL,CAQX,CAAU9G,CAAM8C,CAAAA,aAAN,CAAoB,UAApB,CAAV,CAV8B,CAavC5H;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,oBAAA,CAA8B,QAAQ,CAAC8E,CAAD,CAAQ,CAG5C,IAAM+G,EAAa,CACjB,KAAQ,CAAC,EAAD,CAAK,WAAL,CAAkB7L,CAAAA,CAAAA,OAAAA,CAAAA,GAAIoB,CAAAA,aAAtB,CAAqCpB,CAAAA,CAAAA,OAAAA,CAAAA,GAAI0B,CAAAA,cAAzC,CADS,CAEjB,IAAO,CAAC,EAAD,CAAK,WAAL,CAAkB1B,CAAAA,CAAAA,OAAAA,CAAAA,GAAIoB,CAAAA,aAAtB,CAAqCpB,CAAAA,CAAAA,OAAAA,CAAAA,GAAI0B,CAAAA,cAAzC,CAFU,CAGjB,MAAS,CAAC,SAAD,CAAY,GAAZ,CAAiB1B,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAArB,CAAiCvC,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAArC,CAHQ,CAIjB,SAAY,CAAC,EAAD,CAAK,MAAL,CAAaP,CAAAA,CAAAA,OAAAA,CAAAA,GAAIyB,CAAAA,gBAAjB,CAAmCzB,CAAAA,CAAAA,OAAAA,CAAAA,GAAIyB,CAAAA,gBAAvC,CAJK,CAKjB,SAAY,CAAC,EAAD,CAAK,MAAL,CAAazB,CAAAA,CAAAA,OAAAA,CAAAA,GAAIyB,CAAAA,gBAAjB,CAAmCzB,CAAAA,CAAAA,OAAAA,CAAAA,GAAIyB,CAAAA,gBAAvC,CALK,CAMjB,aAAgB,CAAC,IAAD,CAAO,IAAP,CAAazB,CAAAA,CAAAA,OAAAA,CAAAA,GAAIoB,CAAAA,aAAjB;AAAgCpB,CAAAA,CAAAA,OAAAA,CAAAA,GAAI0B,CAAAA,cAApC,CANC,CAOjB,MAAS,CAAC,IAAD,CAAO,IAAP,CAAa1B,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAjB,CAA6BvC,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAjC,CAPQ,CAAnB,CASMuL,EAAmBhH,CAAM8C,CAAAA,aAAN,CAAoB,UAApB,CACnB,EAAA,CAAA,CAAA,CAAA,OAAA,CAAA,YAAA,CAA4CiE,CAAA,CAAWC,CAAX,CAA5C,CAAA,KAACC,EAAD,CAAA,CAAA,IAAA,EAAA,CAAA,KAAA,CAASC,EAAT,CAAA,CAAA,IAAA,EAAA,CAAA,KAAA,CAAiBC,EAAjB,CAAA,CAAA,IAAA,EAAA,CAAA,KAA6BC,EAAAA,CAA7B,CAAA,CAAA,IAAA,EAAA,CAAA,KACAC,EAAAA,CAAgBnM,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,iBAAvB,CAClBmH,CADkB,CAAhBE,EACa,GAEnB,IAAyB,OAAzB,GAAIL,CAAJ,CAsBEpI,CAAA,CApBqB1D,CAAAA,CAAAA,OAAAA,CAAAA,GAAIyI,CAAAA,gBAAJD,CAAqB,cAArBA,CAAqC,aAArCA,CACdxI,CAAAA,CAAAA,OAAAA,CAAAA,GAAI0I,CAAAA,0BADUF,CAAqC,kiBAArCA,CAoBrB;AAAsB,GAAtB,CAA4B2D,CAA5B,CAA4C,GAtB9C,KAuBO,IAAyB,cAAzB,GAAIL,CAAJ,CAAyC,CACxCM,CAAAA,CAAUpM,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,SAAvB,CACZ9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIoB,CAAAA,aADQ,CAAVgL,EACoB,GAC1B,IAAgB,GAAhB,GAAIA,CAAJ,CACE,MAAO,CAAC,OAAD,CAAUpM,CAAAA,CAAAA,OAAAA,CAAAA,GAAIG,CAAAA,YAAd,CAGTuD,EAAA,CAAOyI,CAAP,CAAuB,KAAvB,CAA+BC,CAA/B,CAAyC,OAPK,CAAzC,IASL1I,EAAA,CAAOqI,CAAP,CAAgBI,CAAhB,CAAgCH,CAElC,OAAO,CAACtI,CAAD,CAAOwI,CAAP,CAnDqC,CAsD9ClM,EAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,WAAA,CAAqB,QAAQ,CAAC8E,CAAD,CAAQ,CAEnC,IAAMiD,EAAY/H,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,OAAvB,CAAgC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqB,CAAAA,cAApC,CAAZ0G,EAAmE,GAGzE,OADI/H,EAAAA,CAAAA,OAAAA,CAAAA,GAAIgD,CAAAA,OAAQ2E,CAAAA,OAAZK,CAAoBlD,CAAM8C,CAAAA,aAAN,CAAoB,KAApB,CAApBI,CAAgDH,CAAAA,CAAAA,4BAAAA,CAAAA,QAASC,CAAAA,QAAzDE,CACJ,CAAiB,MAAjB,CAA0BD,CAA1B,CAAsC,KALH,CASrC/H,EAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,UAAA,CAAoBA,CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,WAEpBA;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,SAAA,CAAmBA,CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,WAEnBA;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,YAAA,CAAsB,QAAQ,CAAC8E,CAAD,CAAQ,CAEpC,IAAMuH,EAAOvH,CAAM8C,CAAAA,aAAN,CAAoB,IAApB,CAGb,QAAQyE,CAAR,EACE,KAAK,KAAL,CACEC,CAAA,CACItM,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAnC,CADJ,EAC+D,SAC/DmD,EAAA,CAAO,YAAP,CAAsB4I,CAAtB,CAA6B,GAC7B,MACF,MAAK,KAAL,CACEA,CAAA,CACItM,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAnC,CADJ,EAC+D,SAC/DmD,EAAA,CAAO,MAAP,CAAgB4I,CAAhB,CAAuB,GACvB,MACF,MAAK,KAAL,CACEA,CAAA,CACItM,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAnC,CADJ,EAC+D,SAC/DmD,EAAA,CAAO,MAAP,CAAgB4I,CAAhB,CAAuB,GACvB,MACF,MAAK,SAAL,CACQ9D,CAAAA,CAAexI,CAAAA,CAAAA,OAAAA,CAAAA,GAAIyI,CAAAA,gBAAJ,CAAqB,WAArB,CAAkC,aAAlC;AAChBzI,CAAAA,CAAAA,OAAAA,CAAAA,GAAI0I,CAAAA,0BADY,CAAkC,iEAAlC,CAKrB4D,EAAA,CAAOtM,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAnC,CAAP,EAAyD,SACzDmB,EAAA,CAAO8E,CAAP,CAAsB,GAAtB,CAA4B8D,CAA5B,CAAmC,GACnC,MAEF,MAAK,QAAL,CACQ9D,CAAAA,CAAexI,CAAAA,CAAAA,OAAAA,CAAAA,GAAIyI,CAAAA,gBAAJ,CAAqB,aAArB,CAAoC,aAApC,CAChBzI,CAAAA,CAAAA,OAAAA,CAAAA,GAAI0I,CAAAA,0BADY,CAAoC,sLAApC,CAOrB4D;CAAA,CAAOtM,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAnC,CAAP,EAAyD,IACzDmB,EAAA,CAAO8E,CAAP,CAAsB,GAAtB,CAA4B8D,CAA5B,CAAmC,GACnC,MAEF,MAAK,MAAL,CAIQ9D,CAAAA,CAAexI,CAAAA,CAAAA,OAAAA,CAAAA,GAAIyI,CAAAA,gBAAJ,CAAqB,YAArB,CAAmC,aAAnC,CAChBzI,CAAAA,CAAAA,OAAAA,CAAAA,GAAI0I,CAAAA,0BADY,CAAmC,qOAAnC,CASrB4D,EAAA,CAAOtM,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAnC,CAAP,EAAyD,IACzDmB;CAAA,CAAO8E,CAAP,CAAsB,GAAtB,CAA4B8D,CAA5B,CAAmC,GACnC,MAEF,MAAK,SAAL,CACQ9D,CAAAA,CAAexI,CAAAA,CAAAA,OAAAA,CAAAA,GAAIyI,CAAAA,gBAAJ,CAAqB,yBAArB,CAAgD,aAAhD,CAChBzI,CAAAA,CAAAA,OAAAA,CAAAA,GAAI0I,CAAAA,0BADY,CAAgD,uPAAhD,CASrB4D,EAAA,CAAOtM,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAnC,CAAP,EAAyD,IACzDmB,EAAA,CAAO8E,CAAP,CAAsB,GAAtB,CAA4B8D,CAA5B,CAAmC,GACnC,MAEF,MAAK,QAAL,CACQ9D,CAAAA;AAAexI,CAAAA,CAAAA,OAAAA,CAAAA,GAAIyI,CAAAA,gBAAJ,CAAqB,kBAArB,CAAyC,aAAzC,CAChBzI,CAAAA,CAAAA,OAAAA,CAAAA,GAAI0I,CAAAA,0BADY,CAAyC,sEAAzC,CAMrB4D,EAAA,CAAOtM,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAnC,CAAP,EAAyD,IACzDmB,EAAA,CAAO8E,CAAP,CAAsB,GAAtB,CAA4B8D,CAA5B,CAAmC,GACnC,MAEF,SACE,KAAMrD,MAAA,CAAM,oBAAN,CAA6BoD,CAA7B,CAAN,CAjFJ,CAmFA,MAAO,CAAC3I,CAAD,CAAO1D,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAX,CAxF6B,CA2FtCP;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,WAAA,CAAqB,QAAQ,CAAC8E,CAAD,CAAQ,CAEnC,IAAMiD,EACF/H,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,UAAvB,CAAmC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIoB,CAAAA,aAAvC,CADE2G,EACuD,GACvD2D,EAAAA,CAAY1L,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,SAAvB,CAAkC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIoB,CAAAA,aAAtC,CAAZsK,EAAoE,GAE1E,OAAO,CADM3D,CACN,CADkB,KAClB,CAD0B2D,CAC1B,CAAO1L,CAAAA,CAAAA,OAAAA,CAAAA,GAAIoB,CAAAA,aAAX,CAN4B,CASrCpB;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,cAAA,CAAwB,QAAQ,CAAC8E,CAAD,CAAQ,CAEtC,IAAMiD,EAAY/H,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,OAAvB,CAAgC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAApC,CAAZwF,EAA+D,GAArE,CACM2D,EAAY1L,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,KAAvB,CAA8B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAlC,CAAZmJ,EAA6D,GAC7Da,EAAAA,CACFvM,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAnC,CADEgK,EACgD,UAGtD,OAAO,CADH,UACG,CADUxE,CACV,CADsB,IACtB,CAD6B2D,CAC7B,CADyC,KACzC,CADiDa,CACjD,CAD6D,GAC7D,CAAOvM,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAX,CAR+B,CAWxCP;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,eAAA,CAAyB,QAAQ,CAAC8E,CAAD,CAAQ,CAEvC,IAAMiD,EAAY/H,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAnC,CAAZwF,EAA8D,GAC9D2D,EAAAA,CAAY1L,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,IAAvB,CAA6B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAjC,CAAZmJ,EAA4D,GAUlE,OAAO,CATc1L,CAAAA,CAAAA,OAAAA,CAAAA,GAAIyI,CAAAA,gBAAJD,CAAqB,iBAArBA,CAAwC,aAAxCA,CACZxI,CAAAA,CAAAA,OAAAA,CAAAA,GAAI0I,CAAAA,0BADQF,CAAwC,0FAAxCA,CASd,CADqB,GACrB,CAD2BT,CAC3B,CADuC,IACvC,CAD8C2D,CAC9C,CAD0D,GAC1D,CAAO1L,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAX,CAbgC,CAgBzCP;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,iBAAA,CAA2B,QAAQ,CAAC8E,CAAD,CAAQ,CAEzC,MAAO,CAAC,mCAAD,CAAsC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAA1C,CAFkC,CAK3CP,EAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,UAAA,CAAoB,QAAQ,CAAC8E,CAAD,CAAQ,CAElC,IAAMiD,EAAY/H,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,GAAvB,CAA4B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAhC,CAAZwF,EAA2D,GAEjE,OAAO,CACL,QADK,EADW/H,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,GAAvB,CAA4B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAhC,CACX,EAD0D,GAC1D,EACkB,IADlB,CACyBwF,CADzB,CACqC,gBADrC,CAEL/H,CAAAA,CAAAA,OAAAA,CAAAA,GAAImB,CAAAA,cAFC,CAJ2B,C,CCzUpC,IAAA,iCAAA,EAOAnB;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,mBAAA,CAA6B,QAAQ,CAAC8E,CAAD,CAAQ,CAKzC,IAAA0H,EAFE1H,CAAMyE,CAAAA,QAAN,CAAe,OAAf,CAAJ,CAEYkD,MAAA,CAAOlF,MAAA,CAAOzC,CAAM8C,CAAAA,aAAN,CAAoB,OAApB,CAAP,CAAP,CAFZ,CAKY5H,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,OAAvB,CAAgC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAImC,CAAAA,gBAApC,CALZ,EAKqE,GAErE,KAAI8I,EAASjL,CAAAA,CAAAA,OAAAA,CAAAA,GAAIkL,CAAAA,eAAJ,CAAoBpG,CAApB,CAA2B,IAA3B,CACbmG,EAAA,CAASjL,CAAAA,CAAAA,OAAAA,CAAAA,GAAI0M,CAAAA,WAAJ,CAAgBzB,CAAhB,CAAwBnG,CAAxB,CACLpB,EAAAA,CAAO,EACX,KAAMiJ,EAAU3M,CAAAA,CAAAA,OAAAA,CAAAA,GAAIgD,CAAAA,OAAQ4J,CAAAA,eAAZ,CAA4B,OAA5B,CAAqC/E,CAAAA,CAAAA,4BAAAA,CAAAA,QAASC,CAAAA,QAA9C,CAAhB,CACI+E,EAASL,CACRA,EAAQM,CAAAA,KAAR,CAAc,OAAd,CAAL,EAAgC,GAAAzH,CAAAA,CAAAA,mCAAYiC,CAAAA,QAAZ,EAAqBkF,CAArB,CAAhC,GACEK,CACA,CADS7M,CAAAA,CAAAA,OAAAA,CAAAA,GAAIgD,CAAAA,OAAQ4J,CAAAA,eAAZ,CAA4B,YAA5B,CAA0C/E,CAAAA,CAAAA,4BAAAA,CAAAA,QAASC,CAAAA,QAAnD,CACT;AAAApE,CAAA,EAAQmJ,CAAR,CAAiB,KAAjB,CAAyBL,CAAzB,CAAmC,KAFrC,CAMA,OAFA9I,EAEA,EAFQ,OAER,CAFkBiJ,CAElB,CAF4B,QAE5B,CAFuCA,CAEvC,CAFiD,KAEjD,CAFyDE,CAEzD,CAFkE,IAElE,CADIF,CACJ,CADc,SACd,CAD0B1B,CAC1B,CADmC,KACnC,CArB2C,CAwB7CjL,EAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,eAAA,CAAyBA,CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,mBAEzBA,EAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,mBAAA,CAA6B,QAAQ,CAAC8E,CAAD,CAAQ,CAE3C,IAAMiI,EAAwC,OAAxCA,GAAQjI,CAAM8C,CAAAA,aAAN,CAAoB,MAApB,CAAd,CACIG,EACA/H,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CACIvC,CADJ,CACW,MADX,CACmBiI,CAAA,CAAQ/M,CAAAA,CAAAA,OAAAA,CAAAA,GAAIe,CAAAA,iBAAZ,CAAgCf,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UADvD,CADAwF,EAGA,OAJJ,CAKIkD,EAASjL,CAAAA,CAAAA,OAAAA,CAAAA,GAAIkL,CAAAA,eAAJ,CAAoBpG,CAApB,CAA2B,IAA3B,CACbmG,EAAA,CAASjL,CAAAA,CAAAA,OAAAA,CAAAA,GAAI0M,CAAAA,WAAJ,CAAgBzB,CAAhB,CAAwBnG,CAAxB,CACLiI,EAAJ,GACEhF,CADF,CACc,GADd,CACoBA,CADpB,CAGA,OAAO,SAAP,CAAmBA,CAAnB,CAA+B,OAA/B,CAAyCkD,CAAzC,CAAkD,KAZP,CAe7CjL;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,YAAA,CAAsB,QAAQ,CAAC8E,CAAD,CAAQ,CAEpC,IAAMkI,EACFhN,CAAAA,CAAAA,OAAAA,CAAAA,GAAIgD,CAAAA,OAAQ2E,CAAAA,OAAZ,CAAoB7C,CAAM8C,CAAAA,aAAN,CAAoB,KAApB,CAApB,CAAgDC,CAAAA,CAAAA,4BAAAA,CAAAA,QAASC,CAAAA,QAAzD,CADJ,CAEMC,EAAY/H,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAImC,CAAAA,gBAAnC,CAAZ4F,EAAoE,GAF1E,CAGM2D,EAAY1L,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,IAAvB,CAA6B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAImC,CAAAA,gBAAjC,CAAZuJ,EAAkE,GAHxE,CAIMuB,EAAYjN,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,IAAvB,CAA6B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAImC,CAAAA,gBAAjC,CAAZ8K,EAAkE,GAJxE,CAKIhC,EAASjL,CAAAA,CAAAA,OAAAA,CAAAA,GAAIkL,CAAAA,eAAJ,CAAoBpG,CAApB,CAA2B,IAA3B,CACbmG,EAAA,CAASjL,CAAAA,CAAAA,OAAAA,CAAAA,GAAI0M,CAAAA,WAAJ,CAAgBzB,CAAhB,CAAwBnG,CAAxB,CAET,IAAI,GAAAO,CAAAA,CAAAA,mCAAYiC,CAAAA,QAAZ,EAAqBS,CAArB,CAAJ,EAAuC,GAAA1C,CAAAA,CAAAA,mCAAYiC,CAAAA,QAAZ,EAAqBoE,CAArB,CAAvC;AACI,GAAArG,CAAAA,CAAAA,mCAAYiC,CAAAA,QAAZ,EAAqB2F,CAArB,CADJ,CACqC,CAEnC,IAAMC,EAAK3F,MAAA,CAAOQ,CAAP,CAALmF,EAA0B3F,MAAA,CAAOmE,CAAP,CAChChI,EAAA,CAAO,OAAP,CAAiBsJ,CAAjB,CAA6B,KAA7B,CAAqCjF,CAArC,CAAiD,IAAjD,CAAwDiF,CAAxD,EACKE,CAAA,CAAK,MAAL,CAAc,MADnB,EAC6BxB,CAD7B,CACyC,IADzC,CACgDsB,CAC1CG,EAAAA,CAAO3F,IAAK4F,CAAAA,GAAL,CAAS7F,MAAA,CAAO0F,CAAP,CAAT,CAMbvJ,EAAA,EALa,CAAbA,GAAIyJ,CAAJzJ,CACEA,CADFA,EACUwJ,CAAA,CAAK,IAAL,CAAY,IADtBxJ,EAGEA,CAHFA,GAGWwJ,CAAA,CAAK,MAAL,CAAc,MAHzBxJ,EAGmCyJ,CAHnCzJ,CAKA,GAAQ,OAAR,CAAkBuH,CAAlB,CAA2B,KAA3B,CAXmC,CADrC,IAcEvH,EA2BA,CA3BO,EA2BP,CAzBI2J,CAyBJ,CAzBetF,CAyBf,CAxBKA,CAAU+E,CAAAA,KAAV,CAAgB,OAAhB,CAwBL,EAxBkC,GAAAzH,CAAAA,CAAAA,mCAAYiC,CAAAA,QAAZ,EAAqBS,CAArB,CAwBlC,GAvBEsF,CAEA,CADIrN,CAAAA,CAAAA,OAAAA,CAAAA,GAAIgD,CAAAA,OAAQ4J,CAAAA,eAAZ,CAA4BI,CAA5B,CAAwC,QAAxC,CAAkDnF,CAAAA,CAAAA,4BAAAA,CAAAA,QAASC,CAAAA,QAA3D,CACJ,CAAApE,CAAA,EAAQ2J,CAAR,CAAmB,KAAnB,CAA2BtF,CAA3B,CAAuC,KAqBzC,EAnBI8E,CAmBJ,CAnBanB,CAmBb,CAlBKA,CAAUoB,CAAAA,KAAV,CAAgB,OAAhB,CAkBL,EAlBkC,GAAAzH,CAAAA,CAAAA,mCAAYiC,CAAAA,QAAZ,EAAqBoE,CAArB,CAkBlC;CAjBEmB,CAEA,CADI7M,CAAAA,CAAAA,OAAAA,CAAAA,GAAIgD,CAAAA,OAAQ4J,CAAAA,eAAZ,CAA4BI,CAA5B,CAAwC,MAAxC,CAAgDnF,CAAAA,CAAAA,4BAAAA,CAAAA,QAASC,CAAAA,QAAzD,CACJ,CAAApE,CAAA,EAAQmJ,CAAR,CAAiB,KAAjB,CAAyBnB,CAAzB,CAAqC,KAevC,EAXM4B,CAWN,CAVItN,CAAAA,CAAAA,OAAAA,CAAAA,GAAIgD,CAAAA,OAAQ4J,CAAAA,eAAZ,CAA4BI,CAA5B,CAAwC,MAAxC,CAAgDnF,CAAAA,CAAAA,4BAAAA,CAAAA,QAASC,CAAAA,QAAzD,CAUJ,CATApE,CASA,EATQ4J,CASR,CATiB,KASjB,CAPE5J,CAOF,CARI,GAAA2B,CAAAA,CAAAA,mCAAYiC,CAAAA,QAAZ,EAAqB2F,CAArB,CAAJ,CACEvJ,CADF,EACU8D,IAAK4F,CAAAA,GAAL,CAASH,CAAT,CADV,CACgC,KADhC,EAGEvJ,CAHF,EAGU,MAHV,CAGmBuJ,CAHnB,CAG+B,MAH/B,CAQA,CAFAvJ,CAEA,CAHAA,CAGA,EAHQ,MAGR,CAHiB2J,CAGjB,CAH4B,KAG5B,CAHoCR,CAGpC,CAH6C,OAG7C,GAFQ7M,CAAAA,CAAAA,OAAAA,CAAAA,GAAIwK,CAAAA,MAEZ,CAFqB8C,CAErB,CAF8B,MAE9B,CAFuCA,CAEvC,CAFgD,KAEhD,EADA5J,CACA,EADQ,KACR,CAAAA,CAAA,EAAQ,OAAR,CAAkBsJ,CAAlB,CAA8B,KAA9B,CAAsCK,CAAtC,CAAiD,IAAjD,CAAwDC,CAAxD,CACI,UADJ,CACiBN,CADjB,CAC6B,MAD7B,CACsCH,CADtC,CAC+C,KAD/C,CACuDG,CADvD,CAEI,MAFJ,CAEaH,CAFb,CAEsB,IAFtB,CAE6BG,CAF7B,CAEyC,MAFzC,CAEkDM,CAFlD,CAE2D,OAF3D,CAGIrC,CAHJ;AAGa,KAEf,OAAOvH,EAxD6B,CA2DtC1D,EAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,gBAAA,CAA0B,QAAQ,CAAC8E,CAAD,CAAQ,CAExC,IAAMkI,EACFhN,CAAAA,CAAAA,OAAAA,CAAAA,GAAIgD,CAAAA,OAAQ2E,CAAAA,OAAZ,CAAoB7C,CAAM8C,CAAAA,aAAN,CAAoB,KAApB,CAApB,CAAgDC,CAAAA,CAAAA,4BAAAA,CAAAA,QAASC,CAAAA,QAAzD,CADJ,CAEMC,EACF/H,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAImC,CAAAA,gBAAnC,CADE4F,EACsD,IAH5D,CAIIkD,EAASjL,CAAAA,CAAAA,OAAAA,CAAAA,GAAIkL,CAAAA,eAAJ,CAAoBpG,CAApB,CAA2B,IAA3B,CACbmG,EAAA,CAASjL,CAAAA,CAAAA,OAAAA,CAAAA,GAAI0M,CAAAA,WAAJ,CAAgBzB,CAAhB,CAAwBnG,CAAxB,CAIT,OADI,WACJ,CADkBiD,CAClB,CAD8B,MAC9B,CADuCiF,CACvC,CADmD,OACnD,CAD6D/B,CAC7D,CADsE,KAV9B,CAc1CjL;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,wBAAA,CAAkC,QAAQ,CAAC8E,CAAD,CAAQ,CAEhD,IAAIyI,EAAO,EACPvN,EAAAA,CAAAA,OAAAA,CAAAA,GAAI4K,CAAAA,gBAAR,GAEE2C,CAFF,EAEUvN,CAAAA,CAAAA,OAAAA,CAAAA,GAAI6K,CAAAA,QAAJ,CAAa7K,CAAAA,CAAAA,OAAAA,CAAAA,GAAI4K,CAAAA,gBAAjB,CAAmC9F,CAAnC,CAFV,CAII9E,EAAAA,CAAAA,OAAAA,CAAAA,GAAI8K,CAAAA,gBAAR,GAGEyC,CAHF,EAGUvN,CAAAA,CAAAA,OAAAA,CAAAA,GAAI6K,CAAAA,QAAJ,CAAa7K,CAAAA,CAAAA,OAAAA,CAAAA,GAAI8K,CAAAA,gBAAjB,CAAmChG,CAAnC,CAHV,CAKA,IAAI9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAI4K,CAAAA,gBAAR,CAA0B,CACxB,IAAM4C,EAAO1I,CAAM2I,CAAAA,eAAN,EACTD,EAAJ,EAAY,CAACA,CAAKE,CAAAA,oBAAlB,GAIEH,CAJF,EAIUvN,CAAAA,CAAAA,OAAAA,CAAAA,GAAI6K,CAAAA,QAAJ,CAAa7K,CAAAA,CAAAA,OAAAA,CAAAA,GAAI4K,CAAAA,gBAAjB,CAAmC4C,CAAnC,CAJV,CAFwB,CAS1B,OAAQ1I,CAAM8C,CAAAA,aAAN,CAAoB,MAApB,CAAR,EACE,KAAK,OAAL,CACE,MAAO2F,EAAP,CAAc,UAChB,MAAK,UAAL,CACE,MAAOA,EAAP,CAAc,aAJlB,CAMA,KAAMtE,MAAA,CAAM,yBAAN,CAAN;AA3BgD,C,CCzHlD,IAAA,iCAAA,EAKAjJ;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,WAAA,CAAqB,QAAQ,CAAC8E,CAAD,CAAQ,CAEnC,IAAI6I,EAAI,CAAR,CACIjK,EAAO,EACP1D,EAAAA,CAAAA,OAAAA,CAAAA,GAAI4K,CAAAA,gBAAR,GAEElH,CAFF,EAEU1D,CAAAA,CAAAA,OAAAA,CAAAA,GAAI6K,CAAAA,QAAJ,CAAa7K,CAAAA,CAAAA,OAAAA,CAAAA,GAAI4K,CAAAA,gBAAjB,CAAmC9F,CAAnC,CAFV,CAIA,GAAG,CACD,IAAA8I,EAAgB5N,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,IAAvB,CAA8B6I,CAA9B,CAAiC3N,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAArC,CAAhBqL,EAAoE,OACpE,KAAAC,EAAa7N,CAAAA,CAAAA,OAAAA,CAAAA,GAAIkL,CAAAA,eAAJ,CAAoBpG,CAApB,CAA2B,IAA3B,CAAkC6I,CAAlC,CACT3N,EAAAA,CAAAA,OAAAA,CAAAA,GAAI8K,CAAAA,gBAAR,GACE+C,CADF,CACe7N,CAAAA,CAAAA,OAAAA,CAAAA,GAAIwF,CAAAA,WAAJ,CACIxF,CAAAA,CAAAA,OAAAA,CAAAA,GAAI6K,CAAAA,QAAJ,CAAa7K,CAAAA,CAAAA,OAAAA,CAAAA,GAAI8K,CAAAA,gBAAjB,CAAmChG,CAAnC,CADJ,CAC+C9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIwK,CAAAA,MADnD,CADf,CAGMqD,CAHN,CAKAnK,EAAA,GAAa,CAAJ,CAAAiK,CAAA,CAAQ,QAAR,CAAmB,EAA5B,EAAkC,MAAlC,CAA2CC,CAA3C,CAA2D,OAA3D,CACIC,CADJ,CACiB,GACjBF,EAAA,EAVC,CAAH,MAWS7I,CAAMgJ,CAAAA,QAAN,CAAe,IAAf,CAAsBH,CAAtB,CAXT,CAaA,IAAI7I,CAAMgJ,CAAAA,QAAN,CAAe,MAAf,CAAJ;AAA8B9N,CAAAA,CAAAA,OAAAA,CAAAA,GAAI8K,CAAAA,gBAAlC,CACE+C,CAMA,CANa7N,CAAAA,CAAAA,OAAAA,CAAAA,GAAIkL,CAAAA,eAAJ,CAAoBpG,CAApB,CAA2B,MAA3B,CAMb,CALI9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAI8K,CAAAA,gBAKR,GAJE+C,CAIF,CAJe7N,CAAAA,CAAAA,OAAAA,CAAAA,GAAIwF,CAAAA,WAAJ,CACIxF,CAAAA,CAAAA,OAAAA,CAAAA,GAAI6K,CAAAA,QAAJ,CAAa7K,CAAAA,CAAAA,OAAAA,CAAAA,GAAI8K,CAAAA,gBAAjB,CAAmChG,CAAnC,CADJ,CAC+C9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIwK,CAAAA,MADnD,CAIf,CAFMqD,CAEN,EAAAnK,CAAA,EAAQ,WAAR,CAAsBmK,CAAtB,CAAmC,GAErC,OAAOnK,EAAP,CAAc,IA9BqB,CAiCrC1D,EAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,eAAA,CAAyBA,CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,WAEzBA;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,aAAA,CAAuB,QAAQ,CAAC8E,CAAD,CAAQ,CAIrC,IAAM8D,EADFU,CAAC,GAAM,IAAPA,CAAa,IAAO,IAApBA,CAA0B,GAAM,GAAhCA,CAAqC,IAAO,IAA5CA,CAAkD,GAAM,GAAxDA,CAA6D,IAAO,IAApEA,CACa,CAAUxE,CAAM8C,CAAAA,aAAN,CAAoB,IAApB,CAAV,CAAjB,CACMd,EAAsB,IAAd,GAAC8B,CAAD,EAAmC,IAAnC,GAAsBA,CAAtB,CAA2C5I,CAAAA,CAAAA,OAAAA,CAAAA,GAAI0B,CAAAA,cAA/C,CAC2C1B,CAAAA,CAAAA,OAAAA,CAAAA,GAAIyB,CAAAA,gBAF7D,CAGMsG,EAAY/H,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,GAAvB,CAA4BgC,CAA5B,CAAZiB,EAAkD,GAClD2D,EAAAA,CAAY1L,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,GAAvB,CAA4BgC,CAA5B,CAAZ4E,EAAkD,GAExD,OAAO,CADM3D,CACN,CADkB,GAClB,CADwBa,CACxB,CADmC,GACnC,CADyC8C,CACzC,CAAO5E,CAAP,CAV8B,CAavC9G;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,eAAA,CAAyB,QAAQ,CAAC8E,CAAD,CAAQ,CAEvC,IAAM8D,EAA0C,KAA/B,GAAC9D,CAAM8C,CAAAA,aAAN,CAAoB,IAApB,CAAD,CAAwC,IAAxC,CAA+C,IAAhE,CACMd,EACY,IAAd,GAAC8B,CAAD,CAAsB5I,CAAAA,CAAAA,OAAAA,CAAAA,GAAI+B,CAAAA,iBAA1B,CAA8C/B,CAAAA,CAAAA,OAAAA,CAAAA,GAAIgC,CAAAA,gBAFtD,CAGI+F,EAAY/H,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,GAAvB,CAA4BgC,CAA5B,CACZ4E,EAAAA,CAAY1L,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,GAAvB,CAA4BgC,CAA5B,CAChB,IAAKiB,CAAL,EAAmB2D,CAAnB,CAIO,CAEL,IAAMqC,EAAgC,IAAd,GAACnF,CAAD,CAAsB,MAAtB,CAA+B,OAClDb,EAAL,GACEA,CADF,CACcgG,CADd,CAGKrC,EAAL,GACEA,CADF,CACcqC,CADd,CANK,CAJP,IAGErC,EAAA,CADA3D,CACA,CADY,OAad,OAAO,CADMA,CACN,CADkB,GAClB,CADwBa,CACxB,CADmC,GACnC,CADyC8C,CACzC,CAAO5E,CAAP,CAtBgC,CAyBzC9G,EAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,YAAA,CAAsB,QAAQ,CAAC8E,CAAD,CAAQ,CAEpC,IAAMgC,EAAQ9G,CAAAA,CAAAA,OAAAA,CAAAA,GAAIe,CAAAA,iBAGlB,OAAO,CADM,GACN,EAFWf,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+BgC,CAA/B,CAEX,EAFoD,MAEpD,EAAOA,CAAP,CAL6B,CAQtC9G;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,aAAA,CAAuB,QAAQ,CAAC8E,CAAD,CAAQ,CAGrC,MAAO,CADuC,MAAjCpB,GAACoB,CAAM8C,CAAAA,aAAN,CAAoB,MAApB,CAADlE,CAA2C,MAA3CA,CAAoD,OAC1D,CAAO1D,CAAAA,CAAAA,OAAAA,CAAAA,GAAIG,CAAAA,YAAX,CAH8B,CAMvCH,EAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,UAAA,CAAoB,QAAQ,CAAC8E,CAAD,CAAQ,CAElC,MAAO,CAAC,MAAD,CAAS9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIG,CAAAA,YAAb,CAF2B,CAKpCH;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,aAAA,CAAuB,QAAQ,CAAC8E,CAAD,CAAQ,CAErC,IAAMkJ,EACFhO,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,IAAvB,CAA6B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIkC,CAAAA,iBAAjC,CADE8L,EACqD,OAD3D,CAEMC,EACFjO,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIkC,CAAAA,iBAAnC,CADE+L,EACuD,MACvDC,EAAAA,CACFlO,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIkC,CAAAA,iBAAnC,CADEgM,EACuD,MAE7D,OAAO,CADMF,CACN,CADiB,KACjB,CADyBC,CACzB,CADsC,KACtC,CAD8CC,CAC9C,CAAOlO,CAAAA,CAAAA,OAAAA,CAAAA,GAAIkC,CAAAA,iBAAX,CAT8B,C,CCtFvC,IAAA,iCAAA,EAOAlC,EAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,kBAAA,CAA4B,QAAQ,CAAC8E,CAAD,CAAQ,CAE1C,MAAO,CAAC,SAAD,CAAY9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAhB,CAFmC,CAK5CP,EAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,iBAAA,CAA2B,QAAQ,CAAC8E,CAAD,CAAQ,CAGzC,IADA,IAAIpB,EAAW4E,KAAJ,CAAUxD,CAAMoD,CAAAA,UAAhB,CAAX,CACSzC,EAAI,CAAb,CAAgBA,CAAhB,CAAoBX,CAAMoD,CAAAA,UAA1B,CAAsCzC,CAAA,EAAtC,CACE/B,CAAA,CAAK+B,CAAL,CAAA,CAAUzF,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,KAAvB,CAA+BW,CAA/B,CAAkCzF,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAtC,CAAV,EAA+D,MAEjEmB,EAAA,CAAO,QAAP,CAAkBA,CAAKK,CAAAA,IAAL,CAAU,IAAV,CAAlB,CAAoC,GACpC,OAAO,CAACL,CAAD,CAAO1D,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAX,CAPkC,CAU3CP;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,YAAA,CAAsB,QAAQ,CAAC8E,CAAD,CAAQ,CAEpC,IAAM0D,EAAexI,CAAAA,CAAAA,OAAAA,CAAAA,GAAIyI,CAAAA,gBAAJ,CAAqB,cAArB,CAAqC,aAArC,CACZzI,CAAAA,CAAAA,OAAAA,CAAAA,GAAI0I,CAAAA,0BADQ,CAAqC,8IAArC,CAArB,CASMyF,EAAUnO,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAnC,CAAV4L,EAA4D,MAC5DC,EAAAA,CAAcpO,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,KAAvB,CAA8B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAlC,CAAd6L,EAA+D,GAErE,OAAO,CADM5F,CACN,CADqB,GACrB,CAD2B2F,CAC3B,CADqC,IACrC,CAD4CC,CAC5C,CAD0D,GAC1D,CAAOpO,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAX,CAd6B,CAiBtCP;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,YAAA,CAAsB,QAAQ,CAAC8E,CAAD,CAAQ,CAEpC,IAAM0D,EAAexI,CAAAA,CAAAA,OAAAA,CAAAA,GAAIyI,CAAAA,gBAAJ,CAAqB,QAArB,CAA+B,aAA/B,CACZzI,CAAAA,CAAAA,OAAAA,CAAAA,GAAI0I,CAAAA,0BADQ,CAA+B,qHAA/B,CASf4D,EAAAA,CAAOtM,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,OAAvB,CAAgC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAApC,CAAP+J,EAA0D,IAChE,OAAO,CAAC9D,CAAD,CAAgB,GAAhB,CAAsB8D,CAAtB,CAA6B,GAA7B,CAAkCtM,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAtC,CAZ6B,CAetCP;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,aAAA,CAAuB,QAAQ,CAAC8E,CAAD,CAAQ,CAIrC,MAAO,CAAC,QAAD,EADH9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,OAAvB,CAAgC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAApC,CACG,EADyD,SACzD,EAAwB,GAAxB,CAA6BP,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAjC,CAJ8B,CAOvCP;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,aAAA,CAAuB,QAAQ,CAAC8E,CAAD,CAAQ,CAErC,IAAMiD,EAAY/H,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAnC,CAAZwF,EAA8D,IAApE,CACM2D,EAAY1L,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,OAAvB,CAAgC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIM,CAAAA,YAApC,CAAZoL,EAAiE,IADvE,CAEI5C,EAAa,KAFjB,CAGIC,EAAkB,EAClBjE,EAAMlC,CAAAA,SAAUmE,CAAAA,OAAQC,CAAAA,aAA5B,GACE8B,CACA,CADa,IACb,CAAAC,CAAA,CAAkB,MAFpB,CA6BA,OAAO,EAxB4B,OAAnCP,GAAI1D,CAAM8C,CAAAA,aAAN,CAAoB,KAApB,CAAJY,CAEiBxI,CAAAA,CAAAA,OAAAA,CAAAA,GAAIyI,CAAAA,gBAAJ,CAAqB,SAArB,CAAgC,aAAhC,CACRzI,CAAAA,CAAAA,OAAAA,CAAAA,GAAI0I,CAAAA,0BADI,CAAgC,wIAAhC;AAGkCK,CAHlC,CAAgC,mBAAhC,CAKRD,CALQ,CAAgC,QAAhC,CAFjBN,CAYiBxI,CAAAA,CAAAA,OAAAA,CAAAA,GAAIyI,CAAAA,gBAAJ,CAAqB,aAArB,CAAoC,aAApC,CACRzI,CAAAA,CAAAA,OAAAA,CAAAA,GAAI0I,CAAAA,0BADI,CAAoC,oCAApC,CAEPI,CAFO,CAAoC,oHAApC,CAImCC,CAJnC,CAAoC,8BAApC,CAYV,EADqB,GACrB,CAD2B2C,CAC3B,CADuC,IACvC,CAD8C3D,CAC9C,CAD0D,GAC1D,CAAO/H,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAX,CAnC8B,CAsCvCP;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,cAAA,CAAwB,QAAQ,CAAC8E,CAAD,CAAQ,CAEtC,IAAMuJ,EAAOvJ,CAAM8C,CAAAA,aAAN,CAAoB,MAApB,CAAPyG,EAAsC,KAE5C,QADcvJ,CAAM8C,CAAAA,aAAN,CAAoB,OAApB,CACd,EAD8C,YAC9C,EACE,KAAK,OAAL,CACE,GAAa,KAAb,GAAIyG,CAAJ,CAIE,MAAO,EAFHrO,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,OAAvB,CAAgC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIM,CAAAA,YAApC,CAEG,EAFkD,SAElD,EADa,KACb,CAAON,CAAAA,CAAAA,OAAAA,CAAAA,GAAIM,CAAAA,YAAX,CACF,IAAa,YAAb,GAAI+N,CAAJ,CAIL,MAAO,CADM,cACN,EAFHrO,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,OAAvB,CAAgC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAApC,CAEG,EAFgD,SAEhD,EAD8B,GAC9B,CAAOvC,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAX,CACF,IAAa,QAAb,GAAI8N,CAAJ,CAGL,MAAO,cAAP,EADIrO,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,OAAvB,CAAgC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAApC,CACJ;AADuD,SACvD,EAA+B,MAEjC,MACF,MAAK,MAAL,CACE,GAAa,KAAb,GAAI8L,CAAJ,CAIE,MAAO,CADM,MACN,EAFHrO,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,OAAvB,CAAgC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAApC,CAEG,EAFgD,SAEhD,EADsB,GACtB,CAAOvC,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAX,CACF,IAAa,YAAb,GAAI8N,CAAJ,CAIL,MAAO,CADM,YACN,EAFHrO,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,OAAvB,CAAgC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAApC,CAEG,EAFgD,SAEhD,EAD4B,GAC5B,CAAOvC,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAX,CACF,IAAa,QAAb,GAAI8N,CAAJ,CAGL,MAAO,YAAP,EADIrO,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,OAAvB,CAAgC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAApC,CACJ,EADuD,SACvD,EAA6B,MAE/B,MACF,MAAK,YAAL,CACE,IAAM6E,EAAKpH,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuG,CAAAA,WAAJ,CAAgBzB,CAAhB;AAAuB,IAAvB,CACX,IAAa,KAAb,GAAIuJ,CAAJ,CAIE,MAAO,EAFHrO,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,OAAvB,CAAgC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIM,CAAAA,YAApC,CAEG,EAFkD,SAElD,EADa,GACb,CADmB8G,CACnB,CADwB,GACxB,CAAOpH,CAAAA,CAAAA,OAAAA,CAAAA,GAAIM,CAAAA,YAAX,CACF,IAAa,YAAb,GAAI+N,CAAJ,CAIL,MAAO,CADM,eACN,EAFHrO,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,OAAvB,CAAgC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAApC,CAEG,EAFgD,SAEhD,EAD+B,IAC/B,CADsC6E,CACtC,CAD2C,SAC3C,CAAOpH,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAX,CACF,IAAa,QAAb,GAAI8N,CAAJ,CAGL,MAAO,eAAP,EADIrO,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,OAAvB,CAAgC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAApC,CACJ,EADuD,SACvD,EAAgC,IAAhC,CAAuC6E,CAAvC,CAA4C,SAE9C,MAEF,MAAK,UAAL,CACE,GAAa,KAAb,GAAIiH,CAAJ,CAKE,MAJM/B,EAIC,CAHHtM,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB;AAAuB,OAAvB,CAAgC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAApC,CAGG,EAHgD,SAGhD,CAFD6E,CAEC,CAFIpH,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuG,CAAAA,WAAJ,CAAgBzB,CAAhB,CAAuB,IAAvB,CAA6B,CAA7B,CAAgC,CAAA,CAAhC,CAEJ,CAAA,CADM,cACN,CADuBwH,CACvB,CAD8B,IAC9B,CADqClF,CACrC,CAD0C,SAC1C,CAAOpH,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAX,CACF,IAAa,YAAb,GAAI8N,CAAJ,EAAsC,QAAtC,GAA6BA,CAA7B,CAAgD,CAC/C/B,CAAAA,CACFtM,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,OAAvB,CAAgC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAApC,CADE+J,EACiD,SACjDlF,EAAAA,CACFpH,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuG,CAAAA,WAAJ,CAAgBzB,CAAhB,CAAuB,IAAvB,CAA6B,CAA7B,CAAgC,CAAA,CAAhC,CAAuC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIsB,CAAAA,iBAA3C,CACEoC,EAAAA,CAAO,eAAPA,CAAyB4I,CAAzB5I,CAAgC,UAAhCA,CAA6C4I,CAA7C5I,CAAoD,MAApDA,CAA6D0D,CAA7D1D,CACF,SACJ,IAAa,YAAb,GAAI2K,CAAJ,CACE,MAAO,CAAC3K,CAAD,CAAO1D,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAX,CACF,IAAa,QAAb,GAAI8N,CAAJ,CACL,MAAO3K,EAAP,CAAc,KAVqC,CAavD,KACF,MAAK,QAAL,CACQ4I,CAAAA;AAAOtM,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,OAAvB,CAAgC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAApC,CAAP+J,EAA0D,SAChE,IAAa,KAAb,GAAI+B,CAAJ,CAOE,MAAO,CANcrO,CAAAA,CAAAA,OAAAA,CAAAA,GAAIyI,CAAAA,gBAAJD,CAAqB,uBAArBA,CAA8C,aAA9CA,CAClBxI,CAAAA,CAAAA,OAAAA,CAAAA,GAAI0I,CAAAA,0BADcF,CAA8C,yDAA9CA,CAMd,CADqB,GACrB,CAD2B8D,CAC3B,CADkC,GAClC,CAAOtM,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAX,CACF,IAAa,YAAb,GAAI8N,CAAJ,CAUL,MAAO,CARHrO,CAAAA,CAAAA,OAAAA,CAAAA,GAAIyI,CAAAA,gBAAJD,CAAqB,8BAArBA,CAAqD,aAArDA,CACDxI,CAAAA,CAAAA,OAAAA,CAAAA,GAAI0I,CAAAA,0BADHF,CAAqD,qGAArDA,CAQG;AADqB,GACrB,CAD2B8D,CAC3B,CADkC,GAClC,CAAOtM,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAX,CACF,IAAa,QAAb,GAAI8N,CAAJ,CAML,MALqBrO,EAAAA,CAAAA,OAAAA,CAAAA,GAAIyI,CAAAA,gBAAJD,CAAqB,0BAArBA,CAAiD,aAAjDA,CAClBxI,CAAAA,CAAAA,OAAAA,CAAAA,GAAI0I,CAAAA,0BADcF,CAAiD,0DAAjDA,CAKrB,CAAsB,GAAtB,CAA4B8D,CAA5B,CAAmC,MAtGzC,CA2GA,KAAMrD,MAAA,CAAM,yCAAN,CAAN,CA/GsC,CAkHxCjJ;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,cAAA,CAAwB,QAAQ,CAAC8E,CAAD,CAAQ,CAGtC,IAAMuJ,EAAOvJ,CAAM8C,CAAAA,aAAN,CAAoB,MAApB,CAAPyG,EAAsC,KAA5C,CACMrF,EAAQlE,CAAM8C,CAAAA,aAAN,CAAoB,OAApB,CAARoB,EAAwC,YAD9C,CAEMT,EAAQvI,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,IAAvB,CAA6B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAImC,CAAAA,gBAAjC,CAARoG,EAA8D,MAapE,QAAQS,CAAR,EACE,KAAK,OAAL,CACE,GAAa,KAAb,GAAIqF,CAAJ,CAGE,OADIrO,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIM,CAAAA,YAAnC,CACJ,EADwD,SACxD,EAAc,QAAd,CAAyBiI,CAAzB,CAAiC,KAC5B,IAAa,QAAb,GAAI8F,CAAJ,CAGL,MAAO,gBAAP,EADIrO,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAnC,CACJ,EADsD,SACtD,EAAiC,IAAjC,CAAwCgG,CAAxC,CAAgD,MAElD,MACF,MAAK,MAAL,CACQ+D,CAAAA,CAAOtM,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB;AAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAnC,CAAP+J,EAAyD,SAC/D,IAAa,KAAb,GAAI+B,CAAJ,CAME,MALqBrO,EAAAA,CAAAA,OAAAA,CAAAA,GAAIyI,CAAAA,gBAAJD,CAAqB,qBAArBA,CAA4C,aAA5CA,CAClBxI,CAAAA,CAAAA,OAAAA,CAAAA,GAAI0I,CAAAA,0BADcF,CAA4C,8DAA5CA,CAKrB,CAAsB,GAAtB,CAA4B8D,CAA5B,CAAmC,IAAnC,CAA0C/D,CAA1C,CAAkD,MAC7C,IAAa,QAAb,GAAI8F,CAAJ,CACL,MAAO,aAAP,CAAuB/B,CAAvB,CAA8B,IAA9B,CAAqC/D,CAArC,CAA6C,MAE/C,MAEF,MAAK,YAAL,CACQnB,CAAAA,CAAKpH,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuG,CAAAA,WAAJ,CAAgBzB,CAAhB,CAAuB,IAAvB,CACX,IAAa,KAAb,GAAIuJ,CAAJ,CAGE,OADIrO,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIM,CAAAA,YAAnC,CACJ,EADwD,SACxD,EAAc,GAAd,CAAoB8G,CAApB,CAAyB,MAAzB,CAAkCmB,CAAlC,CAA0C,KACrC,IAAa,QAAb,GAAI8F,CAAJ,CAGL,MAAO,eAAP;CADIrO,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAnC,CACJ,EADsD,SACtD,EAAgC,IAAhC,CAAuC6E,CAAvC,CAA4C,OAA5C,CAAsDmB,CAAtD,CAA8D,MAEhE,MAEF,MAAK,UAAL,CACQ+D,CAAAA,CAAOtM,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAnC,CAAP+J,EAAyD,SACzDlF,EAAAA,CAAKpH,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuG,CAAAA,WAAJ,CAAgBzB,CAAhB,CAAuB,IAAvB,CAA6B,CAA7B,CACX,IAAa,KAAb,GAAIuJ,CAAJ,CAME,MALqBrO,EAAAA,CAAAA,OAAAA,CAAAA,GAAIyI,CAAAA,gBAAJD,CAAqB,oBAArBA,CAA2C,aAA3CA,CAClBxI,CAAAA,CAAAA,OAAAA,CAAAA,GAAI0I,CAAAA,0BADcF,CAA2C,qEAA3CA,CAKrB,CAAsB,GAAtB,CAA4B8D,CAA5B,CAAmC,IAAnC,CAA0ClF,CAA1C,CAA+C,IAA/C,CAAsDmB,CAAtD,CAA8D,MACzD,IAAa,QAAb,GAAI8F,CAAJ,CAML,MALqBrO,EAAAA,CAAAA,OAAAA,CAAAA,GAAIyI,CAAAA,gBAAJD,CAAqB,uBAArBA;AAA8C,aAA9CA,CAClBxI,CAAAA,CAAAA,OAAAA,CAAAA,GAAI0I,CAAAA,0BADcF,CAA8C,4FAA9CA,CAKrB,CAAsB,GAAtB,CAA4B8D,CAA5B,CAAmC,IAAnC,CAA0ClF,CAA1C,CAA+C,IAA/C,CAAsDmB,CAAtD,CAA8D,MAEhE,MAEF,MAAK,QAAL,CACE+F,CAAA,CACItO,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAI2B,CAAAA,eAAnC,CADJ,EAC2D,SArE7D,IAAI2M,CAAWxB,CAAAA,KAAX,CAAiB,SAAjB,CAAJ,CACE,CAAA,CAAO,EADT,KAAA,CAGMyB,CAAAA,CAAUvO,CAAAA,CAAAA,OAAAA,CAAAA,GAAIgD,CAAAA,OAAQ4J,CAAAA,eAAZ,CAA4B,UAA5B,CAAwC/E,CAAAA,CAAAA,4BAAAA,CAAAA,QAASC,CAAAA,QAAjD,CAChB,KAAMpE,EAAO6K,CAAP7K,CAAiB,MAAjBA,CAA0B4K,CAA1B5K,CAAuC,KAC7C4K,EAAA,CAAaC,CACb,EAAA,CAAO7K,CANP,CAwEQ8K,CAAAA,CAAOxO,CAAAA,CAAAA,OAAAA,CAAAA,GAAIgD,CAAAA,OAAQ4J,CAAAA,eAAZ,CAA4B,OAA5B,CAAqC/E,CAAAA,CAAAA,4BAAAA,CAAAA,QAASC,CAAAA,QAA9C,CACbpE;CAAA,EAAQ8K,CAAR,CAAe,mBAAf,CAAqClC,CAArC,CAA4C,SAC5C,IAAa,KAAb,GAAI+B,CAAJ,CAEE,MADA3K,EACA,EADQ4I,CACR,CADe,GACf,CADqBkC,CACrB,CAD4B,MAC5B,CADqCjG,CACrC,CAD6C,KAC7C,CACK,IAAa,QAAb,GAAI8F,CAAJ,CAEL,MADA3K,EACA,EADQ,eACR,CAD0B4I,CAC1B,CADiC,IACjC,CADwCkC,CACxC,CAD+C,OAC/C,CADyDjG,CACzD,CADiE,MACjE,CAvEN,CA2EA,KAAMU,MAAA,CAAM,yCAAN,CAAN,CA7FsC,CAgGxCjJ;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,gBAAA,CAA0B,QAAQ,CAAC8E,CAAD,CAAQ,CAExC,IAAMwH,EAAOtM,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAnC,CAAP+J,EAAyD,SAA/D,CACMpD,EAASpE,CAAM8C,CAAAA,aAAN,CAAoB,QAApB,CADf,CAEMuB,EAASrE,CAAM8C,CAAAA,aAAN,CAAoB,QAApB,CAEf,IAAe,OAAf,GAAIsB,CAAJ,EAAqC,MAArC,GAA0BC,CAA1B,CAEO,GACHmD,CAAKQ,CAAAA,KAAL,CAAW,SAAX,CADG,EAES,UAFT,GAEF5D,CAFE,EAEkC,YAFlC,GAEuBC,CAFvB,CAEiD,CAItD,OAAQD,CAAR,EACE,KAAK,YAAL,CACEE,CAAA,CAAMpJ,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuG,CAAAA,WAAJ,CAAgBzB,CAAhB,CAAuB,KAAvB,CACN,MACF,MAAK,UAAL,CACEsE,CAAA,CAAMpJ,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuG,CAAAA,WAAJ,CAAgBzB,CAAhB,CAAuB,KAAvB,CAA8B,CAA9B,CAAiC,CAAA,CAAjC,CAAwC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIsB,CAAAA,iBAA5C,CACN8H,EAAA,CAAM,QAAN,CAAiBkD,CAAjB,CAAwB,MAAxB,CAAiClD,CACjC,MACF,MAAK,OAAL,CACEA,CAAA,CAAM,GACN,MACF,SACE,KAAMH,MAAA,CAAM,sCAAN,CAAN;AAZJ,CAgBA,OAAQE,CAAR,EACE,KAAK,YAAL,CACEE,CAAA,CAAMrJ,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuG,CAAAA,WAAJ,CAAgBzB,CAAhB,CAAuB,KAAvB,CAA8B,CAA9B,CAAiC,CAAA,CAAjC,CAAwC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIsB,CAAAA,iBAA5C,CACG+H,EAAT,EAAe,KAGb1D,EAAA,CAFE,GAAAN,CAAAA,CAAAA,mCAAYiC,CAAAA,QAAZ,EAAqBmF,MAAA,CAAOrD,CAAP,CAArB,CAAJ,EACIqD,MAAA,CAAOrD,CAAP,CAAY0D,CAAAA,KAAZ,CAAkB,UAAlB,CADJ,CAEEnH,CAFF,CAEYyD,CAFZ,CAIEzD,CAJF,EAIY,GAJZ,CAIkByD,CAJlB,CAIwB,GAJxB,CAMAzD,EAAA,EAAU,MACV,MACF,MAAK,UAAL,CACE0D,CAAA,CAAMrJ,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuG,CAAAA,WAAJ,CAAgBzB,CAAhB,CAAuB,KAAvB,CAA8B,CAA9B,CAAiC,CAAA,CAAjC,CAAwC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIsB,CAAAA,iBAA5C,CACNqE,EAAA,CAAS,QAAT,CAAoB2G,CAApB,CAA2B,MAA3B,CAAoCjD,CAApC,CAA0C,KAGxC1D,EAAA,CAFE,GAAAN,CAAAA,CAAAA,mCAAYiC,CAAAA,QAAZ,EAAqBmF,MAAA,CAAOrD,CAAP,CAArB,CAAJ,EACIqD,MAAA,CAAOrD,CAAP,CAAY0D,CAAAA,KAAZ,CAAkB,UAAlB,CADJ,CAEEnH,CAFF,CAEYyD,CAFZ,CAIEzD,CAJF,EAIY,GAJZ,CAIkByD,CAJlB,CAIwB,GAJxB,CAMA,MACF,MAAK,MAAL,CACEzD,CAAA,CAAS,QAAT,CAAoB2G,CAApB,CAA2B,MAGzB3G;CAAA,CAFE,GAAAN,CAAAA,CAAAA,mCAAYiC,CAAAA,QAAZ,EAAqBmF,MAAA,CAAOrD,CAAP,CAArB,CAAJ,EACIqD,MAAA,CAAOrD,CAAP,CAAY0D,CAAAA,KAAZ,CAAkB,UAAlB,CADJ,CAEEnH,CAFF,CAEYyD,CAFZ,CAIEzD,CAJF,EAIY,GAJZ,CAIkByD,CAJlB,CAIwB,GAJxB,CAMA,MACF,SACE,KAAMH,MAAA,CAAM,sCAAN,CAAN,CAhCJ,CAkCAvF,CAAA,CAAO,cAAP,CAAwB4I,CAAxB,CAA+B,IAA/B,CAAsClD,CAAtC,CAA4C,IAA5C,CAAmDzD,CAAnD,CAA4D,GAtDN,CAFjD,IAyDA,CACL,IAAMyD,EAAMpJ,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuG,CAAAA,WAAJ,CAAgBzB,CAAhB,CAAuB,KAAvB,CACNuE,EAAAA,CAAMrJ,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuG,CAAAA,WAAJ,CAAgBzB,CAAhB,CAAuB,KAAvB,CAuBZpB,EAAA,CAtBqB1D,CAAAA,CAAAA,OAAAA,CAAAA,GAAIyI,CAAAA,gBAAJD,CAAqB,mBAArBA,CAA0C,aAA1CA,CACdxI,CAAAA,CAAAA,OAAAA,CAAAA,GAAI0I,CAAAA,0BADUF,CAA0C,moBAA1CA,CAsBrB;AAAsB,GAAtB,CAA4B8D,CAA5B,CAAmC,KAAnC,CAA4CpD,CAA5C,CAAqD,KAArD,CAA8DE,CAA9D,CAAoE,KAApE,CACID,CADJ,CACa,KADb,CACsBE,CADtB,CAC4B,GA1BvB,CA4BP,MAAO,CAAC3F,CAAD,CAAO1D,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAX,CA7FiC,CAgG1CP;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,UAAA,CAAoB,QAAQ,CAAC8E,CAAD,CAAQ,CAElC,IAAM2J,EAAWzO,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAnC,CAAXkM,EAA6D,SAAnE,CACMC,EAAiD,GAArC,GAAA5J,CAAM8C,CAAAA,aAAN,CAAoB,WAApB,CAAA,CAA2C,CAA3C,CAA+C,CAAC,CAC5DhC,EAAAA,CAAOd,CAAM8C,CAAAA,aAAN,CAAoB,MAApB,CAmBb,OAAO,CAlBc5H,CAAAA,CAAAA,OAAAA,CAAAA,GAAIyI,CAAAA,gBAAJD,CAAqB,YAArBA,CAAmC,aAAnCA,CACZxI,CAAAA,CAAAA,OAAAA,CAAAA,GAAI0I,CAAAA,0BADQF,CAAmC,4UAAnCA,CAkBd,CADY,GACZ;AADkBiG,CAClB,CAD6B,KAC7B,CADqC7I,CACrC,CAD4C,KAC5C,CADoD8I,CACpD,CADgE,GAChE,CAAW1O,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAf,CAvB2B,CA0BpCP,EAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,WAAA,CAAqB,QAAQ,CAAC8E,CAAD,CAAQ,CAEnC,IAAI6J,EAAc3O,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,OAAvB,CAAgC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAApC,CAAlB,CACMqM,EAAc5O,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,OAAvB,CAAgC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAApC,CAAdqM,EAAiE,IACjEP,EAAAA,CAAOvJ,CAAM8C,CAAAA,aAAN,CAAoB,MAApB,CAEb,IAAa,OAAb,GAAIyG,CAAJ,CACOM,CAGL,GAFEA,CAEF,CAFgB,IAEhB,EAAAnG,CAAA,CAAe,SAJjB,KAKO,IAAa,MAAb,GAAI6F,CAAJ,CACAM,CAGL,GAFEA,CAEF,CAFgB,SAEhB,EAAAnG,CAAA,CAAe,SAJV,KAML,MAAMS,MAAA,CAAM,gBAAN,CAAyBoF,CAAzB,CAAN,CAGF,MAAO,CADM7F,CACN,CADqB,GACrB,CAD2BoG,CAC3B,CADyC,IACzC,CADgDD,CAChD,CAD8D,GAC9D,CAAO3O,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAX,CApB4B,CAuBrCP;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,aAAA,CAAuB,QAAQ,CAAC8E,CAAD,CAAQ,CAIrC,MAAO,CADM,gBACN,EAFM9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAnC,CAEN,EAFwD,IAExD,EADgC,GAChC,CAAOvC,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAX,CAJ8B,C,CCjdvC,IAAA,kCAAA,EAKAP,EAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,aAAA,CAAuB,QAAQ,CAAC8E,CAAD,CAAQ,CAGrC,MAAO,CADM9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAImE,CAAAA,MAAJT,CAAWoB,CAAM8C,CAAAA,aAAN,CAAoB,QAApB,CAAXlE,CACN,CAAO1D,CAAAA,CAAAA,OAAAA,CAAAA,GAAIG,CAAAA,YAAX,CAH8B,CAMvCH,EAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,aAAA,CAAuB,QAAQ,CAAC8E,CAAD,CAAQ,CAQrC,MAAO,CANc9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIyI,CAAAA,gBAAJD,CAAqB,eAArBA,CAAsC,aAAtCA,CACZxI,CAAAA,CAAAA,OAAAA,CAAAA,GAAI0I,CAAAA,0BADQF,CAAsC,wFAAtCA,CAMd,CADqB,IACrB,CAAOxI,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAX,CAR8B,CAWvCP;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,UAAA,CAAoB,QAAQ,CAAC8E,CAAD,CAAQ,CAElC,IAAM+J,EAAM7O,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,KAAvB,CAA8B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAlC,CAANsM,EAAuD,CAA7D,CACMC,EAAQ9O,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,OAAvB,CAAgC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAApC,CAARuM,EAA2D,CAC3DC,EAAAA,CAAO/O,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,MAAvB,CAA+B9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAnC,CAAPwM,EAAyD,CAc/D,OAAO,CAbc/O,CAAAA,CAAAA,OAAAA,CAAAA,GAAIyI,CAAAA,gBAAJD,CAAqB,YAArBA,CAAmC,aAAnCA,CACZxI,CAAAA,CAAAA,OAAAA,CAAAA,GAAI0I,CAAAA,0BADQF,CAAmC,0VAAnCA,CAad;AADqB,GACrB,CAD2BqG,CAC3B,CADiC,IACjC,CADwCC,CACxC,CADgD,IAChD,CADuDC,CACvD,CAD8D,GAC9D,CAAO/O,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAX,CAlB2B,CAqBpCP;CAAAA,CAAAA,OAAAA,CAAAA,GAAA,CAAA,YAAA,CAAsB,QAAQ,CAAC8E,CAAD,CAAQ,CAEpC,IAAMkK,EAAKhP,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,SAAvB,CAAkC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAtC,CAALyM,EAA0D,WAAhE,CACMC,EAAKjP,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,SAAvB,CAAkC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAAtC,CAAL0M,EAA0D,WAC1DC,EAAAA,CAAQlP,CAAAA,CAAAA,OAAAA,CAAAA,GAAIqH,CAAAA,WAAJ,CAAgBvC,CAAhB,CAAuB,OAAvB,CAAgC9E,CAAAA,CAAAA,OAAAA,CAAAA,GAAIuC,CAAAA,UAApC,CAAR2M,EAA2D,EAqBjE,OAAO,CApBclP,CAAAA,CAAAA,OAAAA,CAAAA,GAAIyI,CAAAA,gBAAJD,CAAqB,cAArBA,CAAqC,aAArCA,CACZxI,CAAAA,CAAAA,OAAAA,CAAAA,GAAI0I,CAAAA,0BADQF,CAAqC,8mBAArCA,CAoBd,CADqB,GACrB;AAD2BwG,CAC3B,CADgC,IAChC,CADuCC,CACvC,CAD4C,IAC5C,CADmDC,CACnD,CAD2D,GAC3D,CAAOlP,CAAAA,CAAAA,OAAAA,CAAAA,GAAIO,CAAAA,mBAAX,CAzB6B,C,CCzCtC,IAAA,+BAAA","file":"php_compressed.js","sourceRoot":"./"} \ No newline at end of file diff --git a/python_compressed.js b/python_compressed.js index 457f62e29..55cacf1b6 100644 --- a/python_compressed.js +++ b/python_compressed.js @@ -7,10 +7,11 @@ } else if (typeof exports === 'object') { // Node.js module.exports = factory(require("./blockly_compressed.js")); } else { // Browser - root.Blockly.Python = factory(root.Blockly); + var factoryExports = factory(root.Blockly); + root.Blockly.Python = factoryExports; } -}(this, function(Blockly) { -const $=Blockly.internal_; +}(this, function(__parent__) { +var $=__parent__.__namespace__; var module$contents$Blockly$Python_Python=new $.module$exports$Blockly$Generator.Generator("Python");module$contents$Blockly$Python_Python.addReservedWords("False,None,True,and,as,assert,break,class,continue,def,del,elif,else,except,exec,finally,for,from,global,if,import,in,is,lambda,nonlocal,not,or,pass,print,raise,return,try,while,with,yield,NotImplemented,Ellipsis,__debug__,quit,exit,copyright,license,credits,ArithmeticError,AssertionError,AttributeError,BaseException,BlockingIOError,BrokenPipeError,BufferError,BytesWarning,ChildProcessError,ConnectionAbortedError,ConnectionError,ConnectionRefusedError,ConnectionResetError,DeprecationWarning,EOFError,Ellipsis,EnvironmentError,Exception,FileExistsError,FileNotFoundError,FloatingPointError,FutureWarning,GeneratorExit,IOError,ImportError,ImportWarning,IndentationError,IndexError,InterruptedError,IsADirectoryError,KeyError,KeyboardInterrupt,LookupError,MemoryError,ModuleNotFoundError,NameError,NotADirectoryError,NotImplemented,NotImplementedError,OSError,OverflowError,PendingDeprecationWarning,PermissionError,ProcessLookupError,RecursionError,ReferenceError,ResourceWarning,RuntimeError,RuntimeWarning,StandardError,StopAsyncIteration,StopIteration,SyntaxError,SyntaxWarning,SystemError,SystemExit,TabError,TimeoutError,TypeError,UnboundLocalError,UnicodeDecodeError,UnicodeEncodeError,UnicodeError,UnicodeTranslateError,UnicodeWarning,UserWarning,ValueError,Warning,ZeroDivisionError,_,__build_class__,__debug__,__doc__,__import__,__loader__,__name__,__package__,__spec__,abs,all,any,apply,ascii,basestring,bin,bool,buffer,bytearray,bytes,callable,chr,classmethod,cmp,coerce,compile,complex,copyright,credits,delattr,dict,dir,divmod,enumerate,eval,exec,execfile,exit,file,filter,float,format,frozenset,getattr,globals,hasattr,hash,help,hex,id,input,int,intern,isinstance,issubclass,iter,len,license,list,locals,long,map,max,memoryview,min,next,object,oct,open,ord,pow,print,property,quit,range,raw_input,reduce,reload,repr,reversed,round,set,setattr,slice,sorted,staticmethod,str,sum,super,tuple,type,unichr,unicode,vars,xrange,zip"); module$contents$Blockly$Python_Python.ORDER_ATOMIC=0;module$contents$Blockly$Python_Python.ORDER_COLLECTION=1;module$contents$Blockly$Python_Python.ORDER_STRING_CONVERSION=1;module$contents$Blockly$Python_Python.ORDER_MEMBER=2.1;module$contents$Blockly$Python_Python.ORDER_FUNCTION_CALL=2.2;module$contents$Blockly$Python_Python.ORDER_EXPONENTIATION=3;module$contents$Blockly$Python_Python.ORDER_UNARY_SIGN=4;module$contents$Blockly$Python_Python.ORDER_BITWISE_NOT=4; module$contents$Blockly$Python_Python.ORDER_MULTIPLICATIVE=5;module$contents$Blockly$Python_Python.ORDER_ADDITIVE=6;module$contents$Blockly$Python_Python.ORDER_BITWISE_SHIFT=7;module$contents$Blockly$Python_Python.ORDER_BITWISE_AND=8;module$contents$Blockly$Python_Python.ORDER_BITWISE_XOR=9;module$contents$Blockly$Python_Python.ORDER_BITWISE_OR=10;module$contents$Blockly$Python_Python.ORDER_RELATIONAL=11;module$contents$Blockly$Python_Python.ORDER_LOGICAL_NOT=12; @@ -30,12 +31,12 @@ $.Blockly.Python.text_append=function(a){var b=$.Blockly.Python.nameDB_.getName( $.Blockly.Python.text_isEmpty=function(a){return["not len("+($.Blockly.Python.valueToCode(a,"VALUE",$.Blockly.Python.ORDER_NONE)||"''")+")",$.Blockly.Python.ORDER_LOGICAL_NOT]}; $.Blockly.Python.text_indexOf=function(a){var b="FIRST"===a.getFieldValue("END")?"find":"rfind",c=$.Blockly.Python.valueToCode(a,"FIND",$.Blockly.Python.ORDER_NONE)||"''";b=($.Blockly.Python.valueToCode(a,"VALUE",$.Blockly.Python.ORDER_MEMBER)||"''")+"."+b+"("+c+")";return a.workspace.options.oneBasedIndex?[b+" + 1",$.Blockly.Python.ORDER_ADDITIVE]:[b,$.Blockly.Python.ORDER_FUNCTION_CALL]}; $.Blockly.Python.text_charAt=function(a){var b=a.getFieldValue("WHERE")||"FROM_START",c=$.Blockly.Python.valueToCode(a,"VALUE","RANDOM"===b?$.Blockly.Python.ORDER_NONE:$.Blockly.Python.ORDER_MEMBER)||"''";switch(b){case "FIRST":return[c+"[0]",$.Blockly.Python.ORDER_MEMBER];case "LAST":return[c+"[-1]",$.Blockly.Python.ORDER_MEMBER];case "FROM_START":return a=$.Blockly.Python.getAdjustedInt(a,"AT"),[c+"["+a+"]",$.Blockly.Python.ORDER_MEMBER];case "FROM_END":return a=$.Blockly.Python.getAdjustedInt(a, -"AT",1,!0),[c+"["+a+"]",$.Blockly.Python.ORDER_MEMBER];case "RANDOM":return $.Blockly.Python.definitions_.import_random="import random",[$.Blockly.Python.provideFunction_("text_random_letter",["def "+$.Blockly.Python.FUNCTION_NAME_PLACEHOLDER_+"(text):"," x = int(random.random() * len(text))"," return text[x];"])+"("+c+")",$.Blockly.Python.ORDER_FUNCTION_CALL]}throw Error("Unhandled option (text_charAt).");}; +"AT",1,!0),[c+"["+a+"]",$.Blockly.Python.ORDER_MEMBER];case "RANDOM":return $.Blockly.Python.definitions_.import_random="import random",[$.Blockly.Python.provideFunction_("text_random_letter","\ndef "+$.Blockly.Python.FUNCTION_NAME_PLACEHOLDER_+"(text):\n x = int(random.random() * len(text))\n return text[x]\n")+"("+c+")",$.Blockly.Python.ORDER_FUNCTION_CALL]}throw Error("Unhandled option (text_charAt).");}; $.Blockly.Python.text_getSubstring=function(a){var b=a.getFieldValue("WHERE1"),c=a.getFieldValue("WHERE2"),d=$.Blockly.Python.valueToCode(a,"STRING",$.Blockly.Python.ORDER_MEMBER)||"''";switch(b){case "FROM_START":b=$.Blockly.Python.getAdjustedInt(a,"AT1");0===b&&(b="");break;case "FROM_END":b=$.Blockly.Python.getAdjustedInt(a,"AT1",1,!0);break;case "FIRST":b="";break;default:throw Error("Unhandled option (text_getSubstring)");}switch(c){case "FROM_START":a=$.Blockly.Python.getAdjustedInt(a,"AT2", 1);break;case "FROM_END":a=$.Blockly.Python.getAdjustedInt(a,"AT2",0,!0);(0,$.module$exports$Blockly$utils$string.isNumber)(String(a))?0===a&&(a=""):($.Blockly.Python.definitions_.import_sys="import sys",a+=" or sys.maxsize");break;case "LAST":a="";break;default:throw Error("Unhandled option (text_getSubstring)");}return[d+"["+b+" : "+a+"]",$.Blockly.Python.ORDER_MEMBER]}; $.Blockly.Python.text_changeCase=function(a){var b={UPPERCASE:".upper()",LOWERCASE:".lower()",TITLECASE:".title()"}[a.getFieldValue("CASE")];return[($.Blockly.Python.valueToCode(a,"TEXT",$.Blockly.Python.ORDER_MEMBER)||"''")+b,$.Blockly.Python.ORDER_FUNCTION_CALL]};$.Blockly.Python.text_trim=function(a){var b={LEFT:".lstrip()",RIGHT:".rstrip()",BOTH:".strip()"}[a.getFieldValue("MODE")];return[($.Blockly.Python.valueToCode(a,"TEXT",$.Blockly.Python.ORDER_MEMBER)||"''")+b,$.Blockly.Python.ORDER_FUNCTION_CALL]}; $.Blockly.Python.text_print=function(a){return"print("+($.Blockly.Python.valueToCode(a,"TEXT",$.Blockly.Python.ORDER_NONE)||"''")+")\n"}; -$.Blockly.Python.text_prompt_ext=function(a){var b=$.Blockly.Python.provideFunction_("text_prompt",["def "+$.Blockly.Python.FUNCTION_NAME_PLACEHOLDER_+"(msg):"," try:"," return raw_input(msg)"," except NameError:"," return input(msg)"]);var c=a.getField("TEXT")?$.Blockly.Python.quote_(a.getFieldValue("TEXT")):$.Blockly.Python.valueToCode(a,"TEXT",$.Blockly.Python.ORDER_NONE)||"''";b=b+"("+c+")";"NUMBER"===a.getFieldValue("TYPE")&&(b="float("+b+")");return[b,$.Blockly.Python.ORDER_FUNCTION_CALL]}; +$.Blockly.Python.text_prompt_ext=function(a){var b=$.Blockly.Python.provideFunction_("text_prompt","\ndef "+$.Blockly.Python.FUNCTION_NAME_PLACEHOLDER_+"(msg):\n try:\n return raw_input(msg)\n except NameError:\n return input(msg)\n");var c=a.getField("TEXT")?$.Blockly.Python.quote_(a.getFieldValue("TEXT")):$.Blockly.Python.valueToCode(a,"TEXT",$.Blockly.Python.ORDER_NONE)||"''";b=b+"("+c+")";"NUMBER"===a.getFieldValue("TYPE")&&(b="float("+b+")");return[b,$.Blockly.Python.ORDER_FUNCTION_CALL]}; $.Blockly.Python.text_prompt=$.Blockly.Python.text_prompt_ext;$.Blockly.Python.text_count=function(a){var b=$.Blockly.Python.valueToCode(a,"TEXT",$.Blockly.Python.ORDER_MEMBER)||"''";a=$.Blockly.Python.valueToCode(a,"SUB",$.Blockly.Python.ORDER_NONE)||"''";return[b+".count("+a+")",$.Blockly.Python.ORDER_FUNCTION_CALL]}; $.Blockly.Python.text_replace=function(a){var b=$.Blockly.Python.valueToCode(a,"TEXT",$.Blockly.Python.ORDER_MEMBER)||"''",c=$.Blockly.Python.valueToCode(a,"FROM",$.Blockly.Python.ORDER_NONE)||"''";a=$.Blockly.Python.valueToCode(a,"TO",$.Blockly.Python.ORDER_NONE)||"''";return[b+".replace("+c+", "+a+")",$.Blockly.Python.ORDER_MEMBER]};$.Blockly.Python.text_reverse=function(a){return[($.Blockly.Python.valueToCode(a,"TEXT",$.Blockly.Python.ORDER_MEMBER)||"''")+"[::-1]",$.Blockly.Python.ORDER_MEMBER]};var module$exports$Blockly$Python$procedures={}; $.Blockly.Python.procedures_defreturn=function(a){for(var b=[],c=a.workspace,d=(0,$.module$exports$Blockly$Variables.allUsedVarModels)(c)||[],e=0,f;f=d[e];e++)f=f.name,-1===a.getVars().indexOf(f)&&b.push($.Blockly.Python.nameDB_.getName(f,$.module$exports$Blockly$Names.NameType.VARIABLE));c=(0,$.module$exports$Blockly$Variables.allDeveloperVariables)(c);for(d=0;d 0";break;case "NEGATIVE":d=b+" < 0";break;case "DIVISIBLE_BY":a=$.Blockly.Python.valueToCode(a,"DIVISOR",$.Blockly.Python.ORDER_MULTIPLICATIVE);if(!a||"0"===a)return["False",$.Blockly.Python.ORDER_ATOMIC];d=b+" % "+a+" == 0"}return[d,$.Blockly.Python.ORDER_RELATIONAL]}; +$.Blockly.Python.math_number_property=function(a){var b={EVEN:[" % 2 == 0",$.Blockly.Python.ORDER_MULTIPLICATIVE,$.Blockly.Python.ORDER_RELATIONAL],ODD:[" % 2 == 1",$.Blockly.Python.ORDER_MULTIPLICATIVE,$.Blockly.Python.ORDER_RELATIONAL],WHOLE:[" % 1 == 0",$.Blockly.Python.ORDER_MULTIPLICATIVE,$.Blockly.Python.ORDER_RELATIONAL],POSITIVE:[" > 0",$.Blockly.Python.ORDER_RELATIONAL,$.Blockly.Python.ORDER_RELATIONAL],NEGATIVE:[" < 0",$.Blockly.Python.ORDER_RELATIONAL,$.Blockly.Python.ORDER_RELATIONAL], +DIVISIBLE_BY:[null,$.Blockly.Python.ORDER_MULTIPLICATIVE,$.Blockly.Python.ORDER_RELATIONAL],PRIME:[null,$.Blockly.Python.ORDER_NONE,$.Blockly.Python.ORDER_FUNCTION_CALL]},c=a.getFieldValue("PROPERTY");b=$.$jscomp.makeIterator(b[c]);var d=b.next().value,e=b.next().value;b=b.next().value;e=$.Blockly.Python.valueToCode(a,"NUMBER_TO_CHECK",e)||"0";if("PRIME"===c)$.Blockly.Python.definitions_.import_math="import math",$.Blockly.Python.definitions_.from_numbers_import_Number="from numbers import Number", +a=$.Blockly.Python.provideFunction_("math_isPrime","\ndef "+$.Blockly.Python.FUNCTION_NAME_PLACEHOLDER_+"(n):\n # https://en.wikipedia.org/wiki/Primality_test#Naive_methods\n # If n is not a number but a string, try parsing it.\n if not isinstance(n, Number):\n try:\n n = float(n)\n except:\n return False\n if n == 2 or n == 3:\n return True\n # False if n is negative, is 1, or not whole, or if n is divisible by 2 or 3.\n if n <= 1 or n % 1 != 0 or n % 2 == 0 or n % 3 == 0:\n return False\n # Check all the numbers of form 6k +/- 1, up to sqrt(n).\n for x in range(6, int(math.sqrt(n)) + 2, 6):\n if n % (x - 1) == 0 or n % (x + 1) == 0:\n return False\n return True\n")+ +"("+e+")";else if("DIVISIBLE_BY"===c){a=$.Blockly.Python.valueToCode(a,"DIVISOR",$.Blockly.Python.ORDER_MULTIPLICATIVE)||"0";if("0"===a)return["False",$.Blockly.Python.ORDER_ATOMIC];a=e+" % "+a+" == 0"}else a=e+d;return[a,b]}; $.Blockly.Python.math_change=function(a){$.Blockly.Python.definitions_.from_numbers_import_Number="from numbers import Number";var b=$.Blockly.Python.valueToCode(a,"DELTA",$.Blockly.Python.ORDER_ADDITIVE)||"0";a=$.Blockly.Python.nameDB_.getName(a.getFieldValue("VAR"),$.module$exports$Blockly$Names.NameType.VARIABLE);return a+" = ("+a+" if isinstance("+a+", Number) else 0) + "+b+"\n"};$.Blockly.Python.math_round=$.Blockly.Python.math_single;$.Blockly.Python.math_trig=$.Blockly.Python.math_single; -$.Blockly.Python.math_on_list=function(a){var b=a.getFieldValue("OP");a=$.Blockly.Python.valueToCode(a,"LIST",$.Blockly.Python.ORDER_NONE)||"[]";switch(b){case "SUM":b="sum("+a+")";break;case "MIN":b="min("+a+")";break;case "MAX":b="max("+a+")";break;case "AVERAGE":$.Blockly.Python.definitions_.from_numbers_import_Number="from numbers import Number";b=$.Blockly.Python.provideFunction_("math_mean",["def "+$.Blockly.Python.FUNCTION_NAME_PLACEHOLDER_+"(myList):"," localList = [e for e in myList if isinstance(e, Number)]", -" if not localList: return"," return float(sum(localList)) / len(localList)"])+"("+a+")";break;case "MEDIAN":$.Blockly.Python.definitions_.from_numbers_import_Number="from numbers import Number";b=$.Blockly.Python.provideFunction_("math_median",["def "+$.Blockly.Python.FUNCTION_NAME_PLACEHOLDER_+"(myList):"," localList = sorted([e for e in myList if isinstance(e, Number)])"," if not localList: return"," if len(localList) % 2 == 0:"," return (localList[len(localList) // 2 - 1] + localList[len(localList) // 2]) / 2.0", -" else:"," return localList[(len(localList) - 1) // 2]"])+"("+a+")";break;case "MODE":b=$.Blockly.Python.provideFunction_("math_modes",["def "+$.Blockly.Python.FUNCTION_NAME_PLACEHOLDER_+"(some_list):"," modes = []"," # Using a lists of [item, count] to keep count rather than dict",' # to avoid "unhashable" errors when the counted item is itself a list or dict.'," counts = []"," maxCount = 1"," for item in some_list:"," found = False"," for count in counts:"," if count[0] == item:", -" count[1] += 1"," maxCount = max(maxCount, count[1])"," found = True"," if not found:"," counts.append([item, 1])"," for counted_item, item_count in counts:"," if item_count == maxCount:"," modes.append(counted_item)"," return modes"])+"("+a+")";break;case "STD_DEV":$.Blockly.Python.definitions_.import_math="import math";b=$.Blockly.Python.provideFunction_("math_standard_deviation",["def "+$.Blockly.Python.FUNCTION_NAME_PLACEHOLDER_+"(numbers):"," n = len(numbers)", -" if n == 0: return"," mean = float(sum(numbers)) / n"," variance = sum((x - mean) ** 2 for x in numbers) / n"," return math.sqrt(variance)"])+"("+a+")";break;case "RANDOM":$.Blockly.Python.definitions_.import_random="import random";b="random.choice("+a+")";break;default:throw Error("Unknown operator: "+b);}return[b,$.Blockly.Python.ORDER_FUNCTION_CALL]}; -$.Blockly.Python.math_modulo=function(a){var b=$.Blockly.Python.valueToCode(a,"DIVIDEND",$.Blockly.Python.ORDER_MULTIPLICATIVE)||"0";a=$.Blockly.Python.valueToCode(a,"DIVISOR",$.Blockly.Python.ORDER_MULTIPLICATIVE)||"0";return[b+" % "+a,$.Blockly.Python.ORDER_MULTIPLICATIVE]}; +$.Blockly.Python.math_on_list=function(a){var b=a.getFieldValue("OP");a=$.Blockly.Python.valueToCode(a,"LIST",$.Blockly.Python.ORDER_NONE)||"[]";switch(b){case "SUM":b="sum("+a+")";break;case "MIN":b="min("+a+")";break;case "MAX":b="max("+a+")";break;case "AVERAGE":$.Blockly.Python.definitions_.from_numbers_import_Number="from numbers import Number";b=$.Blockly.Python.provideFunction_("math_mean","\ndef "+$.Blockly.Python.FUNCTION_NAME_PLACEHOLDER_+"(myList):\n localList = [e for e in myList if isinstance(e, Number)]\n if not localList: return\n return float(sum(localList)) / len(localList)\n")+ +"("+a+")";break;case "MEDIAN":$.Blockly.Python.definitions_.from_numbers_import_Number="from numbers import Number";b=$.Blockly.Python.provideFunction_("math_median","\ndef "+$.Blockly.Python.FUNCTION_NAME_PLACEHOLDER_+"(myList):\n localList = sorted([e for e in myList if isinstance(e, Number)])\n if not localList: return\n if len(localList) % 2 == 0:\n return (localList[len(localList) // 2 - 1] + localList[len(localList) // 2]) / 2.0\n else:\n return localList[(len(localList) - 1) // 2]\n")+ +"("+a+")";break;case "MODE":b=$.Blockly.Python.provideFunction_("math_modes","\ndef "+$.Blockly.Python.FUNCTION_NAME_PLACEHOLDER_+'(some_list):\n modes = []\n # Using a lists of [item, count] to keep count rather than dict\n # to avoid "unhashable" errors when the counted item is itself a list or dict.\n counts = []\n maxCount = 1\n for item in some_list:\n found = False\n for count in counts:\n if count[0] == item:\n count[1] += 1\n maxCount = max(maxCount, count[1])\n found = True\n if not found:\n counts.append([item, 1])\n for counted_item, item_count in counts:\n if item_count == maxCount:\n modes.append(counted_item)\n return modes\n')+ +"("+a+")";break;case "STD_DEV":$.Blockly.Python.definitions_.import_math="import math";b=$.Blockly.Python.provideFunction_("math_standard_deviation","\ndef "+$.Blockly.Python.FUNCTION_NAME_PLACEHOLDER_+"(numbers):\n n = len(numbers)\n if n == 0: return\n mean = float(sum(numbers)) / n\n variance = sum((x - mean) ** 2 for x in numbers) / n\n return math.sqrt(variance)\n")+"("+a+")";break;case "RANDOM":$.Blockly.Python.definitions_.import_random="import random";b="random.choice("+a+")";break;default:throw Error("Unknown operator: "+ +b);}return[b,$.Blockly.Python.ORDER_FUNCTION_CALL]};$.Blockly.Python.math_modulo=function(a){var b=$.Blockly.Python.valueToCode(a,"DIVIDEND",$.Blockly.Python.ORDER_MULTIPLICATIVE)||"0";a=$.Blockly.Python.valueToCode(a,"DIVISOR",$.Blockly.Python.ORDER_MULTIPLICATIVE)||"0";return[b+" % "+a,$.Blockly.Python.ORDER_MULTIPLICATIVE]}; $.Blockly.Python.math_constrain=function(a){var b=$.Blockly.Python.valueToCode(a,"VALUE",$.Blockly.Python.ORDER_NONE)||"0",c=$.Blockly.Python.valueToCode(a,"LOW",$.Blockly.Python.ORDER_NONE)||"0";a=$.Blockly.Python.valueToCode(a,"HIGH",$.Blockly.Python.ORDER_NONE)||"float('inf')";return["min(max("+b+", "+c+"), "+a+")",$.Blockly.Python.ORDER_FUNCTION_CALL]}; $.Blockly.Python.math_random_int=function(a){$.Blockly.Python.definitions_.import_random="import random";var b=$.Blockly.Python.valueToCode(a,"FROM",$.Blockly.Python.ORDER_NONE)||"0";a=$.Blockly.Python.valueToCode(a,"TO",$.Blockly.Python.ORDER_NONE)||"0";return["random.randint("+b+", "+a+")",$.Blockly.Python.ORDER_FUNCTION_CALL]};$.Blockly.Python.math_random_float=function(a){$.Blockly.Python.definitions_.import_random="import random";return["random.random()",$.Blockly.Python.ORDER_FUNCTION_CALL]}; $.Blockly.Python.math_atan2=function(a){$.Blockly.Python.definitions_.import_math="import math";var b=$.Blockly.Python.valueToCode(a,"X",$.Blockly.Python.ORDER_NONE)||"0";return["math.atan2("+($.Blockly.Python.valueToCode(a,"Y",$.Blockly.Python.ORDER_NONE)||"0")+", "+b+") / math.pi * 180",$.Blockly.Python.ORDER_MULTIPLICATIVE]};var module$exports$Blockly$Python$loops={}; $.Blockly.Python.controls_repeat_ext=function(a){var b=a.getField("TIMES")?String(parseInt(a.getFieldValue("TIMES"),10)):$.Blockly.Python.valueToCode(a,"TIMES",$.Blockly.Python.ORDER_NONE)||"0";b=(0,$.module$exports$Blockly$utils$string.isNumber)(b)?parseInt(b,10):"int("+b+")";var c=$.Blockly.Python.statementToCode(a,"DO");c=$.Blockly.Python.addLoopTrap(c,a)||$.Blockly.Python.PASS;return"for "+$.Blockly.Python.nameDB_.getDistinctName("count",$.module$exports$Blockly$Names.NameType.VARIABLE)+" in range("+ b+"):\n"+c};$.Blockly.Python.controls_repeat=$.Blockly.Python.controls_repeat_ext;$.Blockly.Python.controls_whileUntil=function(a){var b="UNTIL"===a.getFieldValue("MODE"),c=$.Blockly.Python.valueToCode(a,"BOOL",b?$.Blockly.Python.ORDER_LOGICAL_NOT:$.Blockly.Python.ORDER_NONE)||"False",d=$.Blockly.Python.statementToCode(a,"DO");d=$.Blockly.Python.addLoopTrap(d,a)||$.Blockly.Python.PASS;b&&(c="not "+c);return"while "+c+":\n"+d}; $.Blockly.Python.controls_for=function(a){var b=$.Blockly.Python.nameDB_.getName(a.getFieldValue("VAR"),$.module$exports$Blockly$Names.NameType.VARIABLE),c=$.Blockly.Python.valueToCode(a,"FROM",$.Blockly.Python.ORDER_NONE)||"0",d=$.Blockly.Python.valueToCode(a,"TO",$.Blockly.Python.ORDER_NONE)||"0",e=$.Blockly.Python.valueToCode(a,"BY",$.Blockly.Python.ORDER_NONE)||"1",f=$.Blockly.Python.statementToCode(a,"DO");f=$.Blockly.Python.addLoopTrap(f,a)||$.Blockly.Python.PASS;var g="";a=function(){return $.Blockly.Python.provideFunction_("upRange", -["def "+$.Blockly.Python.FUNCTION_NAME_PLACEHOLDER_+"(start, stop, step):"," while start <= stop:"," yield start"," start += abs(step)"])};var h=function(){return $.Blockly.Python.provideFunction_("downRange",["def "+$.Blockly.Python.FUNCTION_NAME_PLACEHOLDER_+"(start, stop, step):"," while start >= stop:"," yield start"," start -= abs(step)"])};if((0,$.module$exports$Blockly$utils$string.isNumber)(c)&&(0,$.module$exports$Blockly$utils$string.isNumber)(d)&&(0,$.module$exports$Blockly$utils$string.isNumber)(e))c= +"\ndef "+$.Blockly.Python.FUNCTION_NAME_PLACEHOLDER_+"(start, stop, step):\n while start <= stop:\n yield start\n start += abs(step)\n")};var h=function(){return $.Blockly.Python.provideFunction_("downRange","\ndef "+$.Blockly.Python.FUNCTION_NAME_PLACEHOLDER_+"(start, stop, step):\n while start >= stop:\n yield start\n start -= abs(step)\n")};if((0,$.module$exports$Blockly$utils$string.isNumber)(c)&&(0,$.module$exports$Blockly$utils$string.isNumber)(d)&&(0,$.module$exports$Blockly$utils$string.isNumber)(e))c= Number(c),d=Number(d),e=Math.abs(Number(e)),0===c%1&&0===d%1&&0===e%1?(c<=d?(d++,a=0===c&&1===e?d:c+", "+d,1!==e&&(a+=", "+e)):(d--,a=c+", "+d+", -"+e),a="range("+a+")"):(a=c.js. */ @@ -99,49 +99,40 @@ const chunks = [ { name: 'blockly', entry: 'core/blockly.js', - exports: 'Blockly', - importAs: 'Blockly', - factoryPreamble: `const ${NAMESPACE_OBJECT}={};`, - factoryPostamble: - `${NAMESPACE_OBJECT}.Blockly.internal_=${NAMESPACE_OBJECT};`, - }, { + reexport: 'Blockly', + }, + { name: 'blocks', - entry: 'blocks/all.js', - exports: 'Blockly.Blocks', - importAs: 'BlocklyBlocks', - }, { + entry: 'blocks/blocks.js', + reexport: 'Blockly.libraryBlocks', + }, + { name: 'javascript', entry: 'generators/javascript/all.js', - exports: 'Blockly.JavaScript', - }, { + reexport: 'Blockly.JavaScript', + }, + { name: 'python', entry: 'generators/python/all.js', - exports: 'Blockly.Python', - }, { + reexport: 'Blockly.Python', + }, + { name: 'php', entry: 'generators/php/all.js', - exports: 'Blockly.PHP', - }, { + reexport: 'Blockly.PHP', + }, + { name: 'lua', entry: 'generators/lua/all.js', - exports: 'Blockly.Lua', - }, { + reexport: 'Blockly.Lua', + }, + { name: 'dart', entry: 'generators/dart/all.js', - exports: 'Blockly.Dart', + reexport: 'Blockly.Dart', } ]; -/** - * The default factory function premable. - */ -const FACTORY_PREAMBLE = `const ${NAMESPACE_OBJECT}=Blockly.internal_;`; - -/** - * The default factory function postamble. - */ -const FACTORY_POSTAMBLE = ''; - const licenseRegex = `\\/\\*\\* \\* @license \\* (Copyright \\d+ (Google LLC|Massachusetts Institute of Technology)) @@ -162,12 +153,20 @@ function stripApacheLicense() { } /** - * Closure compiler warning groups used to treat warnings as errors. - * For a full list of closure compiler groups, consult: - * https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/DiagnosticGroups.java#L113 + * Closure compiler diagnostic groups we want to be treated as errors. + * These are effected when the --debug or --strict flags are passed. + * For a full list of closure compiler groups, consult the output of + * google-closure-compiler --help or look in the source here: + * https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/DiagnosticGroups.java#L117 + * + * The list in JSCOMP_ERROR contains all the diagnostic groups we know + * about, but some are commented out if we don't want them, and may + * appear in JSCOMP_WARNING or JSCOMP_OFF instead. Items not + * appearing on any list will default to setting provided by the + * compiler, which may vary depending on compilation level. */ var JSCOMP_ERROR = [ - 'accessControls', + // 'accessControls', // Deprecated; means same as visibility. 'checkPrototypalTypes', 'checkRegExp', 'checkTypes', @@ -180,27 +179,29 @@ var JSCOMP_ERROR = [ 'duplicateMessage', 'es5Strict', 'externsValidation', - 'extraRequire', + 'extraRequire', // Undocumented but valid. 'functionParams', 'globalThis', 'invalidCasts', 'misplacedTypeAnnotation', - // 'missingOverride', + // 'missingOverride', // There are many of these, which should be fixed. 'missingPolyfill', 'missingProperties', 'missingProvide', 'missingRequire', 'missingReturn', - // 'missingSourcesWarnings', + // 'missingSourcesWarnings', // Group of several other options. 'moduleLoad', 'msgDescriptions', 'nonStandardJsDocs', - // 'polymer', - // 'reportUnknownTypes', - // 'strictCheckTypes', - // 'strictMissingProperties', + // 'partialAlias', // Don't want this to be an error yet; only warning. + // 'polymer', // Not applicable. + // 'reportUnknownTypes', // VERY verbose. + // 'strictCheckTypes', // Use --strict to enable. + // 'strictMissingProperties', // Part of strictCheckTypes. + 'strictModuleChecks', // Undocumented but valid. 'strictModuleDepCheck', - // 'strictPrimitiveOperators', + // 'strictPrimitiveOperators', // Part of strictCheckTypes. 'suspiciousCode', 'typeInvalidation', 'undefinedVars', @@ -210,7 +211,38 @@ var JSCOMP_ERROR = [ 'unusedPrivateMembers', 'uselessCode', 'untranspilableFeatures', - 'visibility' + // 'visibility', // Disabled; see note in JSCOMP_OFF. +]; + +/** + * Closure compiler diagnostic groups we want to be treated as warnings. + * These are effected when the --debug or --strict flags are passed. + */ +var JSCOMP_WARNING = [ +]; + +/** + * Closure compiler diagnostic groups we want to be ignored. + * These suppressions are always effected by default. + */ +var JSCOMP_OFF = [ + /* In order to transition to ES modules, modules will need to import + * one another by relative paths. This means that the existing + * practice of moving all source files into the same directory for + * compilation (see docs for flattenCorePaths) would break + * imports. Not flattening files in this way breaks our usage + * of @package however; files were flattened so that all Blockly + * source files are in the same directory and can use @package to + * mark methods that are only allowed for use by Blockly, while + * still allowing access between e.g. core/events/* and + * core/utils/*. We were downgrading access control violations + * (including @private) to warnings, but this ends up being so + * spammy that it makes the compiler output nearly useless. + * + * Once ES module migration is complete, they will be re-enabled and + * an alternative to @package will be established. + */ + 'visibility', ]; /** @@ -224,9 +256,10 @@ function buildDeps(done) { 'node_modules/google-closure-library/closure/goog' : 'closure/goog'; + const coreDir = argv.compileTs ? path.join(TSC_OUTPUT_DIR, 'core') : 'core'; const roots = [ closurePath, - 'core', + coreDir, 'blocks', 'generators', ]; @@ -308,29 +341,63 @@ function buildLangfiles(done) { * Definition. */ function chunkWrapper(chunk) { - const fileNames = chunk.dependencies.map( - d => JSON.stringify(`./${d.name}${COMPILED_SUFFIX}.js`)); - const amdDeps = fileNames.join(', '); - const cjsDeps = fileNames.map(f => `require(${f})`).join(', '); - const browserDeps = - chunk.dependencies.map(d => `root.${d.exports}`).join(', '); - const imports = chunk.dependencies.map(d => d.importAs).join(', '); + // Each chunk can have only a single dependency, which is its parent + // chunk. It is used only to retrieve the namespace object, which + // is saved on to the exports object for the chunk so that any child + // chunk(s) can obtain it. + + // JavaScript expressions for the amd, cjs and browser dependencies. + let amdDepsExpr = ''; + let cjsDepsExpr = ''; + let browserDepsExpr = ''; + // Arguments for the factory function. + let factoryArgs = ''; + // Expression to get or create the namespace object. + let namespaceExpr = `{}`; + + if (chunk.parent) { + const parentFilename = + JSON.stringify(`./${chunk.parent.name}${COMPILED_SUFFIX}.js`); + amdDepsExpr = parentFilename; + cjsDepsExpr = `require(${parentFilename})`; + browserDepsExpr = `root.${chunk.parent.reexport}`; + factoryArgs = '__parent__'; + namespaceExpr = `${factoryArgs}.${NAMESPACE_PROPERTY}`; + } + + // Expression that evaluates the the value of the exports object for + // the specified chunk. For now we guess the name that is created + // by the module's goog.module.delcareLegacyNamespace call based on + // chunk.reexport. + const exportsExpression = `${NAMESPACE_VARIABLE}.${chunk.reexport}`; + // In near future we might try to guess the internally-generated + // name for the ES module's exports object. + // const exportsExpression = + // 'module$' + chunk.entry.replace(/\.m?js$/, '').replace(/\//g, '$'); + + + // Note that when loading in a browser the base of the exported path + // (e.g. Blockly.blocks.all - see issue #5932) might not exist + // before factory has been executed, so calling factory() and + // assigning the result are done in separate statements to ensure + // they are sequenced correctly. return `// Do not edit this file; automatically generated. /* eslint-disable */ ;(function(root, factory) { if (typeof define === 'function' && define.amd) { // AMD - define([${amdDeps}], factory); + define([${amdDepsExpr}], factory); } else if (typeof exports === 'object') { // Node.js - module.exports = factory(${cjsDeps}); + module.exports = factory(${cjsDepsExpr}); } else { // Browser - root.${chunk.exports} = factory(${browserDeps}); + var factoryExports = factory(${browserDepsExpr}); + root.${chunk.reexport} = factoryExports; } -}(this, function(${imports}) { -${chunk.factoryPreamble || FACTORY_PREAMBLE} +}(this, function(${factoryArgs}) { +var ${NAMESPACE_VARIABLE}=${namespaceExpr}; %output% -${chunk.factoryPostamble || FACTORY_POSTAMBLE} -return ${NAMESPACE_OBJECT}.${chunk.exports}; +${exportsExpression}.${NAMESPACE_PROPERTY}=${NAMESPACE_VARIABLE}; +return ${exportsExpression}; })); `; }; @@ -350,6 +417,9 @@ return ${NAMESPACE_OBJECT}.${chunk.exports}; * TODO(cpcallen): maybeAddClosureLibrary? Or maybe remove base.js? */ function getChunkOptions() { + if (argv.compileTs) { + chunks[0].entry = path.join(TSC_OUTPUT_DIR, chunks[0].entry); + } const cccArgs = [ '--closure-library-base-js-path ./closure/goog/base_minimal.js', '--deps-file ./tests/deps.js', @@ -401,26 +471,33 @@ function getChunkOptions() { // This is designed to be passed directly as-is as the options // object to the Closure Compiler node API, but we want to replace // the unhelpful entry-point based chunk names (let's call these - // "nicknames") with the ones from chunks. Luckily they will be in - // the same order that the entry points were supplied in - i.e., - // they correspond 1:1 with the entries in chunks. + // "nicknames") with the ones from chunks. Unforutnately there's no + // guarnatee they will be in the same order that the entry points + // were supplied in (though it happens to work out that way if no + // chunk depends on any chunk but the first), so we look for + // one of the entrypoints amongst the files in each chunk. const chunkByNickname = Object.create(null); - let jsFiles = rawOptions.js; - const chunkList = rawOptions.chunk.map((element, index) => { - const [nickname, numJsFiles, dependencyNicks] = element.split(':'); - const chunk = chunks[index]; + const jsFiles = rawOptions.js.slice(); // Will be modified via .splice! + const chunkList = rawOptions.chunk.map((element) => { + const [nickname, numJsFiles, parentNick] = element.split(':'); - // Replace nicknames with our names. + // Get array of files for just this chunk. + const chunkFiles = jsFiles.splice(0, numJsFiles); + + // Figure out which chunk this is by looking for one of the + // known chunk entrypoints in chunkFiles. N.B.: O(n*m). :-( + const chunk = chunks.find( + chunk => chunkFiles.find(f => f.endsWith('/' + chunk.entry))); + if (!chunk) throw new Error('Unable to identify chunk'); + + // Replace nicknames with the names we chose. chunkByNickname[nickname] = chunk; - if (!dependencyNicks) { // Chunk has no dependencies. - chunk.dependencies = []; + if (!parentNick) { // Chunk has no parent. + chunk.parent = null; return `${chunk.name}:${numJsFiles}`; } - chunk.dependencies = - dependencyNicks.split(',').map(nick => chunkByNickname[nick]); - const dependencyNames = - chunk.dependencies.map(dependency => dependency.name).join(','); - return `${chunk.name}:${numJsFiles}:${dependencyNames}`; + chunk.parent = chunkByNickname[parentNick]; + return `${chunk.name}:${numJsFiles}:${chunk.parent.name}`; }); // Generate a chunk wrapper for each chunk. @@ -432,12 +509,12 @@ function getChunkOptions() { return {chunk: chunkList, js: rawOptions.js, chunk_wrapper: chunkWrappers}; } -/** +/** * RegExp that globally matches path.sep (i.e., "/" or "\"). */ const pathSepRegExp = new RegExp(path.sep.replace(/\\/, '\\\\'), "g"); -/** +/** * Modify the supplied gulp.rename path object to relax @package * restrictions in core/. * @@ -457,10 +534,11 @@ const pathSepRegExp = new RegExp(path.sep.replace(/\\/, '\\\\'), "g"); */ function flattenCorePaths(pathObject) { const dirs = pathObject.dirname.split(path.sep); - if (dirs[0] === 'core') { - pathObject.dirname = dirs[0]; + const coreIndex = argv.compileTs ? 2 : 0; + if (dirs[coreIndex] === 'core') { + pathObject.dirname = path.join(...dirs.slice(0, coreIndex + 1)); pathObject.basename = - dirs.slice(1).concat(pathObject.basename).join('-slash-'); + dirs.slice(coreIndex + 1).concat(pathObject.basename).join('-slash-'); } } @@ -485,13 +563,14 @@ function compile(options) { warning_level: argv.verbose ? 'VERBOSE' : 'DEFAULT', language_in: 'ECMASCRIPT_2020', language_out: 'ECMASCRIPT5_STRICT', + jscomp_off: [...JSCOMP_OFF], rewrite_polyfills: true, hide_warnings_for: 'node_modules', define: ['COMPILED=true'], - externs: ['./externs/svg-externs.js'], }; if (argv.debug || argv.strict) { defaultOptions.jscomp_error = [...JSCOMP_ERROR]; + defaultOptions.jscomp_warning = [...JSCOMP_WARNING]; if (argv.strict) { defaultOptions.jscomp_error.push('strictCheckTypes'); } @@ -503,7 +582,7 @@ function compile(options) { } /** - * This task compiles the core library, blocks and generators, creating + * This task compiles the core library, blocks and generators, creating * blockly_compressed.js, blocks_compressed.js, etc. * * The deps.js file must be up-to-date. @@ -517,7 +596,7 @@ function buildCompiled() { define: 'Blockly.VERSION="' + packageJson.version + '"', chunk: chunkOptions.chunk, chunk_wrapper: chunkOptions.chunk_wrapper, - rename_prefix_namespace: NAMESPACE_OBJECT, + rename_prefix_namespace: NAMESPACE_VARIABLE, // Don't supply the list of source files in chunkOptions.js as an // option to Closure Compiler; instead feed them as input via gulp.src. }; @@ -526,10 +605,10 @@ function buildCompiled() { return gulp.src(chunkOptions.js, {base: './'}) .pipe(stripApacheLicense()) .pipe(gulp.sourcemaps.init()) - .pipe(gulp.rename(flattenCorePaths)) + // .pipe(gulp.rename(flattenCorePaths)) .pipe(compile(options)) .pipe(gulp.rename({suffix: COMPILED_SUFFIX})) - .pipe(gulp.sourcemaps.mapSources(unflattenCorePaths)) + // .pipe(gulp.sourcemaps.mapSources(unflattenCorePaths)) .pipe( gulp.sourcemaps.write('.', {includeContent: false, sourceRoot: './'})) .pipe(gulp.dest(BUILD_DIR)); @@ -540,10 +619,15 @@ function buildCompiled() { * closure compiler's ADVANCED_COMPILATION mode. */ function buildAdvancedCompilationTest() { + const coreSrcs = argv.compileTs ? + TSC_OUTPUT_DIR + '/core/**/*.js' : 'core/**/*.js'; const srcs = [ 'closure/goog/base_minimal.js', - 'core/**/*.js', 'blocks/**/*.js', 'generators/**/*.js', - 'tests/compile/main.js', 'tests/compile/test_blocks.js', + coreSrcs, + 'blocks/**/*.js', + 'generators/**/*.js', + 'tests/compile/main.js', + 'tests/compile/test_blocks.js', ]; // Closure Compiler options. @@ -556,9 +640,9 @@ function buildAdvancedCompilationTest() { return gulp.src(srcs, {base: './'}) .pipe(stripApacheLicense()) .pipe(gulp.sourcemaps.init()) - .pipe(gulp.rename(flattenCorePaths)) + // .pipe(gulp.rename(flattenCorePaths)) .pipe(compile(options)) - .pipe(gulp.sourcemaps.mapSources(unflattenCorePaths)) + // .pipe(gulp.sourcemaps.mapSources(unflattenCorePaths)) .pipe(gulp.sourcemaps.write( '.', {includeContent: false, sourceRoot: '../../'})) .pipe(gulp.dest('./tests/compile/')); @@ -610,11 +694,16 @@ function cleanBuildDir(done) { * Runs clang format on all files in the core directory. */ function format() { - return gulp.src(['core/**/*.js'], {base: '.'}) + return gulp.src(['core/**/*.js', 'blocks/**/*.js'], {base: '.'}) .pipe(clangFormatter.format('file', clangFormat)) .pipe(gulp.dest('.')); }; +function buildTypescript(done) { + execSync('npx tsc', {stdio: 'inherit'}); + done(); +} + module.exports = { build: build, deps: buildDeps, @@ -625,4 +714,5 @@ module.exports = { checkinBuilt: checkinBuilt, cleanBuildDir: cleanBuildDir, advancedCompilationTest: buildAdvancedCompilationTest, + buildTypescript: buildTypescript } diff --git a/scripts/gulpfiles/chunks.json b/scripts/gulpfiles/chunks.json index a7fcc3cbf..fce78801f 100644 --- a/scripts/gulpfiles/chunks.json +++ b/scripts/gulpfiles/chunks.json @@ -1,12 +1,12 @@ { "chunk": [ - "blockly:257", - "all:10:blockly", + "blockly:260", + "blocks:10:blockly", + "all:11:blockly", "all1:11:blockly", "all2:11:blockly", "all3:11:blockly", - "all4:11:blockly", - "all5:11:blockly" + "all4:11:blockly" ], "js": [ "./core/inject.js", @@ -21,23 +21,10 @@ "./core/field_number.js", "./core/field_multilineinput.js", "./core/field_label_serializable.js", - "./core/field_dropdown.js", "./core/field_colour.js", "./core/field_checkbox.js", "./core/field_angle.js", "./core/toolbox/collapsible_category.js", - "./core/renderers/zelos/measurables/top_row.js", - "./core/renderers/zelos/measurables/inputs.js", - "./core/renderers/zelos/measurables/row_elements.js", - "./core/renderers/zelos/marker_svg.js", - "./core/renderers/zelos/renderer.js", - "./core/field_textinput.js", - "./core/field_image.js", - "./core/renderers/zelos/info.js", - "./core/renderers/zelos/path_object.js", - "./core/renderers/zelos/drawer.js", - "./core/renderers/zelos/constants.js", - "./core/renderers/zelos/measurables/bottom_row.js", "./core/renderers/zelos/zelos.js", "./core/renderers/thrasos/renderer.js", "./core/renderers/thrasos/info.js", @@ -52,8 +39,8 @@ "./core/renderers/geras/measurables/statement_input.js", "./core/renderers/geras/path_object.js", "./core/renderers/geras/renderer.js", - "./core/renderers/geras/measurables/inline_input.js", "./core/renderers/geras/info.js", + "./core/renderers/geras/measurables/inline_input.js", "./core/renderers/geras/highlight_constants.js", "./core/renderers/geras/highlighter.js", "./core/renderers/geras/drawer.js", @@ -69,33 +56,36 @@ "./core/contextmenu_items.js", "./core/widgetdiv.js", "./core/clipboard.js", - "./core/menuitem.js", - "./core/menu.js", "./core/contextmenu.js", - "./core/blocks.js", "./core/utils/global.js", "./core/utils/useragent.js", "./core/utils/svg.js", "./core/utils/dom.js", - "./core/connection_checker.js", - "./core/keyboard_nav/ast_node.js", - "./core/keyboard_nav/cursor.js", - "./core/registry.js", - "./core/utils/math.js", "./core/utils/idgenerator.js", - "./core/utils/array.js", - "./core/workspace.js", - "./core/utils/object.js", - "./core/events/events_block_delete.js", + "./core/connection_checker.js", "./core/toolbox/separator.js", "./core/toolbox/toolbox_item.js", + "./core/interfaces/i_selectable_toolbox_item.js", + "./core/interfaces/i_collapsible_toolbox_item.js", + "./core/toolbox/category.js", + "./core/serialization/exceptions.js", + "./core/interfaces/i_serializer.js", + "./core/serialization/registry.js", + "./core/serialization/priorities.js", + "./core/serialization/blocks.js", + "./core/utils/toolbox.js", + "./core/utils/math.js", + "./core/utils/array.js", + "./core/workspace.js", + "./core/menu.js", + "./core/menuitem.js", "./core/keyboard_nav/basic_cursor.js", "./core/keyboard_nav/tab_navigate_cursor.js", + "./core/mutator.js", "./core/warning.js", - "./core/events/events_bubble_open.js", "./core/comment.js", - "./core/events/events_block_move.js", "./core/events/events_block_drag.js", + "./core/events/events_block_move.js", "./core/bump_objects.js", "./core/block_dragger.js", "./core/workspace_dragger.js", @@ -107,35 +97,49 @@ "./core/zoom_controls.js", "./core/workspace_drag_surface_svg.js", "./core/events/events_selected.js", - "./core/events/events_comment_move.js", "./core/events/events_comment_delete.js", - "./core/events/events_comment_create.js", - "./core/events/events_comment_base.js", "./core/events/events_comment_change.js", "./core/workspace_comment.js", - "./core/interfaces/i_movable.js", - "./core/interfaces/i_selectable.js", - "./core/interfaces/i_copyable.js", + "./core/events/events_comment_create.js", + "./core/events/events_comment_base.js", + "./core/events/events_comment_move.js", "./core/workspace_comment_svg.js", "./core/workspace_audio.js", "./core/events/events_trashcan_open.js", + "./core/sprites.js", "./core/drag_target.js", "./core/delete_area.js", + "./core/events/events_block_delete.js", "./core/positionable_helpers.js", "./core/trashcan.js", "./core/touch_gesture.js", "./core/theme_manager.js", "./core/scrollbar_pair.js", "./core/options.js", + "./core/interfaces/i_movable.js", + "./core/interfaces/i_selectable.js", + "./core/interfaces/i_copyable.js", "./core/interfaces/i_bounded_element.js", "./core/grid.js", + "./core/css.js", "./core/flyout_button.js", "./core/contextmenu_registry.js", "./core/theme/classic.js", "./core/blockly_options.js", "./core/utils.js", + "./core/renderers/zelos/measurables/top_row.js", + "./core/renderers/zelos/measurables/row_elements.js", + "./core/renderers/zelos/marker_svg.js", + "./core/renderers/zelos/measurables/inputs.js", + "./core/renderers/zelos/path_object.js", + "./core/renderers/zelos/drawer.js", + "./core/renderers/zelos/renderer.js", + "./core/field_textinput.js", + "./core/field_image.js", + "./core/renderers/zelos/constants.js", + "./core/renderers/zelos/measurables/bottom_row.js", + "./core/renderers/zelos/info.js", "./core/renderers/measurables/top_row.js", - "./core/renderers/measurables/statement_input.js", "./core/renderers/measurables/square_corner.js", "./core/renderers/measurables/spacer_row.js", "./core/renderers/measurables/round_corner.js", @@ -153,11 +157,15 @@ "./core/renderers/measurables/previous_connection.js", "./core/renderers/measurables/output_connection.js", "./core/renderers/measurables/jagged_edge.js", + "./core/renderers/measurables/statement_input.js", "./core/renderers/measurables/input_row.js", "./core/renderers/measurables/inline_input.js", "./core/scrollbar.js", + "./core/interfaces/i_toolbox_item.js", + "./core/interfaces/i_toolbox.js", "./core/utils/metrics.js", "./core/interfaces/i_metrics_manager.js", + "./core/interfaces/i_flyout.js", "./core/metrics_manager.js", "./core/interfaces/i_deletable.js", "./core/interfaces/i_draggable.js", @@ -168,9 +176,9 @@ "./core/icon.js", "./core/renderers/measurables/icon.js", "./core/renderers/measurables/hat.js", - "./core/renderers/measurables/field.js", "./core/renderers/measurables/external_value_input.js", "./core/renderers/common/info.js", + "./core/renderers/measurables/field.js", "./core/renderers/common/debugger.js", "./core/renderers/measurables/input_connection.js", "./core/renderers/measurables/in_row_spacer.js", @@ -189,12 +197,15 @@ "./core/names.js", "./core/events/events_block_base.js", "./core/events/events_block_change.js", - "./core/events/events_ui_base.js", "./core/events/events_marker_move.js", "./core/renderers/common/marker_svg.js", "./core/keyboard_nav/marker.js", + "./core/keyboard_nav/ast_node.js", + "./core/keyboard_nav/cursor.js", "./core/marker_manager.js", + "./core/utils/sentinel.js", "./core/field_label.js", + "./core/input_types.js", "./core/interfaces/i_registrable_field.js", "./core/field_registry.js", "./core/input.js", @@ -205,48 +216,37 @@ "./core/interfaces/i_ast_node_location_with_block.js", "./core/interfaces/i_ast_node_location.js", "./core/interfaces/i_ast_node_location_svg.js", - "./core/dropdowndiv.js", "./core/theme.js", "./core/constants.js", "./core/interfaces/i_connection_checker.js", "./core/connection_db.js", + "./core/config.js", "./core/rendered_connection.js", "./core/utils/svg_paths.js", "./core/renderers/common/constants.js", "./core/field.js", + "./core/events/events_ui_base.js", + "./core/events/events_bubble_open.js", "./core/procedures.js", "./core/workspace_svg.js", "./core/utils/rect.js", - "./core/utils/coordinate.js", - "./core/utils/style.js", + "./core/utils/deprecation.js", "./core/utils/svg_math.js", "./core/bubble_dragger.js", + "./core/connection_type.js", + "./core/internal_constants.js", "./core/block_animations.js", "./core/gesture.js", "./core/touch.js", "./core/browser_events.js", "./core/tooltip.js", "./core/block_svg.js", - "./core/interfaces/i_flyout.js", - "./core/interfaces/i_toolbox.js", - "./core/interfaces/i_toolbox_item.js", - "./core/interfaces/i_selectable_toolbox_item.js", - "./core/interfaces/i_collapsible_toolbox_item.js", - "./core/utils/aria.js", - "./core/utils/deprecation.js", - "./core/css.js", - "./core/toolbox/category.js", - "./core/input_types.js", "./core/utils/size.js", - "./core/serialization/exceptions.js", - "./core/interfaces/i_serializer.js", - "./core/serialization/registry.js", - "./core/serialization/priorities.js", - "./core/serialization/blocks.js", - "./core/utils/toolbox.js", - "./core/connection_type.js", - "./core/internal_constants.js", - "./core/mutator.js", + "./core/utils/coordinate.js", + "./core/utils/style.js", + "./core/dropdowndiv.js", + "./core/utils/aria.js", + "./core/field_dropdown.js", "./core/msg.js", "./core/utils/colour.js", "./core/utils/parsing.js", @@ -259,11 +259,14 @@ "./core/events/events_var_create.js", "./core/variable_model.js", "./core/variables.js", + "./core/utils/object.js", "./core/events/events_abstract.js", + "./core/registry.js", "./core/events/utils.js", "./core/xml.js", "./core/connection.js", "./core/common.js", + "./core/blocks.js", "./closure/goog/base_minimal.js", "./core/blockly.js", "./blocks/variables_dynamic.js", @@ -275,7 +278,7 @@ "./blocks/logic.js", "./blocks/lists.js", "./blocks/colour.js", - "./blocks/all.js", + "./blocks/blocks.js", "./generators/javascript/variables_dynamic.js", "./generators/javascript/variables.js", "./generators/javascript/text.js", diff --git a/scripts/gulpfiles/config.js b/scripts/gulpfiles/config.js index c9eb518ca..f8ee8c1a8 100644 --- a/scripts/gulpfiles/config.js +++ b/scripts/gulpfiles/config.js @@ -15,7 +15,7 @@ var path = require('path'); // // TODO(#5007): If you modify these values, you must also modify the // corresponding values in the following files: -// +// // - tests/scripts/compile_typings.sh // - tests/scripts/check_metadata.sh module.exports = { @@ -28,4 +28,8 @@ module.exports = { // Directory to write typings output to. TYPINGS_BUILD_DIR: path.join('build', 'typings'), + + // Directory where typescript compiler output can be found. + // Matches the value in tsconfig.json: outDir + TSC_OUTPUT_DIR: path.join('build', 'ts'), }; diff --git a/scripts/migration/renamings.js b/scripts/migration/renamings.js deleted file mode 100644 index 688e8ba4a..000000000 --- a/scripts/migration/renamings.js +++ /dev/null @@ -1,1016 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview Collected information about modules and module - * exports that have been renamed between versions. - * - * For now this is a node module, not a goog.module. - */ -'use strict'; - -/** - * Map from Blockly core version number to table of renamings made - * *since* that version was released (since we don't know for sure - * what the version number of the release that will incorporate those - * renamings will be yet). - * @type {Object} - */ -const renamings = { - // Example entry: - '0.0.0': { - // These renaming were made after version 0.0.0 was published. - // Each entry is keyed by the original module name. - 'module.name.Old': { - // If the module has been renamed, its new name will be listed. - module: 'module.name.new', - - // Modules which had a default export that has been turned into - // a named export the new name will be given. - export: 'defaultName', - - // For backward-compatibility reasons, we may choose to continue - // to reexport default exports in the same place in the Blockly - // tree that they were previously (or nearby). If so, the full - // path to the former default export will be listed. This path - // is only relevant to code that accesses the export via the - // namespace tree rather than by a named import. - // - // The given example implies that: - // module.name.Old === - // goog.require('module.name.new').defaultExport - // which may not be what what one expects but is very handy. - path: 'module.name.Old', - // If path is not given explicitly then it can be assumed to be - // `${module}.${export}`. - - // Modules which already had named exports can instead list - // information about exports which have been renamed or moved in - // the exports subsection, which is a map from old export name - // to object with info about the new export name. - exports: { - oldExportName: { - // No need to quote this. - - // If the export has been moved to a different module, that - // module is listed. - module: 'module.name.other', - - // If the export has been given a new name, that is listed. - export: 'newExportName', - - // As with default exports, if a named export has been - // reexported on the namespace tree somewhere other than at - // `${module}.${export}` then that can specified explicitly. - path: 'module.name.Old.oldExportName', - // This given example implies that code that previously - // accessed this export via module.name.Old.oldExportName - // can continue to do so; only code using named requires - // needs to update from - // goog.require('module.name.Old').oldExportName - // to - // goog.require('module.name.new').newExportName - }, - // More individual exports in this module can be listed here. - }, - }, - // More modules with renamings can be listed here. - }, - - '4.20201217.0': { - 'Blockly': { - exports: { - // bind/unbind events functions. See PR #4642 - EventData: {module: 'Blockly.eventHandling', export: 'Data'}, - bindEvent_: {module: 'Blockly.browserEvents', export: 'bind'}, - unbindEvent_: {module: 'Blockly.browserEvents', export: 'unbind'}, - bindEventWithChecks_: { - module: 'Blockly.browserEvents', - export: 'conditionalBind', - }, - }, - }, - }, - '6.20210701.0': { - 'Blockly': { - exports: { - // Align. - ALIGN_LEFT: { - module: 'Blockly.Input', - export: 'Align.LEFT', - path: 'Blockly.ALIGN_LEFT', - }, - ALIGN_CENTRE: { - module: 'Blockly.Input', - export: 'Align.CENTRE', - path: 'Blockly.ALIGN_CENTRE', - }, - ALIGN_RIGHT: { - module: 'Blockly.Input', - export: 'Align.RIGHT', - path: 'Blockly.ALIGN_RIGHT', - }, - // Clipboard. See PR #5237. - clipboardXml_: {module: 'Blockly.clipboard', export: 'xml'}, - clipboardSource_: {module: 'Blockly.clipboard', export: 'source'}, - clipboardTypeCounts_: { - module: 'Blockly.clipboard', - export: 'typeCounts', - }, - copy: {module: 'Blockly.clipboard'}, - paste: {module: 'Blockly.clipboard'}, - duplicate: {module: 'Blockly.clipboard'}, - - // mainWorkspace. See PR #5244. - mainWorkspace: { - module: 'Blockly.common', - get: 'getMainWorkspace', - set: 'setMainWorkspace', - }, - getMainWorkspace: {module: 'Blockly.common'}, - - // parentContainer, draggingConnections. See PR #5262. - parentContainer: { - module: 'Blockly.common', - get: 'getParentContainer', - set: 'setParentContainer', - }, - setParentContainer: {module: 'Blockly.common'}, - draggingConnections: {module: 'Blockly.common'}, - // Dialogs. See PR #5457. - alert: { - module: 'Blockly.dialog', - export: 'alert', - set: 'setAlert', - }, - confirm: { - module: 'Blockly.dialog', - export: 'confirm', - set: 'setConfirm', - }, - prompt: { - module: 'Blockly.dialog', - export: 'prompt', - set: 'setPrompt', - }, - // hueToHex. See PR #5462. - hueToHex: {module: 'Blockly.utils.colour'}, - // Blockly.hideChaff() became - // Blockly.common.getMainWorkspace().hideChaff(). See PR #5460. - - // selected. See PR #5489. - selected: { - module: 'Blockly.common', - get: 'getSelected', - set: 'setSelected', - }, - }, - }, - 'Blockly.Blocks': { - module: 'Blockly.blocks', - export: 'Blocks', // Previous default export now named. - path: 'Blockly.Blocks', // But still on tree with original name. - }, - 'Blockly.ContextMenu': { - exports: { - currentBlock: {get: 'getCurrentBlock', set: 'setCurrentBlock'}, - }, - }, - 'Blockly.Events': { - exports: { - recordUndo: {get: 'getRecordUndo', set: 'setRecordUndo'}, - }, - }, - 'Blockly.Tooltip': { - exports: { - DIV: {get: 'getDiv', set: 'setDiv'}, - visible: {get: 'isVisible'}, - }, - }, - 'Blockly.WidgetDiv': { - exports: { - DIV: {get: 'getDiv'}, - }, - }, - 'Blockly.connectionTypes': { - module: 'Blockly.ConnectionType', - export: 'ConnectionType', // Previous default export now named. - path: 'Blockly.ConnectionType', // Type reexported directly. - }, - 'Blockly.utils': { - exports: { - genUid: {module: 'Blockly.utils.idGenerator'}, - getScrollDelta: {module: 'Blockly.utils.browserEvents'}, - isTargetInput: {module: 'Blockly.utils.browserEvents'}, - isRightButton: {module: 'Blockly.utils.browserEvents'}, - mouseToSvg: {module: 'Blockly.utils.browserEvents'}, - }, - }, - 'Blockly.utils.global': { - export: 'globalThis', // Previous default export now named. - path: 'Blockly.utils.global', // But still exported under original name. - }, - 'Blockly.utils.IdGenerator': { - module: 'Blockly.utils.idGenerator', - }, - 'Blockly.utils.xml': { - exports: { - // document was a function before, too - not a static property - // or get accessor. - document: {export: 'getDocument'}, - }, - }, - }, - '7.20211209.0-beta.0': { - 'Blockly.Blocks.colour': {module: 'Blockly.blocks.colour'}, - // Blockly.Blocks.lists not previously provided. - 'Blockly.Blocks.logic': {module: 'Blockly.blocks.logic'}, - 'Blockly.Blocks.loops': {module: 'Blockly.blocks.loops'}, - 'Blockly.Blocks.math': {module: 'Blockly.blocks.math'}, - 'Blockly.Blocks.procedures': {module: 'Blockly.blocks.procedures'}, - 'Blockly.Blocks.texts': {module: 'Blockly.blocks.texts'}, - 'Blockly.Blocks.variables': {module: 'Blockly.blocks.variables'}, - // Blockly.Blocks.variablesDynamic not previously provided. - 'Blockly.utils': { - exports: { - screenToWsCoordinates: {module: 'Blockly.utils.svgMath'}, - getDocumentScroll: {module: 'Blockly.utils.svgMath'}, - getViewportBBox: {module: 'Blockly.utils.svgMath'}, - is3dSupported: {module: 'Blockly.utils.svgMath'}, - getRelativeXY: {module: 'Blockly.utils.svgMath'}, - getInjectionDivXY_: - {module: 'Blockly.utils.svgMath', export: 'getInjectionDivXY'}, - parseBlockColour: {module: 'Blockly.utils.parsing'}, - checkMessageReferences: {module: 'Blockly.utils.parsing'}, - replaceMessageReferences: {module: 'Blockly.utils.parsing'}, - tokenizeInterpolation: {module: 'Blockly.utils.parsing'}, - arrayRemove: {module: 'Blockly.utils.array', export: 'removeElem'}, - getBlockTypeCounts: - {module: 'Blockly.common', export: 'getBlockTypeCounts'}, - runAfterPageLoad: - {module: 'Blockly.Extensions', export: 'runAfterPageLoad'}, - }, - }, - 'Blockly.Events.Abstract': { - export: 'Abstract', - path: 'Blockly.Events.Abstract', - }, - 'Blockly.Events.BlockBase': { - export: 'BlockBase', - path: 'Blockly.Events.BlockBase', - }, - 'Blockly.Events.BlockChange': { - export: 'BlockChange', - path: 'Blockly.Events.BlockChange', - }, - 'Blockly.Events.BlockCreate': { - export: 'BlockCreate', - path: 'Blockly.Events.BlockCreate', - }, - 'Blockly.Events.BlockDelete': { - export: 'BlockDelete', - path: 'Blockly.Events.BlockDelete', - }, - 'Blockly.Events.BlockDrag': { - export: 'BlockDrag', - path: 'Blockly.Events.BlockDrag', - }, - 'Blockly.Events.BlockMove': { - export: 'BlockMove', - path: 'Blockly.Events.BlockMove', - }, - 'Blockly.Events.BubbleOpen': { - export: 'BubbleOpen', - path: 'Blockly.Events.BubbleOpen', - }, - 'Blockly.Events.Click': { - export: 'Click', - path: 'Blockly.Events.Click', - }, - 'Blockly.Events.CommentBase': { - export: 'CommentBase', - path: 'Blockly.Events.CommentBase', - }, - 'Blockly.Events.CommentChange': { - export: 'CommentChange', - path: 'Blockly.Events.CommentChange', - }, - 'Blockly.Events.CommentCreate': { - export: 'CommentCreate', - path: 'Blockly.Events.CommentCreate', - }, - 'Blockly.Events.CommentDelete': { - export: 'CommentDelete', - path: 'Blockly.Events.CommentDelete', - }, - 'Blockly.Events.CommentMove': { - export: 'CommentMove', - path: 'Blockly.Events.CommentMove', - }, - 'Blockly.Events.MarkerMove': { - export: 'MarkerMove', - path: 'Blockly.Events.MarkerMove', - }, - 'Blockly.Events.Selected': { - export: 'Selected', - path: 'Blockly.Events.Selected', - }, - 'Blockly.Events.ThemeChange': { - export: 'ThemeChange', - path: 'Blockly.Events.ThemeChange', - }, - 'Blockly.Events.ToolboxItemSelect': { - export: 'ToolboxItemSelect', - path: 'Blockly.Events.ToolboxItemSelect', - }, - 'Blockly.Events.TrashcanOpen': { - export: 'TrashcanOpen', - path: 'Blockly.Events.TrashcanOpen', - }, - 'Blockly.Events.Ui': { - export: 'Ui', - path: 'Blockly.Events.Ui', - }, - 'Blockly.Events.UiBase': { - export: 'UiBase', - path: 'Blockly.Events.UiBase', - }, - 'Blockly.Events.VarBase': { - export: 'VarBase', - path: 'Blockly.Events.VarBase', - }, - 'Blockly.Events.VarCreate': { - export: 'VarCreate', - path: 'Blockly.Events.VarCreate', - }, - 'Blockly.Events.VarDelete': { - export: 'VarDelete', - path: 'Blockly.Events.VarDelete', - }, - 'Blockly.Events.VarRename': { - export: 'VarRename', - path: 'Blockly.Events.VarRename', - }, - 'Blockly.Events.ViewportChange': { - export: 'ViewportChange', - path: 'Blockly.Events.ViewportChange', - }, - 'Blockly.Events.FinishedLoading': { - export: 'FinishedLoading', - path: 'Blockly.Events.FinishedLoading', - }, - 'Blockly.IASTNodeLocation': { - export: 'IASTNodeLocation', - path: 'Blockly.IASTNodeLocation', - }, - 'Blockly.IASTNodeLocationSvg': { - export: 'IASTNodeLocationSvg', - path: 'Blockly.IASTNodeLocationSvg', - }, - 'Blockly.IASTNodeLocationWithBlock': { - export: 'IASTNodeLocationWithBlock', - path: 'Blockly.IASTNodeLocationWithBlock', - }, - 'Blockly.IAutoHideable': { - export: 'IAutoHideable', - path: 'Blockly.IAutoHideable', - }, - 'Blockly.IBlockDragger': { - export: 'IBlockDragger', - path: 'Blockly.IBlockDragger', - }, - 'Blockly.IBoundedElement': { - export: 'IBoundedElement', - path: 'Blockly.IBoundedElement', - }, - 'Blockly.IBubble': { - export: 'IBubble', - path: 'Blockly.IBubble', - }, - 'Blockly.ICollapsibleToolboxItem': { - export: 'ICollapsibleToolboxItem', - path: 'Blockly.ICollapsibleToolboxItem', - }, - 'Blockly.IComponent': { - export: 'IComponent', - path: 'Blockly.IComponent', - }, - 'Blockly.IConnectionChecker': { - export: 'IConnectionChecker', - path: 'Blockly.IConnectionChecker', - }, - 'Blockly.IContextMenu': { - export: 'IContextMenu', - path: 'Blockly.IContextMenu', - }, - 'Blockly.ICopyable': { - export: 'ICopyable', - path: 'Blockly.ICopyable', - }, - 'Blockly.IDeletable': { - export: 'IDeletable', - path: 'Blockly.IDeletable', - }, - 'Blockly.IDeleteArea': { - export: 'IDeleteArea', - path: 'Blockly.IDeleteArea', - }, - 'Blockly.IDragTarget': { - export: 'IDragTarget', - path: 'Blockly.IDragTarget', - }, - 'Blockly.IDraggable': { - export: 'IDraggable', - path: 'Blockly.IDraggable', - }, - 'Blockly.IFlyout': { - export: 'IFlyout', - path: 'Blockly.IFlyout', - }, - 'Blockly.IKeyboardAccessible': { - export: 'IKeyboardAccessible', - path: 'Blockly.IKeyboardAccessible', - }, - 'Blockly.IMetricsManager': { - export: 'IMetricsManager', - path: 'Blockly.IMetricsManager', - }, - 'Blockly.IMovable': { - export: 'IMovable', - path: 'Blockly.IMovable', - }, - 'Blockly.IPositionable': { - export: 'IPositionable', - path: 'Blockly.IPositionable', - }, - 'Blockly.IRegistrable': { - export: 'IRegistrable', - path: 'Blockly.IRegistrable', - }, - 'Blockly.IRegistrableField': { - export: 'IRegistrableField', - path: 'Blockly.IRegistrableField', - }, - 'Blockly.ISelectable': { - export: 'ISelectable', - path: 'Blockly.ISelectable', - }, - 'Blockly.ISelectableToolboxItem': { - export: 'ISelectableToolboxItem', - path: 'Blockly.ISelectableToolboxItem', - }, - 'Blockly.IStyleable': { - export: 'IStyleable', - path: 'Blockly.IStyleable', - }, - 'Blockly.IToolbox': { - export: 'IToolbox', - path: 'Blockly.IToolbox', - }, - 'Blockly.IToolboxItem': { - export: 'IToolboxItem', - path: 'Blockly.IToolboxItem', - }, - 'Blockly.blockRendering.ConstantProvider': { - export: 'ConstantProvider', - path: 'Blockly.blockRendering.ConstantProvider', - }, - 'Blockly.blockRendering.Debug': { - export: 'Debug', - path: 'Blockly.blockRendering.Debug', - }, - 'Blockly.blockRendering.Drawer': { - export: 'Drawer', - path: 'Blockly.blockRendering.Drawer', - }, - 'Blockly.blockRendering.IPathObject': { - export: 'IPathObject', - path: 'Blockly.blockRendering.IPathObject', - }, - 'Blockly.blockRendering.RenderInfo': { - export: 'RenderInfo', - path: 'Blockly.blockRendering.RenderInfo', - }, - 'Blockly.blockRendering.MarkerSvg': { - export: 'MarkerSvg', - path: 'Blockly.blockRendering.MarkerSvg', - }, - 'Blockly.blockRendering.PathObject': { - export: 'PathObject', - path: 'Blockly.blockRendering.PathObject', - }, - 'Blockly.blockRendering.Renderer': { - export: 'Renderer', - path: 'Blockly.blockRendering.Renderer', - }, - 'Blockly.geras.InlineInput': { - export: 'InlineInput', - path: 'Blockly.geras.InlineInput', - }, - 'Blockly.geras.StatementInput': { - export: 'StatementInput', - path: 'Blockly.geras.StatementInput', - }, - 'Blockly.geras.ConstantProvider': { - export: 'ConstantProvider', - path: 'Blockly.geras.ConstantProvider', - }, - 'Blockly.geras.Drawer': { - export: 'Drawer', - path: 'Blockly.geras.Drawer', - }, - 'Blockly.geras.HighlightConstantProvider': { - export: 'HighlightConstantProvider', - path: 'Blockly.geras.HighlightConstantProvider', - }, - 'Blockly.geras.Highlighter': { - export: 'Highlighter', - path: 'Blockly.geras.Highlighter', - }, - 'Blockly.geras.RenderInfo': { - export: 'RenderInfo', - path: 'Blockly.geras.RenderInfo', - }, - 'Blockly.geras.PathObject': { - export: 'PathObject', - path: 'Blockly.geras.PathObject', - }, - 'Blockly.geras.Renderer': { - export: 'Renderer', - path: 'Blockly.geras.Renderer', - }, - 'Blockly.blockRendering.Measurable': { - export: 'Measurable', - path: 'Blockly.blockRendering.Measurable', - }, - 'Blockly.blockRendering.BottomRow': { - export: 'BottomRow', - path: 'Blockly.blockRendering.BottomRow', - }, - 'Blockly.blockRendering.Connection': { - export: 'Connection', - path: 'Blockly.blockRendering.Connection', - }, - 'Blockly.blockRendering.ExternalValueInput': { - export: 'ExternalValueInput', - path: 'Blockly.blockRendering.ExternalValueInput', - }, - 'Blockly.blockRendering.Field': { - export: 'Field', - path: 'Blockly.blockRendering.Field', - }, - 'Blockly.blockRendering.Hat': { - export: 'Hat', - path: 'Blockly.blockRendering.Hat', - }, - 'Blockly.blockRendering.Icon': { - export: 'Icon', - path: 'Blockly.blockRendering.Icon', - }, - 'Blockly.blockRendering.InRowSpacer': { - export: 'InRowSpacer', - path: 'Blockly.blockRendering.InRowSpacer', - }, - 'Blockly.blockRendering.InlineInput': { - export: 'InlineInput', - path: 'Blockly.blockRendering.InlineInput', - }, - 'Blockly.blockRendering.InputConnection': { - export: 'InputConnection', - path: 'Blockly.blockRendering.InputConnection', - }, - 'Blockly.blockRendering.InputRow': { - export: 'InputRow', - path: 'Blockly.blockRendering.InputRow', - }, - 'Blockly.blockRendering.JaggedEdge': { - export: 'JaggedEdge', - path: 'Blockly.blockRendering.JaggedEdge', - }, - 'Blockly.blockRendering.NextConnection': { - export: 'NextConnection', - path: 'Blockly.blockRendering.NextConnection', - }, - 'Blockly.blockRendering.OutputConnection': { - export: 'OutputConnection', - path: 'Blockly.blockRendering.OutputConnection', - }, - 'Blockly.blockRendering.PreviousConnection': { - export: 'PreviousConnection', - path: 'Blockly.blockRendering.PreviousConnection', - }, - 'Blockly.blockRendering.RoundCorner': { - export: 'RoundCorner', - path: 'Blockly.blockRendering.RoundCorner', - }, - 'Blockly.blockRendering.Row': { - export: 'Row', - path: 'Blockly.blockRendering.Row', - }, - 'Blockly.blockRendering.SpacerRow': { - export: 'SpacerRow', - path: 'Blockly.blockRendering.SpacerRow', - }, - 'Blockly.blockRendering.SquareCorner': { - export: 'SquareCorner', - path: 'Blockly.blockRendering.SquareCorner', - }, - 'Blockly.blockRendering.StatementInput': { - export: 'StatementInput', - path: 'Blockly.blockRendering.StatementInput', - }, - 'Blockly.blockRendering.TopRow': { - export: 'TopRow', - path: 'Blockly.blockRendering.TopRow', - }, - 'Blockly.blockRendering.Types': { - export: 'Types', - path: 'Blockly.blockRendering.Types', - }, - 'Blockly.minimalist.ConstantProvider': { - export: 'ConstantProvider', - path: 'Blockly.minimalist.ConstantProvider', - }, - 'Blockly.minimalist.Drawer': { - export: 'Drawer', - path: 'Blockly.minimalist.Drawer', - }, - 'Blockly.minimalist.RenderInfo': { - export: 'RenderInfo', - path: 'Blockly.minimalist.RenderInfo', - }, - 'Blockly.minimalist.Renderer': { - export: 'Renderer', - path: 'Blockly.minimalist.Renderer', - }, - 'Blockly.thrasos.RenderInfo': { - export: 'RenderInfo', - path: 'Blockly.thrasos.RenderInfo', - }, - 'Blockly.thrasos.Renderer': { - export: 'Renderer', - path: 'Blockly.thrasos.Renderer', - }, - 'Blockly.zelos.BottomRow': { - export: 'BottomRow', - path: 'Blockly.zelos.BottomRow', - }, - 'Blockly.zelos.StatementInput': { - export: 'StatementInput', - path: 'Blockly.zelos.StatementInput', - }, - 'Blockly.zelos.RightConnectionShape': { - export: 'RightConnectionShape', - path: 'Blockly.zelos.RightConnectionShape', - }, - 'Blockly.zelos.TopRow': { - export: 'TopRow', - path: 'Blockly.zelos.TopRow', - }, - 'Blockly.zelos.ConstantProvider': { - export: 'ConstantProvider', - path: 'Blockly.zelos.ConstantProvider', - }, - 'Blockly.zelos.Drawer': { - export: 'Drawer', - path: 'Blockly.zelos.Drawer', - }, - 'Blockly.zelos.RenderInfo': { - export: 'RenderInfo', - path: 'Blockly.zelos.RenderInfo', - }, - 'Blockly.zelos.MarkerSvg': { - export: 'MarkerSvg', - path: 'Blockly.zelos.MarkerSvg', - }, - 'Blockly.zelos.PathObject': { - export: 'PathObject', - path: 'Blockly.zelos.PathObject', - }, - 'Blockly.zelos.Renderer': { - export: 'Renderer', - path: 'Blockly.zelos.Renderer', - }, - 'Blockly.Themes.Classic': { - export: 'Classic', - path: 'Blockly.Themes.Classic', - }, - 'Blockly.Themes.Zelos': { - export: 'Zelos', - path: 'Blockly.Themes.Zelos', - }, - 'Blockly.ToolboxCategory': { - export: 'ToolboxCategory', - path: 'Blockly.ToolboxCategory', - }, - 'Blockly.CollapsibleToolboxCategory': { - export: 'CollapsibleToolboxCategory', - path: 'Blockly.CollapsibleToolboxCategory', - }, - 'Blockly.ToolboxSeparator': { - export: 'ToolboxSeparator', - path: 'Blockly.ToolboxSeparator', - }, - 'Blockly.Toolbox': { - export: 'Toolbox', - path: 'Blockly.Toolbox', - }, - 'Blockly.ToolboxItem': { - export: 'ToolboxItem', - path: 'Blockly.ToolboxItem', - }, - 'Blockly.utils.Coordinate': { - export: 'Coordinate', - path: 'Blockly.utils.Coordinate', - }, - 'Blockly.utils.KeyCodes': { - export: 'KeyCodes', - path: 'Blockly.utils.KeyCodes', - }, - 'Blockly.utils.Metrics': { - export: 'Metrics', - path: 'Blockly.utils.Metrics', - }, - 'Blockly.utils.Rect': { - export: 'Rect', - path: 'Blockly.utils.Rect', - }, - 'Blockly.utils.Size': { - export: 'Size', - path: 'Blockly.utils.Size', - }, - 'Blockly.utils.Svg': { - export: 'Svg', - path: 'Blockly.utils.Svg', - }, - 'Blockly.BlocklyOptions': { - export: 'BlocklyOptions', - path: 'Blockly.BlocklyOptions', - }, - 'Blockly.Bubble': { - export: 'Bubble', - path: 'Blockly.Bubble', - }, - 'Blockly.BubbleDragger': { - export: 'BubbleDragger', - path: 'Blockly.BubbleDragger', - }, - 'Blockly.Comment': { - export: 'Comment', - path: 'Blockly.Comment', - }, - 'Blockly.ComponentManager': { - export: 'ComponentManager', - path: 'Blockly.ComponentManager', - }, - 'Blockly.Connection': { - export: 'Connection', - path: 'Blockly.Connection', - }, - 'Blockly.ConnectionChecker': { - export: 'ConnectionChecker', - path: 'Blockly.ConnectionChecker', - }, - 'Blockly.ConnectionDB': { - export: 'ConnectionDB', - path: 'Blockly.ConnectionDB', - }, - 'Blockly.ContextMenuRegistry': { - export: 'ContextMenuRegistry', - path: 'Blockly.ContextMenuRegistry', - }, - 'Blockly.DeleteArea': { - export: 'DeleteArea', - path: 'Blockly.DeleteArea', - }, - 'Blockly.DragTarget': { - export: 'DragTarget', - path: 'Blockly.DragTarget', - }, - 'Blockly.DropDownDiv': { - export: 'DropDownDiv', - path: 'Blockly.DropDownDiv', - }, - 'Blockly.Field': { - export: 'Field', - path: 'Blockly.Field', - }, - 'Blockly.FieldAngle': { - export: 'FieldAngle', - path: 'Blockly.FieldAngle', - }, - 'Blockly.FieldCheckbox': { - export: 'FieldCheckbox', - path: 'Blockly.FieldCheckbox', - }, - 'Blockly.FieldColour': { - export: 'FieldColour', - path: 'Blockly.FieldColour', - }, - 'Blockly.FieldDropdown': { - export: 'FieldDropdown', - path: 'Blockly.FieldDropdown', - }, - 'Blockly.FieldImage': { - export: 'FieldImage', - path: 'Blockly.FieldImage', - }, - 'Blockly.FieldLabel': { - export: 'FieldLabel', - path: 'Blockly.FieldLabel', - }, - 'Blockly.FieldLabelSerializable': { - export: 'FieldLabelSerializable', - path: 'Blockly.FieldLabelSerializable', - }, - 'Blockly.FieldMultilineInput': { - export: 'FieldMultilineInput', - path: 'Blockly.FieldMultilineInput', - }, - 'Blockly.FieldNumber': { - export: 'FieldNumber', - path: 'Blockly.FieldNumber', - }, - 'Blockly.FieldTextInput': { - export: 'FieldTextInput', - path: 'Blockly.FieldTextInput', - }, - 'Blockly.FieldVariable': { - export: 'FieldVariable', - path: 'Blockly.FieldVariable', - }, - 'Blockly.Flyout': { - export: 'Flyout', - path: 'Blockly.Flyout', - }, - 'Blockly.FlyoutButton': { - export: 'FlyoutButton', - path: 'Blockly.FlyoutButton', - }, - 'Blockly.HorizontalFlyout': { - export: 'HorizontalFlyout', - path: 'Blockly.HorizontalFlyout', - }, - 'Blockly.FlyoutMetricsManager': { - export: 'FlyoutMetricsManager', - path: 'Blockly.FlyoutMetricsManager', - }, - 'Blockly.VerticalFlyout': { - export: 'VerticalFlyout', - path: 'Blockly.VerticalFlyout', - }, - 'Blockly.Generator': { - export: 'Generator', - path: 'Blockly.Generator', - }, - 'Blockly.Gesture': { - export: 'Gesture', - path: 'Blockly.Gesture', - }, - 'Blockly.Grid': { - export: 'Grid', - path: 'Blockly.Grid', - }, - 'Blockly.Icon': { - export: 'Icon', - path: 'Blockly.Icon', - }, - 'Blockly.inject': { - export: 'inject', - path: 'Blockly.inject', - }, - 'Blockly.Input': { - export: 'Input', - path: 'Blockly.Input', - }, - 'Blockly.inputTypes': { - export: 'inputTypes', - path: 'Blockly.inputTypes', - }, - 'Blockly.InsertionMarkerManager': { - export: 'InsertionMarkerManager', - path: 'Blockly.InsertionMarkerManager', - }, - 'Blockly.MarkerManager': { - export: 'MarkerManager', - path: 'Blockly.MarkerManager', - }, - 'Blockly.Menu': { - export: 'Menu', - path: 'Blockly.Menu', - }, - 'Blockly.MenuItem': { - export: 'MenuItem', - path: 'Blockly.MenuItem', - }, - 'Blockly.MetricsManager': { - export: 'MetricsManager', - path: 'Blockly.MetricsManager', - }, - 'Blockly.Msg': { - export: 'Msg', - path: 'Blockly.Msg', - }, - 'Blockly.Mutator': { - export: 'Mutator', - path: 'Blockly.Mutator', - }, - 'Blockly.Names': { - export: 'Names', - path: 'Blockly.Names', - }, - 'Blockly.Options': { - export: 'Options', - path: 'Blockly.Options', - }, - 'Blockly.RenderedConnection': { - export: 'RenderedConnection', - path: 'Blockly.RenderedConnection', - }, - 'Blockly.Scrollbar': { - export: 'Scrollbar', - path: 'Blockly.Scrollbar', - }, - 'Blockly.ScrollbarPair': { - export: 'ScrollbarPair', - path: 'Blockly.ScrollbarPair', - }, - 'Blockly.ShortcutRegistry': { - export: 'ShortcutRegistry', - path: 'Blockly.ShortcutRegistry', - }, - 'Blockly.Theme': { - export: 'Theme', - path: 'Blockly.Theme', - }, - 'Blockly.ThemeManager': { - export: 'ThemeManager', - path: 'Blockly.ThemeManager', - }, - 'Blockly.TouchGesture': { - export: 'TouchGesture', - path: 'Blockly.TouchGesture', - }, - 'Blockly.Trashcan': { - export: 'Trashcan', - path: 'Blockly.Trashcan', - }, - 'Blockly.VariableMap': { - export: 'VariableMap', - path: 'Blockly.VariableMap', - }, - 'Blockly.VariableModel': { - export: 'VariableModel', - path: 'Blockly.VariableModel', - }, - 'Blockly.Warning': { - export: 'Warning', - path: 'Blockly.Warning', - }, - 'Blockly.Workspace': { - export: 'Workspace', - path: 'Blockly.Workspace', - }, - 'Blockly.WorkspaceAudio': { - export: 'WorkspaceAudio', - path: 'Blockly.WorkspaceAudio', - }, - 'Blockly.WorkspaceComment': { - export: 'WorkspaceComment', - path: 'Blockly.WorkspaceComment', - }, - 'Blockly.WorkspaceCommentSvg': { - export: 'WorkspaceCommentSvg', - path: 'Blockly.WorkspaceCommentSvg', - }, - 'Blockly.WorkspaceDragSurfaceSvg': { - export: 'WorkspaceDragSurfaceSvg', - path: 'Blockly.WorkspaceDragSurfaceSvg', - }, - 'Blockly.WorkspaceDragger': { - export: 'WorkspaceDragger', - path: 'Blockly.WorkspaceDragger', - }, - 'Blockly.WorkspaceSvg': { - export: 'WorkspaceSvg', - path: 'Blockly.WorkspaceSvg', - }, - 'Blockly.ZoomControls': { - export: 'ZoomControls', - path: 'Blockly.ZoomControls', - }, - 'Blockly': { - exports: { - svgSize: {module: 'Blockly.utils.svgMath'}, - resizeSvgContents: {module: 'Blockly.WorkspaecSvg'}, - defineBlocksWithJsonArray: {module: 'Blockly.common'}, - isNumber: {module: 'Blockly.utils.string'}, - }, - }, - }, -}; - -exports.renamings = renamings; diff --git a/scripts/migration/renamings.json5 b/scripts/migration/renamings.json5 new file mode 100644 index 000000000..2d456384d --- /dev/null +++ b/scripts/migration/renamings.json5 @@ -0,0 +1,1339 @@ +/** + * @license + * Copyright 2021 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Collected information about modules and module + * exports that have been renamed between versions. + * + * This file is in JSON5 format; see https://json5.org/. + */ + +{ + // Example entry: + '0.0.0': [ // Version that includes the rename. + // Each entry in the array is a module. + // All of the properties are optional. + { + // The name that the module had before this version. + oldName: 'old.module.name', + + // The name that the module now has in this version. + newName: 'new.module.name', + + // If a module had a default export and now has a named export, + // the name of the named export can be given here. + newExport: 'newNameForDefaultExport', + + // The old location of this module on the Blockly tree (the + // mega-object that people access if they are importing all of + // Blockly). Usually this is the same as the oldName and can be + // omitted. + oldPath: 'old.path.on.Blockly.tree', + + // The new location of this module on the Blockly tree. To be + // backwards compatible with people accessing the Blockly tree, + // this should be the same as the oldPath. If omitted, newPath + // is assumed to be the same newName. + newPath: 'new.path.on.Blockly.tree', + + // Each entry in this map is the old name of a named export. + exports: { + // The name that the export had before this version. + // All of the properties on this object are optional. + 'oldExportName': { + // The new module that the export is in in this version. If + // this is not provided, the newModule is assumed to be the + // parent module's newPath. + newModule: 'new.module.name', + + // The name that the export now has in this version. + newExport: 'newExportName', + + // The old location of this module on the Blockly tree. + // If omitted, oldPath defaults to + // .. + oldPath: 'old.path.on.Blockly.tree', + + // The new location of this module on the Blockly tree. To + // be backwards compatible with people accessing the + // Blockkly tree, this should be the same as the oldPath. + // Defaults to `${newModule}.${newExport}`. + newPath: 'new.path.on.Blockly.tree', + + // If a named export has been changed to a private variable + // with a get method, this is the name of the get method. + getMethod: 'getMethodName', + + // If a named export has been changed to a private variable + // with a set method, this is the name of the set method. + setMethod: 'setMethodName', + }, + + // This file can also document the renaming of a property or + // sub-property of an (object-valued) named export. + 'exportExport.oldProperty': { + newExport: 'newExport.newProperty', + // Additional options as above. + }, + + // Or promotion of property to named export, etc. + 'exportExport.oldProperty': { + newExport: 'newTopLevelExport', + }, + }, + }, + ], + + '5.20210325.0': [ + { + oldName: 'Blockly', + exports: { + // bind/unbind events functions. See PR #4642 + EventData: {newModule: 'Blockly.browserEvents', newExport: 'Data'}, + bindEvent_: {newModule: 'Blockly.browserEvents', newExport: 'bind'}, + unbindEvent_: {newModule: 'Blockly.browserEvents', newExport: 'unbind'}, + bindEventWithChecks_: { + newModule: 'Blockly.browserEvents', + newExport: 'conditionalBind', + }, + }, + } + ], + + '7.20211209.0-beta.0': [ + { + oldName: 'Blockly', + exports: { + // Clipboard. See PR #5237. + clipboardXml_: {newModule: 'Blockly.clipboard', newExport: 'xml'}, + clipboardSource_: {newModule: 'Blockly.clipboard', newExport: 'source'}, + clipboardTypeCounts_: { + newModule: 'Blockly.clipboard', + newExport: 'typeCounts', + }, + copy: {newModule: 'Blockly.clipboard'}, + paste: {newModule: 'Blockly.clipboard'}, + duplicate: {newModule: 'Blockly.clipboard'}, + + // mainWorkspace. See PR #5244. + mainWorkspace: { + newModule: 'Blockly.common', + getMethod: 'getMainWorkspace', + setMethod: 'setMainWorkspace', + }, + getMainWorkspace: {newModule: 'Blockly.common'}, + + // parentContainer, draggingConnections. See PR #5262. + parentContainer: { + newModule: 'Blockly.common', + getMethod: 'getParentContainer', + setMethod: 'setParentContainer', + }, + setParentContainer: {newModule: 'Blockly.common'}, + draggingConnections: {newModule: 'Blockly.common'}, + + // Dialogs. See PR #5457. + alert: { + newModule: 'Blockly.dialog', + newExport: 'alert', + setMethod: 'setAlert', + }, + confirm: { + newModule: 'Blockly.dialog', + newExport: 'confirm', + setMethod: 'setConfirm', + }, + prompt: { + newModule: 'Blockly.dialog', + newExport: 'prompt', + setMethod: 'setPrompt', + }, + // hueToHex. See PR #5462. + hueToHex: {newModule: 'Blockly.utils.colour'}, + // Blockly.hideChaff() became + // Blockly.common.getMainWorkspace().hideChaff(). See PR #5460. + + // selected. See PR #5489. + selected: { + newModule: 'Blockly.common', + getMethod: 'getSelected', + setMethod: 'setSelected', + }, + } + }, + { + oldName: 'Blockly.Blocks', + newName: 'Blockly.blocks', + newExport: 'Blocks', // Previous default export now named. + newPath: 'Blockly.Blocks', // But still on tree with original name. + }, + { + oldName: 'Blockly.ContextMenu', + exports: { + currentBlock: { + getMethod: 'getCurrentBlock', + setMethod: 'setCurrentBlock' + }, + }, + }, + { + oldName: 'Blockly.Events', + exports: { + recordUndo: {getMethod: 'getRecordUndo', setMethod: 'setRecordUndo'}, + }, + }, + { + oldName: 'Blockly.Tooltip', + exports: { + DIV: {getMethod: 'getDiv', setMethod: 'setDiv'}, + visible: {getMethod: 'isVisible'}, + }, + }, + { + oldName: 'Blockly.WidgetDiv', + exports: { + DIV: {getMethod: 'getDiv'}, + }, + }, + { + oldName: 'Blockly.connectionTypes', + newName: 'Blockly.ConnectionType', + newExport: 'ConnectionType', // Previous default export now named. + newPath: 'Blockly.ConnectionType', // Type reexported directly. + }, + { + oldName: 'Blockly.utils', + exports: { + genUid: {newModule: 'Blockly.utils.idGenerator'}, + getScrollDelta: {newModule: 'Blockly.utils.browserEvents'}, + isTargetInput: {newModule: 'Blockly.utils.browserEvents'}, + isRightButton: {newModule: 'Blockly.utils.browserEvents'}, + mouseToSvg: {newModule: 'Blockly.utils.browserEvents'}, + }, + }, + { + oldName: 'Blockly.utils.global', + newExport: 'globalThis', + newPath: 'Blockly.utils.global', + }, + { + oldName: 'Blockly.utils.IdGenerator', + newName: 'Blockly.utils.idGenerator', + }, + { + oldName: 'Blockly.utils.xml', + exports: { + // document was a function before, too - not a static property + // or get accessor. + document: {newExport: 'getDocument'}, + }, + }, + ], + + '7.20211209.0': [ + { + oldName: 'Blockly', + exports: { + // Align. + ALIGN_LEFT: { + newModule: 'Blockly.Input', + newExport: 'Align.LEFT', + newPath: 'Blockly.ALIGN_LEFT', + }, + ALIGN_CENTRE: { + newModule: 'Blockly.Input', + newExport: 'Align.CENTRE', + newPath: 'Blockly.ALIGN_CENTRE', + }, + ALIGN_RIGHT: { + newModule: 'Blockly.Input', + newExport: 'Align.RIGHT', + newPath: 'Blockly.ALIGN_RIGHT', + }, + + svgSize: {newModule: 'Blockly.utils.svgMath'}, + resizeSvgContents: {newModule: 'Blockly.WorkspaecSvg'}, + defineBlocksWithJsonArray: {newModule: 'Blockly.common'}, + isNumber: {newModule: 'Blockly.utils.string'}, + } + }, + + { + oldName: 'Blockly.Blocks.colour', + newName: 'Blockly.blocks.colour' + }, + // Blockly.Blocks.lists not previously provided. + { + oldName: 'Blockly.Blocks.logic', + newName: 'Blockly.blocks.logic' + }, + { + oldName: 'Blockly.Blocks.loops', + newName: 'Blockly.blocks.loops' + }, + { + oldName: 'Blockly.Blocks.math', + newName: 'Blockly.blocks.math' + }, + { + oldName: 'Blockly.Blocks.procedures', + newName: 'Blockly.blocks.procedures' + }, + { + oldName: 'Blockly.Blocks.texts', + newName: 'Blockly.blocks.texts' + }, + { + oldName: 'Blockly.Blocks.variables', + newName: 'Blockly.blocks.variables' + }, + // Blockly.Blocks.variablesDynamic not previously provided. + + { + oldName: 'Blockly.utils', + exports: { + screenToWsCoordinates: {newModule: 'Blockly.utils.svgMath'}, + getDocumentScroll: {newModule: 'Blockly.utils.svgMath'}, + getViewportBBox: {newModule: 'Blockly.utils.svgMath'}, + is3dSupported: {newModule: 'Blockly.utils.svgMath'}, + getRelativeXY: {newModule: 'Blockly.utils.svgMath'}, + getInjectionDivXY_: { + newModule: 'Blockly.utils.svgMath', + newExport: 'getInjectionDivXY' + }, + parseBlockColour: {newModule: 'Blockly.utils.parsing'}, + checkMessageReferences: {newModule: 'Blockly.utils.parsing'}, + replaceMessageReferences: {newModule: 'Blockly.utils.parsing'}, + tokenizeInterpolation: {newModule: 'Blockly.utils.parsing'}, + arrayRemove: { + newModule: 'Blockly.utils.array', + newExport: 'removeElem' + }, + getBlockTypeCounts: + {newModule: 'Blockly.common', newExport: 'getBlockTypeCounts'}, + runAfterPageLoad: + {newModule: 'Blockly.Extensions', newExport: 'runAfterPageLoad'}, + }, + }, + + // Default exports to named exports. + { + oldName: 'Blockly.Events.Abstract', + newExport: 'Abstract', + newPath: 'Blockly.Events.Abstract', + }, + { + oldName: 'Blockly.Events.BlockBase', + newExport: 'BlockBase', + newPath: 'Blockly.Events.BlockBase', + }, + { + oldName: 'Blockly.Events.BlockChange', + newExport: 'BlockChange', + newPath: 'Blockly.Events.BlockChange', + }, + { + oldName: 'Blockly.Events.BlockCreate', + newExport: 'BlockCreate', + newPath: 'Blockly.Events.BlockCreate', + }, + { + oldName: 'Blockly.Events.BlockDelete', + newExport: 'BlockDelete', + newPath: 'Blockly.Events.BlockDelete', + }, + { + oldName: 'Blockly.Events.BlockDrag', + newExport: 'BlockDrag', + newPath: 'Blockly.Events.BlockDrag', + }, + { + oldName: 'Blockly.Events.BlockMove', + newExport: 'BlockMove', + newPath: 'Blockly.Events.BlockMove', + }, + { + oldName: 'Blockly.Events.BubbleOpen', + newExport: 'BubbleOpen', + newPath: 'Blockly.Events.BubbleOpen', + }, + { + oldName: 'Blockly.Events.Click', + newExport: 'Click', + newPath: 'Blockly.Events.Click', + }, + { + oldName: 'Blockly.Events.CommentBase', + newExport: 'CommentBase', + newPath: 'Blockly.Events.CommentBase', + }, + { + oldName: 'Blockly.Events.CommentChange', + newExport: 'CommentChange', + newPath: 'Blockly.Events.CommentChange', + }, + { + oldName: 'Blockly.Events.CommentCreate', + newExport: 'CommentCreate', + newPath: 'Blockly.Events.CommentCreate', + }, + { + oldName: 'Blockly.Events.CommentDelete', + newExport: 'CommentDelete', + newPath: 'Blockly.Events.CommentDelete', + }, + { + oldName: 'Blockly.Events.CommentMove', + newExport: 'CommentMove', + newPath: 'Blockly.Events.CommentMove', + }, + { + oldName: 'Blockly.Events.MarkerMove', + newExport: 'MarkerMove', + newPath: 'Blockly.Events.MarkerMove', + }, + { + oldName: 'Blockly.Events.Selected', + newExport: 'Selected', + newPath: 'Blockly.Events.Selected', + }, + { + oldName: 'Blockly.Events.ThemeChange', + newExport: 'ThemeChange', + newPath: 'Blockly.Events.ThemeChange', + }, + { + oldName: 'Blockly.Events.ToolboxItemSelect', + newExport: 'ToolboxItemSelect', + newPath: 'Blockly.Events.ToolboxItemSelect', + }, + { + oldName: 'Blockly.Events.TrashcanOpen', + newExport: 'TrashcanOpen', + newPath: 'Blockly.Events.TrashcanOpen', + }, + { + oldName: 'Blockly.Events.Ui', + newExport: 'Ui', + newPath: 'Blockly.Events.Ui', + }, + { + oldName: 'Blockly.Events.UiBase', + newExport: 'UiBase', + newPath: 'Blockly.Events.UiBase', + }, + { + oldName: 'Blockly.Events.VarBase', + newExport: 'VarBase', + newPath: 'Blockly.Events.VarBase', + }, + { + oldName: 'Blockly.Events.VarCreate', + newExport: 'VarCreate', + newPath: 'Blockly.Events.VarCreate', + }, + { + oldName: 'Blockly.Events.VarDelete', + newExport: 'VarDelete', + newPath: 'Blockly.Events.VarDelete', + }, + { + oldName: 'Blockly.Events.VarRename', + newExport: 'VarRename', + newPath: 'Blockly.Events.VarRename', + }, + { + oldName: 'Blockly.Events.ViewportChange', + newExport: 'ViewportChange', + newPath: 'Blockly.Events.ViewportChange', + }, + { + oldName: 'Blockly.Events.FinishedLoading', + newExport: 'FinishedLoading', + newPath: 'Blockly.Events.FinishedLoading', + }, + { + oldName: 'Blockly.IASTNodeLocation', + newExport: 'IASTNodeLocation', + newPath: 'Blockly.IASTNodeLocation', + }, + { + oldName: 'Blockly.IASTNodeLocationSvg', + newExport: 'IASTNodeLocationSvg', + newPath: 'Blockly.IASTNodeLocationSvg', + }, + { + oldName: 'Blockly.IASTNodeLocationWithBlock', + newExport: 'IASTNodeLocationWithBlock', + newPath: 'Blockly.IASTNodeLocationWithBlock', + }, + { + oldName: 'Blockly.IAutoHideable', + newExport: 'IAutoHideable', + newPath: 'Blockly.IAutoHideable', + }, + { + oldName: 'Blockly.IBlockDragger', + newExport: 'IBlockDragger', + newPath: 'Blockly.IBlockDragger', + }, + { + oldName: 'Blockly.IBoundedElement', + newExport: 'IBoundedElement', + newPath: 'Blockly.IBoundedElement', + }, + { + oldName: 'Blockly.IBubble', + newExport: 'IBubble', + newPath: 'Blockly.IBubble', + }, + { + oldName: 'Blockly.ICollapsibleToolboxItem', + newExport: 'ICollapsibleToolboxItem', + newPath: 'Blockly.ICollapsibleToolboxItem', + }, + { + oldName: 'Blockly.IComponent', + newExport: 'IComponent', + newPath: 'Blockly.IComponent', + }, + { + oldName: 'Blockly.IConnectionChecker', + newExport: 'IConnectionChecker', + newPath: 'Blockly.IConnectionChecker', + }, + { + oldName: 'Blockly.IContextMenu', + newExport: 'IContextMenu', + newPath: 'Blockly.IContextMenu', + }, + { + oldName: 'Blockly.ICopyable', + newExport: 'ICopyable', + newPath: 'Blockly.ICopyable', + }, + { + oldName: 'Blockly.IDeletable', + newExport: 'IDeletable', + newPath: 'Blockly.IDeletable', + }, + { + oldName: 'Blockly.IDeleteArea', + newExport: 'IDeleteArea', + newPath: 'Blockly.IDeleteArea', + }, + { + oldName: 'Blockly.IDragTarget', + newExport: 'IDragTarget', + newPath: 'Blockly.IDragTarget', + }, + { + oldName: 'Blockly.IDraggable', + newExport: 'IDraggable', + newPath: 'Blockly.IDraggable', + }, + { + oldName: 'Blockly.IFlyout', + newExport: 'IFlyout', + newPath: 'Blockly.IFlyout', + }, + { + oldName: 'Blockly.IKeyboardAccessible', + newExport: 'IKeyboardAccessible', + newPath: 'Blockly.IKeyboardAccessible', + }, + { + oldName: 'Blockly.IMetricsManager', + newExport: 'IMetricsManager', + newPath: 'Blockly.IMetricsManager', + }, + { + oldName: 'Blockly.IMovable', + newExport: 'IMovable', + newPath: 'Blockly.IMovable', + }, + { + oldName: 'Blockly.IPositionable', + newExport: 'IPositionable', + newPath: 'Blockly.IPositionable', + }, + { + oldName: 'Blockly.IRegistrable', + newExport: 'IRegistrable', + newPath: 'Blockly.IRegistrable', + }, + { + oldName: 'Blockly.IRegistrableField', + newExport: 'IRegistrableField', + newPath: 'Blockly.IRegistrableField', + }, + { + oldName: 'Blockly.ISelectable', + newExport: 'ISelectable', + newPath: 'Blockly.ISelectable', + }, + { + oldName: 'Blockly.ISelectableToolboxItem', + newExport: 'ISelectableToolboxItem', + newPath: 'Blockly.ISelectableToolboxItem', + }, + { + oldName: 'Blockly.IStyleable', + newExport: 'IStyleable', + newPath: 'Blockly.IStyleable', + }, + { + oldName: 'Blockly.IToolbox', + newExport: 'IToolbox', + newPath: 'Blockly.IToolbox', + }, + { + oldName: 'Blockly.IToolboxItem', + newExport: 'IToolboxItem', + newPath: 'Blockly.IToolboxItem', + }, + { + oldName: 'Blockly.blockRendering.ConstantProvider', + newExport: 'ConstantProvider', + newPath: 'Blockly.blockRendering.ConstantProvider', + }, + { + oldName: 'Blockly.blockRendering.Debug', + newExport: 'Debug', + newPath: 'Blockly.blockRendering.Debug', + }, + { + oldName: 'Blockly.blockRendering.Drawer', + newExport: 'Drawer', + newPath: 'Blockly.blockRendering.Drawer', + }, + { + oldName: 'Blockly.blockRendering.IPathObject', + newExport: 'IPathObject', + newPath: 'Blockly.blockRendering.IPathObject', + }, + { + oldName: 'Blockly.blockRendering.RenderInfo', + newExport: 'RenderInfo', + newPath: 'Blockly.blockRendering.RenderInfo', + }, + { + oldName: 'Blockly.blockRendering.MarkerSvg', + newExport: 'MarkerSvg', + newPath: 'Blockly.blockRendering.MarkerSvg', + }, + { + oldName: 'Blockly.blockRendering.PathObject', + newExport: 'PathObject', + newPath: 'Blockly.blockRendering.PathObject', + }, + { + oldName: 'Blockly.blockRendering.Renderer', + newExport: 'Renderer', + newPath: 'Blockly.blockRendering.Renderer', + }, + { + oldName: 'Blockly.geras.InlineInput', + newExport: 'InlineInput', + newPath: 'Blockly.geras.InlineInput', + }, + { + oldName: 'Blockly.geras.StatementInput', + newExport: 'StatementInput', + newPath: 'Blockly.geras.StatementInput', + }, + { + oldName: 'Blockly.geras.ConstantProvider', + newExport: 'ConstantProvider', + newPath: 'Blockly.geras.ConstantProvider', + }, + { + oldName: 'Blockly.geras.Drawer', + newExport: 'Drawer', + newPath: 'Blockly.geras.Drawer', + }, + { + oldName: 'Blockly.geras.HighlightConstantProvider', + newExport: 'HighlightConstantProvider', + newPath: 'Blockly.geras.HighlightConstantProvider', + }, + { + oldName: 'Blockly.geras.Highlighter', + newExport: 'Highlighter', + newPath: 'Blockly.geras.Highlighter', + }, + { + oldName: 'Blockly.geras.RenderInfo', + newExport: 'RenderInfo', + newPath: 'Blockly.geras.RenderInfo', + }, + { + oldName: 'Blockly.geras.PathObject', + newExport: 'PathObject', + newPath: 'Blockly.geras.PathObject', + }, + { + oldName: 'Blockly.geras.Renderer', + newExport: 'Renderer', + newPath: 'Blockly.geras.Renderer', + }, + { + oldName: 'Blockly.blockRendering.Measurable', + newExport: 'Measurable', + newPath: 'Blockly.blockRendering.Measurable', + }, + { + oldName: 'Blockly.blockRendering.BottomRow', + newExport: 'BottomRow', + newPath: 'Blockly.blockRendering.BottomRow', + }, + { + oldName: 'Blockly.blockRendering.Connection', + newExport: 'Connection', + newPath: 'Blockly.blockRendering.Connection', + }, + { + oldName: 'Blockly.blockRendering.ExternalValueInput', + newExport: 'ExternalValueInput', + newPath: 'Blockly.blockRendering.ExternalValueInput', + }, + { + oldName: 'Blockly.blockRendering.Field', + newExport: 'Field', + newPath: 'Blockly.blockRendering.Field', + }, + { + oldName: 'Blockly.blockRendering.Hat', + newExport: 'Hat', + newPath: 'Blockly.blockRendering.Hat', + }, + { + oldName: 'Blockly.blockRendering.Icon', + newExport: 'Icon', + newPath: 'Blockly.blockRendering.Icon', + }, + { + oldName: 'Blockly.blockRendering.InRowSpacer', + newExport: 'InRowSpacer', + newPath: 'Blockly.blockRendering.InRowSpacer', + }, + { + oldName: 'Blockly.blockRendering.InlineInput', + newExport: 'InlineInput', + newPath: 'Blockly.blockRendering.InlineInput', + }, + { + oldName: 'Blockly.blockRendering.InputConnection', + newExport: 'InputConnection', + newPath: 'Blockly.blockRendering.InputConnection', + }, + { + oldName: 'Blockly.blockRendering.InputRow', + newExport: 'InputRow', + newPath: 'Blockly.blockRendering.InputRow', + }, + { + oldName: 'Blockly.blockRendering.JaggedEdge', + newExport: 'JaggedEdge', + newPath: 'Blockly.blockRendering.JaggedEdge', + }, + { + oldName: 'Blockly.blockRendering.NextConnection', + newExport: 'NextConnection', + newPath: 'Blockly.blockRendering.NextConnection', + }, + { + oldName: 'Blockly.blockRendering.OutputConnection', + newExport: 'OutputConnection', + newPath: 'Blockly.blockRendering.OutputConnection', + }, + { + oldName: 'Blockly.blockRendering.PreviousConnection', + newExport: 'PreviousConnection', + newPath: 'Blockly.blockRendering.PreviousConnection', + }, + { + oldName: 'Blockly.blockRendering.RoundCorner', + newExport: 'RoundCorner', + newPath: 'Blockly.blockRendering.RoundCorner', + }, + { + oldName: 'Blockly.blockRendering.Row', + newExport: 'Row', + newPath: 'Blockly.blockRendering.Row', + }, + { + oldName: 'Blockly.blockRendering.SpacerRow', + newExport: 'SpacerRow', + newPath: 'Blockly.blockRendering.SpacerRow', + }, + { + oldName: 'Blockly.blockRendering.SquareCorner', + newExport: 'SquareCorner', + newPath: 'Blockly.blockRendering.SquareCorner', + }, + { + oldName: 'Blockly.blockRendering.StatementInput', + newExport: 'StatementInput', + newPath: 'Blockly.blockRendering.StatementInput', + }, + { + oldName: 'Blockly.blockRendering.TopRow', + newExport: 'TopRow', + newPath: 'Blockly.blockRendering.TopRow', + }, + { + oldName: 'Blockly.blockRendering.Types', + newExport: 'Types', + newPath: 'Blockly.blockRendering.Types', + }, + { + oldName: 'Blockly.minimalist.ConstantProvider', + newExport: 'ConstantProvider', + newPath: 'Blockly.minimalist.ConstantProvider', + }, + { + oldName: 'Blockly.minimalist.Drawer', + newExport: 'Drawer', + newPath: 'Blockly.minimalist.Drawer', + }, + { + oldName: 'Blockly.minimalist.RenderInfo', + newExport: 'RenderInfo', + newPath: 'Blockly.minimalist.RenderInfo', + }, + { + oldName: 'Blockly.minimalist.Renderer', + newExport: 'Renderer', + newPath: 'Blockly.minimalist.Renderer', + }, + { + oldName: 'Blockly.thrasos.RenderInfo', + newExport: 'RenderInfo', + newPath: 'Blockly.thrasos.RenderInfo', + }, + { + oldName: 'Blockly.thrasos.Renderer', + newExport: 'Renderer', + newPath: 'Blockly.thrasos.Renderer', + }, + { + oldName: 'Blockly.zelos.BottomRow', + newExport: 'BottomRow', + newPath: 'Blockly.zelos.BottomRow', + }, + { + oldName: 'Blockly.zelos.StatementInput', + newExport: 'StatementInput', + newPath: 'Blockly.zelos.StatementInput', + }, + { + oldName: 'Blockly.zelos.RightConnectionShape', + newExport: 'RightConnectionShape', + newPath: 'Blockly.zelos.RightConnectionShape', + }, + { + oldName: 'Blockly.zelos.TopRow', + newExport: 'TopRow', + newPath: 'Blockly.zelos.TopRow', + }, + { + oldName: 'Blockly.zelos.ConstantProvider', + newExport: 'ConstantProvider', + newPath: 'Blockly.zelos.ConstantProvider', + }, + { + oldName: 'Blockly.zelos.Drawer', + newExport: 'Drawer', + newPath: 'Blockly.zelos.Drawer', + }, + { + oldName: 'Blockly.zelos.RenderInfo', + newExport: 'RenderInfo', + newPath: 'Blockly.zelos.RenderInfo', + }, + { + oldName: 'Blockly.zelos.MarkerSvg', + newExport: 'MarkerSvg', + newPath: 'Blockly.zelos.MarkerSvg', + }, + { + oldName: 'Blockly.zelos.PathObject', + newExport: 'PathObject', + newPath: 'Blockly.zelos.PathObject', + }, + { + oldName: 'Blockly.zelos.Renderer', + newExport: 'Renderer', + newPath: 'Blockly.zelos.Renderer', + }, + { + oldName: 'Blockly.Themes.Classic', + newExport: 'Classic', + newPath: 'Blockly.Themes.Classic', + }, + { + oldName: 'Blockly.Themes.Zelos', + newExport: 'Zelos', + newPath: 'Blockly.Themes.Zelos', + }, + { + oldName: 'Blockly.ToolboxCategory', + newExport: 'ToolboxCategory', + newPath: 'Blockly.ToolboxCategory', + }, + { + oldName: 'Blockly.CollapsibleToolboxCategory', + newExport: 'CollapsibleToolboxCategory', + newPath: 'Blockly.CollapsibleToolboxCategory', + }, + { + oldName: 'Blockly.ToolboxSeparator', + newExport: 'ToolboxSeparator', + newPath: 'Blockly.ToolboxSeparator', + }, + { + oldName: 'Blockly.Toolbox', + newExport: 'Toolbox', + newPath: 'Blockly.Toolbox', + }, + { + oldName: 'Blockly.ToolboxItem', + newExport: 'ToolboxItem', + newPath: 'Blockly.ToolboxItem', + }, + { + oldName: 'Blockly.utils.Coordinate', + newExport: 'Coordinate', + newPath: 'Blockly.utils.Coordinate', + }, + { + oldName: 'Blockly.utils.KeyCodes', + newExport: 'KeyCodes', + newPath: 'Blockly.utils.KeyCodes', + }, + { + oldName: 'Blockly.utils.Metrics', + newExport: 'Metrics', + newPath: 'Blockly.utils.Metrics', + }, + { + oldName: 'Blockly.utils.Rect', + newExport: 'Rect', + newPath: 'Blockly.utils.Rect', + }, + { + oldName: 'Blockly.utils.Size', + newExport: 'Size', + newPath: 'Blockly.utils.Size', + }, + { + oldName: 'Blockly.utils.Svg', + newExport: 'Svg', + newPath: 'Blockly.utils.Svg', + }, + { + oldName: 'Blockly.BlocklyOptions', + newExport: 'BlocklyOptions', + newPath: 'Blockly.BlocklyOptions', + }, + { + oldName: 'Blockly.Bubble', + newExport: 'Bubble', + newPath: 'Blockly.Bubble', + }, + { + oldName: 'Blockly.BubbleDragger', + newExport: 'BubbleDragger', + newPath: 'Blockly.BubbleDragger', + }, + { + oldName: 'Blockly.Comment', + newExport: 'Comment', + newPath: 'Blockly.Comment', + }, + { + oldName: 'Blockly.ComponentManager', + newExport: 'ComponentManager', + newPath: 'Blockly.ComponentManager', + }, + { + oldName: 'Blockly.Connection', + newExport: 'Connection', + newPath: 'Blockly.Connection', + }, + { + oldName: 'Blockly.ConnectionChecker', + newExport: 'ConnectionChecker', + newPath: 'Blockly.ConnectionChecker', + }, + { + oldName: 'Blockly.ConnectionDB', + newExport: 'ConnectionDB', + newPath: 'Blockly.ConnectionDB', + }, + { + oldName: 'Blockly.ContextMenuRegistry', + newExport: 'ContextMenuRegistry', + newPath: 'Blockly.ContextMenuRegistry', + }, + { + oldName: 'Blockly.DeleteArea', + newExport: 'DeleteArea', + newPath: 'Blockly.DeleteArea', + }, + { + oldName: 'Blockly.DragTarget', + newExport: 'DragTarget', + newPath: 'Blockly.DragTarget', + }, + { + oldName: 'Blockly.DropDownDiv', + newExport: 'DropDownDiv', + newPath: 'Blockly.DropDownDiv', + }, + { + oldName: 'Blockly.Field', + newExport: 'Field', + newPath: 'Blockly.Field', + }, + { + oldName: 'Blockly.FieldAngle', + newExport: 'FieldAngle', + newPath: 'Blockly.FieldAngle', + }, + { + oldName: 'Blockly.FieldCheckbox', + newExport: 'FieldCheckbox', + newPath: 'Blockly.FieldCheckbox', + }, + { + oldName: 'Blockly.FieldColour', + newExport: 'FieldColour', + newPath: 'Blockly.FieldColour', + }, + { + oldName: 'Blockly.FieldDropdown', + newExport: 'FieldDropdown', + newPath: 'Blockly.FieldDropdown', + }, + { + oldName: 'Blockly.FieldImage', + newExport: 'FieldImage', + newPath: 'Blockly.FieldImage', + }, + { + oldName: 'Blockly.FieldLabel', + newExport: 'FieldLabel', + newPath: 'Blockly.FieldLabel', + }, + { + oldName: 'Blockly.FieldLabelSerializable', + newExport: 'FieldLabelSerializable', + newPath: 'Blockly.FieldLabelSerializable', + }, + { + oldName: 'Blockly.FieldMultilineInput', + newExport: 'FieldMultilineInput', + newPath: 'Blockly.FieldMultilineInput', + }, + { + oldName: 'Blockly.FieldNumber', + newExport: 'FieldNumber', + newPath: 'Blockly.FieldNumber', + }, + { + oldName: 'Blockly.FieldTextInput', + newExport: 'FieldTextInput', + newPath: 'Blockly.FieldTextInput', + }, + { + oldName: 'Blockly.FieldVariable', + newExport: 'FieldVariable', + newPath: 'Blockly.FieldVariable', + }, + { + oldName: 'Blockly.Flyout', + newExport: 'Flyout', + newPath: 'Blockly.Flyout', + }, + { + oldName: 'Blockly.FlyoutButton', + newExport: 'FlyoutButton', + newPath: 'Blockly.FlyoutButton', + }, + { + oldName: 'Blockly.HorizontalFlyout', + newExport: 'HorizontalFlyout', + newPath: 'Blockly.HorizontalFlyout', + }, + { + oldName: 'Blockly.FlyoutMetricsManager', + newExport: 'FlyoutMetricsManager', + newPath: 'Blockly.FlyoutMetricsManager', + }, + { + oldName: 'Blockly.VerticalFlyout', + newExport: 'VerticalFlyout', + newPath: 'Blockly.VerticalFlyout', + }, + { + oldName: 'Blockly.Generator', + newExport: 'Generator', + newPath: 'Blockly.Generator', + }, + { + oldName: 'Blockly.Gesture', + newExport: 'Gesture', + newPath: 'Blockly.Gesture', + }, + { + oldName: 'Blockly.Grid', + newExport: 'Grid', + newPath: 'Blockly.Grid', + }, + { + oldName: 'Blockly.Icon', + newExport: 'Icon', + newPath: 'Blockly.Icon', + }, + { + oldName: 'Blockly.inject', + newExport: 'inject', + newPath: 'Blockly.inject', + }, + { + oldName: 'Blockly.Input', + newExport: 'Input', + newPath: 'Blockly.Input', + }, + { + oldName: 'Blockly.inputTypes', + newExport: 'inputTypes', + newPath: 'Blockly.inputTypes', + }, + { + oldName: 'Blockly.InsertionMarkerManager', + newExport: 'InsertionMarkerManager', + newPath: 'Blockly.InsertionMarkerManager', + }, + { + oldName: 'Blockly.MarkerManager', + newExport: 'MarkerManager', + newPath: 'Blockly.MarkerManager', + }, + { + oldName: 'Blockly.Menu', + newExport: 'Menu', + newPath: 'Blockly.Menu', + }, + { + oldName: 'Blockly.MenuItem', + newExport: 'MenuItem', + newPath: 'Blockly.MenuItem', + }, + { + oldName: 'Blockly.MetricsManager', + newExport: 'MetricsManager', + newPath: 'Blockly.MetricsManager', + }, + { + oldName: 'Blockly.Msg', + newExport: 'Msg', + newPath: 'Blockly.Msg', + }, + { + oldName: 'Blockly.Mutator', + newExport: 'Mutator', + newPath: 'Blockly.Mutator', + }, + { + oldName: 'Blockly.Names', + newExport: 'Names', + newPath: 'Blockly.Names', + }, + { + oldName: 'Blockly.Options', + newExport: 'Options', + newPath: 'Blockly.Options', + }, + { + oldName: 'Blockly.RenderedConnection', + newExport: 'RenderedConnection', + newPath: 'Blockly.RenderedConnection', + }, + { + oldName: 'Blockly.Scrollbar', + newExport: 'Scrollbar', + newPath: 'Blockly.Scrollbar', + }, + { + oldName: 'Blockly.ScrollbarPair', + newExport: 'ScrollbarPair', + newPath: 'Blockly.ScrollbarPair', + }, + { + oldName: 'Blockly.ShortcutRegistry', + newExport: 'ShortcutRegistry', + newPath: 'Blockly.ShortcutRegistry', + }, + { + oldName: 'Blockly.Theme', + newExport: 'Theme', + newPath: 'Blockly.Theme', + }, + { + oldName: 'Blockly.ThemeManager', + newExport: 'ThemeManager', + newPath: 'Blockly.ThemeManager', + }, + { + oldName: 'Blockly.TouchGesture', + newExport: 'TouchGesture', + newPath: 'Blockly.TouchGesture', + }, + { + oldName: 'Blockly.Trashcan', + newExport: 'Trashcan', + newPath: 'Blockly.Trashcan', + }, + { + oldName: 'Blockly.VariableMap', + newExport: 'VariableMap', + newPath: 'Blockly.VariableMap', + }, + { + oldName: 'Blockly.VariableModel', + newExport: 'VariableModel', + newPath: 'Blockly.VariableModel', + }, + { + oldName: 'Blockly.Warning', + newExport: 'Warning', + newPath: 'Blockly.Warning', + }, + { + oldName: 'Blockly.Workspace', + newExport: 'Workspace', + newPath: 'Blockly.Workspace', + }, + { + oldName: 'Blockly.WorkspaceAudio', + newExport: 'WorkspaceAudio', + newPath: 'Blockly.WorkspaceAudio', + }, + { + oldName: 'Blockly.WorkspaceComment', + newExport: 'WorkspaceComment', + newPath: 'Blockly.WorkspaceComment', + }, + { + oldName: 'Blockly.WorkspaceCommentSvg', + newExport: 'WorkspaceCommentSvg', + newPath: 'Blockly.WorkspaceCommentSvg', + }, + { + oldName: 'Blockly.WorkspaceDragSurfaceSvg', + newExport: 'WorkspaceDragSurfaceSvg', + newPath: 'Blockly.WorkspaceDragSurfaceSvg', + }, + { + oldName: 'Blockly.WorkspaceDragger', + newExport: 'WorkspaceDragger', + newPath: 'Blockly.WorkspaceDragger', + }, + { + oldName: 'Blockly.WorkspaceSvg', + newExport: 'WorkspaceSvg', + newPath: 'Blockly.WorkspaceSvg', + }, + { + oldName: 'Blockly.ZoomControls', + newExport: 'ZoomControls', + newPath: 'Blockly.ZoomControls', + }, + ], + + 'develop': [ + { + oldName: 'Blockly', + exports: { + DRAG_RADIUS: { + newModule: 'Blockly.config', + newExport: 'config.dragRadius', + }, + FLYOUT_DRAG_RADIUS: { + newModule: 'Blockly.config', + newExport: 'config.flyoutDragRadius', + }, + SNAP_RADIUS: { + newModule: 'Blockly.config', + newExport: 'config.snapRadius', + }, + CONNECTING_SNAP_RADIUS: { + newModule: 'Blockly.config', + newExport: 'config.connectingSnapRadius', + }, + CURRENT_CONNECTION_PREFERENCE: { + newModule: 'Blockly.config', + newExport: 'config.currentConnectionPreference', + }, + BUMP_DELAY: { + newModule: 'Blockly.config', + newExport: 'config.bumpDelay', + }, + SPRITE: { + newModule: 'Blockly.constants', + }, + }, + }, + { + oldName: 'Blockly.blocks.all', + newName: 'Blockly.libraryBlocks', + }, + { + oldName: 'Blockly.blocks.colour', + newName: 'Blockly.libraryBlocks.colour', + }, + { + oldName: 'Blockly.blocks.lists', + newName: 'Blockly.libraryBlocks.lists', + }, + { + oldName: 'Blockly.blocks.logic', + newName: 'Blockly.libraryBlocks.logic', + }, + { + oldName: 'Blockly.blocks.loops', + newName: 'Blockly.libraryBlocks.loops', + }, + { + oldName: 'Blockly.blocks.math', + newName: 'Blockly.libraryBlocks.math', + }, + { + oldName: 'Blockly.blocks.procedures', + newName: 'Blockly.libraryBlocks.procedures', + }, + { + oldName: 'Blockly.blocks.text', + newName: 'Blockly.libraryBlocks.text', + }, + { + oldName: 'Blockly.blocks.variables', + newName: 'Blockly.libraryBlocks.variables', + }, + { + oldName: 'Blockly.blocks.variablesDynamic', + newName: 'Blockly.libraryBlocks.variablesDynamic', + }, + { + oldName: 'Blockly.DropDownDiv', + newName: 'Blockly.dropDownDiv', + newPath: 'Blockly.DropDownDiv', + }, + ] +} diff --git a/tests/compile/main.js b/tests/compile/main.js index f7ab99e7a..b5d20a770 100644 --- a/tests/compile/main.js +++ b/tests/compile/main.js @@ -4,19 +4,33 @@ * SPDX-License-Identifier: Apache-2.0 */ -goog.provide('Main'); +goog.module('Main'); + // Core // Either require 'Blockly.requires', or just the components you use: -goog.require('Blockly'); +/* eslint-disable-next-line no-unused-vars */ +const {BlocklyOptions} = goog.requireType('Blockly.BlocklyOptions'); +const {inject} = goog.require('Blockly.inject'); +/** @suppress {extraRequire} */ goog.require('Blockly.geras.Renderer'); +/** @suppress {extraRequire} */ goog.require('Blockly.VerticalFlyout'); // Blocks -goog.require('Blockly.blocks.all'); -goog.require('Blockly.blocks.testBlocks'); +/** @suppress {extraRequire} */ +goog.require('Blockly.libraryBlocks.logic'); +/** @suppress {extraRequire} */ +goog.require('Blockly.libraryBlocks.loops'); +/** @suppress {extraRequire} */ +goog.require('Blockly.libraryBlocks.math'); +/** @suppress {extraRequire} */ +goog.require('Blockly.libraryBlocks.texts'); +/** @suppress {extraRequire} */ +goog.require('Blockly.libraryBlocks.testBlocks'); -Main.init = function() { - Blockly.inject('blocklyDiv', { - 'toolbox': document.getElementById('toolbox') - }); + +function init() { + inject('blocklyDiv', /** @type {BlocklyOptions} */ ({ + 'toolbox': document.getElementById('toolbox') + })); }; -window.addEventListener('load', Main.init); +window.addEventListener('load', init); diff --git a/tests/compile/test_blocks.js b/tests/compile/test_blocks.js index 9cf7efd68..2b8cc6621 100644 --- a/tests/compile/test_blocks.js +++ b/tests/compile/test_blocks.js @@ -9,7 +9,7 @@ */ 'use strict'; -goog.provide('Blockly.blocks.testBlocks'); +goog.provide('Blockly.libraryBlocks.testBlocks'); goog.require('Blockly'); diff --git a/tests/deps.js b/tests/deps.js index a46277b5a..317703f1a 100644 --- a/tests/deps.js +++ b/tests/deps.js @@ -1,98 +1,99 @@ -goog.addDependency('../../blocks/all.js', ['Blockly.blocks.all'], ['Blockly.blocks.colour', 'Blockly.blocks.lists', 'Blockly.blocks.logic', 'Blockly.blocks.loops', 'Blockly.blocks.math', 'Blockly.blocks.procedures', 'Blockly.blocks.texts', 'Blockly.blocks.variables', 'Blockly.blocks.variablesDynamic'], {'module': 'goog'}); -goog.addDependency('../../blocks/colour.js', ['Blockly.blocks.colour'], ['Blockly.FieldColour', 'Blockly.common'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../blocks/lists.js', ['Blockly.blocks.lists'], ['Blockly.ConnectionType', 'Blockly.FieldDropdown', 'Blockly.FieldDropdown', 'Blockly.Input', 'Blockly.Msg', 'Blockly.Mutator', 'Blockly.blocks', 'Blockly.common', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../blocks/logic.js', ['Blockly.blocks.logic'], ['Blockly.Events', 'Blockly.Extensions', 'Blockly.FieldDropdown', 'Blockly.FieldLabel', 'Blockly.Msg', 'Blockly.Mutator', 'Blockly.common', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../blocks/loops.js', ['Blockly.blocks.loops'], ['Blockly.ContextMenu', 'Blockly.Events', 'Blockly.Extensions', 'Blockly.FieldDropdown', 'Blockly.FieldLabel', 'Blockly.FieldNumber', 'Blockly.FieldVariable', 'Blockly.Msg', 'Blockly.Variables', 'Blockly.Warning', 'Blockly.common', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../blocks/math.js', ['Blockly.blocks.math'], ['Blockly.Extensions', 'Blockly.FieldDropdown', 'Blockly.FieldLabel', 'Blockly.FieldNumber', 'Blockly.FieldVariable', 'Blockly.common', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../blocks/procedures.js', ['Blockly.blocks.procedures'], ['Blockly.Comment', 'Blockly.ContextMenu', 'Blockly.Events', 'Blockly.FieldCheckbox', 'Blockly.FieldLabel', 'Blockly.FieldTextInput', 'Blockly.Input', 'Blockly.Msg', 'Blockly.Mutator', 'Blockly.Names', 'Blockly.Procedures', 'Blockly.Variables', 'Blockly.Warning', 'Blockly.Xml', 'Blockly.blocks', 'Blockly.internalConstants', 'Blockly.utils.xml'], {'lang': 'es9', 'module': 'goog'}); -goog.addDependency('../../blocks/text.js', ['Blockly.blocks.texts'], ['Blockly.ConnectionType', 'Blockly.Extensions', 'Blockly.FieldDropdown', 'Blockly.FieldImage', 'Blockly.FieldMultilineInput', 'Blockly.FieldTextInput', 'Blockly.FieldVariable', 'Blockly.Input', 'Blockly.Msg', 'Blockly.Mutator', 'Blockly.blocks', 'Blockly.common', 'Blockly.utils.xml'], {'lang': 'es9', 'module': 'goog'}); -goog.addDependency('../../blocks/variables.js', ['Blockly.blocks.variables'], ['Blockly.ContextMenu', 'Blockly.Extensions', 'Blockly.FieldLabel', 'Blockly.FieldVariable', 'Blockly.Msg', 'Blockly.Variables', 'Blockly.common', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../blocks/variables_dynamic.js', ['Blockly.blocks.variablesDynamic'], ['Blockly.ContextMenu', 'Blockly.Extensions', 'Blockly.FieldLabel', 'Blockly.FieldVariable', 'Blockly.Msg', 'Blockly.Variables', 'Blockly.common', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../blocks/blocks.js', ['Blockly.libraryBlocks'], ['Blockly.libraryBlocks.colour', 'Blockly.libraryBlocks.lists', 'Blockly.libraryBlocks.logic', 'Blockly.libraryBlocks.loops', 'Blockly.libraryBlocks.math', 'Blockly.libraryBlocks.procedures', 'Blockly.libraryBlocks.texts', 'Blockly.libraryBlocks.variables', 'Blockly.libraryBlocks.variablesDynamic'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../blocks/colour.js', ['Blockly.libraryBlocks.colour'], ['Blockly.FieldColour', 'Blockly.common'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../blocks/lists.js', ['Blockly.libraryBlocks.lists'], ['Blockly.ConnectionType', 'Blockly.FieldDropdown', 'Blockly.FieldDropdown', 'Blockly.Input', 'Blockly.Msg', 'Blockly.Mutator', 'Blockly.common', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../blocks/logic.js', ['Blockly.libraryBlocks.logic'], ['Blockly.Events', 'Blockly.Extensions', 'Blockly.FieldDropdown', 'Blockly.FieldLabel', 'Blockly.Msg', 'Blockly.Mutator', 'Blockly.common', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../blocks/loops.js', ['Blockly.libraryBlocks.loops'], ['Blockly.ContextMenu', 'Blockly.Events', 'Blockly.Extensions', 'Blockly.FieldDropdown', 'Blockly.FieldLabel', 'Blockly.FieldNumber', 'Blockly.FieldVariable', 'Blockly.Msg', 'Blockly.Variables', 'Blockly.Warning', 'Blockly.common', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../blocks/math.js', ['Blockly.libraryBlocks.math'], ['Blockly.Extensions', 'Blockly.FieldDropdown', 'Blockly.FieldLabel', 'Blockly.FieldNumber', 'Blockly.FieldVariable', 'Blockly.common', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../blocks/procedures.js', ['Blockly.libraryBlocks.procedures'], ['Blockly.Comment', 'Blockly.ContextMenu', 'Blockly.Events', 'Blockly.FieldCheckbox', 'Blockly.FieldLabel', 'Blockly.FieldTextInput', 'Blockly.Input', 'Blockly.Msg', 'Blockly.Mutator', 'Blockly.Names', 'Blockly.Procedures', 'Blockly.Variables', 'Blockly.Warning', 'Blockly.Xml', 'Blockly.common', 'Blockly.config', 'Blockly.utils.xml'], {'lang': 'es9', 'module': 'goog'}); +goog.addDependency('../../blocks/text.js', ['Blockly.libraryBlocks.texts'], ['Blockly.ConnectionType', 'Blockly.Extensions', 'Blockly.FieldDropdown', 'Blockly.FieldImage', 'Blockly.FieldMultilineInput', 'Blockly.FieldTextInput', 'Blockly.FieldVariable', 'Blockly.Input', 'Blockly.Msg', 'Blockly.Mutator', 'Blockly.common', 'Blockly.utils.xml'], {'lang': 'es9', 'module': 'goog'}); +goog.addDependency('../../blocks/variables.js', ['Blockly.libraryBlocks.variables'], ['Blockly.ContextMenu', 'Blockly.Extensions', 'Blockly.FieldLabel', 'Blockly.FieldVariable', 'Blockly.Msg', 'Blockly.Variables', 'Blockly.common', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../blocks/variables_dynamic.js', ['Blockly.libraryBlocks.variablesDynamic'], ['Blockly.ContextMenu', 'Blockly.Extensions', 'Blockly.FieldLabel', 'Blockly.FieldVariable', 'Blockly.Msg', 'Blockly.Variables', 'Blockly.common', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/block.js', ['Blockly.Block'], ['Blockly.ASTNode', 'Blockly.Connection', 'Blockly.ConnectionType', 'Blockly.Events.BlockChange', 'Blockly.Events.BlockCreate', 'Blockly.Events.BlockDelete', 'Blockly.Events.BlockMove', 'Blockly.Events.utils', 'Blockly.Extensions', 'Blockly.IASTNodeLocation', 'Blockly.IDeletable', 'Blockly.Input', 'Blockly.Tooltip', 'Blockly.blocks', 'Blockly.common', 'Blockly.constants', 'Blockly.fieldRegistry', 'Blockly.inputTypes', 'Blockly.utils.Coordinate', 'Blockly.utils.Size', 'Blockly.utils.array', 'Blockly.utils.idGenerator', 'Blockly.utils.object', 'Blockly.utils.parsing'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/block_animations.js', ['Blockly.blockAnimations'], ['Blockly.utils.Svg', 'Blockly.utils.dom'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/block_drag_surface.js', ['Blockly.BlockDragSurfaceSvg'], ['Blockly.utils.Coordinate', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.svgMath'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/block_dragger.js', ['Blockly.BlockDragger'], ['Blockly.Events.BlockDrag', 'Blockly.Events.BlockMove', 'Blockly.Events.utils', 'Blockly.IBlockDragger', 'Blockly.InsertionMarkerManager', 'Blockly.blockAnimations', 'Blockly.bumpObjects', 'Blockly.common', 'Blockly.registry', 'Blockly.utils.Coordinate', 'Blockly.utils.dom'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/block_svg.js', ['Blockly.BlockSvg'], ['Blockly.ASTNode', 'Blockly.Block', 'Blockly.ConnectionType', 'Blockly.ContextMenu', 'Blockly.ContextMenuRegistry', 'Blockly.Events.BlockMove', 'Blockly.Events.Selected', 'Blockly.Events.utils', 'Blockly.FieldLabel', 'Blockly.IASTNodeLocationSvg', 'Blockly.IBoundedElement', 'Blockly.ICopyable', 'Blockly.IDraggable', 'Blockly.MarkerManager', 'Blockly.Msg', 'Blockly.RenderedConnection', 'Blockly.TabNavigateCursor', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.blockAnimations', 'Blockly.browserEvents', 'Blockly.common', 'Blockly.constants', 'Blockly.internalConstants', 'Blockly.serialization.blocks', 'Blockly.utils.Coordinate', 'Blockly.utils.Rect', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.svgMath', 'Blockly.utils.userAgent'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/blockly.js', ['Blockly'], ['Blockly.ASTNode', 'Blockly.BasicCursor', 'Blockly.Block', 'Blockly.BlockDragSurfaceSvg', 'Blockly.BlockDragger', 'Blockly.BlockSvg', 'Blockly.BlocklyOptions', 'Blockly.Bubble', 'Blockly.BubbleDragger', 'Blockly.CollapsibleToolboxCategory', 'Blockly.Comment', 'Blockly.ComponentManager', 'Blockly.Connection', 'Blockly.ConnectionChecker', 'Blockly.ConnectionDB', 'Blockly.ConnectionType', 'Blockly.ContextMenu', 'Blockly.ContextMenuItems', 'Blockly.ContextMenuRegistry', 'Blockly.Css', 'Blockly.Cursor', 'Blockly.DeleteArea', 'Blockly.DragTarget', 'Blockly.DropDownDiv', 'Blockly.Events', 'Blockly.Events.BlockCreate', 'Blockly.Events.FinishedLoading', 'Blockly.Events.Ui', 'Blockly.Events.UiBase', 'Blockly.Events.VarCreate', 'Blockly.Extensions', 'Blockly.Field', 'Blockly.FieldAngle', 'Blockly.FieldCheckbox', 'Blockly.FieldColour', 'Blockly.FieldDropdown', 'Blockly.FieldImage', 'Blockly.FieldLabel', 'Blockly.FieldLabelSerializable', 'Blockly.FieldMultilineInput', 'Blockly.FieldNumber', 'Blockly.FieldTextInput', 'Blockly.FieldVariable', 'Blockly.Flyout', 'Blockly.FlyoutButton', 'Blockly.FlyoutMetricsManager', 'Blockly.Generator', 'Blockly.Gesture', 'Blockly.Grid', 'Blockly.HorizontalFlyout', 'Blockly.IASTNodeLocation', 'Blockly.IASTNodeLocationSvg', 'Blockly.IASTNodeLocationWithBlock', 'Blockly.IAutoHideable', 'Blockly.IBlockDragger', 'Blockly.IBoundedElement', 'Blockly.IBubble', 'Blockly.ICollapsibleToolboxItem', 'Blockly.IComponent', 'Blockly.IConnectionChecker', 'Blockly.IContextMenu', 'Blockly.ICopyable', 'Blockly.IDeletable', 'Blockly.IDeleteArea', 'Blockly.IDragTarget', 'Blockly.IDraggable', 'Blockly.IFlyout', 'Blockly.IKeyboardAccessible', 'Blockly.IMetricsManager', 'Blockly.IMovable', 'Blockly.IPositionable', 'Blockly.IRegistrable', 'Blockly.IRegistrableField', 'Blockly.ISelectable', 'Blockly.ISelectableToolboxItem', 'Blockly.IStyleable', 'Blockly.IToolbox', 'Blockly.IToolboxItem', 'Blockly.Icon', 'Blockly.Input', 'Blockly.InsertionMarkerManager', 'Blockly.Marker', 'Blockly.MarkerManager', 'Blockly.Menu', 'Blockly.MenuItem', 'Blockly.MetricsManager', 'Blockly.Msg', 'Blockly.Mutator', 'Blockly.Names', 'Blockly.Options', 'Blockly.Procedures', 'Blockly.RenderedConnection', 'Blockly.Scrollbar', 'Blockly.ScrollbarPair', 'Blockly.ShortcutItems', 'Blockly.ShortcutRegistry', 'Blockly.TabNavigateCursor', 'Blockly.Theme', 'Blockly.ThemeManager', 'Blockly.Themes', 'Blockly.Toolbox', 'Blockly.ToolboxCategory', 'Blockly.ToolboxItem', 'Blockly.ToolboxSeparator', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.TouchGesture', 'Blockly.Trashcan', 'Blockly.VariableMap', 'Blockly.VariableModel', 'Blockly.Variables', 'Blockly.VariablesDynamic', 'Blockly.VerticalFlyout', 'Blockly.Warning', 'Blockly.WidgetDiv', 'Blockly.Workspace', 'Blockly.WorkspaceAudio', 'Blockly.WorkspaceComment', 'Blockly.WorkspaceCommentSvg', 'Blockly.WorkspaceDragSurfaceSvg', 'Blockly.WorkspaceDragger', 'Blockly.WorkspaceSvg', 'Blockly.Xml', 'Blockly.ZoomControls', 'Blockly.blockAnimations', 'Blockly.blockRendering', 'Blockly.blocks', 'Blockly.browserEvents', 'Blockly.bumpObjects', 'Blockly.clipboard', 'Blockly.common', 'Blockly.constants', 'Blockly.dialog', 'Blockly.fieldRegistry', 'Blockly.geras', 'Blockly.inject', 'Blockly.inputTypes', 'Blockly.internalConstants', 'Blockly.minimalist', 'Blockly.registry', 'Blockly.serialization.ISerializer', 'Blockly.serialization.blocks', 'Blockly.serialization.exceptions', 'Blockly.serialization.priorities', 'Blockly.serialization.registry', 'Blockly.serialization.variables', 'Blockly.serialization.workspaces', 'Blockly.thrasos', 'Blockly.uiPosition', 'Blockly.utils', 'Blockly.utils.colour', 'Blockly.utils.deprecation', 'Blockly.utils.global', 'Blockly.utils.svgMath', 'Blockly.utils.toolbox', 'Blockly.zelos'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/block_svg.js', ['Blockly.BlockSvg'], ['Blockly.ASTNode', 'Blockly.Block', 'Blockly.ConnectionType', 'Blockly.ContextMenu', 'Blockly.ContextMenuRegistry', 'Blockly.Events.BlockMove', 'Blockly.Events.Selected', 'Blockly.Events.utils', 'Blockly.FieldLabel', 'Blockly.IASTNodeLocationSvg', 'Blockly.IBoundedElement', 'Blockly.ICopyable', 'Blockly.IDraggable', 'Blockly.MarkerManager', 'Blockly.Msg', 'Blockly.RenderedConnection', 'Blockly.TabNavigateCursor', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.blockAnimations', 'Blockly.browserEvents', 'Blockly.common', 'Blockly.config', 'Blockly.constants', 'Blockly.internalConstants', 'Blockly.serialization.blocks', 'Blockly.utils.Coordinate', 'Blockly.utils.Rect', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.svgMath', 'Blockly.utils.userAgent'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/blockly.js', ['Blockly'], ['Blockly.ASTNode', 'Blockly.BasicCursor', 'Blockly.Block', 'Blockly.BlockDragSurfaceSvg', 'Blockly.BlockDragger', 'Blockly.BlockSvg', 'Blockly.BlocklyOptions', 'Blockly.Bubble', 'Blockly.BubbleDragger', 'Blockly.CollapsibleToolboxCategory', 'Blockly.Comment', 'Blockly.ComponentManager', 'Blockly.Connection', 'Blockly.ConnectionChecker', 'Blockly.ConnectionDB', 'Blockly.ConnectionType', 'Blockly.ContextMenu', 'Blockly.ContextMenuItems', 'Blockly.ContextMenuRegistry', 'Blockly.Css', 'Blockly.Cursor', 'Blockly.DeleteArea', 'Blockly.DragTarget', 'Blockly.Events', 'Blockly.Events.BlockCreate', 'Blockly.Events.FinishedLoading', 'Blockly.Events.Ui', 'Blockly.Events.UiBase', 'Blockly.Events.VarCreate', 'Blockly.Extensions', 'Blockly.Field', 'Blockly.FieldAngle', 'Blockly.FieldCheckbox', 'Blockly.FieldColour', 'Blockly.FieldDropdown', 'Blockly.FieldImage', 'Blockly.FieldLabel', 'Blockly.FieldLabelSerializable', 'Blockly.FieldMultilineInput', 'Blockly.FieldNumber', 'Blockly.FieldTextInput', 'Blockly.FieldVariable', 'Blockly.Flyout', 'Blockly.FlyoutButton', 'Blockly.FlyoutMetricsManager', 'Blockly.Generator', 'Blockly.Gesture', 'Blockly.Grid', 'Blockly.HorizontalFlyout', 'Blockly.IASTNodeLocation', 'Blockly.IASTNodeLocationSvg', 'Blockly.IASTNodeLocationWithBlock', 'Blockly.IAutoHideable', 'Blockly.IBlockDragger', 'Blockly.IBoundedElement', 'Blockly.IBubble', 'Blockly.ICollapsibleToolboxItem', 'Blockly.IComponent', 'Blockly.IConnectionChecker', 'Blockly.IContextMenu', 'Blockly.ICopyable', 'Blockly.IDeletable', 'Blockly.IDeleteArea', 'Blockly.IDragTarget', 'Blockly.IDraggable', 'Blockly.IFlyout', 'Blockly.IKeyboardAccessible', 'Blockly.IMetricsManager', 'Blockly.IMovable', 'Blockly.IPositionable', 'Blockly.IRegistrable', 'Blockly.IRegistrableField', 'Blockly.ISelectable', 'Blockly.ISelectableToolboxItem', 'Blockly.IStyleable', 'Blockly.IToolbox', 'Blockly.IToolboxItem', 'Blockly.Icon', 'Blockly.Input', 'Blockly.InsertionMarkerManager', 'Blockly.Marker', 'Blockly.MarkerManager', 'Blockly.Menu', 'Blockly.MenuItem', 'Blockly.MetricsManager', 'Blockly.Msg', 'Blockly.Mutator', 'Blockly.Names', 'Blockly.Options', 'Blockly.Procedures', 'Blockly.RenderedConnection', 'Blockly.Scrollbar', 'Blockly.ScrollbarPair', 'Blockly.ShortcutItems', 'Blockly.ShortcutRegistry', 'Blockly.TabNavigateCursor', 'Blockly.Theme', 'Blockly.ThemeManager', 'Blockly.Themes', 'Blockly.Toolbox', 'Blockly.ToolboxCategory', 'Blockly.ToolboxItem', 'Blockly.ToolboxSeparator', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.TouchGesture', 'Blockly.Trashcan', 'Blockly.VariableMap', 'Blockly.VariableModel', 'Blockly.Variables', 'Blockly.VariablesDynamic', 'Blockly.VerticalFlyout', 'Blockly.Warning', 'Blockly.WidgetDiv', 'Blockly.Workspace', 'Blockly.WorkspaceAudio', 'Blockly.WorkspaceComment', 'Blockly.WorkspaceCommentSvg', 'Blockly.WorkspaceDragSurfaceSvg', 'Blockly.WorkspaceDragger', 'Blockly.WorkspaceSvg', 'Blockly.Xml', 'Blockly.ZoomControls', 'Blockly.blockAnimations', 'Blockly.blockRendering', 'Blockly.blocks', 'Blockly.browserEvents', 'Blockly.bumpObjects', 'Blockly.clipboard', 'Blockly.common', 'Blockly.config', 'Blockly.constants', 'Blockly.dialog', 'Blockly.dropDownDiv', 'Blockly.fieldRegistry', 'Blockly.geras', 'Blockly.inject', 'Blockly.inputTypes', 'Blockly.internalConstants', 'Blockly.minimalist', 'Blockly.registry', 'Blockly.serialization.ISerializer', 'Blockly.serialization.blocks', 'Blockly.serialization.exceptions', 'Blockly.serialization.priorities', 'Blockly.serialization.registry', 'Blockly.serialization.variables', 'Blockly.serialization.workspaces', 'Blockly.thrasos', 'Blockly.uiPosition', 'Blockly.utils', 'Blockly.utils.colour', 'Blockly.utils.deprecation', 'Blockly.utils.global', 'Blockly.utils.svgMath', 'Blockly.utils.toolbox', 'Blockly.zelos'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/blockly_options.js', ['Blockly.BlocklyOptions'], [], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/blocks.js', ['Blockly.blocks'], [], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/browser_events.js', ['Blockly.browserEvents'], ['Blockly.Touch', 'Blockly.internalConstants', 'Blockly.utils.global', 'Blockly.utils.userAgent'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/browser_events.js', ['Blockly.browserEvents'], ['Blockly.Touch', 'Blockly.utils.global', 'Blockly.utils.userAgent'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/bubble.js', ['Blockly.Bubble'], ['Blockly.IBubble', 'Blockly.Scrollbar', 'Blockly.Touch', 'Blockly.Workspace', 'Blockly.browserEvents', 'Blockly.utils.Coordinate', 'Blockly.utils.Size', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.math', 'Blockly.utils.userAgent'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/bubble_dragger.js', ['Blockly.BubbleDragger'], ['Blockly.Bubble', 'Blockly.ComponentManager', 'Blockly.Events.CommentMove', 'Blockly.Events.utils', 'Blockly.constants', 'Blockly.utils.Coordinate', 'Blockly.utils.svgMath'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/bump_objects.js', ['Blockly.bumpObjects'], ['Blockly.Events.utils', 'Blockly.utils.math'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/clipboard.js', ['Blockly.clipboard'], ['Blockly.Events.utils'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/comment.js', ['Blockly.Comment'], ['Blockly.Bubble', 'Blockly.Css', 'Blockly.Events.BlockChange', 'Blockly.Events.BubbleOpen', 'Blockly.Events.utils', 'Blockly.Icon', 'Blockly.Warning', 'Blockly.browserEvents', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.userAgent'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/clipboard.js', ['Blockly.clipboard'], [], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/comment.js', ['Blockly.Comment'], ['Blockly.Bubble', 'Blockly.Css', 'Blockly.Events.BlockChange', 'Blockly.Events.BubbleOpen', 'Blockly.Events.utils', 'Blockly.Icon', 'Blockly.Warning', 'Blockly.browserEvents', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.userAgent'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/common.js', ['Blockly.common'], ['Blockly.blocks'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/component_manager.js', ['Blockly.ComponentManager'], ['Blockly.utils.array'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/config.js', ['Blockly.config'], [], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/connection.js', ['Blockly.Connection'], ['Blockly.ConnectionType', 'Blockly.Events.BlockMove', 'Blockly.Events.utils', 'Blockly.IASTNodeLocationWithBlock', 'Blockly.Xml', 'Blockly.constants', 'Blockly.serialization.blocks'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/connection_checker.js', ['Blockly.ConnectionChecker'], ['Blockly.Connection', 'Blockly.ConnectionType', 'Blockly.IConnectionChecker', 'Blockly.common', 'Blockly.internalConstants', 'Blockly.registry'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/connection_db.js', ['Blockly.ConnectionDB'], ['Blockly.ConnectionType', 'Blockly.constants'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/connection_type.js', ['Blockly.ConnectionType'], [], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/constants.js', ['Blockly.constants'], [], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/contextmenu.js', ['Blockly.ContextMenu'], ['Blockly.Events.BlockCreate', 'Blockly.Events.utils', 'Blockly.Menu', 'Blockly.MenuItem', 'Blockly.Msg', 'Blockly.WidgetDiv', 'Blockly.Xml', 'Blockly.browserEvents', 'Blockly.clipboard', 'Blockly.internalConstants', 'Blockly.utils.Coordinate', 'Blockly.utils.Rect', 'Blockly.utils.aria', 'Blockly.utils.deprecation', 'Blockly.utils.dom', 'Blockly.utils.svgMath', 'Blockly.utils.userAgent'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/contextmenu.js', ['Blockly.ContextMenu'], ['Blockly.Events.BlockCreate', 'Blockly.Events.utils', 'Blockly.Menu', 'Blockly.MenuItem', 'Blockly.Msg', 'Blockly.WidgetDiv', 'Blockly.Xml', 'Blockly.browserEvents', 'Blockly.clipboard', 'Blockly.config', 'Blockly.utils.Coordinate', 'Blockly.utils.Rect', 'Blockly.utils.aria', 'Blockly.utils.deprecation', 'Blockly.utils.dom', 'Blockly.utils.svgMath', 'Blockly.utils.userAgent'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/contextmenu_items.js', ['Blockly.ContextMenuItems'], ['Blockly.ContextMenuRegistry', 'Blockly.Events', 'Blockly.Events.utils', 'Blockly.Msg', 'Blockly.clipboard', 'Blockly.dialog', 'Blockly.inputTypes', 'Blockly.utils.idGenerator', 'Blockly.utils.userAgent'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/contextmenu_registry.js', ['Blockly.ContextMenuRegistry'], [], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/css.js', ['Blockly.Css'], ['Blockly.utils.deprecation'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/delete_area.js', ['Blockly.DeleteArea'], ['Blockly.BlockSvg', 'Blockly.DragTarget', 'Blockly.IDeleteArea', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/delete_area.js', ['Blockly.DeleteArea'], ['Blockly.BlockSvg', 'Blockly.DragTarget', 'Blockly.IDeleteArea'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/dialog.js', ['Blockly.dialog'], [], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/drag_target.js', ['Blockly.DragTarget'], ['Blockly.IDragTarget'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/dropdowndiv.js', ['Blockly.DropDownDiv'], ['Blockly.common', 'Blockly.utils.Rect', 'Blockly.utils.dom', 'Blockly.utils.math', 'Blockly.utils.style'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/dropdowndiv.js', ['Blockly.dropDownDiv'], ['Blockly.common', 'Blockly.utils.Rect', 'Blockly.utils.dom', 'Blockly.utils.math', 'Blockly.utils.style'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/events/events.js', ['Blockly.Events'], ['Blockly.Events.Abstract', 'Blockly.Events.BlockBase', 'Blockly.Events.BlockChange', 'Blockly.Events.BlockCreate', 'Blockly.Events.BlockDelete', 'Blockly.Events.BlockDrag', 'Blockly.Events.BlockMove', 'Blockly.Events.BubbleOpen', 'Blockly.Events.Click', 'Blockly.Events.CommentBase', 'Blockly.Events.CommentChange', 'Blockly.Events.CommentCreate', 'Blockly.Events.CommentDelete', 'Blockly.Events.CommentMove', 'Blockly.Events.FinishedLoading', 'Blockly.Events.MarkerMove', 'Blockly.Events.Selected', 'Blockly.Events.ThemeChange', 'Blockly.Events.ToolboxItemSelect', 'Blockly.Events.TrashcanOpen', 'Blockly.Events.Ui', 'Blockly.Events.UiBase', 'Blockly.Events.VarBase', 'Blockly.Events.VarCreate', 'Blockly.Events.VarDelete', 'Blockly.Events.VarRename', 'Blockly.Events.ViewportChange', 'Blockly.Events.utils', 'Blockly.utils.deprecation'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/events/events_abstract.js', ['Blockly.Events.Abstract'], ['Blockly.Events.utils'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/events/events_block_base.js', ['Blockly.Events.BlockBase'], ['Blockly.Events.Abstract', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/events/events_block_change.js', ['Blockly.Events.BlockChange'], ['Blockly.Events.BlockBase', 'Blockly.Events.utils', 'Blockly.Xml', 'Blockly.registry', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/events/events_block_create.js', ['Blockly.Events.BlockCreate'], ['Blockly.Events.BlockBase', 'Blockly.Events.utils', 'Blockly.Xml', 'Blockly.registry', 'Blockly.serialization.blocks', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/events/events_block_delete.js', ['Blockly.Events.BlockDelete'], ['Blockly.Events.BlockBase', 'Blockly.Events.utils', 'Blockly.Xml', 'Blockly.registry', 'Blockly.serialization.blocks', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/events/events_block_drag.js', ['Blockly.Events.BlockDrag'], ['Blockly.Events.UiBase', 'Blockly.Events.utils', 'Blockly.registry', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/events/events_block_move.js', ['Blockly.Events.BlockMove'], ['Blockly.ConnectionType', 'Blockly.Events.BlockBase', 'Blockly.Events.utils', 'Blockly.registry', 'Blockly.utils.Coordinate', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/events/events_bubble_open.js', ['Blockly.Events.BubbleOpen'], ['Blockly.Events.UiBase', 'Blockly.Events.utils', 'Blockly.registry', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/events/events_click.js', ['Blockly.Events.Click'], ['Blockly.Events.UiBase', 'Blockly.Events.utils', 'Blockly.registry', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/events/events_comment_base.js', ['Blockly.Events.CommentBase'], ['Blockly.Events.Abstract', 'Blockly.Events.utils', 'Blockly.Xml', 'Blockly.utils.object', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/events/events_comment_change.js', ['Blockly.Events.CommentChange'], ['Blockly.Events.CommentBase', 'Blockly.Events.utils', 'Blockly.registry', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/events/events_comment_create.js', ['Blockly.Events.CommentCreate'], ['Blockly.Events.CommentBase', 'Blockly.Events.utils', 'Blockly.Xml', 'Blockly.registry', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/events/events_comment_delete.js', ['Blockly.Events.CommentDelete'], ['Blockly.Events.CommentBase', 'Blockly.Events.utils', 'Blockly.registry', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/events/events_comment_move.js', ['Blockly.Events.CommentMove'], ['Blockly.Events.CommentBase', 'Blockly.Events.utils', 'Blockly.registry', 'Blockly.utils.Coordinate', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/events/events_marker_move.js', ['Blockly.Events.MarkerMove'], ['Blockly.ASTNode', 'Blockly.Events.UiBase', 'Blockly.Events.utils', 'Blockly.registry', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/events/events_selected.js', ['Blockly.Events.Selected'], ['Blockly.Events.UiBase', 'Blockly.Events.utils', 'Blockly.registry', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/events/events_theme_change.js', ['Blockly.Events.ThemeChange'], ['Blockly.Events.UiBase', 'Blockly.Events.utils', 'Blockly.registry', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/events/events_toolbox_item_select.js', ['Blockly.Events.ToolboxItemSelect'], ['Blockly.Events.UiBase', 'Blockly.Events.utils', 'Blockly.registry', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/events/events_trashcan_open.js', ['Blockly.Events.TrashcanOpen'], ['Blockly.Events.UiBase', 'Blockly.Events.utils', 'Blockly.registry', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/events/events_ui.js', ['Blockly.Events.Ui'], ['Blockly.Events.UiBase', 'Blockly.Events.utils', 'Blockly.registry', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/events/events_ui_base.js', ['Blockly.Events.UiBase'], ['Blockly.Events.Abstract', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/events/events_var_base.js', ['Blockly.Events.VarBase'], ['Blockly.Events.Abstract', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/events/events_var_create.js', ['Blockly.Events.VarCreate'], ['Blockly.Events.VarBase', 'Blockly.Events.utils', 'Blockly.registry', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/events/events_var_delete.js', ['Blockly.Events.VarDelete'], ['Blockly.Events.VarBase', 'Blockly.Events.utils', 'Blockly.registry', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/events/events_var_rename.js', ['Blockly.Events.VarRename'], ['Blockly.Events.VarBase', 'Blockly.Events.utils', 'Blockly.registry', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/events/events_viewport.js', ['Blockly.Events.ViewportChange'], ['Blockly.Events.UiBase', 'Blockly.Events.utils', 'Blockly.registry', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/events/events_block_base.js', ['Blockly.Events.BlockBase'], ['Blockly.Events.Abstract'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/events/events_block_change.js', ['Blockly.Events.BlockChange'], ['Blockly.Events.BlockBase', 'Blockly.Events.utils', 'Blockly.Xml', 'Blockly.registry'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/events/events_block_create.js', ['Blockly.Events.BlockCreate'], ['Blockly.Events.BlockBase', 'Blockly.Events.utils', 'Blockly.Xml', 'Blockly.registry', 'Blockly.serialization.blocks'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/events/events_block_delete.js', ['Blockly.Events.BlockDelete'], ['Blockly.Events.BlockBase', 'Blockly.Events.utils', 'Blockly.Xml', 'Blockly.registry', 'Blockly.serialization.blocks'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/events/events_block_drag.js', ['Blockly.Events.BlockDrag'], ['Blockly.Events.UiBase', 'Blockly.Events.utils', 'Blockly.registry'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/events/events_block_move.js', ['Blockly.Events.BlockMove'], ['Blockly.ConnectionType', 'Blockly.Events.BlockBase', 'Blockly.Events.utils', 'Blockly.registry', 'Blockly.utils.Coordinate'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/events/events_bubble_open.js', ['Blockly.Events.BubbleOpen'], ['Blockly.Events.UiBase', 'Blockly.Events.utils', 'Blockly.registry'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/events/events_click.js', ['Blockly.Events.Click'], ['Blockly.Events.UiBase', 'Blockly.Events.utils', 'Blockly.registry'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/events/events_comment_base.js', ['Blockly.Events.CommentBase'], ['Blockly.Events.Abstract', 'Blockly.Events.utils', 'Blockly.Xml', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/events/events_comment_change.js', ['Blockly.Events.CommentChange'], ['Blockly.Events.CommentBase', 'Blockly.Events.utils', 'Blockly.registry'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/events/events_comment_create.js', ['Blockly.Events.CommentCreate'], ['Blockly.Events.CommentBase', 'Blockly.Events.utils', 'Blockly.Xml', 'Blockly.registry'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/events/events_comment_delete.js', ['Blockly.Events.CommentDelete'], ['Blockly.Events.CommentBase', 'Blockly.Events.utils', 'Blockly.registry'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/events/events_comment_move.js', ['Blockly.Events.CommentMove'], ['Blockly.Events.CommentBase', 'Blockly.Events.utils', 'Blockly.registry', 'Blockly.utils.Coordinate'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/events/events_marker_move.js', ['Blockly.Events.MarkerMove'], ['Blockly.ASTNode', 'Blockly.Events.UiBase', 'Blockly.Events.utils', 'Blockly.registry'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/events/events_selected.js', ['Blockly.Events.Selected'], ['Blockly.Events.UiBase', 'Blockly.Events.utils', 'Blockly.registry'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/events/events_theme_change.js', ['Blockly.Events.ThemeChange'], ['Blockly.Events.UiBase', 'Blockly.Events.utils', 'Blockly.registry'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/events/events_toolbox_item_select.js', ['Blockly.Events.ToolboxItemSelect'], ['Blockly.Events.UiBase', 'Blockly.Events.utils', 'Blockly.registry'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/events/events_trashcan_open.js', ['Blockly.Events.TrashcanOpen'], ['Blockly.Events.UiBase', 'Blockly.Events.utils', 'Blockly.registry'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/events/events_ui.js', ['Blockly.Events.Ui'], ['Blockly.Events.UiBase', 'Blockly.Events.utils', 'Blockly.registry'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/events/events_ui_base.js', ['Blockly.Events.UiBase'], ['Blockly.Events.Abstract'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/events/events_var_base.js', ['Blockly.Events.VarBase'], ['Blockly.Events.Abstract'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/events/events_var_create.js', ['Blockly.Events.VarCreate'], ['Blockly.Events.VarBase', 'Blockly.Events.utils', 'Blockly.registry'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/events/events_var_delete.js', ['Blockly.Events.VarDelete'], ['Blockly.Events.VarBase', 'Blockly.Events.utils', 'Blockly.registry'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/events/events_var_rename.js', ['Blockly.Events.VarRename'], ['Blockly.Events.VarBase', 'Blockly.Events.utils', 'Blockly.registry'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/events/events_viewport.js', ['Blockly.Events.ViewportChange'], ['Blockly.Events.UiBase', 'Blockly.Events.utils', 'Blockly.registry'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/events/utils.js', ['Blockly.Events.utils'], ['Blockly.registry', 'Blockly.utils.idGenerator'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/events/workspace_events.js', ['Blockly.Events.FinishedLoading'], ['Blockly.Events.Abstract', 'Blockly.Events.utils', 'Blockly.registry', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/extensions.js', ['Blockly.Extensions'], ['Blockly.utils.parsing'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/field.js', ['Blockly.Field'], ['Blockly.DropDownDiv', 'Blockly.Events.BlockChange', 'Blockly.Events.utils', 'Blockly.Gesture', 'Blockly.IASTNodeLocationSvg', 'Blockly.IASTNodeLocationWithBlock', 'Blockly.IKeyboardAccessible', 'Blockly.IRegistrable', 'Blockly.MarkerManager', 'Blockly.Tooltip', 'Blockly.WidgetDiv', 'Blockly.Xml', 'Blockly.browserEvents', 'Blockly.utils.Rect', 'Blockly.utils.Size', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.parsing', 'Blockly.utils.style', 'Blockly.utils.userAgent', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/field_angle.js', ['Blockly.FieldAngle'], ['Blockly.Css', 'Blockly.DropDownDiv', 'Blockly.FieldTextInput', 'Blockly.WidgetDiv', 'Blockly.browserEvents', 'Blockly.fieldRegistry', 'Blockly.utils.KeyCodes', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.math', 'Blockly.utils.object', 'Blockly.utils.userAgent'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/field_checkbox.js', ['Blockly.FieldCheckbox'], ['Blockly.Events.BlockChange', 'Blockly.Field', 'Blockly.fieldRegistry', 'Blockly.utils.dom', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/field_colour.js', ['Blockly.FieldColour'], ['Blockly.Css', 'Blockly.DropDownDiv', 'Blockly.Events.BlockChange', 'Blockly.Field', 'Blockly.browserEvents', 'Blockly.fieldRegistry', 'Blockly.utils.KeyCodes', 'Blockly.utils.Size', 'Blockly.utils.aria', 'Blockly.utils.colour', 'Blockly.utils.dom', 'Blockly.utils.idGenerator', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/field_dropdown.js', ['Blockly.FieldDropdown'], ['Blockly.DropDownDiv', 'Blockly.Field', 'Blockly.Menu', 'Blockly.MenuItem', 'Blockly.fieldRegistry', 'Blockly.utils.Coordinate', 'Blockly.utils.Svg', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.parsing', 'Blockly.utils.string', 'Blockly.utils.userAgent'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/field_image.js', ['Blockly.FieldImage'], ['Blockly.Field', 'Blockly.fieldRegistry', 'Blockly.utils.Size', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.parsing'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/field_label.js', ['Blockly.FieldLabel'], ['Blockly.Field', 'Blockly.fieldRegistry', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.parsing'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/field_label_serializable.js', ['Blockly.FieldLabelSerializable'], ['Blockly.FieldLabel', 'Blockly.fieldRegistry', 'Blockly.utils.object', 'Blockly.utils.parsing'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/field_multilineinput.js', ['Blockly.FieldMultilineInput'], ['Blockly.Css', 'Blockly.Field', 'Blockly.FieldTextInput', 'Blockly.WidgetDiv', 'Blockly.fieldRegistry', 'Blockly.utils.KeyCodes', 'Blockly.utils.Svg', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.parsing', 'Blockly.utils.userAgent'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/field_number.js', ['Blockly.FieldNumber'], ['Blockly.FieldTextInput', 'Blockly.fieldRegistry', 'Blockly.utils.aria', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/events/workspace_events.js', ['Blockly.Events.FinishedLoading'], ['Blockly.Events.Abstract', 'Blockly.Events.utils', 'Blockly.registry'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/extensions.js', ['Blockly.Extensions'], ['Blockly.FieldDropdown', 'Blockly.utils.parsing'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/field.js', ['Blockly.Field'], ['Blockly.Events.BlockChange', 'Blockly.Events.utils', 'Blockly.Gesture', 'Blockly.IASTNodeLocationSvg', 'Blockly.IASTNodeLocationWithBlock', 'Blockly.IKeyboardAccessible', 'Blockly.IRegistrable', 'Blockly.MarkerManager', 'Blockly.Tooltip', 'Blockly.WidgetDiv', 'Blockly.Xml', 'Blockly.browserEvents', 'Blockly.dropDownDiv', 'Blockly.utils.Rect', 'Blockly.utils.Sentinel', 'Blockly.utils.Size', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.parsing', 'Blockly.utils.style', 'Blockly.utils.userAgent', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/field_angle.js', ['Blockly.FieldAngle'], ['Blockly.Css', 'Blockly.Field', 'Blockly.FieldTextInput', 'Blockly.WidgetDiv', 'Blockly.browserEvents', 'Blockly.dropDownDiv', 'Blockly.fieldRegistry', 'Blockly.utils.KeyCodes', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.math', 'Blockly.utils.userAgent'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/field_checkbox.js', ['Blockly.FieldCheckbox'], ['Blockly.Events.BlockChange', 'Blockly.Field', 'Blockly.fieldRegistry', 'Blockly.utils.dom'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/field_colour.js', ['Blockly.FieldColour'], ['Blockly.Css', 'Blockly.Events.BlockChange', 'Blockly.Field', 'Blockly.browserEvents', 'Blockly.dropDownDiv', 'Blockly.fieldRegistry', 'Blockly.utils.KeyCodes', 'Blockly.utils.Size', 'Blockly.utils.aria', 'Blockly.utils.colour', 'Blockly.utils.dom', 'Blockly.utils.idGenerator'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/field_dropdown.js', ['Blockly.FieldDropdown'], ['Blockly.Field', 'Blockly.Menu', 'Blockly.MenuItem', 'Blockly.dropDownDiv', 'Blockly.fieldRegistry', 'Blockly.utils.Coordinate', 'Blockly.utils.Svg', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.parsing', 'Blockly.utils.string', 'Blockly.utils.userAgent'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/field_image.js', ['Blockly.FieldImage'], ['Blockly.Field', 'Blockly.fieldRegistry', 'Blockly.utils.Size', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.parsing'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/field_label.js', ['Blockly.FieldLabel'], ['Blockly.Field', 'Blockly.fieldRegistry', 'Blockly.utils.dom', 'Blockly.utils.parsing'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/field_label_serializable.js', ['Blockly.FieldLabelSerializable'], ['Blockly.FieldLabel', 'Blockly.fieldRegistry', 'Blockly.utils.parsing'], {'lang': 'es_2020', 'module': 'goog'}); +goog.addDependency('../../core/field_multilineinput.js', ['Blockly.FieldMultilineInput'], ['Blockly.Css', 'Blockly.Field', 'Blockly.FieldTextInput', 'Blockly.WidgetDiv', 'Blockly.fieldRegistry', 'Blockly.utils.KeyCodes', 'Blockly.utils.Svg', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.parsing', 'Blockly.utils.userAgent'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/field_number.js', ['Blockly.FieldNumber'], ['Blockly.Field', 'Blockly.FieldTextInput', 'Blockly.fieldRegistry', 'Blockly.utils.aria'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/field_registry.js', ['Blockly.fieldRegistry'], ['Blockly.registry'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/field_textinput.js', ['Blockly.FieldTextInput'], ['Blockly.DropDownDiv', 'Blockly.Events.BlockChange', 'Blockly.Events.utils', 'Blockly.Field', 'Blockly.Msg', 'Blockly.WidgetDiv', 'Blockly.browserEvents', 'Blockly.dialog', 'Blockly.fieldRegistry', 'Blockly.utils.Coordinate', 'Blockly.utils.KeyCodes', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.parsing', 'Blockly.utils.userAgent'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/field_variable.js', ['Blockly.FieldVariable'], ['Blockly.Events.BlockChange', 'Blockly.FieldDropdown', 'Blockly.Msg', 'Blockly.VariableModel', 'Blockly.Variables', 'Blockly.Xml', 'Blockly.fieldRegistry', 'Blockly.internalConstants', 'Blockly.utils.Size', 'Blockly.utils.object', 'Blockly.utils.parsing'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/flyout_base.js', ['Blockly.Flyout'], ['Blockly.ComponentManager', 'Blockly.DeleteArea', 'Blockly.Events.BlockCreate', 'Blockly.Events.VarCreate', 'Blockly.Events.utils', 'Blockly.FlyoutMetricsManager', 'Blockly.Gesture', 'Blockly.IFlyout', 'Blockly.ScrollbarPair', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.Variables', 'Blockly.WorkspaceSvg', 'Blockly.Xml', 'Blockly.blockRendering', 'Blockly.browserEvents', 'Blockly.common', 'Blockly.serialization.blocks', 'Blockly.utils.Coordinate', 'Blockly.utils.Rect', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.idGenerator', 'Blockly.utils.object', 'Blockly.utils.toolbox'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/field_textinput.js', ['Blockly.FieldTextInput'], ['Blockly.Events.BlockChange', 'Blockly.Events.utils', 'Blockly.Field', 'Blockly.Msg', 'Blockly.WidgetDiv', 'Blockly.browserEvents', 'Blockly.dialog', 'Blockly.dropDownDiv', 'Blockly.fieldRegistry', 'Blockly.utils.Coordinate', 'Blockly.utils.KeyCodes', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.parsing', 'Blockly.utils.userAgent'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/field_variable.js', ['Blockly.FieldVariable'], ['Blockly.Events.BlockChange', 'Blockly.Field', 'Blockly.FieldDropdown', 'Blockly.Msg', 'Blockly.VariableModel', 'Blockly.Variables', 'Blockly.Xml', 'Blockly.fieldRegistry', 'Blockly.internalConstants', 'Blockly.utils.Size', 'Blockly.utils.parsing'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/flyout_base.js', ['Blockly.Flyout'], ['Blockly.ComponentManager', 'Blockly.DeleteArea', 'Blockly.Events.BlockCreate', 'Blockly.Events.VarCreate', 'Blockly.Events.utils', 'Blockly.FlyoutMetricsManager', 'Blockly.Gesture', 'Blockly.IFlyout', 'Blockly.ScrollbarPair', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.Variables', 'Blockly.WorkspaceSvg', 'Blockly.Xml', 'Blockly.blockRendering', 'Blockly.browserEvents', 'Blockly.common', 'Blockly.serialization.blocks', 'Blockly.utils.Coordinate', 'Blockly.utils.Rect', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.idGenerator', 'Blockly.utils.toolbox'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/flyout_button.js', ['Blockly.FlyoutButton'], ['Blockly.Css', 'Blockly.browserEvents', 'Blockly.utils.Coordinate', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.parsing', 'Blockly.utils.style'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/flyout_horizontal.js', ['Blockly.HorizontalFlyout'], ['Blockly.DropDownDiv', 'Blockly.Flyout', 'Blockly.Scrollbar', 'Blockly.WidgetDiv', 'Blockly.browserEvents', 'Blockly.registry', 'Blockly.utils.Rect', 'Blockly.utils.object', 'Blockly.utils.toolbox'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/flyout_metrics_manager.js', ['Blockly.FlyoutMetricsManager'], ['Blockly.MetricsManager', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/flyout_vertical.js', ['Blockly.VerticalFlyout'], ['Blockly.Block', 'Blockly.DropDownDiv', 'Blockly.Flyout', 'Blockly.Scrollbar', 'Blockly.WidgetDiv', 'Blockly.browserEvents', 'Blockly.constants', 'Blockly.registry', 'Blockly.utils.Rect', 'Blockly.utils.object', 'Blockly.utils.toolbox'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/flyout_horizontal.js', ['Blockly.HorizontalFlyout'], ['Blockly.Flyout', 'Blockly.Scrollbar', 'Blockly.WidgetDiv', 'Blockly.browserEvents', 'Blockly.dropDownDiv', 'Blockly.registry', 'Blockly.utils.Rect', 'Blockly.utils.toolbox'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/flyout_metrics_manager.js', ['Blockly.FlyoutMetricsManager'], ['Blockly.MetricsManager'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/flyout_vertical.js', ['Blockly.VerticalFlyout'], ['Blockly.Block', 'Blockly.Flyout', 'Blockly.Scrollbar', 'Blockly.WidgetDiv', 'Blockly.browserEvents', 'Blockly.constants', 'Blockly.dropDownDiv', 'Blockly.registry', 'Blockly.utils.Rect', 'Blockly.utils.toolbox'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/generator.js', ['Blockly.Generator'], ['Blockly.Names', 'Blockly.common', 'Blockly.utils.deprecation'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/gesture.js', ['Blockly.Gesture'], ['Blockly.BlockDragger', 'Blockly.BubbleDragger', 'Blockly.Events.Click', 'Blockly.Events.utils', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.Workspace', 'Blockly.WorkspaceDragger', 'Blockly.blockAnimations', 'Blockly.browserEvents', 'Blockly.common', 'Blockly.internalConstants', 'Blockly.registry', 'Blockly.utils.Coordinate'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/gesture.js', ['Blockly.Gesture'], ['Blockly.BlockDragger', 'Blockly.BubbleDragger', 'Blockly.Events.Click', 'Blockly.Events.utils', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.Workspace', 'Blockly.WorkspaceDragger', 'Blockly.blockAnimations', 'Blockly.browserEvents', 'Blockly.common', 'Blockly.config', 'Blockly.internalConstants', 'Blockly.registry', 'Blockly.utils.Coordinate'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/grid.js', ['Blockly.Grid'], ['Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.userAgent'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/icon.js', ['Blockly.Icon'], ['Blockly.browserEvents', 'Blockly.utils.Coordinate', 'Blockly.utils.Size', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.svgMath'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/inject.js', ['Blockly.inject'], ['Blockly.BlockDragSurfaceSvg', 'Blockly.Css', 'Blockly.DropDownDiv', 'Blockly.Grid', 'Blockly.Msg', 'Blockly.Options', 'Blockly.ScrollbarPair', 'Blockly.ShortcutRegistry', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.WidgetDiv', 'Blockly.Workspace', 'Blockly.WorkspaceDragSurfaceSvg', 'Blockly.WorkspaceSvg', 'Blockly.browserEvents', 'Blockly.bumpObjects', 'Blockly.common', 'Blockly.utils.Svg', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.userAgent'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/inject.js', ['Blockly.inject'], ['Blockly.BlockDragSurfaceSvg', 'Blockly.Css', 'Blockly.Grid', 'Blockly.Msg', 'Blockly.Options', 'Blockly.ScrollbarPair', 'Blockly.ShortcutRegistry', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.WidgetDiv', 'Blockly.Workspace', 'Blockly.WorkspaceDragSurfaceSvg', 'Blockly.WorkspaceSvg', 'Blockly.browserEvents', 'Blockly.bumpObjects', 'Blockly.common', 'Blockly.dropDownDiv', 'Blockly.utils.Svg', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.userAgent'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/input.js', ['Blockly.Input'], ['Blockly.FieldLabel', 'Blockly.fieldRegistry', 'Blockly.inputTypes'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/input_types.js', ['Blockly.inputTypes'], ['Blockly.ConnectionType'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/insertion_marker_manager.js', ['Blockly.InsertionMarkerManager'], ['Blockly.ComponentManager', 'Blockly.ConnectionType', 'Blockly.Events.utils', 'Blockly.blockAnimations', 'Blockly.common', 'Blockly.constants', 'Blockly.internalConstants'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/insertion_marker_manager.js', ['Blockly.InsertionMarkerManager'], ['Blockly.ComponentManager', 'Blockly.ConnectionType', 'Blockly.Events.utils', 'Blockly.blockAnimations', 'Blockly.common', 'Blockly.config', 'Blockly.constants'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/interfaces/i_ast_node_location.js', ['Blockly.IASTNodeLocation'], [], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/interfaces/i_ast_node_location_svg.js', ['Blockly.IASTNodeLocationSvg'], ['Blockly.IASTNodeLocation'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/interfaces/i_ast_node_location_with_block.js', ['Blockly.IASTNodeLocationWithBlock'], ['Blockly.IASTNodeLocation'], {'lang': 'es6', 'module': 'goog'}); @@ -124,82 +125,82 @@ goog.addDependency('../../core/interfaces/i_toolbox.js', ['Blockly.IToolbox'], [ goog.addDependency('../../core/interfaces/i_toolbox_item.js', ['Blockly.IToolboxItem'], [], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/internal_constants.js', ['Blockly.internalConstants'], ['Blockly.ConnectionType'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/keyboard_nav/ast_node.js', ['Blockly.ASTNode'], ['Blockly.ConnectionType', 'Blockly.utils.Coordinate'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/keyboard_nav/basic_cursor.js', ['Blockly.BasicCursor'], ['Blockly.ASTNode', 'Blockly.Cursor', 'Blockly.registry', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/keyboard_nav/cursor.js', ['Blockly.Cursor'], ['Blockly.ASTNode', 'Blockly.Marker', 'Blockly.registry', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/keyboard_nav/basic_cursor.js', ['Blockly.BasicCursor'], ['Blockly.ASTNode', 'Blockly.Cursor', 'Blockly.registry'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/keyboard_nav/cursor.js', ['Blockly.Cursor'], ['Blockly.ASTNode', 'Blockly.Marker', 'Blockly.registry'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/keyboard_nav/marker.js', ['Blockly.Marker'], [], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/keyboard_nav/tab_navigate_cursor.js', ['Blockly.TabNavigateCursor'], ['Blockly.ASTNode', 'Blockly.BasicCursor', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/keyboard_nav/tab_navigate_cursor.js', ['Blockly.TabNavigateCursor'], ['Blockly.ASTNode', 'Blockly.BasicCursor'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/marker_manager.js', ['Blockly.MarkerManager'], [], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/menu.js', ['Blockly.Menu'], ['Blockly.browserEvents', 'Blockly.utils.Coordinate', 'Blockly.utils.KeyCodes', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.style'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/menuitem.js', ['Blockly.MenuItem'], ['Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.idGenerator'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/metrics_manager.js', ['Blockly.MetricsManager'], ['Blockly.IMetricsManager', 'Blockly.registry', 'Blockly.utils.Size', 'Blockly.utils.toolbox'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/msg.js', ['Blockly.Msg'], [], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/mutator.js', ['Blockly.Mutator'], ['Blockly.Bubble', 'Blockly.Events.BlockChange', 'Blockly.Events.BubbleOpen', 'Blockly.Events.utils', 'Blockly.Icon', 'Blockly.Options', 'Blockly.WorkspaceSvg', 'Blockly.internalConstants', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.toolbox', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/mutator.js', ['Blockly.Mutator'], ['Blockly.Bubble', 'Blockly.Events.BlockChange', 'Blockly.Events.BubbleOpen', 'Blockly.Events.utils', 'Blockly.Icon', 'Blockly.Options', 'Blockly.WorkspaceSvg', 'Blockly.config', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.toolbox', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/names.js', ['Blockly.Names'], ['Blockly.Msg', 'Blockly.Variables'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/options.js', ['Blockly.Options'], ['Blockly.Theme', 'Blockly.Themes.Classic', 'Blockly.registry', 'Blockly.utils.idGenerator', 'Blockly.utils.toolbox'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/positionable_helpers.js', ['Blockly.uiPosition'], ['Blockly.Scrollbar', 'Blockly.utils.Rect', 'Blockly.utils.toolbox'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/procedures.js', ['Blockly.Procedures'], ['Blockly.Events.BlockChange', 'Blockly.Events.utils', 'Blockly.Msg', 'Blockly.Names', 'Blockly.Variables', 'Blockly.Workspace', 'Blockly.Xml', 'Blockly.blocks', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/registry.js', ['Blockly.registry'], [], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/rendered_connection.js', ['Blockly.RenderedConnection'], ['Blockly.Connection', 'Blockly.ConnectionType', 'Blockly.Events.utils', 'Blockly.common', 'Blockly.internalConstants', 'Blockly.utils.Coordinate', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.svgMath', 'Blockly.utils.svgPaths'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/rendered_connection.js', ['Blockly.RenderedConnection'], ['Blockly.Connection', 'Blockly.ConnectionType', 'Blockly.Events.utils', 'Blockly.common', 'Blockly.config', 'Blockly.internalConstants', 'Blockly.utils.Coordinate', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.svgMath', 'Blockly.utils.svgPaths'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/renderers/common/block_rendering.js', ['Blockly.blockRendering'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.Connection', 'Blockly.blockRendering.ConstantProvider', 'Blockly.blockRendering.Debug', 'Blockly.blockRendering.Drawer', 'Blockly.blockRendering.ExternalValueInput', 'Blockly.blockRendering.Field', 'Blockly.blockRendering.Hat', 'Blockly.blockRendering.IPathObject', 'Blockly.blockRendering.Icon', 'Blockly.blockRendering.InRowSpacer', 'Blockly.blockRendering.InlineInput', 'Blockly.blockRendering.InputConnection', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.JaggedEdge', 'Blockly.blockRendering.MarkerSvg', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.NextConnection', 'Blockly.blockRendering.OutputConnection', 'Blockly.blockRendering.PathObject', 'Blockly.blockRendering.PreviousConnection', 'Blockly.blockRendering.RenderInfo', 'Blockly.blockRendering.Renderer', 'Blockly.blockRendering.RoundCorner', 'Blockly.blockRendering.Row', 'Blockly.blockRendering.SpacerRow', 'Blockly.blockRendering.SquareCorner', 'Blockly.blockRendering.StatementInput', 'Blockly.blockRendering.TopRow', 'Blockly.blockRendering.Types', 'Blockly.blockRendering.debug', 'Blockly.registry', 'Blockly.utils.deprecation'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/renderers/common/constants.js', ['Blockly.blockRendering.ConstantProvider'], ['Blockly.ConnectionType', 'Blockly.utils.Svg', 'Blockly.utils.colour', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.parsing', 'Blockly.utils.svgPaths', 'Blockly.utils.userAgent'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/common/debug.js', ['Blockly.blockRendering.debug'], [], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/common/debugger.js', ['Blockly.blockRendering.Debug'], ['Blockly.ConnectionType', 'Blockly.FieldLabel', 'Blockly.blockRendering.Types', 'Blockly.utils.Svg', 'Blockly.utils.dom'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/common/drawer.js', ['Blockly.blockRendering.Drawer'], ['Blockly.blockRendering.Types', 'Blockly.blockRendering.debug', 'Blockly.utils.svgPaths'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/common/debug.js', ['Blockly.blockRendering.debug'], ['Blockly.utils.deprecation'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/common/debugger.js', ['Blockly.blockRendering.Debug'], ['Blockly.ConnectionType', 'Blockly.FieldLabel', 'Blockly.blockRendering.Field', 'Blockly.blockRendering.InputConnection', 'Blockly.blockRendering.Types', 'Blockly.utils.Svg', 'Blockly.utils.dom'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/common/drawer.js', ['Blockly.blockRendering.Drawer'], ['Blockly.blockRendering.Connection', 'Blockly.blockRendering.Types', 'Blockly.blockRendering.debug', 'Blockly.utils.svgPaths'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/renderers/common/i_path_object.js', ['Blockly.blockRendering.IPathObject'], [], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/renderers/common/info.js', ['Blockly.blockRendering.RenderInfo'], ['Blockly.Input', 'Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.ExternalValueInput', 'Blockly.blockRendering.Field', 'Blockly.blockRendering.Hat', 'Blockly.blockRendering.Icon', 'Blockly.blockRendering.InRowSpacer', 'Blockly.blockRendering.InlineInput', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.JaggedEdge', 'Blockly.blockRendering.NextConnection', 'Blockly.blockRendering.OutputConnection', 'Blockly.blockRendering.PreviousConnection', 'Blockly.blockRendering.RoundCorner', 'Blockly.blockRendering.SpacerRow', 'Blockly.blockRendering.SquareCorner', 'Blockly.blockRendering.StatementInput', 'Blockly.blockRendering.TopRow', 'Blockly.blockRendering.Types', 'Blockly.inputTypes'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/renderers/common/marker_svg.js', ['Blockly.blockRendering.MarkerSvg'], ['Blockly.ASTNode', 'Blockly.ConnectionType', 'Blockly.Events.MarkerMove', 'Blockly.Events.utils', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.svgPaths'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/renderers/common/path_object.js', ['Blockly.blockRendering.PathObject'], ['Blockly.blockRendering.IPathObject', 'Blockly.utils.Svg', 'Blockly.utils.dom'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/renderers/common/renderer.js', ['Blockly.blockRendering.Renderer'], ['Blockly.Connection', 'Blockly.ConnectionType', 'Blockly.IRegistrable', 'Blockly.InsertionMarkerManager', 'Blockly.blockRendering.ConstantProvider', 'Blockly.blockRendering.Drawer', 'Blockly.blockRendering.MarkerSvg', 'Blockly.blockRendering.PathObject', 'Blockly.blockRendering.RenderInfo', 'Blockly.blockRendering.debug', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/geras/constants.js', ['Blockly.geras.ConstantProvider'], ['Blockly.blockRendering.ConstantProvider', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/geras/drawer.js', ['Blockly.geras.Drawer'], ['Blockly.blockRendering.Drawer', 'Blockly.blockRendering.debug', 'Blockly.geras.Highlighter', 'Blockly.utils.object', 'Blockly.utils.svgPaths'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/geras/constants.js', ['Blockly.geras.ConstantProvider'], ['Blockly.blockRendering.ConstantProvider'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/geras/drawer.js', ['Blockly.geras.Drawer'], ['Blockly.blockRendering.Drawer', 'Blockly.blockRendering.debug', 'Blockly.geras.Highlighter', 'Blockly.geras.InlineInput', 'Blockly.utils.svgPaths'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/renderers/geras/geras.js', ['Blockly.geras'], ['Blockly.geras.ConstantProvider', 'Blockly.geras.Drawer', 'Blockly.geras.HighlightConstantProvider', 'Blockly.geras.Highlighter', 'Blockly.geras.InlineInput', 'Blockly.geras.PathObject', 'Blockly.geras.RenderInfo', 'Blockly.geras.Renderer', 'Blockly.geras.StatementInput'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/renderers/geras/highlight_constants.js', ['Blockly.geras.HighlightConstantProvider'], ['Blockly.utils.svgPaths'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/geras/highlighter.js', ['Blockly.geras.Highlighter'], ['Blockly.blockRendering.Types', 'Blockly.utils.svgPaths'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/geras/info.js', ['Blockly.geras.RenderInfo'], ['Blockly.blockRendering.ExternalValueInput', 'Blockly.blockRendering.InRowSpacer', 'Blockly.blockRendering.RenderInfo', 'Blockly.blockRendering.Types', 'Blockly.geras.InlineInput', 'Blockly.geras.StatementInput', 'Blockly.inputTypes', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/geras/measurables/inline_input.js', ['Blockly.geras.InlineInput'], ['Blockly.blockRendering.InlineInput', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/geras/measurables/statement_input.js', ['Blockly.geras.StatementInput'], ['Blockly.blockRendering.StatementInput', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/geras/path_object.js', ['Blockly.geras.PathObject'], ['Blockly.blockRendering.PathObject', 'Blockly.utils.Svg', 'Blockly.utils.colour', 'Blockly.utils.dom', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/geras/renderer.js', ['Blockly.geras.Renderer'], ['Blockly.blockRendering', 'Blockly.blockRendering.Renderer', 'Blockly.geras.ConstantProvider', 'Blockly.geras.Drawer', 'Blockly.geras.HighlightConstantProvider', 'Blockly.geras.PathObject', 'Blockly.geras.RenderInfo', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/geras/highlighter.js', ['Blockly.geras.Highlighter'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.SpacerRow', 'Blockly.blockRendering.TopRow', 'Blockly.blockRendering.Types', 'Blockly.geras.InlineInput', 'Blockly.utils.svgPaths'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/geras/info.js', ['Blockly.geras.RenderInfo'], ['Blockly.blockRendering.ExternalValueInput', 'Blockly.blockRendering.InRowSpacer', 'Blockly.blockRendering.RenderInfo', 'Blockly.blockRendering.Types', 'Blockly.geras.InlineInput', 'Blockly.geras.StatementInput', 'Blockly.inputTypes'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/geras/measurables/inline_input.js', ['Blockly.geras.InlineInput'], ['Blockly.blockRendering.InlineInput'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/geras/measurables/statement_input.js', ['Blockly.geras.StatementInput'], ['Blockly.blockRendering.StatementInput'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/geras/path_object.js', ['Blockly.geras.PathObject'], ['Blockly.blockRendering.PathObject', 'Blockly.utils.Svg', 'Blockly.utils.colour', 'Blockly.utils.dom'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/geras/renderer.js', ['Blockly.geras.Renderer'], ['Blockly.blockRendering', 'Blockly.blockRendering.Renderer', 'Blockly.geras.ConstantProvider', 'Blockly.geras.Drawer', 'Blockly.geras.HighlightConstantProvider', 'Blockly.geras.PathObject', 'Blockly.geras.RenderInfo'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/renderers/measurables/base.js', ['Blockly.blockRendering.Measurable'], ['Blockly.blockRendering.Types'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/measurables/bottom_row.js', ['Blockly.blockRendering.BottomRow'], ['Blockly.blockRendering.Row', 'Blockly.blockRendering.Types', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/measurables/connection.js', ['Blockly.blockRendering.Connection'], ['Blockly.blockRendering.Measurable', 'Blockly.blockRendering.Types', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/measurables/external_value_input.js', ['Blockly.blockRendering.ExternalValueInput'], ['Blockly.blockRendering.InputConnection', 'Blockly.blockRendering.Types', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/measurables/field.js', ['Blockly.blockRendering.Field'], ['Blockly.blockRendering.Measurable', 'Blockly.blockRendering.Types', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/measurables/hat.js', ['Blockly.blockRendering.Hat'], ['Blockly.blockRendering.Measurable', 'Blockly.blockRendering.Types', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/measurables/icon.js', ['Blockly.blockRendering.Icon'], ['Blockly.blockRendering.Measurable', 'Blockly.blockRendering.Types', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/measurables/in_row_spacer.js', ['Blockly.blockRendering.InRowSpacer'], ['Blockly.blockRendering.Measurable', 'Blockly.blockRendering.Types', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/measurables/inline_input.js', ['Blockly.blockRendering.InlineInput'], ['Blockly.blockRendering.InputConnection', 'Blockly.blockRendering.Types', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/measurables/input_connection.js', ['Blockly.blockRendering.InputConnection'], ['Blockly.blockRendering.Connection', 'Blockly.blockRendering.Types', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/measurables/input_row.js', ['Blockly.blockRendering.InputRow'], ['Blockly.blockRendering.Row', 'Blockly.blockRendering.Types', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/measurables/jagged_edge.js', ['Blockly.blockRendering.JaggedEdge'], ['Blockly.blockRendering.Measurable', 'Blockly.blockRendering.Types', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/measurables/next_connection.js', ['Blockly.blockRendering.NextConnection'], ['Blockly.blockRendering.Connection', 'Blockly.blockRendering.Types', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/measurables/output_connection.js', ['Blockly.blockRendering.OutputConnection'], ['Blockly.blockRendering.Connection', 'Blockly.blockRendering.Types', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/measurables/previous_connection.js', ['Blockly.blockRendering.PreviousConnection'], ['Blockly.blockRendering.Connection', 'Blockly.blockRendering.Types', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/measurables/round_corner.js', ['Blockly.blockRendering.RoundCorner'], ['Blockly.blockRendering.Measurable', 'Blockly.blockRendering.Types', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/measurables/bottom_row.js', ['Blockly.blockRendering.BottomRow'], ['Blockly.blockRendering.Row', 'Blockly.blockRendering.Types'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/measurables/connection.js', ['Blockly.blockRendering.Connection'], ['Blockly.blockRendering.Measurable', 'Blockly.blockRendering.Types'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/measurables/external_value_input.js', ['Blockly.blockRendering.ExternalValueInput'], ['Blockly.blockRendering.InputConnection', 'Blockly.blockRendering.Types'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/measurables/field.js', ['Blockly.blockRendering.Field'], ['Blockly.blockRendering.Measurable', 'Blockly.blockRendering.Types'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/measurables/hat.js', ['Blockly.blockRendering.Hat'], ['Blockly.blockRendering.Measurable', 'Blockly.blockRendering.Types'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/measurables/icon.js', ['Blockly.blockRendering.Icon'], ['Blockly.blockRendering.Measurable', 'Blockly.blockRendering.Types'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/measurables/in_row_spacer.js', ['Blockly.blockRendering.InRowSpacer'], ['Blockly.blockRendering.Measurable', 'Blockly.blockRendering.Types'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/measurables/inline_input.js', ['Blockly.blockRendering.InlineInput'], ['Blockly.blockRendering.InputConnection', 'Blockly.blockRendering.Types'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/measurables/input_connection.js', ['Blockly.blockRendering.InputConnection'], ['Blockly.blockRendering.Connection', 'Blockly.blockRendering.Types'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/measurables/input_row.js', ['Blockly.blockRendering.InputRow'], ['Blockly.blockRendering.ExternalValueInput', 'Blockly.blockRendering.InputConnection', 'Blockly.blockRendering.Row', 'Blockly.blockRendering.StatementInput', 'Blockly.blockRendering.Types'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/measurables/jagged_edge.js', ['Blockly.blockRendering.JaggedEdge'], ['Blockly.blockRendering.Measurable', 'Blockly.blockRendering.Types'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/measurables/next_connection.js', ['Blockly.blockRendering.NextConnection'], ['Blockly.blockRendering.Connection', 'Blockly.blockRendering.Types'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/measurables/output_connection.js', ['Blockly.blockRendering.OutputConnection'], ['Blockly.blockRendering.Connection', 'Blockly.blockRendering.Types'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/measurables/previous_connection.js', ['Blockly.blockRendering.PreviousConnection'], ['Blockly.blockRendering.Connection', 'Blockly.blockRendering.Types'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/measurables/round_corner.js', ['Blockly.blockRendering.RoundCorner'], ['Blockly.blockRendering.Measurable', 'Blockly.blockRendering.Types'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/renderers/measurables/row.js', ['Blockly.blockRendering.Row'], ['Blockly.blockRendering.Types'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/measurables/spacer_row.js', ['Blockly.blockRendering.SpacerRow'], ['Blockly.blockRendering.InRowSpacer', 'Blockly.blockRendering.Row', 'Blockly.blockRendering.Types', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/measurables/square_corner.js', ['Blockly.blockRendering.SquareCorner'], ['Blockly.blockRendering.Measurable', 'Blockly.blockRendering.Types', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/measurables/statement_input.js', ['Blockly.blockRendering.StatementInput'], ['Blockly.blockRendering.InputConnection', 'Blockly.blockRendering.Types', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/measurables/top_row.js', ['Blockly.blockRendering.TopRow'], ['Blockly.blockRendering.Row', 'Blockly.blockRendering.Types', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/measurables/spacer_row.js', ['Blockly.blockRendering.SpacerRow'], ['Blockly.blockRendering.InRowSpacer', 'Blockly.blockRendering.Row', 'Blockly.blockRendering.Types'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/measurables/square_corner.js', ['Blockly.blockRendering.SquareCorner'], ['Blockly.blockRendering.Measurable', 'Blockly.blockRendering.Types'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/measurables/statement_input.js', ['Blockly.blockRendering.StatementInput'], ['Blockly.blockRendering.InputConnection', 'Blockly.blockRendering.Types'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/measurables/top_row.js', ['Blockly.blockRendering.TopRow'], ['Blockly.blockRendering.Hat', 'Blockly.blockRendering.Row', 'Blockly.blockRendering.Types'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/renderers/measurables/types.js', ['Blockly.blockRendering.Types'], [], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/minimalist/constants.js', ['Blockly.minimalist.ConstantProvider'], ['Blockly.blockRendering.ConstantProvider', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/minimalist/drawer.js', ['Blockly.minimalist.Drawer'], ['Blockly.blockRendering.Drawer', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/minimalist/info.js', ['Blockly.minimalist.RenderInfo'], ['Blockly.blockRendering.RenderInfo', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/minimalist/constants.js', ['Blockly.minimalist.ConstantProvider'], ['Blockly.blockRendering.ConstantProvider'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/minimalist/drawer.js', ['Blockly.minimalist.Drawer'], ['Blockly.blockRendering.Drawer'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/minimalist/info.js', ['Blockly.minimalist.RenderInfo'], ['Blockly.blockRendering.RenderInfo'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/renderers/minimalist/minimalist.js', ['Blockly.minimalist'], ['Blockly.minimalist.ConstantProvider', 'Blockly.minimalist.Drawer', 'Blockly.minimalist.RenderInfo', 'Blockly.minimalist.Renderer'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/minimalist/renderer.js', ['Blockly.minimalist.Renderer'], ['Blockly.blockRendering', 'Blockly.blockRendering.Renderer', 'Blockly.minimalist.ConstantProvider', 'Blockly.minimalist.Drawer', 'Blockly.minimalist.RenderInfo', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/thrasos/info.js', ['Blockly.thrasos.RenderInfo'], ['Blockly.blockRendering.InRowSpacer', 'Blockly.blockRendering.RenderInfo', 'Blockly.blockRendering.Types', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/thrasos/renderer.js', ['Blockly.thrasos.Renderer'], ['Blockly.blockRendering', 'Blockly.blockRendering.Renderer', 'Blockly.thrasos.RenderInfo', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/minimalist/renderer.js', ['Blockly.minimalist.Renderer'], ['Blockly.blockRendering', 'Blockly.blockRendering.Renderer', 'Blockly.minimalist.ConstantProvider', 'Blockly.minimalist.Drawer', 'Blockly.minimalist.RenderInfo'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/thrasos/info.js', ['Blockly.thrasos.RenderInfo'], ['Blockly.blockRendering.InRowSpacer', 'Blockly.blockRendering.RenderInfo', 'Blockly.blockRendering.Types'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/thrasos/renderer.js', ['Blockly.thrasos.Renderer'], ['Blockly.blockRendering', 'Blockly.blockRendering.Renderer', 'Blockly.thrasos.RenderInfo'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/renderers/thrasos/thrasos.js', ['Blockly.thrasos'], ['Blockly.thrasos.RenderInfo', 'Blockly.thrasos.Renderer'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/zelos/constants.js', ['Blockly.zelos.ConstantProvider'], ['Blockly.ConnectionType', 'Blockly.blockRendering.ConstantProvider', 'Blockly.utils.Svg', 'Blockly.utils.colour', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.svgPaths'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/zelos/drawer.js', ['Blockly.zelos.Drawer'], ['Blockly.blockRendering.Drawer', 'Blockly.blockRendering.debug', 'Blockly.utils.object', 'Blockly.utils.svgPaths'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/zelos/info.js', ['Blockly.zelos.RenderInfo'], ['Blockly.FieldImage', 'Blockly.FieldLabel', 'Blockly.FieldTextInput', 'Blockly.Input', 'Blockly.blockRendering.InRowSpacer', 'Blockly.blockRendering.RenderInfo', 'Blockly.blockRendering.Types', 'Blockly.inputTypes', 'Blockly.utils.object', 'Blockly.zelos.BottomRow', 'Blockly.zelos.RightConnectionShape', 'Blockly.zelos.StatementInput', 'Blockly.zelos.TopRow'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/zelos/marker_svg.js', ['Blockly.zelos.MarkerSvg'], ['Blockly.blockRendering.MarkerSvg', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/zelos/measurables/bottom_row.js', ['Blockly.zelos.BottomRow'], ['Blockly.blockRendering.BottomRow', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/zelos/measurables/inputs.js', ['Blockly.zelos.StatementInput'], ['Blockly.blockRendering.StatementInput', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/zelos/measurables/row_elements.js', ['Blockly.zelos.RightConnectionShape'], ['Blockly.blockRendering.Measurable', 'Blockly.blockRendering.Types', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/zelos/measurables/top_row.js', ['Blockly.zelos.TopRow'], ['Blockly.blockRendering.TopRow', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/zelos/path_object.js', ['Blockly.zelos.PathObject'], ['Blockly.blockRendering.PathObject', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/renderers/zelos/renderer.js', ['Blockly.zelos.Renderer'], ['Blockly.ConnectionType', 'Blockly.InsertionMarkerManager', 'Blockly.blockRendering', 'Blockly.blockRendering.Renderer', 'Blockly.utils.object', 'Blockly.zelos.ConstantProvider', 'Blockly.zelos.Drawer', 'Blockly.zelos.MarkerSvg', 'Blockly.zelos.PathObject', 'Blockly.zelos.RenderInfo'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/zelos/constants.js', ['Blockly.zelos.ConstantProvider'], ['Blockly.ConnectionType', 'Blockly.blockRendering.ConstantProvider', 'Blockly.utils.Svg', 'Blockly.utils.colour', 'Blockly.utils.dom', 'Blockly.utils.svgPaths'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/zelos/drawer.js', ['Blockly.zelos.Drawer'], ['Blockly.blockRendering.Drawer', 'Blockly.blockRendering.Types', 'Blockly.blockRendering.debug', 'Blockly.utils.svgPaths'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/zelos/info.js', ['Blockly.zelos.RenderInfo'], ['Blockly.FieldImage', 'Blockly.FieldLabel', 'Blockly.FieldTextInput', 'Blockly.Input', 'Blockly.blockRendering.Field', 'Blockly.blockRendering.InRowSpacer', 'Blockly.blockRendering.InputConnection', 'Blockly.blockRendering.RenderInfo', 'Blockly.blockRendering.Row', 'Blockly.blockRendering.Types', 'Blockly.inputTypes', 'Blockly.zelos.BottomRow', 'Blockly.zelos.RightConnectionShape', 'Blockly.zelos.StatementInput', 'Blockly.zelos.TopRow'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/zelos/marker_svg.js', ['Blockly.zelos.MarkerSvg'], ['Blockly.blockRendering.MarkerSvg', 'Blockly.utils.Svg', 'Blockly.utils.dom'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/zelos/measurables/bottom_row.js', ['Blockly.zelos.BottomRow'], ['Blockly.blockRendering.BottomRow'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/zelos/measurables/inputs.js', ['Blockly.zelos.StatementInput'], ['Blockly.blockRendering.StatementInput'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/zelos/measurables/row_elements.js', ['Blockly.zelos.RightConnectionShape'], ['Blockly.blockRendering.Measurable', 'Blockly.blockRendering.Types'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/zelos/measurables/top_row.js', ['Blockly.zelos.TopRow'], ['Blockly.blockRendering.TopRow'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/zelos/path_object.js', ['Blockly.zelos.PathObject'], ['Blockly.blockRendering.PathObject', 'Blockly.utils.Svg', 'Blockly.utils.dom'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/renderers/zelos/renderer.js', ['Blockly.zelos.Renderer'], ['Blockly.ConnectionType', 'Blockly.InsertionMarkerManager', 'Blockly.blockRendering', 'Blockly.blockRendering.Renderer', 'Blockly.zelos.ConstantProvider', 'Blockly.zelos.Drawer', 'Blockly.zelos.MarkerSvg', 'Blockly.zelos.PathObject', 'Blockly.zelos.RenderInfo'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/renderers/zelos/zelos.js', ['Blockly.zelos'], ['Blockly.zelos.BottomRow', 'Blockly.zelos.ConstantProvider', 'Blockly.zelos.Drawer', 'Blockly.zelos.MarkerSvg', 'Blockly.zelos.PathObject', 'Blockly.zelos.RenderInfo', 'Blockly.zelos.Renderer', 'Blockly.zelos.RightConnectionShape', 'Blockly.zelos.StatementInput', 'Blockly.zelos.TopRow'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/scrollbar.js', ['Blockly.Scrollbar'], ['Blockly.Touch', 'Blockly.browserEvents', 'Blockly.utils.Coordinate', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.svgMath'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/scrollbar_pair.js', ['Blockly.ScrollbarPair'], ['Blockly.Events.utils', 'Blockly.Scrollbar', 'Blockly.utils.Svg', 'Blockly.utils.dom'], {'lang': 'es6', 'module': 'goog'}); @@ -208,23 +209,24 @@ goog.addDependency('../../core/serialization/exceptions.js', ['Blockly.serializa goog.addDependency('../../core/serialization/priorities.js', ['Blockly.serialization.priorities'], [], {'module': 'goog'}); goog.addDependency('../../core/serialization/registry.js', ['Blockly.serialization.registry'], ['Blockly.registry'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/serialization/variables.js', ['Blockly.serialization.variables'], ['Blockly.serialization.ISerializer', 'Blockly.serialization.priorities', 'Blockly.serialization.registry'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/serialization/workspaces.js', ['Blockly.serialization.workspaces'], ['Blockly.Events.utils', 'Blockly.Workspace', 'Blockly.registry', 'Blockly.utils.dom'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/serialization/workspaces.js', ['Blockly.serialization.workspaces'], ['Blockly.Events.utils', 'Blockly.Workspace', 'Blockly.WorkspaceSvg', 'Blockly.registry', 'Blockly.utils.dom'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/shortcut_items.js', ['Blockly.ShortcutItems'], ['Blockly.Gesture', 'Blockly.ShortcutRegistry', 'Blockly.clipboard', 'Blockly.common', 'Blockly.utils.KeyCodes'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/shortcut_registry.js', ['Blockly.ShortcutRegistry'], ['Blockly.utils.KeyCodes', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/sprites.js', ['Blockly.sprite'], [], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/theme.js', ['Blockly.Theme'], ['Blockly.registry', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/theme/classic.js', ['Blockly.Themes.Classic'], ['Blockly.Theme'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/theme/themes.js', ['Blockly.Themes'], ['Blockly.Themes.Classic', 'Blockly.Themes.Zelos'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/theme/zelos.js', ['Blockly.Themes.Zelos'], ['Blockly.Theme'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/theme_manager.js', ['Blockly.ThemeManager'], ['Blockly.utils.array', 'Blockly.utils.dom'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/toolbox/category.js', ['Blockly.ToolboxCategory'], ['Blockly.Css', 'Blockly.ISelectableToolboxItem', 'Blockly.ToolboxItem', 'Blockly.registry', 'Blockly.utils.aria', 'Blockly.utils.colour', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.parsing', 'Blockly.utils.toolbox'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/toolbox/collapsible_category.js', ['Blockly.CollapsibleToolboxCategory'], ['Blockly.ICollapsibleToolboxItem', 'Blockly.ToolboxCategory', 'Blockly.ToolboxSeparator', 'Blockly.registry', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.toolbox'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/toolbox/collapsible_category.js', ['Blockly.CollapsibleToolboxCategory'], ['Blockly.ICollapsibleToolboxItem', 'Blockly.ToolboxCategory', 'Blockly.ToolboxSeparator', 'Blockly.registry', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.toolbox'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/toolbox/separator.js', ['Blockly.ToolboxSeparator'], ['Blockly.Css', 'Blockly.ToolboxItem', 'Blockly.registry', 'Blockly.utils.dom', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/toolbox/toolbox.js', ['Blockly.Toolbox'], ['Blockly.BlockSvg', 'Blockly.CollapsibleToolboxCategory', 'Blockly.ComponentManager', 'Blockly.Css', 'Blockly.DeleteArea', 'Blockly.Events.ToolboxItemSelect', 'Blockly.Events.utils', 'Blockly.IAutoHideable', 'Blockly.IKeyboardAccessible', 'Blockly.IStyleable', 'Blockly.IToolbox', 'Blockly.Options', 'Blockly.Touch', 'Blockly.browserEvents', 'Blockly.common', 'Blockly.registry', 'Blockly.utils.KeyCodes', 'Blockly.utils.Rect', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.toolbox'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/toolbox/toolbox.js', ['Blockly.Toolbox'], ['Blockly.BlockSvg', 'Blockly.CollapsibleToolboxCategory', 'Blockly.ComponentManager', 'Blockly.Css', 'Blockly.DeleteArea', 'Blockly.Events.ToolboxItemSelect', 'Blockly.Events.utils', 'Blockly.IAutoHideable', 'Blockly.IKeyboardAccessible', 'Blockly.IStyleable', 'Blockly.IToolbox', 'Blockly.Options', 'Blockly.Touch', 'Blockly.browserEvents', 'Blockly.common', 'Blockly.registry', 'Blockly.utils.KeyCodes', 'Blockly.utils.Rect', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.toolbox'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/toolbox/toolbox_item.js', ['Blockly.ToolboxItem'], ['Blockly.IToolboxItem', 'Blockly.utils.idGenerator'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/tooltip.js', ['Blockly.Tooltip'], ['Blockly.browserEvents', 'Blockly.common', 'Blockly.utils.deprecation', 'Blockly.utils.string'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/touch.js', ['Blockly.Touch'], ['Blockly.internalConstants', 'Blockly.utils.global', 'Blockly.utils.string'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/touch_gesture.js', ['Blockly.TouchGesture'], ['Blockly.Gesture', 'Blockly.Touch', 'Blockly.browserEvents', 'Blockly.utils.Coordinate', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/trashcan.js', ['Blockly.Trashcan'], ['Blockly.ComponentManager', 'Blockly.DeleteArea', 'Blockly.Events.TrashcanOpen', 'Blockly.Events.utils', 'Blockly.IAutoHideable', 'Blockly.IPositionable', 'Blockly.Options', 'Blockly.browserEvents', 'Blockly.internalConstants', 'Blockly.registry', 'Blockly.uiPosition', 'Blockly.utils.Rect', 'Blockly.utils.Size', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.toolbox'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/touch.js', ['Blockly.Touch'], ['Blockly.utils.global', 'Blockly.utils.string'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/touch_gesture.js', ['Blockly.TouchGesture'], ['Blockly.Gesture', 'Blockly.Touch', 'Blockly.browserEvents', 'Blockly.utils.Coordinate'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/trashcan.js', ['Blockly.Trashcan'], ['Blockly.ComponentManager', 'Blockly.DeleteArea', 'Blockly.Events.TrashcanOpen', 'Blockly.Events.utils', 'Blockly.IAutoHideable', 'Blockly.IPositionable', 'Blockly.Options', 'Blockly.browserEvents', 'Blockly.registry', 'Blockly.sprite', 'Blockly.uiPosition', 'Blockly.utils.Rect', 'Blockly.utils.Size', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.toolbox'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/utils.js', ['Blockly.utils'], ['Blockly.Extensions', 'Blockly.browserEvents', 'Blockly.common', 'Blockly.utils.Coordinate', 'Blockly.utils.KeyCodes', 'Blockly.utils.Metrics', 'Blockly.utils.Rect', 'Blockly.utils.Size', 'Blockly.utils.Svg', 'Blockly.utils.aria', 'Blockly.utils.array', 'Blockly.utils.colour', 'Blockly.utils.deprecation', 'Blockly.utils.dom', 'Blockly.utils.global', 'Blockly.utils.idGenerator', 'Blockly.utils.math', 'Blockly.utils.object', 'Blockly.utils.parsing', 'Blockly.utils.string', 'Blockly.utils.style', 'Blockly.utils.svgMath', 'Blockly.utils.svgPaths', 'Blockly.utils.toolbox', 'Blockly.utils.userAgent', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/utils/aria.js', ['Blockly.utils.aria'], [], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/utils/array.js', ['Blockly.utils.array'], [], {'lang': 'es6', 'module': 'goog'}); @@ -240,6 +242,7 @@ goog.addDependency('../../core/utils/metrics.js', ['Blockly.utils.Metrics'], [], goog.addDependency('../../core/utils/object.js', ['Blockly.utils.object'], [], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/utils/parsing.js', ['Blockly.utils.parsing'], ['Blockly.Msg', 'Blockly.utils.colour', 'Blockly.utils.string'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/utils/rect.js', ['Blockly.utils.Rect'], [], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/utils/sentinel.js', ['Blockly.utils.Sentinel'], [], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/utils/size.js', ['Blockly.utils.Size'], [], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/utils/string.js', ['Blockly.utils.string'], [], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/utils/style.js', ['Blockly.utils.style'], ['Blockly.utils.Coordinate', 'Blockly.utils.Size'], {'lang': 'es6', 'module': 'goog'}); @@ -253,17 +256,17 @@ goog.addDependency('../../core/variable_map.js', ['Blockly.VariableMap'], ['Bloc goog.addDependency('../../core/variable_model.js', ['Blockly.VariableModel'], ['Blockly.Events.VarCreate', 'Blockly.Events.utils', 'Blockly.utils.idGenerator'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/variables.js', ['Blockly.Variables'], ['Blockly.Msg', 'Blockly.VariableModel', 'Blockly.Xml', 'Blockly.blocks', 'Blockly.dialog', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/variables_dynamic.js', ['Blockly.VariablesDynamic'], ['Blockly.Msg', 'Blockly.VariableModel', 'Blockly.Variables', 'Blockly.blocks', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/warning.js', ['Blockly.Warning'], ['Blockly.Bubble', 'Blockly.Events.BubbleOpen', 'Blockly.Events.utils', 'Blockly.Icon', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/warning.js', ['Blockly.Warning'], ['Blockly.Bubble', 'Blockly.Events.BubbleOpen', 'Blockly.Events.utils', 'Blockly.Icon', 'Blockly.utils.Svg', 'Blockly.utils.dom'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/widgetdiv.js', ['Blockly.WidgetDiv'], ['Blockly.common', 'Blockly.utils.deprecation', 'Blockly.utils.dom'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/workspace.js', ['Blockly.Workspace'], ['Blockly.ConnectionChecker', 'Blockly.Events.utils', 'Blockly.IASTNodeLocation', 'Blockly.Options', 'Blockly.VariableMap', 'Blockly.registry', 'Blockly.utils.array', 'Blockly.utils.idGenerator', 'Blockly.utils.math'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/workspace_audio.js', ['Blockly.WorkspaceAudio'], ['Blockly.internalConstants', 'Blockly.utils.global', 'Blockly.utils.userAgent'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/workspace_audio.js', ['Blockly.WorkspaceAudio'], ['Blockly.utils.global', 'Blockly.utils.userAgent'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/workspace_comment.js', ['Blockly.WorkspaceComment'], ['Blockly.Events.CommentChange', 'Blockly.Events.CommentCreate', 'Blockly.Events.CommentDelete', 'Blockly.Events.CommentMove', 'Blockly.Events.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.idGenerator', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/workspace_comment_svg.js', ['Blockly.WorkspaceCommentSvg'], ['Blockly.ContextMenu', 'Blockly.Css', 'Blockly.Events.CommentCreate', 'Blockly.Events.CommentDelete', 'Blockly.Events.CommentMove', 'Blockly.Events.Selected', 'Blockly.Events.utils', 'Blockly.IBoundedElement', 'Blockly.IBubble', 'Blockly.ICopyable', 'Blockly.Touch', 'Blockly.WorkspaceComment', 'Blockly.browserEvents', 'Blockly.common', 'Blockly.utils.Coordinate', 'Blockly.utils.Rect', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.svgMath'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/workspace_comment_svg.js', ['Blockly.WorkspaceCommentSvg'], ['Blockly.ContextMenu', 'Blockly.Css', 'Blockly.Events.CommentCreate', 'Blockly.Events.CommentDelete', 'Blockly.Events.CommentMove', 'Blockly.Events.Selected', 'Blockly.Events.utils', 'Blockly.IBoundedElement', 'Blockly.IBubble', 'Blockly.ICopyable', 'Blockly.Touch', 'Blockly.WorkspaceComment', 'Blockly.browserEvents', 'Blockly.common', 'Blockly.utils.Coordinate', 'Blockly.utils.Rect', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.svgMath'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/workspace_drag_surface_svg.js', ['Blockly.WorkspaceDragSurfaceSvg'], ['Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.svgMath'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/workspace_dragger.js', ['Blockly.WorkspaceDragger'], ['Blockly.common', 'Blockly.utils.Coordinate'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/workspace_svg.js', ['Blockly.WorkspaceSvg'], ['Blockly.BlockSvg', 'Blockly.ComponentManager', 'Blockly.ConnectionDB', 'Blockly.ContextMenu', 'Blockly.ContextMenuRegistry', 'Blockly.DropDownDiv', 'Blockly.Events.BlockCreate', 'Blockly.Events.ThemeChange', 'Blockly.Events.ViewportChange', 'Blockly.Events.utils', 'Blockly.Gesture', 'Blockly.Grid', 'Blockly.IASTNodeLocationSvg', 'Blockly.MarkerManager', 'Blockly.MetricsManager', 'Blockly.Msg', 'Blockly.Options', 'Blockly.ThemeManager', 'Blockly.Themes.Classic', 'Blockly.Tooltip', 'Blockly.TouchGesture', 'Blockly.WidgetDiv', 'Blockly.Workspace', 'Blockly.WorkspaceAudio', 'Blockly.Xml', 'Blockly.blockRendering', 'Blockly.browserEvents', 'Blockly.common', 'Blockly.internalConstants', 'Blockly.registry', 'Blockly.serialization.blocks', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Rect', 'Blockly.utils.Size', 'Blockly.utils.Svg', 'Blockly.utils.array', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.svgMath', 'Blockly.utils.toolbox', 'Blockly.utils.userAgent'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/workspace_svg.js', ['Blockly.WorkspaceSvg'], ['Blockly.BlockSvg', 'Blockly.ComponentManager', 'Blockly.ConnectionDB', 'Blockly.ContextMenu', 'Blockly.ContextMenuRegistry', 'Blockly.Events.BlockCreate', 'Blockly.Events.ThemeChange', 'Blockly.Events.ViewportChange', 'Blockly.Events.utils', 'Blockly.Gesture', 'Blockly.Grid', 'Blockly.IASTNodeLocationSvg', 'Blockly.MarkerManager', 'Blockly.MetricsManager', 'Blockly.Msg', 'Blockly.Options', 'Blockly.ThemeManager', 'Blockly.Themes.Classic', 'Blockly.Tooltip', 'Blockly.TouchGesture', 'Blockly.WidgetDiv', 'Blockly.Workspace', 'Blockly.WorkspaceAudio', 'Blockly.Xml', 'Blockly.blockRendering', 'Blockly.browserEvents', 'Blockly.common', 'Blockly.config', 'Blockly.dropDownDiv', 'Blockly.registry', 'Blockly.serialization.blocks', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Rect', 'Blockly.utils.Size', 'Blockly.utils.Svg', 'Blockly.utils.array', 'Blockly.utils.dom', 'Blockly.utils.svgMath', 'Blockly.utils.toolbox', 'Blockly.utils.userAgent'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/xml.js', ['Blockly.Xml'], ['Blockly.Events.utils', 'Blockly.inputTypes', 'Blockly.utils.Size', 'Blockly.utils.dom', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/zoom_controls.js', ['Blockly.ZoomControls'], ['Blockly.ComponentManager', 'Blockly.Css', 'Blockly.Events.Click', 'Blockly.Events.utils', 'Blockly.IPositionable', 'Blockly.Touch', 'Blockly.browserEvents', 'Blockly.internalConstants', 'Blockly.uiPosition', 'Blockly.utils.Rect', 'Blockly.utils.Size', 'Blockly.utils.Svg', 'Blockly.utils.dom'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/zoom_controls.js', ['Blockly.ZoomControls'], ['Blockly.ComponentManager', 'Blockly.Css', 'Blockly.Events.Click', 'Blockly.Events.utils', 'Blockly.IPositionable', 'Blockly.Touch', 'Blockly.browserEvents', 'Blockly.sprite', 'Blockly.uiPosition', 'Blockly.utils.Rect', 'Blockly.utils.Size', 'Blockly.utils.Svg', 'Blockly.utils.dom'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../generators/dart.js', ['Blockly.Dart'], ['Blockly.Generator', 'Blockly.Names', 'Blockly.Variables', 'Blockly.inputTypes', 'Blockly.utils.string'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../generators/dart/all.js', ['Blockly.Dart.all'], ['Blockly.Dart.colour', 'Blockly.Dart.lists', 'Blockly.Dart.logic', 'Blockly.Dart.loops', 'Blockly.Dart.math', 'Blockly.Dart.procedures', 'Blockly.Dart.texts', 'Blockly.Dart.variables', 'Blockly.Dart.variablesDynamic'], {'module': 'goog'}); goog.addDependency('../../generators/dart/colour.js', ['Blockly.Dart.colour'], ['Blockly.Dart'], {'lang': 'es6', 'module': 'goog'}); @@ -321,4 +324,5 @@ goog.addDependency('../../generators/python/variables.js', ['Blockly.Python.vari goog.addDependency('../../generators/python/variables_dynamic.js', ['Blockly.Python.variablesDynamic'], ['Blockly.Python', 'Blockly.Python.variables'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('base.js', [], []); goog.addDependency('base_minimal.js', [], []); +goog.addDependency('goog.js', [], [], {'lang': 'es6', 'module': 'es6'}); diff --git a/tests/deps.mocha.js b/tests/deps.mocha.js index 4be80abef..ba58a1b31 100644 --- a/tests/deps.mocha.js +++ b/tests/deps.mocha.js @@ -1,64 +1,74 @@ goog.addDependency('../../tests/mocha/.mocharc.js', [], []); -goog.addDependency('../../tests/mocha/astnode_test.js', ['Blockly.test.astNode'], ['Blockly.ASTNode', 'Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/block_change_event_test.js', ['Blockly.test.blockChangeEvent'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/block_create_event_test.js', ['Blockly.test.blockCreateEvent'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/astnode_test.js', ['Blockly.test.astNode'], ['Blockly.ASTNode', 'Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/block_change_event_test.js', ['Blockly.test.blockChangeEvent'], ['Blockly.test.helpers.blockDefinitions', 'Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/block_create_event_test.js', ['Blockly.test.blockCreateEvent'], ['Blockly.Events.utils', 'Blockly.test.helpers.events', 'Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../tests/mocha/block_json_test.js', ['Blockly.test.blockJson'], ['Blockly.Input'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/block_test.js', ['Blockly.test.blocks'], ['Blockly.ConnectionType', 'Blockly.Events.utils', 'Blockly.blocks', 'Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/comment_test.js', ['Blockly.test.comments'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/connection_checker_test.js', ['Blockly.test.connectionChecker'], ['Blockly.ConnectionType', 'Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/connection_db_test.js', ['Blockly.test.connectionDb'], ['Blockly.ConnectionType', 'Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/connection_test.js', ['Blockly.test.connection'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/contextmenu_items_test.js', ['Blockly.test.contextMenuItem'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/cursor_test.js', ['Blockly.test.cursor'], ['Blockly.ASTNode', 'Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/dropdowndiv_test.js', ['Blockly.test.dropdown'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/event_test.js', ['Blockly.test.event'], ['Blockly.ASTNode', 'Blockly.Events.utils', 'Blockly.WorkspaceComment', 'Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/extensions_test.js', ['Blockly.test.extensions'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/field_angle_test.js', ['Blockly.test.fieldAngle'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/field_checkbox_test.js', ['Blockly.test.fieldCheckbox'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/field_colour_test.js', ['Blockly.test.fieldColour'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/field_dropdown_test.js', ['Blockly.test.fieldDropdown'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/field_image_test.js', ['Blockly.test.fieldImage'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/field_label_serializable_test.js', ['Blockly.test.fieldLabelSerialization'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/field_label_test.js', ['Blockly.test.fieldLabel'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/field_multilineinput_test.js', ['Blockly.test.fieldMultiline'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/field_number_test.js', ['Blockly.test.fieldNumber'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/field_registry_test.js', ['Blockly.test.fieldRegistry'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/field_test.js', ['Blockly.test.fieldTest'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/field_textinput_test.js', ['Blockly.test.fieldTextInput'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/field_variable_test.js', ['Blockly.test.fieldVariable'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/flyout_test.js', ['Blockly.test.flyout'], ['Blockly.test.helpers', 'Blockly.test.toolboxHelpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/generator_test.js', ['Blockly.test.generator'], ['Blockly.Dart', 'Blockly.JavaScript', 'Blockly.Lua', 'Blockly.PHP', 'Blockly.Python', 'Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/gesture_test.js', ['Blockly.test.gesture'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/input_test.js', ['Blockly.test.input'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/insertion_marker_test.js', ['Blockly.test.insertionMarker'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/jso_deserialization_test.js', ['Blockly.test.jsoDeserialization'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/jso_serialization_test.js', ['Blockly.test.jsoSerialization'], ['Blockly.test.helpers'], {'lang': 'es8', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/json_test.js', ['Blockly.test.json'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/keydown_test.js', ['Blockly.test.keydown'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/logic_ternary_test.js', ['Blockly.test.logicTernary'], ['Blockly.Events.utils', 'Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/metrics_test.js', ['Blockly.test.metrics'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/mutator_test.js', ['Blockly.test.mutator'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/names_test.js', ['Blockly.test.names'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/procedures_test.js', ['Blockly.test.procedures'], ['Blockly', 'Blockly.Msg', 'Blockly.test.helpers', 'Blockly.test.procedureHelpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/procedures_test_helpers.js', ['Blockly.test.procedureHelpers'], ['Blockly.ConnectionType'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/registry_test.js', ['Blockly.test.registry'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/block_test.js', ['Blockly.test.blocks'], ['Blockly.ConnectionType', 'Blockly.Events.utils', 'Blockly.blocks', 'Blockly.test.helpers.blockDefinitions', 'Blockly.test.helpers.setupTeardown', 'Blockly.test.helpers.warnings'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/comment_deserialization_test.js', ['Blockly.test.commentDeserialization'], ['Blockly.test.helpers.setupTeardown', 'Blockly.test.helpers.userInput'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/comment_test.js', ['Blockly.test.comments'], ['Blockly.Events.utils', 'Blockly.test.helpers.events', 'Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/connection_checker_test.js', ['Blockly.test.connectionChecker'], ['Blockly.ConnectionType', 'Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/connection_db_test.js', ['Blockly.test.connectionDb'], ['Blockly.ConnectionType', 'Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/connection_test.js', ['Blockly.test.connection'], ['Blockly.test.helpers.blockDefinitions', 'Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/contextmenu_items_test.js', ['Blockly.test.contextMenuItem'], ['Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/cursor_test.js', ['Blockly.test.cursor'], ['Blockly.ASTNode', 'Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/dropdowndiv_test.js', ['Blockly.test.dropdown'], ['Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/event_test.js', ['Blockly.test.event'], ['Blockly.ASTNode', 'Blockly.Events.utils', 'Blockly.WorkspaceComment', 'Blockly.test.helpers.events', 'Blockly.test.helpers.setupTeardown', 'Blockly.test.helpers.variables'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/extensions_test.js', ['Blockly.test.extensions'], ['Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/field_angle_test.js', ['Blockly.test.fieldAngle'], ['Blockly.test.helpers.blockDefinitions', 'Blockly.test.helpers.fields', 'Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/field_checkbox_test.js', ['Blockly.test.fieldCheckbox'], ['Blockly.test.helpers.blockDefinitions', 'Blockly.test.helpers.fields', 'Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/field_colour_test.js', ['Blockly.test.fieldColour'], ['Blockly.test.helpers.blockDefinitions', 'Blockly.test.helpers.fields', 'Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/field_dropdown_test.js', ['Blockly.test.fieldDropdown'], ['Blockly.test.helpers.blockDefinitions', 'Blockly.test.helpers.fields', 'Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/field_image_test.js', ['Blockly.test.fieldImage'], ['Blockly.test.helpers.fields', 'Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/field_label_serializable_test.js', ['Blockly.test.fieldLabelSerialization'], ['Blockly.test.helpers.blockDefinitions', 'Blockly.test.helpers.fields', 'Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/field_label_test.js', ['Blockly.test.fieldLabel'], ['Blockly.test.helpers.blockDefinitions', 'Blockly.test.helpers.fields', 'Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/field_multilineinput_test.js', ['Blockly.test.fieldMultiline'], ['Blockly.test.helpers.blockDefinitions', 'Blockly.test.helpers.codeGeneration', 'Blockly.test.helpers.fields', 'Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/field_number_test.js', ['Blockly.test.fieldNumber'], ['Blockly.test.helpers.blockDefinitions', 'Blockly.test.helpers.common', 'Blockly.test.helpers.fields', 'Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/field_registry_test.js', ['Blockly.test.fieldRegistry'], ['Blockly.test.helpers.setupTeardown', 'Blockly.test.helpers.warnings'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/field_test.js', ['Blockly.test.fieldTest'], ['Blockly.test.helpers.setupTeardown', 'Blockly.test.helpers.warnings'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/field_textinput_test.js', ['Blockly.test.fieldTextInput'], ['Blockly.test.helpers.blockDefinitions', 'Blockly.test.helpers.fields', 'Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/field_variable_test.js', ['Blockly.test.fieldVariable'], ['Blockly.test.helpers.blockDefinitions', 'Blockly.test.helpers.fields', 'Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/flyout_test.js', ['Blockly.test.flyout'], ['Blockly.test.helpers.setupTeardown', 'Blockly.test.helpers.toolboxDefinitions'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/generator_test.js', ['Blockly.test.generator'], ['Blockly.Dart', 'Blockly.JavaScript', 'Blockly.Lua', 'Blockly.PHP', 'Blockly.Python', 'Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/gesture_test.js', ['Blockly.test.gesture'], ['Blockly.Events.utils', 'Blockly.test.helpers.blockDefinitions', 'Blockly.test.helpers.events', 'Blockly.test.helpers.setupTeardown', 'Blockly.test.helpers.userInput'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/input_test.js', ['Blockly.test.input'], ['Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/insertion_marker_test.js', ['Blockly.test.insertionMarker'], ['Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/jso_deserialization_test.js', ['Blockly.test.jsoDeserialization'], ['Blockly.Events.utils', 'Blockly.test.helpers.events', 'Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/jso_serialization_test.js', ['Blockly.test.jsoSerialization'], ['Blockly.test.helpers.blockDefinitions', 'Blockly.test.helpers.setupTeardown'], {'lang': 'es8', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/json_test.js', ['Blockly.test.json'], ['Blockly.test.helpers.setupTeardown', 'Blockly.test.helpers.warnings'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/keydown_test.js', ['Blockly.test.keydown'], ['Blockly.test.helpers.blockDefinitions', 'Blockly.test.helpers.setupTeardown', 'Blockly.test.helpers.userInput'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/logic_ternary_test.js', ['Blockly.test.logicTernary'], ['Blockly.Events.utils', 'Blockly.test.helpers.serialization', 'Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/metrics_test.js', ['Blockly.test.metrics'], ['Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/mutator_test.js', ['Blockly.test.mutator'], ['Blockly.test.helpers.blockDefinitions', 'Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/names_test.js', ['Blockly.test.names'], ['Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/procedures_test.js', ['Blockly.test.procedures'], ['Blockly', 'Blockly.Msg', 'Blockly.test.helpers.procedures', 'Blockly.test.helpers.serialization', 'Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/registry_test.js', ['Blockly.test.registry'], ['Blockly.test.helpers.setupTeardown', 'Blockly.test.helpers.warnings'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../tests/mocha/run_mocha_tests_in_browser.js', [], [], {'lang': 'es8'}); -goog.addDependency('../../tests/mocha/serializer_test.js', ['Blockly.test.serialization'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/shortcut_registry_test.js', ['Blockly.test.shortcutRegistry'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/test_helpers.js', ['Blockly.test.helpers'], ['Blockly.Events.utils', 'Blockly.blocks', 'Blockly.utils.KeyCodes'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/theme_test.js', ['Blockly.test.theme'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/toolbox_helper.js', ['Blockly.test.toolboxHelpers'], [], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/toolbox_test.js', ['Blockly.test.toolbox'], ['Blockly.test.helpers', 'Blockly.test.toolboxHelpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/tooltip_test.js', ['Blockly.test.tooltip'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/trashcan_test.js', ['Blockly.test.trashcan'], ['Blockly.Events.utils', 'Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/utils_test.js', ['Blockly.test.utils'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/variable_map_test.js', ['Blockly.test.variableMap'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/variable_model_test.js', ['Blockly.test.variableModel'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/variables_test.js', ['Blockly.test.variables'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/widget_div_test.js', ['Blockly.test.widgetDiv'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/workspace_comment_test.js', ['Blockly.test.workspaceComment'], ['Blockly.WorkspaceComment', 'Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/workspace_helpers.js', ['Blockly.test.workspaceHelpers'], ['Blockly.Events.utils', 'Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/workspace_svg_test.js', ['Blockly.test.workspaceSvg'], ['Blockly.test.helpers', 'Blockly.test.workspaceHelpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/workspace_test.js', ['Blockly.test.workspace'], ['Blockly.test.helpers', 'Blockly.test.workspaceHelpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/xml_test.js', ['Blockly.test.xml'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../tests/mocha/zoom_controls_test.js', ['Blockly.test.zoomControls'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/serializer_test.js', ['Blockly.test.serialization'], ['Blockly.test.helpers.common', 'Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/shortcut_registry_test.js', ['Blockly.test.shortcutRegistry'], ['Blockly.test.helpers.setupTeardown', 'Blockly.test.helpers.userInput'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/test_helpers/block_definitions.js', ['Blockly.test.helpers.blockDefinitions'], [], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/test_helpers/code_generation.js', ['Blockly.test.helpers.codeGeneration'], ['Blockly.test.helpers.common'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/test_helpers/common.js', ['Blockly.test.helpers.common'], [], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/test_helpers/events.js', ['Blockly.test.helpers.events'], [], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/test_helpers/fields.js', ['Blockly.test.helpers.fields'], ['Blockly.test.helpers.common'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/test_helpers/procedures.js', ['Blockly.test.helpers.procedures'], ['Blockly.ConnectionType'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/test_helpers/serialization.js', ['Blockly.test.helpers.serialization'], ['Blockly.test.helpers.common'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/test_helpers/setup_teardown.js', ['Blockly.test.helpers.setupTeardown'], ['Blockly.Events.utils', 'Blockly.blocks'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/test_helpers/toolbox_definitions.js', ['Blockly.test.helpers.toolboxDefinitions'], [], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/test_helpers/user_input.js', ['Blockly.test.helpers.userInput'], ['Blockly.utils.KeyCodes'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/test_helpers/variables.js', ['Blockly.test.helpers.variables'], [], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/test_helpers/warnings.js', ['Blockly.test.helpers.warnings'], [], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/test_helpers/workspace.js', ['Blockly.test.helpers.workspace'], ['Blockly.Events.utils', 'Blockly.test.helpers.setupTeardown', 'Blockly.test.helpers.variables', 'Blockly.test.helpers.warnings'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/theme_test.js', ['Blockly.test.theme'], ['Blockly.Events.utils', 'Blockly.test.helpers.events', 'Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/toolbox_test.js', ['Blockly.test.toolbox'], ['Blockly.test.helpers.blockDefinitions', 'Blockly.test.helpers.setupTeardown', 'Blockly.test.helpers.toolboxDefinitions'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/tooltip_test.js', ['Blockly.test.tooltip'], ['Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/trashcan_test.js', ['Blockly.test.trashcan'], ['Blockly.Events.utils', 'Blockly.test.helpers.blockDefinitions', 'Blockly.test.helpers.events', 'Blockly.test.helpers.setupTeardown', 'Blockly.test.helpers.userInput'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/utils_test.js', ['Blockly.test.utils'], ['Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/variable_map_test.js', ['Blockly.test.variableMap'], ['Blockly.test.helpers.setupTeardown', 'Blockly.test.helpers.variables'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/variable_model_test.js', ['Blockly.test.variableModel'], ['Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/variables_test.js', ['Blockly.test.variables'], ['Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/widget_div_test.js', ['Blockly.test.widgetDiv'], ['Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/workspace_comment_test.js', ['Blockly.test.workspaceComment'], ['Blockly.WorkspaceComment', 'Blockly.test.helpers.setupTeardown'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/workspace_svg_test.js', ['Blockly.test.workspaceSvg'], ['Blockly.Events.utils', 'Blockly.test.helpers.blockDefinitions', 'Blockly.test.helpers.events', 'Blockly.test.helpers.setupTeardown', 'Blockly.test.helpers.variables', 'Blockly.test.helpers.workspace'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/workspace_test.js', ['Blockly.test.workspace'], ['Blockly.test.helpers.setupTeardown', 'Blockly.test.helpers.variables', 'Blockly.test.helpers.workspace'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/xml_test.js', ['Blockly.test.xml'], ['Blockly.test.helpers.setupTeardown', 'Blockly.test.helpers.variables'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../tests/mocha/zoom_controls_test.js', ['Blockly.test.zoomControls'], ['Blockly.Events.utils', 'Blockly.test.helpers.events', 'Blockly.test.helpers.setupTeardown', 'Blockly.test.helpers.userInput'], {'lang': 'es6', 'module': 'goog'}); diff --git a/tests/generators/golden/generated.dart b/tests/generators/golden/generated.dart index 2c45db3b4..a47886588 100644 --- a/tests/generators/golden/generated.dart +++ b/tests/generators/golden/generated.dart @@ -440,12 +440,16 @@ void test_number_properties() { unittest_assertequals(42 % 2 == 0, true, 'even'); unittest_assertequals(42.1 % 2 == 1, false, 'odd'); unittest_assertequals(math_isPrime(5), true, 'prime 5'); + unittest_assertequals(math_isPrime(5 + 2), true, 'prime 5 + 2 (extra parentheses)'); unittest_assertequals(math_isPrime(25), false, 'prime 25'); unittest_assertequals(math_isPrime(-31.1), false, 'prime negative'); unittest_assertequals(Math.pi % 1 == 0, false, 'whole'); unittest_assertequals(double.infinity > 0, true, 'positive'); + unittest_assertequals(5 + 2 > 0, true, '5 + 2 is positive (extra parentheses)'); unittest_assertequals(-42 < 0, true, 'negative'); + unittest_assertequals(3 + 2 < 0, false, '3 + 2 is negative (extra parentheses)'); unittest_assertequals(42 % 2 == 0, true, 'divisible'); + unittest_assertequals(!false, true, 'divisible by 0'); } /// Tests the "round" block. @@ -1380,10 +1384,10 @@ void test_split() { List lists_sort(List list, String type, int direction) { var compareFuncs = { - "NUMERIC": (a, b) => (direction * a.compareTo(b)).toInt(), - "TEXT": (a, b) => direction * a.toString().compareTo(b.toString()), - "IGNORE_CASE": - (a, b) => direction * + 'NUMERIC': (a, b) => (direction * a.compareTo(b)).toInt(), + 'TEXT': (a, b) => direction * a.toString().compareTo(b.toString()), + 'IGNORE_CASE': + (a, b) => direction * a.toString().toLowerCase().compareTo(b.toString().toLowerCase()) }; list = new List.from(list); diff --git a/tests/generators/golden/generated.js b/tests/generators/golden/generated.js index 01e9bd311..48a4e20ac 100644 --- a/tests/generators/golden/generated.js +++ b/tests/generators/golden/generated.js @@ -449,12 +449,16 @@ function test_number_properties() { assertEquals(42 % 2 === 0, true, 'even'); assertEquals(42.1 % 2 === 1, false, 'odd'); assertEquals(mathIsPrime(5), true, 'prime 5'); + assertEquals(mathIsPrime(5 + 2), true, 'prime 5 + 2 (extra parentheses)'); assertEquals(mathIsPrime(25), false, 'prime 25'); assertEquals(mathIsPrime(-31.1), false, 'prime negative'); assertEquals(Math.PI % 1 === 0, false, 'whole'); assertEquals(Infinity > 0, true, 'positive'); + assertEquals(5 + 2 > 0, true, '5 + 2 is positive (extra parentheses)'); assertEquals(-42 < 0, true, 'negative'); + assertEquals(3 + 2 < 0, false, '3 + 2 is negative (extra parentheses)'); assertEquals(42 % 2 === 0, true, 'divisible'); + assertEquals(!(42 % 0 === 0), true, 'divisible by 0'); } // Tests the "round" block. @@ -900,8 +904,8 @@ function test_text_reverse() { } function textReplace(haystack, needle, replacement) { - needle = needle.replace(/([-()\[\]{}+?*.$\^|,:# b.toString() ? 1 : -1; }, - "IGNORE_CASE": function(a, b) { + 'IGNORE_CASE': function(a, b) { return a.toString().toLowerCase() > b.toString().toLowerCase() ? 1 : -1; }, }; var compare = compareFuncs[type]; - return function(a, b) { return compare(a, b) * direction; } + return function(a, b) { return compare(a, b) * direction; }; } // Tests the "alphabetic sort" block. diff --git a/tests/generators/golden/generated.lua b/tests/generators/golden/generated.lua index 2724c1d3b..085a76070 100644 --- a/tests/generators/golden/generated.lua +++ b/tests/generators/golden/generated.lua @@ -450,12 +450,16 @@ function test_number_properties() assertEquals(42 % 2 == 0, true, 'even') assertEquals(42.1 % 2 == 1, false, 'odd') assertEquals(math_isPrime(5), true, 'prime 5') + assertEquals(math_isPrime(5 + 2), true, 'prime 5 + 2 (extra parentheses)') assertEquals(math_isPrime(25), false, 'prime 25') assertEquals(math_isPrime(-31.1), false, 'prime negative') assertEquals(math.pi % 1 == 0, false, 'whole') assertEquals(math.huge > 0, true, 'positive') + assertEquals(5 + 2 > 0, true, '5 + 2 is positive (extra parentheses)') assertEquals(-42 < 0, true, 'negative') + assertEquals(3 + 2 < 0, false, '3 + 2 is negative (extra parentheses)') assertEquals(42 % 2 == 0, true, 'divisible') + assertEquals(not nil, true, 'divisible by 0') end @@ -521,23 +525,23 @@ function math_median(t) if #t == 0 then return 0 end - local temp={} + local temp = {} for _, v in ipairs(t) do - if type(v) == "number" then + if type(v) == 'number' then table.insert(temp, v) end end table.sort(temp) if #temp % 2 == 0 then - return (temp[#temp/2] + temp[(#temp/2)+1]) / 2 + return (temp[#temp / 2] + temp[(#temp / 2) + 1]) / 2 else - return temp[math.ceil(#temp/2)] + return temp[math.ceil(#temp / 2)] end end function math_modes(t) -- Source: http://lua-users.org/wiki/SimpleStats - local counts={} + local counts = {} for _, v in ipairs(t) do if counts[v] == nil then counts[v] = 1 @@ -551,7 +555,7 @@ function math_modes(t) biggestCount = v end end - local temp={} + local temp = {} for k, v in pairs(counts) do if v == biggestCount then table.insert(temp, k) @@ -703,9 +707,8 @@ function firstIndexOf(str, substr) local i = string.find(str, substr, 1, true) if i == nil then return 0 - else - return i end + return i end function lastIndexOf(str, substr) diff --git a/tests/generators/golden/generated.php b/tests/generators/golden/generated.php index 9d6436125..4a16b6f5c 100644 --- a/tests/generators/golden/generated.php +++ b/tests/generators/golden/generated.php @@ -444,12 +444,16 @@ function test_number_properties() { assertEquals(42 % 2 == 0, true, 'even'); assertEquals(42.1 % 2 == 1, false, 'odd'); assertEquals(math_isPrime(5), true, 'prime 5'); + assertEquals(math_isPrime(5 + 2), true, 'prime 5 + 2 (extra parentheses)'); assertEquals(math_isPrime(25), false, 'prime 25'); assertEquals(math_isPrime(-31.1), false, 'prime negative'); assertEquals(is_int(M_PI), false, 'whole'); assertEquals(INF > 0, true, 'positive'); + assertEquals(5 + 2 > 0, true, '5 + 2 is positive (extra parentheses)'); assertEquals(-42 < 0, true, 'negative'); + assertEquals(3 + 2 < 0, false, '3 + 2 is negative (extra parentheses)'); assertEquals(42 % 2 == 0, true, 'divisible'); + assertEquals(!false, true, 'divisible by 0'); } // Tests the "round" block. @@ -474,8 +478,8 @@ function math_mean($myList) { function math_median($arr) { sort($arr,SORT_NUMERIC); - return (count($arr) % 2) ? $arr[floor(count($arr)/2)] : - ($arr[floor(count($arr)/2)] + $arr[floor(count($arr)/2) - 1]) / 2; + return (count($arr) % 2) ? $arr[floor(count($arr) / 2)] : + ($arr[floor(count($arr) / 2)] + $arr[floor(count($arr) / 2) - 1]) / 2; } function math_modes($values) { @@ -599,9 +603,8 @@ function test_empty_text() { function length($value) { if (is_string($value)) { return strlen($value); - } else { - return count($value); } + return count($value); } // Tests the "length" block. @@ -1380,9 +1383,9 @@ function test_split() { function lists_sort($list, $type, $direction) { $sortCmpFuncs = array( - "NUMERIC" => "strnatcasecmp", - "TEXT" => "strcmp", - "IGNORE_CASE" => "strcasecmp" + 'NUMERIC' => 'strnatcasecmp', + 'TEXT' => 'strcmp', + 'IGNORE_CASE' => 'strcasecmp' ); $sortCmp = $sortCmpFuncs[$type]; $list2 = $list; diff --git a/tests/generators/golden/generated.py b/tests/generators/golden/generated.py index 42764d368..28a673b74 100644 --- a/tests/generators/golden/generated.py +++ b/tests/generators/golden/generated.py @@ -398,12 +398,16 @@ def test_number_properties(): assertEquals(42 % 2 == 0, True, 'even') assertEquals(42.1 % 2 == 1, False, 'odd') assertEquals(math_isPrime(5), True, 'prime 5') + assertEquals(math_isPrime(5 + 2), True, 'prime 5 + 2 (extra parentheses)') assertEquals(math_isPrime(25), False, 'prime 25') assertEquals(math_isPrime(-31.1), False, 'prime negative') assertEquals(math.pi % 1 == 0, False, 'whole') assertEquals(float('inf') > 0, True, 'positive') + assertEquals(5 + 2 > 0, True, '5 + 2 is positive (extra parentheses)') assertEquals(-42 < 0, True, 'negative') + assertEquals(3 + 2 < 0, False, '3 + 2 is negative (extra parentheses)') assertEquals(42 % 2 == 0, True, 'divisible') + assertEquals(not False, True, 'divisible by 0') # Tests the "round" block. def test_round(): @@ -595,7 +599,7 @@ def test_find_text_complex(): def text_random_letter(text): x = int(random.random() * len(text)) - return text[x]; + return text[x] # Tests the "get letter" block with a variable. def test_get_text_simple(): diff --git a/tests/generators/index.html b/tests/generators/index.html index b258ad18d..9e37f05b6 100644 --- a/tests/generators/index.html +++ b/tests/generators/index.html @@ -13,7 +13,7 @@ goog.require('Blockly.JavaScript.all'); goog.require('Blockly.Lua.all'); goog.require('Blockly.PHP.all'); goog.require('Blockly.Python.all'); -goog.require('Blockly.blocks.all'); +goog.require('Blockly.libraryBlocks'); diff --git a/tests/generators/math.xml b/tests/generators/math.xml index ff079d2e1..682d143ac 100644 --- a/tests/generators/math.xml +++ b/tests/generators/math.xml @@ -1,4 +1,8 @@ + + varToChange + rand + Math @@ -992,10 +996,10 @@ - FALSE + TRUE - prime 25 + prime 5 + 2 (extra parentheses) @@ -1003,8 +1007,18 @@ PRIME - - 25 + + ADD + + + 5 + + + + + 2 + + @@ -1014,7 +1028,7 @@ FALSE - prime negative + prime 25 @@ -1023,7 +1037,7 @@ PRIME - -31.1 + 25 @@ -1033,35 +1047,35 @@ FALSE - whole + prime negative - WHOLE + PRIME - - PI + + -31.1 - TRUE + FALSE - positive + whole - POSITIVE + WHOLE - INFINITY + PI @@ -1071,16 +1085,16 @@ TRUE - negative + positive - NEGATIVE + POSITIVE - - -42 + + INFINITY @@ -1090,25 +1104,138 @@ TRUE - divisible + 5 + 2 is positive (extra parentheses) - - DIVISIBLE_BY + + POSITIVE - - 42 - - - - - 2 + + ADD + + + 5 + + + + + 2 + + + + + TRUE + + + negative + + + + + + NEGATIVE + + + -42 + + + + + + + FALSE + + + 3 + 2 is negative (extra parentheses) + + + + + + NEGATIVE + + + ADD + + + 3 + + + + + 2 + + + + + + + + + TRUE + + + divisible + + + + + + DIVISIBLE_BY + + + 42 + + + + + 2 + + + + + + + TRUE + + + divisible by 0 + + + + + + + + DIVISIBLE_BY + + + 42 + + + + + 0 + + + + + + + + + + + + + + @@ -1128,7 +1255,7 @@ - + test round Tests the "round" block. @@ -1204,7 +1331,7 @@ - + test change Tests the "change" block. diff --git a/tests/migration/renamings-schema.json b/tests/migration/renamings-schema.json new file mode 100644 index 000000000..6dc643b20 --- /dev/null +++ b/tests/migration/renamings-schema.json @@ -0,0 +1,57 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/google/blockly/tests/migration/renamings-schema.json", + "title": "Renamings", + "description": "A file containing information about module and module export renamings", + "type": "object", + "patternProperties": { + "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$": {"$ref": "#/$defs/version"} + }, + "properties": { + "develop": {"$ref": "#/$defs/version"} + }, + "additionalProperties": false, + "$defs": { + "version": { + "description": "All the renamings that happened in a paricular release.", + "type": "array", + "items": { + "description": "All the renamings in/of a particular module.", + "type": "object", + "properties": { + "oldName": {"$ref": "#/$defs/dottedIdentifier"}, + "newName": {"$ref": "#/$defs/dottedIdentifier"}, + "newExport": {"$ref": "#/$defs/dottedIdentifier"}, + "oldPath": {"$ref": "#/$defs/dottedIdentifier"}, + "newPath": {"$ref": "#/$defs/dottedIdentifier"}, + "exports": { + "description": "A list of the exports that have been renamed.", + "type": "object", + "patternProperties": { + "^[A-Za-z$_][A-Za-z0-9$_]*(\\.[A-Za-z$_][A-Za-z0-9$_]*)*$": { + "description": "A single renamed (or moved) export.", + "type": "object", + "properties": { + "newModule": {"$ref": "#/$defs/dottedIdentifier"}, + "newExport": {"$ref": "#/$defs/dottedIdentifier"}, + "oldPath": {"$ref": "#/$defs/dottedIdentifier"}, + "newPath": {"$ref": "#/$defs/dottedIdentifier"}, + "getMethod": {"$ref": "#/$defs/dottedIdentifier"}, + "setMethod": {"$ref": "#/$defs/dottedIdentifier"} + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "required": ["oldName"], + "additionalProperties": false + } + }, + "dottedIdentifier": { + "type": "string", + "pattern": "^[A-Za-z$_][A-Za-z0-9$_]*(\\.[A-Za-z$_][A-Za-z0-9$_]*)*$" + } + } +} diff --git a/tests/migration/validate-renamings.js b/tests/migration/validate-renamings.js new file mode 100755 index 000000000..8b66e3bea --- /dev/null +++ b/tests/migration/validate-renamings.js @@ -0,0 +1,73 @@ +#!/usr/bin/env node +/** + * @license + * Copyright 2022 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview A script to validate the renamings file + * (scripts/migration/renamings.json5) agaist the schema + * (renamings-schema.json). + */ + +/* global require __dirname process */ + +const JsonSchema = require('@hyperjump/json-schema'); +const JSON5 = require('json5'); +const fs = require('fs'); +const path = require('path'); + + +/** + * Renaming schema filename. + * @type {string} + */ +const SCHEMA_FILENAME = path.join(__dirname, 'renamings-schema.json'); + +/** + * Renamings filename. + * @type {string} + */ +const RENAMINGS_FILENAME = + path.resolve(__dirname, '../../scripts/migration/renamings.json5'); + +// Can't use top-level await outside a module, and can't use require +// in a module, so use an IIAFE. +(async function() { + const schemaUrl = 'file://' + path.resolve(SCHEMA_FILENAME); + const schema = await JsonSchema.get(schemaUrl); + + const renamingsJson5 = fs.readFileSync(RENAMINGS_FILENAME); + const renamings = JSON5.parse(renamingsJson5); + + const output = + await JsonSchema.validate(schema, renamings, JsonSchema.DETAILED); + + if (!output.valid) { + console.log('Renamings file is invalid.'); + console.log('Maybe this validator output will help you find the problem:'); + console.log(JSON5.stringify(output, undefined, ' ')); + process.exit(1); + } + + // File passed schema validation. Do some additional checks. + let ok = true; + Object.entries(renamings).forEach(([version, modules]) => { + // Scan through modules and check for duplicates. + const seen = new Set(); + for (const {oldName} of modules) { + if (seen.has(oldName)) { + console.log(`Duplicate entry for module ${oldName} ` + + `in version ${version}.`); + ok = false; + } + seen.add(oldName); + } + }); + if (!ok) { + console.log('Renamings file is invalid.'); + process.exit(1); + } + // Default is a successful exit 0. +})(); diff --git a/tests/mocha/.eslintrc.json b/tests/mocha/.eslintrc.json index dad7c25ca..8ebf31ae6 100644 --- a/tests/mocha/.eslintrc.json +++ b/tests/mocha/.eslintrc.json @@ -1,40 +1,16 @@ { - "parser": "babel-eslint", "env": { "browser": true, "mocha": true }, "globals": { "chai": false, - "sinon": false, - "testHelpers": true + "sinon": false }, "rules": { "no-unused-vars": ["off"], - "es5/no-arrow-functions": ["off"], - "es5/no-binary-and-octal-literals": ["off"], - "es5/no-block-scoping": ["off"], - "es5/no-classes": ["off"], - "es5/no-computed-properties": ["off"], - "es5/no-default-parameters": ["off"], - "es5/no-destructuring": ["off"], - "es5/no-es6-methods": ["off"], - "es5/no-es6-static-methods": ["off"], - "es5/no-for-of": ["off"], - "es5/no-generators": ["off"], - "es5/no-modules": ["off"], - "es5/no-object-super": ["off"], - "es5/no-rest-parameters": ["off"], - "es5/no-shorthand-properties": ["off"], - "es5/no-spread": ["off"], - "es5/no-template-literals": ["off"], - "es5/no-typeof-symbol": ["off"], - "es5/no-unicode-code-point-escape": ["off"], - "es5/no-unicode-regex": ["off"], // Allow uncommented helper functions in tests. "require-jsdoc": ["off"], - // In mocha tests in suites, `this` is meaningful and useful. - "no-invalid-this": ["off"], "prefer-rest-params": ["off"] }, "extends": "../../.eslintrc.json" diff --git a/tests/mocha/astnode_test.js b/tests/mocha/astnode_test.js index e370d4dd7..f974f3ea8 100644 --- a/tests/mocha/astnode_test.js +++ b/tests/mocha/astnode_test.js @@ -7,7 +7,7 @@ goog.module('Blockly.test.astNode'); const {ASTNode} = goog.require('Blockly.ASTNode'); -const {sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers'); +const {sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('ASTNode', function() { diff --git a/tests/mocha/block_change_event_test.js b/tests/mocha/block_change_event_test.js index e4e3f49cf..6cfbca578 100644 --- a/tests/mocha/block_change_event_test.js +++ b/tests/mocha/block_change_event_test.js @@ -6,7 +6,8 @@ goog.module('Blockly.test.blockChangeEvent'); -const {defineMutatorBlocks, sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); +const {defineMutatorBlocks} = goog.require('Blockly.test.helpers.blockDefinitions'); suite('Block Change Event', function() { diff --git a/tests/mocha/block_create_event_test.js b/tests/mocha/block_create_event_test.js index 91a84af3e..475991908 100644 --- a/tests/mocha/block_create_event_test.js +++ b/tests/mocha/block_create_event_test.js @@ -6,7 +6,9 @@ goog.module('Blockly.test.blockCreateEvent'); -const {assertEventFired, sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {assertEventFired} = goog.require('Blockly.test.helpers.events'); +const eventUtils = goog.require('Blockly.Events.utils'); +const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('Block Create Event', function() { @@ -48,7 +50,7 @@ suite('Block Create Event', function() { assertEventFired( this.eventsFireStub, Blockly.Events.BlockCreate, - {'recordUndo': false}, + {'recordUndo': false, 'type': eventUtils.BLOCK_CREATE}, this.workspace.id, 'shadowId'); const calls = this.eventsFireStub.getCalls(); diff --git a/tests/mocha/block_test.js b/tests/mocha/block_test.js index 36cc583d9..13e515534 100644 --- a/tests/mocha/block_test.js +++ b/tests/mocha/block_test.js @@ -6,10 +6,12 @@ goog.module('Blockly.test.blocks'); -const eventUtils = goog.require('Blockly.Events.utils'); const {Blocks} = goog.require('Blockly.blocks'); const {ConnectionType} = goog.require('Blockly.ConnectionType'); -const {createDeprecationWarningStub, createRenderedBlock, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers'); +const {createDeprecationWarningStub} = goog.require('Blockly.test.helpers.warnings'); +const {createRenderedBlock} = goog.require('Blockly.test.helpers.blockDefinitions'); +const eventUtils = goog.require('Blockly.Events.utils'); +const {sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('Blocks', function() { diff --git a/tests/mocha/comment_deserialization_test.js b/tests/mocha/comment_deserialization_test.js new file mode 100644 index 000000000..f3eb48dc5 --- /dev/null +++ b/tests/mocha/comment_deserialization_test.js @@ -0,0 +1,117 @@ +/** + * @license + * Copyright 2022 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +goog.module('Blockly.test.commentDeserialization'); + +const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); +const {simulateClick} = goog.require('Blockly.test.helpers.userInput'); + + +suite('Comment Deserialization', function() { + setup(function() { + sharedTestSetup.call(this); + Blockly.defineBlocksWithJsonArray([ + { + "type": "empty_block", + "message0": "", + "args0": [], + }, + ]); + const toolboxXml = ` + + + + test toolbox text + + + + `; + this.workspace = Blockly.inject('blocklyDiv', { + comments: true, + scrollbars: true, + trashcan: true, + maxTrashcanContents: Infinity, + toolbox: Blockly.Xml.textToDom(toolboxXml), + }); + }); + teardown(function() { + sharedTestTeardown.call(this); + }); + suite('Pattern', function() { + teardown(function() { + // Delete all blocks. + this.workspace.clear(); + }); + function createBlock(workspace) { + const block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom( + '' + ), workspace); + block.setCommentText('test text'); + return block; + } + function assertComment(workspace, text) { + // Show comment. + const block = workspace.getAllBlocks()[0]; + block.comment.setVisible(true); + // Check comment bubble size. + const comment = block.getCommentIcon(); + const bubbleSize = comment.getBubbleSize(); + chai.assert.isNotNaN(bubbleSize.width); + chai.assert.isNotNaN(bubbleSize.height); + // Check comment text. + chai.assert.equal(comment.textarea_.value, text); + } + test('Trashcan', function() { + // Create block. + this.block = createBlock(this.workspace); + // Delete block. + this.block.checkAndDelete(); + chai.assert.equal(this.workspace.getAllBlocks().length, 0); + // Open trashcan. + simulateClick(this.workspace.trashcan.svgGroup_); + // Place from trashcan. + simulateClick(this.workspace.trashcan.flyout.svgGroup_.querySelector('.blocklyDraggable')); + chai.assert.equal(this.workspace.getAllBlocks().length, 1); + // Check comment. + assertComment(this.workspace, 'test text'); + }); + test('Undo', function() { + // Create block. + this.block = createBlock(this.workspace); + // Delete block. + this.block.checkAndDelete(); + chai.assert.equal(this.workspace.getAllBlocks().length, 0); + // Undo. + this.workspace.undo(false); + chai.assert.equal(this.workspace.getAllBlocks().length, 1); + // Check comment. + assertComment(this.workspace, 'test text'); + }); + test('Redo', function() { + // Create block. + this.block = createBlock(this.workspace); + // Undo & undo. + this.workspace.undo(false); + this.workspace.undo(false); + chai.assert.equal(this.workspace.getAllBlocks().length, 0); + // Redo & redo. + this.workspace.undo(true); + this.workspace.undo(true); + chai.assert.equal(this.workspace.getAllBlocks().length, 1); + // Check comment. + assertComment(this.workspace, 'test text'); + }); + test('Toolbox', function() { + // Place from toolbox. + const toolbox = this.workspace.getToolbox(); + simulateClick(toolbox.HtmlDiv.querySelector('.blocklyTreeRow')); + simulateClick(toolbox.getFlyout().svgGroup_.querySelector('.blocklyDraggable')); + chai.assert.equal(this.workspace.getAllBlocks().length, 1); + // Check comment. + assertComment(this.workspace, 'test toolbox text'); + }); + }); +}); diff --git a/tests/mocha/comment_test.js b/tests/mocha/comment_test.js index fa3a86d6b..afee012f6 100644 --- a/tests/mocha/comment_test.js +++ b/tests/mocha/comment_test.js @@ -6,7 +6,9 @@ goog.module('Blockly.test.comments'); -const {assertEventFired, sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {assertEventFired} = goog.require('Blockly.test.helpers.events'); +const eventUtils = goog.require('Blockly.Events.utils'); +const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('Comments', function() { @@ -54,7 +56,7 @@ suite('Comments', function() { assertEditable(this.comment); assertEventFired( this.eventsFireStub, Blockly.Events.BubbleOpen, - {bubbleType: 'comment', isOpen: true}, this.workspace.id, + {bubbleType: 'comment', isOpen: true, type: eventUtils.BUBBLE_OPEN}, this.workspace.id, this.block.id); }); test('Not Editable', function() { @@ -66,8 +68,8 @@ suite('Comments', function() { assertNotEditable(this.comment); assertEventFired( this.eventsFireStub, Blockly.Events.BubbleOpen, - {bubbleType: 'comment', isOpen: true}, this.workspace.id, - this.block.id); + {bubbleType: 'comment', isOpen: true, type: eventUtils.BUBBLE_OPEN}, + this.workspace.id, this.block.id); }); test('Editable -> Not Editable', function() { this.comment.setVisible(true); @@ -79,8 +81,8 @@ suite('Comments', function() { assertNotEditable(this.comment); assertEventFired( this.eventsFireStub, Blockly.Events.BubbleOpen, - {bubbleType: 'comment', isOpen: true}, this.workspace.id, - this.block.id); + {bubbleType: 'comment', isOpen: true, type: eventUtils.BUBBLE_OPEN}, + this.workspace.id, this.block.id); }); test('Not Editable -> Editable', function() { const editableStub = sinon.stub(this.block, 'isEditable').returns(false); @@ -94,8 +96,8 @@ suite('Comments', function() { assertEditable(this.comment); assertEventFired( this.eventsFireStub, Blockly.Events.BubbleOpen, - {bubbleType: 'comment', isOpen: true}, this.workspace.id, - this.block.id); + {bubbleType: 'comment', isOpen: true, type: eventUtils.BUBBLE_OPEN}, + this.workspace.id, this.block.id); }); }); suite('Set/Get Bubble Size', function() { diff --git a/tests/mocha/connection_checker_test.js b/tests/mocha/connection_checker_test.js index 53582daaf..523fecfcc 100644 --- a/tests/mocha/connection_checker_test.js +++ b/tests/mocha/connection_checker_test.js @@ -7,7 +7,7 @@ goog.module('Blockly.test.connectionChecker'); const {ConnectionType} = goog.require('Blockly.ConnectionType'); -const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('Connection checker', function() { diff --git a/tests/mocha/connection_db_test.js b/tests/mocha/connection_db_test.js index 83ec64663..cdc343bd5 100644 --- a/tests/mocha/connection_db_test.js +++ b/tests/mocha/connection_db_test.js @@ -7,7 +7,7 @@ goog.module('Blockly.test.connectionDb'); const {ConnectionType} = goog.require('Blockly.ConnectionType'); -const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('Connection Database', function() { diff --git a/tests/mocha/connection_test.js b/tests/mocha/connection_test.js index 24e763683..37bca1240 100644 --- a/tests/mocha/connection_test.js +++ b/tests/mocha/connection_test.js @@ -6,7 +6,8 @@ goog.module('Blockly.test.connection'); -const {assertSingleDeprecationWarningCall, createDeprecationWarningStub, createGenUidStubWithReturns, defineRowBlock, defineStatementBlock, defineStackBlock, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers'); +const {createGenUidStubWithReturns, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); +const {defineRowBlock, defineStatementBlock, defineStackBlock} = goog.require('Blockly.test.helpers.blockDefinitions'); suite('Connection', function() { diff --git a/tests/mocha/contextmenu_items_test.js b/tests/mocha/contextmenu_items_test.js index 56ac23f4b..c9b9b8127 100644 --- a/tests/mocha/contextmenu_items_test.js +++ b/tests/mocha/contextmenu_items_test.js @@ -6,7 +6,7 @@ goog.module('Blockly.test.contextMenuItem'); -const {sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers'); +const {sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('Context Menu Items', function() { @@ -17,10 +17,9 @@ suite('Context Menu Items', function() { const toolbox = document.getElementById('toolbox-categories'); this.workspace = Blockly.inject('blocklyDiv', {toolbox: toolbox}); - // Declare a new registry to ensure default options are called. - new Blockly.ContextMenuRegistry(); - Blockly.ContextMenuItems.registerDefaultOptions(); this.registry = Blockly.ContextMenuRegistry.registry; + this.registry.reset(); + Blockly.ContextMenuItems.registerDefaultOptions(); }); teardown(function() { diff --git a/tests/mocha/cursor_test.js b/tests/mocha/cursor_test.js index a86268d87..77b22470a 100644 --- a/tests/mocha/cursor_test.js +++ b/tests/mocha/cursor_test.js @@ -6,7 +6,7 @@ goog.module('Blockly.test.cursor'); -const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); const {ASTNode} = goog.require('Blockly.ASTNode'); diff --git a/tests/mocha/dropdowndiv_test.js b/tests/mocha/dropdowndiv_test.js index c45aee795..6cd8dca53 100644 --- a/tests/mocha/dropdowndiv_test.js +++ b/tests/mocha/dropdowndiv_test.js @@ -6,7 +6,7 @@ goog.module('Blockly.test.dropdown'); -const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('DropDownDiv', function() { diff --git a/tests/mocha/event_test.js b/tests/mocha/event_test.js index c975fc05d..9ee53a689 100644 --- a/tests/mocha/event_test.js +++ b/tests/mocha/event_test.js @@ -6,9 +6,11 @@ goog.module('Blockly.test.event'); -const eventUtils = goog.require('Blockly.Events.utils'); -const {assertEventEquals, assertNthCallEventArgEquals, assertVariableValues, createFireChangeListenerSpy, createGenUidStubWithReturns, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers'); const {ASTNode} = goog.require('Blockly.ASTNode'); +const {assertEventEquals, assertNthCallEventArgEquals, createFireChangeListenerSpy} = goog.require('Blockly.test.helpers.events'); +const {assertVariableValues} = goog.require('Blockly.test.helpers.variables'); +const {createGenUidStubWithReturns, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); +const eventUtils = goog.require('Blockly.Events.utils'); goog.require('Blockly.WorkspaceComment'); diff --git a/tests/mocha/extensions_test.js b/tests/mocha/extensions_test.js index d4aebf38e..6c2873ff1 100644 --- a/tests/mocha/extensions_test.js +++ b/tests/mocha/extensions_test.js @@ -6,7 +6,7 @@ goog.module('Blockly.test.extensions'); -const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('Extensions', function() { @@ -244,8 +244,8 @@ suite('Extensions', function() { // Make sure all of the functions were installed correctly. chai.assert.equal(block.domToMutation(), 'domToMutationFn'); chai.assert.equal(block.mutationToDom(), 'mutationToDomFn'); - chai.assert.isFalse(Object.prototype.hasOwnProperty.call(block, 'compose')); - chai.assert.isFalse(Object.prototype.hasOwnProperty.call(block, 'decompose')); + chai.assert.isUndefined(block['compose']); + chai.assert.isUndefined(block['decompose']); }); }); }); diff --git a/tests/mocha/field_angle_test.js b/tests/mocha/field_angle_test.js index 2d3736e67..1639eb206 100644 --- a/tests/mocha/field_angle_test.js +++ b/tests/mocha/field_angle_test.js @@ -6,7 +6,9 @@ goog.module('Blockly.test.fieldAngle'); -const {createTestBlock, defineRowBlock, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers'); +const {assertFieldValue, runConstructorSuiteTests, runFromJsonSuiteTests, runSetValueTests} = goog.require('Blockly.test.helpers.fields'); +const {createTestBlock, defineRowBlock} = goog.require('Blockly.test.helpers.blockDefinitions'); +const {sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('Angle Fields', function() { @@ -60,7 +62,7 @@ suite('Angle Fields', function() { * @param {FieldTemplate} field The field to check. */ const assertFieldDefault = function(field) { - testHelpers.assertFieldValue(field, defaultFieldValue); + assertFieldValue(field, defaultFieldValue); }; /** * Asserts that the field properties are correct based on the test case. @@ -68,14 +70,14 @@ suite('Angle Fields', function() { * @param {!FieldValueTestCase} testCase The test case. */ const validTestCaseAssertField = function(field, testCase) { - testHelpers.assertFieldValue(field, testCase.expectedValue); + assertFieldValue(field, testCase.expectedValue); }; - testHelpers.runConstructorSuiteTests( + runConstructorSuiteTests( Blockly.FieldAngle, validValueTestCases, invalidValueTestCases, validTestCaseAssertField, assertFieldDefault); - testHelpers.runFromJsonSuiteTests( + runFromJsonSuiteTests( Blockly.FieldAngle, validValueTestCases, invalidValueTestCases, validTestCaseAssertField, assertFieldDefault); @@ -84,12 +86,12 @@ suite('Angle Fields', function() { setup(function() { this.field = new Blockly.FieldAngle(); }); - testHelpers.runSetValueTests( + runSetValueTests( validValueTestCases, invalidValueTestCases, defaultFieldValue); test('With source block', function() { this.field.setSourceBlock(createTestBlock()); this.field.setValue(2.5); - testHelpers.assertFieldValue(this.field, 2.5); + assertFieldValue(this.field, 2.5); }); }); suite('Value -> New Value', function() { @@ -97,12 +99,12 @@ suite('Angle Fields', function() { setup(function() { this.field = new Blockly.FieldAngle(initialValue); }); - testHelpers.runSetValueTests( + runSetValueTests( validValueTestCases, invalidValueTestCases, initialValue); test('With source block', function() { this.field.setSourceBlock(createTestBlock()); this.field.setValue(2.5); - testHelpers.assertFieldValue(this.field, 2.5); + assertFieldValue(this.field, 2.5); }); }); }); @@ -142,12 +144,12 @@ suite('Angle Fields', function() { this.field.isBeingEdited_ = true; this.field.htmlInput_.value = String(suiteInfo.value); this.field.onHtmlInputChange_(null); - testHelpers.assertFieldValue( + assertFieldValue( this.field, suiteInfo.expectedValue, String(suiteInfo.value)); }); test('When Not Editing', function() { this.field.setValue(suiteInfo.value); - testHelpers.assertFieldValue(this.field, suiteInfo.expectedValue); + assertFieldValue(this.field, suiteInfo.expectedValue); }); }); }); diff --git a/tests/mocha/field_checkbox_test.js b/tests/mocha/field_checkbox_test.js index 2c6b8eeb2..5de565984 100644 --- a/tests/mocha/field_checkbox_test.js +++ b/tests/mocha/field_checkbox_test.js @@ -6,7 +6,9 @@ goog.module('Blockly.test.fieldCheckbox'); -const {defineRowBlock, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers'); +const {assertFieldValue, runConstructorSuiteTests, runFromJsonSuiteTests, runSetValueTests} = goog.require('Blockly.test.helpers.fields'); +const {sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); +const {defineRowBlock} = goog.require('Blockly.test.helpers.blockDefinitions'); suite('Checkbox Fields', function() { @@ -61,7 +63,7 @@ suite('Checkbox Fields', function() { * @param {!Blockly.FieldCheckbox} field The field to check. */ const assertFieldDefault = function(field) { - testHelpers.assertFieldValue( + assertFieldValue( field, defaultFieldValue, defaultFieldValue.toLowerCase()); }; /** @@ -70,15 +72,15 @@ suite('Checkbox Fields', function() { * @param {!FieldValueTestCase} testCase The test case. */ const validTestCaseAssertField = function(field, testCase) { - testHelpers.assertFieldValue( + assertFieldValue( field, testCase.expectedValue, testCase.expectedValue.toLowerCase()); }; - testHelpers.runConstructorSuiteTests( + runConstructorSuiteTests( Blockly.FieldCheckbox, validValueTestCases, invalidValueTestCases, validTestCaseAssertField, assertFieldDefault); - testHelpers.runFromJsonSuiteTests( + runFromJsonSuiteTests( Blockly.FieldCheckbox, validValueTestCases, invalidValueTestCases, validTestCaseAssertField, assertFieldDefault); @@ -87,14 +89,14 @@ suite('Checkbox Fields', function() { setup(function() { this.field = new Blockly.FieldCheckbox('TRUE'); }); - testHelpers.runSetValueTests( + runSetValueTests( validValueTestCases, invalidValueTestCases, 'TRUE', 'true'); }); suite('False -> New Value', function() { setup(function() { this.field = new Blockly.FieldCheckbox('FALSE'); }); - testHelpers.runSetValueTests( + runSetValueTests( validValueTestCases, invalidValueTestCases, 'FALSE', 'false'); }); }); @@ -131,7 +133,7 @@ suite('Checkbox Fields', function() { }); test('New Value', function() { this.field.setValue(suiteInfo.value); - testHelpers.assertFieldValue( + assertFieldValue( this.field, suiteInfo.expectedValue, String(suiteInfo.expectedValue).toLowerCase()); }); diff --git a/tests/mocha/field_colour_test.js b/tests/mocha/field_colour_test.js index 97fd27c96..eb0389702 100644 --- a/tests/mocha/field_colour_test.js +++ b/tests/mocha/field_colour_test.js @@ -6,7 +6,9 @@ goog.module('Blockly.test.fieldColour'); -const {createTestBlock, defineRowBlock, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers'); +const {assertFieldValue, runConstructorSuiteTests, runFromJsonSuiteTests, runSetValueTests} = goog.require('Blockly.test.helpers.fields'); +const {createTestBlock, defineRowBlock} = goog.require('Blockly.test.helpers.blockDefinitions'); +const {sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('Colour Fields', function() { @@ -87,7 +89,7 @@ suite('Colour Fields', function() { * @param {FieldTemplate} field The field to check. */ const assertFieldDefault = function(field) { - testHelpers.assertFieldValue(field, defaultFieldValue, defaultTextValue); + assertFieldValue(field, defaultFieldValue, defaultTextValue); }; /** * Asserts that the field properties are correct based on the test case. @@ -95,15 +97,15 @@ suite('Colour Fields', function() { * @param {!FieldValueTestCase} testCase The test case. */ const validTestCaseAssertField = function(field, testCase) { - testHelpers.assertFieldValue( + assertFieldValue( field, testCase.expectedValue, testCase.expectedText); }; - testHelpers.runConstructorSuiteTests( + runConstructorSuiteTests( Blockly.FieldColour, validValueTestCases, invalidValueTestCases, validTestCaseAssertField, assertFieldDefault); - testHelpers.runFromJsonSuiteTests( + runFromJsonSuiteTests( Blockly.FieldColour, validValueTestCases, invalidValueTestCases, validTestCaseAssertField, assertFieldDefault); @@ -112,25 +114,25 @@ suite('Colour Fields', function() { setup(function() { this.field = new Blockly.FieldColour(); }); - testHelpers.runSetValueTests( + runSetValueTests( validValueTestCases, invalidValueTestCases, defaultFieldValue, defaultTextValue); test('With source block', function() { this.field.setSourceBlock(createTestBlock()); this.field.setValue('#bcbcbc'); - testHelpers.assertFieldValue(this.field, '#bcbcbc', '#bcbcbc'); + assertFieldValue(this.field, '#bcbcbc', '#bcbcbc'); }); }); suite('Value -> New Value', function() { setup(function() { this.field = new Blockly.FieldColour('#aaaaaa'); }); - testHelpers.runSetValueTests( + runSetValueTests( validValueTestCases, invalidValueTestCases, '#aaaaaa', '#aaa'); test('With source block', function() { this.field.setSourceBlock(createTestBlock()); this.field.setValue('#bcbcbc'); - testHelpers.assertFieldValue(this.field, '#bcbcbc', '#bcbcbc'); + assertFieldValue(this.field, '#bcbcbc', '#bcbcbc'); }); }); }); @@ -161,7 +163,7 @@ suite('Colour Fields', function() { }); test('New Value', function() { this.field.setValue(suiteInfo.value); - testHelpers.assertFieldValue( + assertFieldValue( this.field, suiteInfo.expectedValue, suiteInfo.expectedText); }); }); diff --git a/tests/mocha/field_dropdown_test.js b/tests/mocha/field_dropdown_test.js index 11b517b6e..1b67525f4 100644 --- a/tests/mocha/field_dropdown_test.js +++ b/tests/mocha/field_dropdown_test.js @@ -6,7 +6,9 @@ goog.module('Blockly.test.fieldDropdown'); -const {createTestBlock, defineRowBlock, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers'); +const {assertFieldValue, runConstructorSuiteTests, runFromJsonSuiteTests, runSetValueTests} = goog.require('Blockly.test.helpers.fields'); +const {createTestBlock, defineRowBlock} = goog.require('Blockly.test.helpers.blockDefinitions'); +const {sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('Dropdown Fields', function() { @@ -74,14 +76,14 @@ suite('Dropdown Fields', function() { * @param {!FieldValueTestCase} testCase The test case. */ const validTestCaseAssertField = function(field, testCase) { - testHelpers.assertFieldValue(field, testCase.expectedValue, testCase.expectedText); + assertFieldValue(field, testCase.expectedValue, testCase.expectedText); }; - testHelpers.runConstructorSuiteTests( + runConstructorSuiteTests( Blockly.FieldDropdown, validValueCreationTestCases, invalidValueCreationTestCases, validTestCaseAssertField); - testHelpers.runFromJsonSuiteTests( + runFromJsonSuiteTests( Blockly.FieldDropdown, validValueCreationTestCases, invalidValueCreationTestCases, validTestCaseAssertField); @@ -107,12 +109,12 @@ suite('Dropdown Fields', function() { this.field = new Blockly.FieldDropdown( [['a', 'A'], ['b', 'B'], ['c', 'C']]); }); - testHelpers.runSetValueTests( + runSetValueTests( validValueSetValueTestCases, invalidValueSetValueTestCases, 'A', 'a'); test('With source block', function() { this.field.setSourceBlock(createTestBlock()); this.field.setValue('B'); - testHelpers.assertFieldValue(this.field, 'B', 'b'); + assertFieldValue(this.field, 'B', 'b'); }); }); @@ -133,7 +135,7 @@ suite('Dropdown Fields', function() { }); test('New Value', function() { this.dropdownField.setValue('1B'); - testHelpers.assertFieldValue(this.dropdownField, '1A', '1a'); + assertFieldValue(this.dropdownField, '1A', '1a'); }); }); suite('Force 1s Validator', function() { @@ -144,7 +146,7 @@ suite('Dropdown Fields', function() { }); test('New Value', function() { this.dropdownField.setValue('2B'); - testHelpers.assertFieldValue(this.dropdownField, '1B', '1b'); + assertFieldValue(this.dropdownField, '1B', '1b'); }); }); suite('Returns Undefined Validator', function() { @@ -153,7 +155,7 @@ suite('Dropdown Fields', function() { }); test('New Value', function() { this.dropdownField.setValue('1B'); - testHelpers.assertFieldValue(this.dropdownField, '1B', '1b'); + assertFieldValue(this.dropdownField, '1B', '1b'); }); }); }); diff --git a/tests/mocha/field_image_test.js b/tests/mocha/field_image_test.js index fcc68fd67..62826398a 100644 --- a/tests/mocha/field_image_test.js +++ b/tests/mocha/field_image_test.js @@ -6,7 +6,8 @@ goog.module('Blockly.test.fieldImage'); -const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {assertFieldValue, runConstructorSuiteTests, runFromJsonSuiteTests, runSetValueTests} = goog.require('Blockly.test.helpers.fields'); +const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('Image Fields', function() { @@ -56,14 +57,14 @@ suite('Image Fields', function() { * @param {!FieldValueTestCase} testCase The test case. */ const validTestCaseAssertField = function(field, testCase) { - testHelpers.assertFieldValue(field, testCase.expectedValue, testCase.expectedText); + assertFieldValue(field, testCase.expectedValue, testCase.expectedText); }; - testHelpers.runConstructorSuiteTests( + runConstructorSuiteTests( Blockly.FieldImage, validValueCreationTestCases, invalidValueTestCases, validTestCaseAssertField); - testHelpers.runFromJsonSuiteTests( + runFromJsonSuiteTests( Blockly.FieldImage, validValueCreationTestCases, invalidValueTestCases, validTestCaseAssertField); @@ -80,7 +81,7 @@ suite('Image Fields', function() { setup(function() { this.field = new Blockly.FieldImage('src', 1, 1, 'alt'); }); - testHelpers.runSetValueTests( + runSetValueTests( validValueSetValueTestCases, invalidValueTestCases, 'src', 'alt'); }); @@ -126,15 +127,15 @@ suite('Image Fields', function() { }); test('Null', function() { this.imageField.setAlt(null); - testHelpers.assertFieldValue(this.imageField, 'src', ''); + assertFieldValue(this.imageField, 'src', ''); }); test('Empty String', function() { this.imageField.setAlt(''); - testHelpers.assertFieldValue(this.imageField, 'src', ''); + assertFieldValue(this.imageField, 'src', ''); }); test('Good Alt', function() { this.imageField.setAlt('newAlt'); - testHelpers.assertFieldValue(this.imageField, 'src', 'newAlt'); + assertFieldValue(this.imageField, 'src', 'newAlt'); }); }); test('JS Configuration - Simple', function() { diff --git a/tests/mocha/field_label_serializable_test.js b/tests/mocha/field_label_serializable_test.js index 212173b2a..b5ba42824 100644 --- a/tests/mocha/field_label_serializable_test.js +++ b/tests/mocha/field_label_serializable_test.js @@ -6,7 +6,9 @@ goog.module('Blockly.test.fieldLabelSerialization'); -const {createTestBlock, defineRowBlock, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers'); +const {assertFieldValue, runConstructorSuiteTests, runFromJsonSuiteTests, runSetValueTests} = goog.require('Blockly.test.helpers.fields'); +const {sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); +const {createTestBlock, defineRowBlock} = goog.require('Blockly.test.helpers.blockDefinitions'); suite('Label Serializable Fields', function() { @@ -53,7 +55,7 @@ suite('Label Serializable Fields', function() { * @param {!Blockly.FieldLabelSerializable} field The field to check. */ const assertFieldDefault = function(field) { - testHelpers.assertFieldValue(field, defaultFieldValue); + assertFieldValue(field, defaultFieldValue); }; /** * Asserts that the field properties are correct based on the test case. @@ -61,14 +63,14 @@ suite('Label Serializable Fields', function() { * @param {!FieldValueTestCase} testCase The test case. */ const validTestCaseAssertField = function(field, testCase) { - testHelpers.assertFieldValue(field, testCase.expectedValue); + assertFieldValue(field, testCase.expectedValue); }; - testHelpers.runConstructorSuiteTests( + runConstructorSuiteTests( Blockly.FieldLabelSerializable, validValueTestCases, invalidValueTestCases, validTestCaseAssertField, assertFieldDefault); - testHelpers.runFromJsonSuiteTests( + runFromJsonSuiteTests( Blockly.FieldLabelSerializable, validValueTestCases, invalidValueTestCases, validTestCaseAssertField, assertFieldDefault); @@ -77,12 +79,12 @@ suite('Label Serializable Fields', function() { setup(function() { this.field = new Blockly.FieldLabelSerializable(); }); - testHelpers.runSetValueTests( + runSetValueTests( validValueTestCases, invalidValueTestCases, defaultFieldValue); test('With source block', function() { this.field.setSourceBlock(createTestBlock()); this.field.setValue('value'); - testHelpers.assertFieldValue(this.field, 'value'); + assertFieldValue(this.field, 'value'); }); }); suite('Value -> New Value', function() { @@ -90,12 +92,12 @@ suite('Label Serializable Fields', function() { setup(function() { this.field = new Blockly.FieldLabelSerializable(initialValue); }); - testHelpers.runSetValueTests( + runSetValueTests( validValueTestCases, invalidValueTestCases, initialValue); test('With source block', function() { this.field.setSourceBlock(createTestBlock()); this.field.setValue('value'); - testHelpers.assertFieldValue(this.field, 'value'); + assertFieldValue(this.field, 'value'); }); }); }); diff --git a/tests/mocha/field_label_test.js b/tests/mocha/field_label_test.js index 52fedef90..62f088b96 100644 --- a/tests/mocha/field_label_test.js +++ b/tests/mocha/field_label_test.js @@ -6,7 +6,9 @@ goog.module('Blockly.test.fieldLabel'); -const {createTestBlock, sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {assertFieldValue, runConstructorSuiteTests, runFromJsonSuiteTests, runSetValueTests} = goog.require('Blockly.test.helpers.fields'); +const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); +const {createTestBlock} = goog.require('Blockly.test.helpers.blockDefinitions'); suite('Label Fields', function() { @@ -53,7 +55,7 @@ suite('Label Fields', function() { * @param {!Blockly.FieldLabel} field The field to check. */ const assertFieldDefault = function(field) { - testHelpers.assertFieldValue(field, defaultFieldValue); + assertFieldValue(field, defaultFieldValue); }; /** * Asserts that the field properties are correct based on the test case. @@ -61,14 +63,14 @@ suite('Label Fields', function() { * @param {!FieldValueTestCase} testCase The test case. */ const validTestCaseAssertField = function(field, testCase) { - testHelpers.assertFieldValue(field, testCase.expectedValue); + assertFieldValue(field, testCase.expectedValue); }; - testHelpers.runConstructorSuiteTests( + runConstructorSuiteTests( Blockly.FieldLabel, validValueTestCases, invalidValueTestCases, validTestCaseAssertField, assertFieldDefault); - testHelpers.runFromJsonSuiteTests( + runFromJsonSuiteTests( Blockly.FieldLabel, validValueTestCases, invalidValueTestCases, validTestCaseAssertField, assertFieldDefault); @@ -77,12 +79,12 @@ suite('Label Fields', function() { setup(function() { this.field = new Blockly.FieldLabel(); }); - testHelpers.runSetValueTests( + runSetValueTests( validValueTestCases, invalidValueTestCases, defaultFieldValue); test('With source block', function() { this.field.setSourceBlock(createTestBlock()); this.field.setValue('value'); - testHelpers.assertFieldValue(this.field, 'value'); + assertFieldValue(this.field, 'value'); }); }); suite('Value -> New Value', function() { @@ -90,12 +92,12 @@ suite('Label Fields', function() { setup(function() { this.field = new Blockly.FieldLabel(initialValue); }); - testHelpers.runSetValueTests( + runSetValueTests( validValueTestCases, invalidValueTestCases, initialValue); test('With source block', function() { this.field.setSourceBlock(createTestBlock()); this.field.setValue('value'); - testHelpers.assertFieldValue(this.field, 'value'); + assertFieldValue(this.field, 'value'); }); }); }); diff --git a/tests/mocha/field_multilineinput_test.js b/tests/mocha/field_multilineinput_test.js index 8283df431..6096384fe 100644 --- a/tests/mocha/field_multilineinput_test.js +++ b/tests/mocha/field_multilineinput_test.js @@ -6,7 +6,10 @@ goog.module('Blockly.test.fieldMultiline'); -const {createTestBlock, defineRowBlock, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers'); +const {assertFieldValue, runConstructorSuiteTests, runFromJsonSuiteTests, runSetValueTests} = goog.require('Blockly.test.helpers.fields'); +const {createTestBlock, defineRowBlock} = goog.require('Blockly.test.helpers.blockDefinitions'); +const {sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); +const {runCodeGenerationTestSuites} = goog.require('Blockly.test.helpers.codeGeneration'); suite('Multiline Input Fields', function() { @@ -55,7 +58,7 @@ suite('Multiline Input Fields', function() { * @param {!Blockly.FieldMultilineInput} field The field to check. */ const assertFieldDefault = function(field) { - testHelpers.assertFieldValue(field, defaultFieldValue); + assertFieldValue(field, defaultFieldValue); }; /** * Asserts that the field properties are correct based on the test case. @@ -63,14 +66,14 @@ suite('Multiline Input Fields', function() { * @param {!FieldValueTestCase} testCase The test case. */ const validTestCaseAssertField = function(field, testCase) { - testHelpers.assertFieldValue(field, testCase.expectedValue); + assertFieldValue(field, testCase.expectedValue); }; - testHelpers.runConstructorSuiteTests( + runConstructorSuiteTests( Blockly.FieldMultilineInput, validValueTestCases, invalidValueTestCases, validTestCaseAssertField, assertFieldDefault); - testHelpers.runFromJsonSuiteTests( + runFromJsonSuiteTests( Blockly.FieldMultilineInput, validValueTestCases, invalidValueTestCases, validTestCaseAssertField, assertFieldDefault); @@ -79,12 +82,12 @@ suite('Multiline Input Fields', function() { setup(function() { this.field = new Blockly.FieldMultilineInput(); }); - testHelpers.runSetValueTests( + runSetValueTests( validValueTestCases, invalidValueTestCases, defaultFieldValue); test('With source block', function() { this.field.setSourceBlock(createTestBlock()); this.field.setValue('value'); - testHelpers.assertFieldValue(this.field, 'value'); + assertFieldValue(this.field, 'value'); }); }); suite('Value -> New Value', function() { @@ -92,12 +95,12 @@ suite('Multiline Input Fields', function() { setup(function() { this.field = new Blockly.FieldMultilineInput(initialValue); }); - testHelpers.runSetValueTests( + runSetValueTests( validValueTestCases, invalidValueTestCases, initialValue); test('With source block', function() { this.field.setSourceBlock(createTestBlock()); this.field.setValue('value'); - testHelpers.assertFieldValue(this.field, 'value'); + assertFieldValue(this.field, 'value'); }); }); }); @@ -156,7 +159,7 @@ suite('Multiline Input Fields', function() { createBlock: createBlockFn('bark bark\n bark bark bark\n bark bar bark bark\n')}, ]}, ]; - testHelpers.runCodeGenerationTestSuites(testSuites); + runCodeGenerationTestSuites(testSuites); }); suite('Serialization', function() { diff --git a/tests/mocha/field_number_test.js b/tests/mocha/field_number_test.js index 379bb5b83..fd23cc661 100644 --- a/tests/mocha/field_number_test.js +++ b/tests/mocha/field_number_test.js @@ -6,7 +6,10 @@ goog.module('Blockly.test.fieldNumber'); -const {defineRowBlock, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers'); +const {assertFieldValue, runConstructorSuiteTests, runFromJsonSuiteTests, runSetValueTests} = goog.require('Blockly.test.helpers.fields'); +const {defineRowBlock} = goog.require('Blockly.test.helpers.blockDefinitions'); +const {sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); +const {runTestCases} = goog.require('Blockly.test.helpers.common'); suite('Number Fields', function() { @@ -64,7 +67,7 @@ suite('Number Fields', function() { */ function assertNumberField(field, expectedMin, expectedMax, expectedPrecision, expectedValue) { - testHelpers.assertFieldValue(field, expectedValue); + assertFieldValue(field, expectedValue); chai.assert.equal(field.getMin(), expectedMin, 'Min'); chai.assert.equal(field.getMax(), expectedMax, 'Max'); chai.assert.equal( @@ -88,11 +91,11 @@ suite('Number Fields', function() { testCase.expectedValue, testCase.expectedValue); }; - testHelpers.runConstructorSuiteTests( + runConstructorSuiteTests( Blockly.FieldNumber, validValueTestCases, invalidValueTestCases, validTestCaseAssertField, assertFieldDefault); - testHelpers.runFromJsonSuiteTests( + runFromJsonSuiteTests( Blockly.FieldNumber, validValueTestCases, invalidValueTestCases, validTestCaseAssertField, assertFieldDefault); @@ -101,7 +104,7 @@ suite('Number Fields', function() { setup(function() { this.field = new Blockly.FieldNumber(); }); - testHelpers.runSetValueTests( + runSetValueTests( validValueTestCases, invalidValueTestCases, defaultFieldValue); }); suite('Value -> New Value', function() { @@ -109,7 +112,7 @@ suite('Number Fields', function() { setup(function() { this.field = new Blockly.FieldNumber(initialValue); }); - testHelpers.runSetValueTests( + runSetValueTests( validValueTestCases, invalidValueTestCases, initialValue); }); suite('Constraints', function() { @@ -127,11 +130,11 @@ suite('Number Fields', function() { expectedValue: 123}, ]; suite('Precision', function() { - testHelpers.runTestCases(testCases, function(testCase) { + runTestCases(testCases, function(testCase) { return function() { const field = Blockly.FieldNumber.fromJson(testCase.json); field.setValue(testCase.value); - testHelpers.assertFieldValue(field, testCase.expectedValue); + assertFieldValue(field, testCase.expectedValue); }; }); test('Null', function() { @@ -144,7 +147,7 @@ suite('Number Fields', function() { const field = Blockly.FieldNumber.fromJson(testCase.json); testCase.values.forEach(function(value, i) { field.setValue(value); - testHelpers.assertFieldValue( + assertFieldValue( field, testCase.expectedValues[i]); }); }; @@ -158,7 +161,7 @@ suite('Number Fields', function() { {title: '+10', json: {min: 10}, values: [-20, 0, 20], expectedValues: [10, 10, 20]}, ]; - testHelpers.runTestCases(testCases, setValueBoundsTestFn); + runTestCases(testCases, setValueBoundsTestFn); test('Null', function() { const field = Blockly.FieldNumber.fromJson({min: null}); chai.assert.equal(field.getMin(), -Infinity); @@ -173,7 +176,7 @@ suite('Number Fields', function() { {title: '+10', json: {max: 10}, values: [-20, 0, 20], expectedValues: [-20, 0, 10]}, ]; - testHelpers.runTestCases(testCases, setValueBoundsTestFn); + runTestCases(testCases, setValueBoundsTestFn); test('Null', function() { const field = Blockly.FieldNumber.fromJson({max: null}); chai.assert.equal(field.getMax(), Infinity); @@ -217,12 +220,12 @@ suite('Number Fields', function() { this.field.isBeingEdited_ = true; this.field.htmlInput_.value = String(suiteInfo.value); this.field.onHtmlInputChange_(null); - testHelpers.assertFieldValue( + assertFieldValue( this.field, suiteInfo.expectedValue, String(suiteInfo.value)); }); test('When Not Editing', function() { this.field.setValue(suiteInfo.value); - testHelpers.assertFieldValue(this.field, suiteInfo.expectedValue); + assertFieldValue(this.field, suiteInfo.expectedValue); }); }); }); diff --git a/tests/mocha/field_registry_test.js b/tests/mocha/field_registry_test.js index 0e49b5620..394081365 100644 --- a/tests/mocha/field_registry_test.js +++ b/tests/mocha/field_registry_test.js @@ -6,17 +6,20 @@ goog.module('Blockly.test.fieldRegistry'); -const {createDeprecationWarningStub, sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {createDeprecationWarningStub} = goog.require('Blockly.test.helpers.warnings'); +const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('Field Registry', function() { - function CustomFieldType(value) { - CustomFieldType.superClass_.constructor.call(this, value); + class CustomFieldType extends Blockly.Field { + constructor(value) { + super(value); + } + + static fromJson(options) { + return new CustomFieldType(options['value']); + } } - Blockly.utils.object.inherits(CustomFieldType, Blockly.Field); - CustomFieldType.fromJson = function(options) { - return new CustomFieldType(options['value']); - }; setup(function() { sharedTestSetup.call(this); diff --git a/tests/mocha/field_test.js b/tests/mocha/field_test.js index 6ef1cbf24..4d1887aa0 100644 --- a/tests/mocha/field_test.js +++ b/tests/mocha/field_test.js @@ -6,7 +6,8 @@ goog.module('Blockly.test.fieldTest'); -const {addBlockTypeToCleanup, addMessageToCleanup, createDeprecationWarningStub, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers'); +const {addBlockTypeToCleanup, addMessageToCleanup, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); +const {createDeprecationWarningStub} = goog.require('Blockly.test.helpers.warnings'); suite('Abstract Fields', function() { @@ -20,29 +21,40 @@ suite('Abstract Fields', function() { suite('Is Serializable', function() { // Both EDITABLE and SERIALIZABLE are default. - function FieldDefault() { - this.name = 'NAME'; + class FieldDefault extends Blockly.Field { + constructor() { + super(); + this.name = 'NAME'; + } } - FieldDefault.prototype = Object.create(Blockly.Field.prototype); + // EDITABLE is false and SERIALIZABLE is default. - function FieldFalseDefault() { - this.name = 'NAME'; + class FieldFalseDefault extends Blockly.Field { + constructor() { + super(); + this.name = 'NAME'; + this.EDITABLE = false; + } } - FieldFalseDefault.prototype = Object.create(Blockly.Field.prototype); - FieldFalseDefault.prototype.EDITABLE = false; + // EDITABLE is default and SERIALIZABLE is true. - function FieldDefaultTrue() { - this.name = 'NAME'; + class FieldDefaultTrue extends Blockly.Field { + constructor() { + super(); + this.name = 'NAME'; + this.SERIALIZABLE = true; + } } - FieldDefaultTrue.prototype = Object.create(Blockly.Field.prototype); - FieldDefaultTrue.prototype.SERIALIZABLE = true; + // EDITABLE is false and SERIALIZABLE is true. - function FieldFalseTrue() { - this.name = 'NAME'; + class FieldFalseTrue extends Blockly.Field { + constructor() { + super(); + this.name = 'NAME'; + this.EDITABLE = false; + this.SERIALIZABLE = true; + } } - FieldFalseTrue.prototype = Object.create(Blockly.Field.prototype); - FieldFalseTrue.prototype.EDITABLE = false; - FieldFalseTrue.prototype.SERIALIZABLE = true; /* Test Backwards Compatibility */ test('Editable Default(true), Serializable Default(false)', function() { @@ -585,14 +597,15 @@ suite('Abstract Fields', function() { suite('Customization', function() { // All this field does is wrap the abstract field. - function CustomField(opt_config) { - CustomField.superClass_.constructor.call( - this, 'value', null, opt_config); + class CustomField extends Blockly.Field { + constructor(opt_config) { + super('value', null, opt_config); + } + + static fromJson(options) { + return new CustomField(options); + } } - Blockly.utils.object.inherits(CustomField, Blockly.Field); - CustomField.fromJson = function(options) { - return new CustomField(options); - }; suite('Tooltip', function() { test('JS Constructor', function() { diff --git a/tests/mocha/field_textinput_test.js b/tests/mocha/field_textinput_test.js index d95e31204..efd01d702 100644 --- a/tests/mocha/field_textinput_test.js +++ b/tests/mocha/field_textinput_test.js @@ -6,7 +6,9 @@ goog.module('Blockly.test.fieldTextInput'); -const {createTestBlock, defineRowBlock, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers'); +const {assertFieldValue, runConstructorSuiteTests, runFromJsonSuiteTests, runSetValueTests} = goog.require('Blockly.test.helpers.fields'); +const {createTestBlock, defineRowBlock} = goog.require('Blockly.test.helpers.blockDefinitions'); +const {sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('Text Input Fields', function() { @@ -53,7 +55,7 @@ suite('Text Input Fields', function() { * @param {!Blockly.FieldTextInput} field The field to check. */ const assertFieldDefault = function(field) { - testHelpers.assertFieldValue(field, defaultFieldValue); + assertFieldValue(field, defaultFieldValue); }; /** * Asserts that the field properties are correct based on the test case. @@ -61,14 +63,14 @@ suite('Text Input Fields', function() { * @param {!FieldValueTestCase} testCase The test case. */ const validTestCaseAssertField = function(field, testCase) { - testHelpers.assertFieldValue(field, testCase.expectedValue); + assertFieldValue(field, testCase.expectedValue); }; - testHelpers.runConstructorSuiteTests( + runConstructorSuiteTests( Blockly.FieldTextInput, validValueTestCases, invalidValueTestCases, validTestCaseAssertField, assertFieldDefault); - testHelpers.runFromJsonSuiteTests( + runFromJsonSuiteTests( Blockly.FieldTextInput, validValueTestCases, invalidValueTestCases, validTestCaseAssertField, assertFieldDefault); @@ -77,12 +79,12 @@ suite('Text Input Fields', function() { setup(function() { this.field = new Blockly.FieldTextInput(); }); - testHelpers.runSetValueTests( + runSetValueTests( validValueTestCases, invalidValueTestCases, defaultFieldValue); test('With source block', function() { this.field.setSourceBlock(createTestBlock()); this.field.setValue('value'); - testHelpers.assertFieldValue(this.field, 'value'); + assertFieldValue(this.field, 'value'); }); }); suite('Value -> New Value', function() { @@ -90,12 +92,12 @@ suite('Text Input Fields', function() { setup(function() { this.field = new Blockly.FieldTextInput(initialValue); }); - testHelpers.runSetValueTests( + runSetValueTests( validValueTestCases, invalidValueTestCases, initialValue); test('With source block', function() { this.field.setSourceBlock(createTestBlock()); this.field.setValue('value'); - testHelpers.assertFieldValue(this.field, 'value'); + assertFieldValue(this.field, 'value'); }); }); }); @@ -136,12 +138,12 @@ suite('Text Input Fields', function() { this.field.isBeingEdited_ = true; this.field.htmlInput_.value = suiteInfo.value; this.field.onHtmlInputChange_(null); - testHelpers.assertFieldValue( + assertFieldValue( this.field, suiteInfo.expectedValue, suiteInfo.value); }); test('When Not Editing', function() { this.field.setValue(suiteInfo.value); - testHelpers.assertFieldValue(this.field, suiteInfo.expectedValue); + assertFieldValue(this.field, suiteInfo.expectedValue); }); }); }); diff --git a/tests/mocha/field_variable_test.js b/tests/mocha/field_variable_test.js index 4a93ccf41..2eafb6f42 100644 --- a/tests/mocha/field_variable_test.js +++ b/tests/mocha/field_variable_test.js @@ -6,7 +6,9 @@ goog.module('Blockly.test.fieldVariable'); -const {createGenUidStubWithReturns, createTestBlock, defineRowBlock, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers'); +const {assertFieldValue, runConstructorSuiteTests, runFromJsonSuiteTests, runSetValueTests} = goog.require('Blockly.test.helpers.fields'); +const {createGenUidStubWithReturns, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); +const {createTestBlock, defineRowBlock} = goog.require('Blockly.test.helpers.blockDefinitions'); suite('Variable Fields', function() { @@ -83,7 +85,7 @@ suite('Variable Fields', function() { * @param {!Blockly.FieldVariable} field The field to check. */ const assertFieldDefault = function(field) { - testHelpers.assertFieldValue(field, FAKE_ID, defaultFieldName); + assertFieldValue(field, FAKE_ID, defaultFieldName); }; /** * Asserts that the field properties are correct based on the test case. @@ -91,15 +93,15 @@ suite('Variable Fields', function() { * @param {!FieldValueTestCase} testCase The test case. */ const validTestCaseAssertField = function(field, testCase) { - testHelpers.assertFieldValue(field, FAKE_ID, testCase.expectedText); + assertFieldValue(field, FAKE_ID, testCase.expectedText); }; - testHelpers.runConstructorSuiteTests( + runConstructorSuiteTests( Blockly.FieldVariable, validValueCreationTestCases, invalidValueCreationTestCases, validTestCaseAssertField, assertFieldDefault, customCreateWithJs); - testHelpers.runFromJsonSuiteTests( + runFromJsonSuiteTests( Blockly.FieldVariable, validValueCreationTestCases, invalidValueCreationTestCases, validTestCaseAssertField, assertFieldDefault, customCreateWithJson); @@ -149,7 +151,7 @@ suite('Variable Fields', function() { teardown(function() { console.warn = this.nativeConsoleWarn; }); - testHelpers.runSetValueTests(validValueTestCases, invalidValueTestCases, + runSetValueTests(validValueTestCases, invalidValueTestCases, FAKE_ID, defaultFieldName); }); @@ -215,7 +217,7 @@ suite('Variable Fields', function() { }); test('New Value', function() { this.variableField.setValue('id2'); - testHelpers.assertFieldValue(this.variableField, 'id1', 'name1'); + assertFieldValue(this.variableField, 'id1', 'name1'); }); }); suite('Force \'id\' ID Validator', function() { @@ -228,7 +230,7 @@ suite('Variable Fields', function() { // Must create the var so that the field doesn't throw an error. this.workspace.createVariable('thing2', null, 'other2'); this.variableField.setValue('other2'); - testHelpers.assertFieldValue(this.variableField, 'id2', 'name2'); + assertFieldValue(this.variableField, 'id2', 'name2'); }); }); suite('Returns Undefined Validator', function() { @@ -237,7 +239,7 @@ suite('Variable Fields', function() { }); test('New Value', function() { this.variableField.setValue('id2'); - testHelpers.assertFieldValue(this.variableField, 'id2', 'name2'); + assertFieldValue(this.variableField, 'id2', 'name2'); }); }); }); diff --git a/tests/mocha/flyout_test.js b/tests/mocha/flyout_test.js index 8f3e56693..8fbc38659 100644 --- a/tests/mocha/flyout_test.js +++ b/tests/mocha/flyout_test.js @@ -6,8 +6,8 @@ goog.module('Blockly.test.flyout'); -const {defineStackBlock, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers'); -const {getBasicToolbox, getChildItem, getCollapsibleItem, getDeeplyNestedJSON, getInjectedToolbox, getNonCollapsibleItem, getProperSimpleJson, getSeparator, getSimpleJson, getXmlArray} = goog.require('Blockly.test.toolboxHelpers'); +const {defineStackBlock, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); +const {getBasicToolbox, getChildItem, getCollapsibleItem, getDeeplyNestedJSON, getInjectedToolbox, getNonCollapsibleItem, getProperSimpleJson, getSeparator, getSimpleJson, getXmlArray} = goog.require('Blockly.test.helpers.toolboxDefinitions'); suite('Flyout', function() { diff --git a/tests/mocha/generator_test.js b/tests/mocha/generator_test.js index 2231263d2..4167d44f6 100644 --- a/tests/mocha/generator_test.js +++ b/tests/mocha/generator_test.js @@ -11,7 +11,7 @@ goog.require('Blockly.JavaScript'); goog.require('Blockly.Lua'); goog.require('Blockly.PHP'); goog.require('Blockly.Python'); -const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('Generator', function() { diff --git a/tests/mocha/gesture_test.js b/tests/mocha/gesture_test.js index 8ecaee0de..6e21287de 100644 --- a/tests/mocha/gesture_test.js +++ b/tests/mocha/gesture_test.js @@ -6,7 +6,11 @@ goog.module('Blockly.test.gesture'); -const {assertEventFired, assertEventNotFired, defineBasicBlockWithField, dispatchPointerEvent, sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {assertEventFired, assertEventNotFired} = goog.require('Blockly.test.helpers.events'); +const {defineBasicBlockWithField} = goog.require('Blockly.test.helpers.blockDefinitions'); +const {dispatchPointerEvent} = goog.require('Blockly.test.helpers.userInput'); +const eventUtils = goog.require('Blockly.Events.utils'); +const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('Gesture', function() { @@ -34,8 +38,8 @@ suite('Gesture', function() { assertEventFired(eventsFireStub, Blockly.Events.Selected, - {oldElementId: null, newElementId: block.id}, fieldWorkspace.id); - assertEventNotFired(eventsFireStub, Blockly.Events.Click, {}); + {oldElementId: null, newElementId: block.id, type: eventUtils.SELECTED}, fieldWorkspace.id); + assertEventNotFired(eventsFireStub, Blockly.Events.Click, {type: eventUtils.CLICK}); } function getTopFlyoutBlock(flyout) { diff --git a/tests/mocha/index.html b/tests/mocha/index.html index b054c4af8..27edd9d6a 100644 --- a/tests/mocha/index.html +++ b/tests/mocha/index.html @@ -18,16 +18,16 @@ } -
+ - @@ -43,15 +43,15 @@ goog.require('Blockly.PHP.texts'); goog.require('Blockly.Python'); goog.require('Blockly.Python.texts'); - goog.require('Blockly.blocks.colour'); - goog.require('Blockly.blocks.logic'); - goog.require('Blockly.blocks.lists'); - goog.require('Blockly.blocks.loops'); - goog.require('Blockly.blocks.math'); - goog.require('Blockly.blocks.procedures'); - goog.require('Blockly.blocks.texts'); - goog.require('Blockly.blocks.variables'); - goog.require('Blockly.blocks.variablesDynamic'); + goog.require('Blockly.libraryBlocks.colour'); + goog.require('Blockly.libraryBlocks.logic'); + goog.require('Blockly.libraryBlocks.lists'); + goog.require('Blockly.libraryBlocks.loops'); + goog.require('Blockly.libraryBlocks.math'); + goog.require('Blockly.libraryBlocks.procedures'); + goog.require('Blockly.libraryBlocks.texts'); + goog.require('Blockly.libraryBlocks.variables'); + goog.require('Blockly.libraryBlocks.variablesDynamic'); // Run tests. goog.require('Blockly.test.astNode'); @@ -60,6 +60,7 @@ goog.require('Blockly.test.blockJson'); goog.require('Blockly.test.blocks'); goog.require('Blockly.test.comments'); + goog.require('Blockly.test.commentDeserialization'); goog.require('Blockly.test.connectionChecker'); goog.require('Blockly.test.connectionDb'); goog.require('Blockly.test.connection'); @@ -174,10 +175,16 @@ diff --git a/tests/mocha/input_test.js b/tests/mocha/input_test.js index 3a7f1055b..cd6997212 100644 --- a/tests/mocha/input_test.js +++ b/tests/mocha/input_test.js @@ -6,7 +6,7 @@ goog.module('Blockly.test.input'); -const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('Inputs', function() { diff --git a/tests/mocha/insertion_marker_test.js b/tests/mocha/insertion_marker_test.js index 318d25306..53958b55b 100644 --- a/tests/mocha/insertion_marker_test.js +++ b/tests/mocha/insertion_marker_test.js @@ -6,7 +6,7 @@ goog.module('Blockly.test.insertionMarker'); -const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('InsertionMarkers', function() { diff --git a/tests/mocha/jso_deserialization_test.js b/tests/mocha/jso_deserialization_test.js index 3a0d51f8e..7f89f1189 100644 --- a/tests/mocha/jso_deserialization_test.js +++ b/tests/mocha/jso_deserialization_test.js @@ -6,7 +6,9 @@ goog.module('Blockly.test.jsoDeserialization'); -const {assertEventFired, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers'); +const {sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); +const {assertEventFired} = goog.require('Blockly.test.helpers.events'); +const eventUtils = goog.require('Blockly.Events.utils'); suite('JSO Deserialization', function() { @@ -38,7 +40,7 @@ suite('JSO Deserialization', function() { assertEventFired( this.eventsFireStub, Blockly.Events.FinishedLoading, - {}, + {type: eventUtils.FINISHED_LOADING}, this.workspace.id); }); @@ -58,9 +60,8 @@ suite('JSO Deserialization', function() { Blockly.Events.setGroup('my group'); Blockly.serialization.workspaces.load(state, this.workspace); assertEventFired( - this.eventsFireStub, - Blockly.Events.FinishedLoading, - {'group': 'my group'}, + this.eventsFireStub, Blockly.Events.FinishedLoading, + {'group': 'my group', "type": eventUtils.FINISHED_LOADING}, this.workspace.id); }); @@ -107,13 +108,12 @@ suite('JSO Deserialization', function() { }; Blockly.serialization.workspaces.load(state, this.workspace); assertEventFired( - this.eventsFireStub, - Blockly.Events.VarCreate, - { + this.eventsFireStub, Blockly.Events.VarCreate, { 'varName': 'test', 'varId': 'testId', 'varType': '', 'recordUndo': false, + "type": eventUtils.VAR_CREATE, }, this.workspace.id); }); @@ -129,13 +129,12 @@ suite('JSO Deserialization', function() { }; Blockly.serialization.workspaces.load(state, this.workspace, {recordUndo: true}); assertEventFired( - this.eventsFireStub, - Blockly.Events.VarCreate, - { + this.eventsFireStub, Blockly.Events.VarCreate, { 'varName': 'test', 'varId': 'testId', 'varType': '', 'recordUndo': true, + "type": eventUtils.VAR_CREATE, }, this.workspace.id); }); @@ -152,13 +151,12 @@ suite('JSO Deserialization', function() { Blockly.Events.setGroup('my group'); Blockly.serialization.workspaces.load(state, this.workspace); assertEventFired( - this.eventsFireStub, - Blockly.Events.VarCreate, - { + this.eventsFireStub, Blockly.Events.VarCreate, { 'varName': 'test', 'varId': 'testId', 'varType': '', 'group': 'my group', + "type": eventUtils.VAR_CREATE, }, this.workspace.id); }); @@ -216,9 +214,12 @@ suite('JSO Deserialization', function() { }, 0); chai.assert.equal(count, 1); assertEventFired( - this.eventsFireStub, - Blockly.Events.VarCreate, - {'varName': 'test', 'varId': 'testId', 'varType': ''}, + this.eventsFireStub, Blockly.Events.VarCreate, { + 'varName': 'test', + 'varId': 'testId', + 'varType': '', + "type": eventUtils.VAR_CREATE, + }, this.workspace.id); }); }); @@ -242,7 +243,7 @@ suite('JSO Deserialization', function() { assertEventFired( this.eventsFireStub, Blockly.Events.BlockCreate, - {'recordUndo': false}, + {'recordUndo': false, "type": eventUtils.BLOCK_CREATE}, this.workspace.id, 'testId'); }); @@ -264,7 +265,7 @@ suite('JSO Deserialization', function() { assertEventFired( this.eventsFireStub, Blockly.Events.BlockCreate, - {'recordUndo': true}, + {'recordUndo': true, "type": eventUtils.BLOCK_CREATE}, this.workspace.id, 'testId'); }); @@ -287,7 +288,7 @@ suite('JSO Deserialization', function() { assertEventFired( this.eventsFireStub, Blockly.Events.BlockCreate, - {'group': 'my group'}, + {'group': 'my group', "type": eventUtils.BLOCK_CREATE}, this.workspace.id, 'testId'); }); @@ -348,7 +349,7 @@ suite('JSO Deserialization', function() { assertEventFired( this.eventsFireStub, Blockly.Events.BlockCreate, - {}, + {type: eventUtils.BLOCK_CREATE}, this.workspace.id, 'id1'); }); @@ -383,7 +384,7 @@ suite('JSO Deserialization', function() { assertEventFired( this.eventsFireStub, Blockly.Events.BlockCreate, - {'recordUndo': true}, + {'recordUndo': true, "type": eventUtils.BLOCK_CREATE}, this.workspace.id, 'testId'); }); @@ -400,7 +401,7 @@ suite('JSO Deserialization', function() { assertEventFired( this.eventsFireStub, Blockly.Events.BlockCreate, - {'group': 'my group'}, + {'group': 'my group', "type": eventUtils.BLOCK_CREATE}, this.workspace.id, 'testId'); }); @@ -662,7 +663,7 @@ suite('JSO Deserialization', function() { Blockly.serialization.workspaces.load( {'first': {}, 'third': {}, 'second': {}}, this.workspace); - + Blockly.serialization.registry.unregister('first'); Blockly.serialization.registry.unregister('second'); Blockly.serialization.registry.unregister('third'); diff --git a/tests/mocha/jso_serialization_test.js b/tests/mocha/jso_serialization_test.js index 02d97e34e..d4abd5ffb 100644 --- a/tests/mocha/jso_serialization_test.js +++ b/tests/mocha/jso_serialization_test.js @@ -6,7 +6,8 @@ goog.module('Blockly.test.jsoSerialization'); -const {defineStackBlock, defineRowBlock, defineStatementBlock, createGenUidStubWithReturns, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers'); +const {createGenUidStubWithReturns, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); +const {defineRowBlock, defineStackBlock, defineStatementBlock} = goog.require('Blockly.test.helpers.blockDefinitions'); suite('JSO Serialization', function() { diff --git a/tests/mocha/json_test.js b/tests/mocha/json_test.js index 0a0b032b2..3e20926cb 100644 --- a/tests/mocha/json_test.js +++ b/tests/mocha/json_test.js @@ -6,7 +6,8 @@ goog.module('Blockly.test.json'); -const {addMessageToCleanup, assertNoWarnings, assertWarnings, sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {addMessageToCleanup, sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); +const {assertNoWarnings, assertWarnings} = goog.require('Blockly.test.helpers.warnings'); suite('JSON Block Definitions', function() { diff --git a/tests/mocha/keydown_test.js b/tests/mocha/keydown_test.js index 9236e3784..a5e695448 100644 --- a/tests/mocha/keydown_test.js +++ b/tests/mocha/keydown_test.js @@ -6,7 +6,9 @@ goog.module('Blockly.test.keydown'); -const {createKeyDownEvent, defineStackBlock, sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {createKeyDownEvent} = goog.require('Blockly.test.helpers.userInput'); +const {defineStackBlock} = goog.require('Blockly.test.helpers.blockDefinitions'); +const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('Key Down', function() { diff --git a/tests/mocha/logic_ternary_test.js b/tests/mocha/logic_ternary_test.js index 36eb0192e..5f4c82580 100644 --- a/tests/mocha/logic_ternary_test.js +++ b/tests/mocha/logic_ternary_test.js @@ -6,8 +6,9 @@ goog.module('Blockly.test.logicTernary'); -const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); const eventUtils = goog.require('Blockly.Events.utils'); +const {runSerializationTestSuite} = goog.require('Blockly.test.helpers.serialization'); +const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('Logic ternary', function() { @@ -73,7 +74,7 @@ suite('Logic ternary', function() { }, }, ]; - testHelpers.runSerializationTestSuite(testCases); + runSerializationTestSuite(testCases); suite('Connections', function() { function connectParentAndCheckConnections( diff --git a/tests/mocha/metrics_test.js b/tests/mocha/metrics_test.js index df3c7bd7e..5fcc0b69c 100644 --- a/tests/mocha/metrics_test.js +++ b/tests/mocha/metrics_test.js @@ -6,7 +6,7 @@ goog.module('Blockly.test.metrics'); -const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('Metrics', function() { diff --git a/tests/mocha/mutator_test.js b/tests/mocha/mutator_test.js index af5f22cdf..f08d9879c 100644 --- a/tests/mocha/mutator_test.js +++ b/tests/mocha/mutator_test.js @@ -7,7 +7,8 @@ goog.module('Blockly.test.mutator'); -const {createRenderedBlock, defineMutatorBlocks, sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); +const {createRenderedBlock, defineMutatorBlocks} = goog.require('Blockly.test.helpers.blockDefinitions'); suite('Mutator', function() { diff --git a/tests/mocha/names_test.js b/tests/mocha/names_test.js index 0c266cc05..705a3ff9b 100644 --- a/tests/mocha/names_test.js +++ b/tests/mocha/names_test.js @@ -6,7 +6,7 @@ goog.module('Blockly.test.names'); -const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('Names', function() { diff --git a/tests/mocha/procedures_test.js b/tests/mocha/procedures_test.js index ae95d796a..6d7888a9f 100644 --- a/tests/mocha/procedures_test.js +++ b/tests/mocha/procedures_test.js @@ -8,8 +8,9 @@ goog.module('Blockly.test.procedures'); goog.require('Blockly'); goog.require('Blockly.Msg'); -const {sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers'); -const {assertCallBlockStructure, assertDefBlockStructure, createProcDefBlock, createProcCallBlock} = goog.require('Blockly.test.procedureHelpers'); +const {assertCallBlockStructure, assertDefBlockStructure, createProcDefBlock, createProcCallBlock} = goog.require('Blockly.test.helpers.procedures'); +const {runSerializationTestSuite} = goog.require('Blockly.test.helpers.serialization'); +const {createGenUidStubWithReturns, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('Procedures', function() { @@ -293,8 +294,12 @@ suite('Procedures', function() { }); }); suite('caller param mismatch', function() { - test.skip('callreturn with missing args', function() { - // TODO: How do we want it to behave in this situation? + setup(function() { + this.TEST_VAR_ID = 'test-id'; + this.genUidStub = createGenUidStubWithReturns(this.TEST_VAR_ID); + }); + + test('callreturn with missing args', function() { const defBlock = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(` do something @@ -309,10 +314,9 @@ suite('Procedures', function() { '' ), this.workspace); assertDefBlockStructure(defBlock, true, ['x'], ['arg']); - assertCallBlockStructure(callBlock, ['x'], ['arg']); + assertCallBlockStructure(callBlock, [], [], 'do something2'); }); - test.skip('callreturn with bad args', function() { - // TODO: How do we want it to behave in this situation? + test('callreturn with bad args', function() { const defBlock = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(` do something @@ -329,10 +333,10 @@ suite('Procedures', function() { `), this.workspace); assertDefBlockStructure(defBlock, true, ['x'], ['arg']); - assertCallBlockStructure(callBlock, ['x'], ['arg']); + assertCallBlockStructure( + callBlock, ['y'], [this.TEST_VAR_ID], 'do something2'); }); - test.skip('callnoreturn with missing args', function() { - // TODO: How do we want it to behave in this situation? + test('callnoreturn with missing args', function() { const defBlock = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(` do something @@ -347,10 +351,9 @@ suite('Procedures', function() { '' ), this.workspace); assertDefBlockStructure(defBlock, false, ['x'], ['arg']); - assertCallBlockStructure(callBlock, ['x'], ['arg']); + assertCallBlockStructure(callBlock, [], [], 'do something2'); }); - test.skip('callnoreturn with bad args', function() { - // TODO: How do we want it to behave in this situation? + test('callnoreturn with bad args', function() { const defBlock = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(` do something @@ -367,7 +370,8 @@ suite('Procedures', function() { `), this.workspace); assertDefBlockStructure(defBlock, false, ['x'], ['arg']); - assertCallBlockStructure(callBlock, ['x'], ['arg']); + assertCallBlockStructure( + callBlock, ['y'], [this.TEST_VAR_ID], 'do something2'); }); }); }); @@ -1203,7 +1207,7 @@ suite('Procedures', function() { }, }, ]; - testHelpers.runSerializationTestSuite(testCases); + runSerializationTestSuite(testCases); }); }); }); diff --git a/tests/mocha/registry_test.js b/tests/mocha/registry_test.js index 7865ee27d..b6fabfea1 100644 --- a/tests/mocha/registry_test.js +++ b/tests/mocha/registry_test.js @@ -6,7 +6,8 @@ goog.module('Blockly.test.registry'); -const {assertWarnings, sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {assertWarnings} = goog.require('Blockly.test.helpers.warnings'); +const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('Registry', function() { diff --git a/tests/mocha/run_mocha_tests_in_browser.js b/tests/mocha/run_mocha_tests_in_browser.js index d4dc83fbe..217c9992b 100644 --- a/tests/mocha/run_mocha_tests_in_browser.js +++ b/tests/mocha/run_mocha_tests_in_browser.js @@ -61,8 +61,18 @@ async function runMochaTestsInBrowser() { const elem = await browser.$('#failureCount'); const numOfFailure = await elem.getAttribute('tests_failed'); + if (numOfFailure > 0) { + console.log('============Blockly Mocha Test Failures================') + const failureMessagesEls = await browser.$$('#failureMessages p'); + if (!failureMessagesEls.length) { + console.log('There is at least one test failure, but no messages reported. Mocha may be failing because no tests are being run.'); + } + for (let el of failureMessagesEls) { + console.log(await el.getText()); + } + } + console.log('============Blockly Mocha Test Summary================='); - console.log(numOfFailure); console.log(numOfFailure + ' tests failed'); console.log('============Blockly Mocha Test Summary================='); if (parseInt(numOfFailure) !== 0) { diff --git a/tests/mocha/serializer_test.js b/tests/mocha/serializer_test.js index 43b77c51e..85247cc98 100644 --- a/tests/mocha/serializer_test.js +++ b/tests/mocha/serializer_test.js @@ -6,7 +6,8 @@ goog.module('Blockly.test.serialization'); -const {sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers'); +const {TestCase, TestSuite, runTestCases, runTestSuites} = goog.require('Blockly.test.helpers.common'); +const {sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); // TODO: Move this into samples as part of the dev-tools package. @@ -23,7 +24,7 @@ function SerializerTestCase(title, xml) { this.title = title; this.xml = xml; } -SerializerTestCase.prototype = new testHelpers.TestCase(); +SerializerTestCase.prototype = new TestCase(); /** * The XML we want to ensure round-trips through the serializer. @@ -39,7 +40,7 @@ SerializerTestCase.prototype.xml = ''; function SerializerTestSuite(title) { this.title = title; } -SerializerTestSuite.prototype = new testHelpers.TestSuite(); +SerializerTestSuite.prototype = new TestSuite(); const Serializer = new SerializerTestSuite('Serializer'); @@ -166,6 +167,7 @@ Serializer.Attributes.testSuites = [ ]; Serializer.Fields = new SerializerTestSuite('Fields'); +Serializer.Fields.skip = true; Serializer.Fields.Angle = new SerializerTestSuite('Angle'); Serializer.Fields.Angle.Simple = new SerializerTestCase('Simple', @@ -1818,9 +1820,9 @@ const runSerializerTestSuite = (serializer, deserializer, testSuite) => { sharedTestTeardown.call(this); }); - testHelpers.runTestSuites( + runTestSuites( testSuite.testSuites, createTestCaseFunction); - testHelpers.runTestCases(testSuite.testCases, createTestFunction); + runTestCases(testSuite.testCases, createTestFunction); }); }; diff --git a/tests/mocha/shortcut_registry_test.js b/tests/mocha/shortcut_registry_test.js index 8039659e9..c5bbf1d9a 100644 --- a/tests/mocha/shortcut_registry_test.js +++ b/tests/mocha/shortcut_registry_test.js @@ -6,13 +6,15 @@ goog.module('Blockly.test.shortcutRegistry'); -const {createKeyDownEvent, sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {createKeyDownEvent} = goog.require('Blockly.test.helpers.userInput'); +const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('Keyboard Shortcut Registry Test', function() { setup(function() { sharedTestSetup.call(this); - this.registry = new Blockly.ShortcutRegistry(); + this.registry = Blockly.ShortcutRegistry.registry; + this.registry.reset(); Blockly.ShortcutItems.registerDefaultShortcuts(); }); teardown(function() { diff --git a/tests/mocha/test_helpers.js b/tests/mocha/test_helpers.js deleted file mode 100644 index 9a404e629..000000000 --- a/tests/mocha/test_helpers.js +++ /dev/null @@ -1,719 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -goog.module('Blockly.test.helpers'); - -const {KeyCodes} = goog.require('Blockly.utils.KeyCodes'); -const eventUtils = goog.require('Blockly.Events.utils'); -const {Blocks} = goog.require('Blockly.blocks'); - - -/** - * Check if a variable with the given values exists. - * @param {Blockly.Workspace|Blockly.VariableMap} container The workspace or - * variableMap the checked variable belongs to. - * @param {!string} name The expected name of the variable. - * @param {!string} type The expected type of the variable. - * @param {!string} id The expected id of the variable. - */ -function assertVariableValues(container, name, type, id) { - const variable = container.getVariableById(id); - chai.assert.isDefined(variable); - chai.assert.equal(variable.name, name); - chai.assert.equal(variable.type, type); - chai.assert.equal(variable.getId(), id); -} -exports.assertVariableValues = assertVariableValues; - -/** - * Asserts that the given function logs the provided warning messages. - * @param {function()} innerFunc The function to call. - * @param {Array|!RegExp} messages A list of regex for the expected - * messages (in the expected order). - */ -function assertWarnings(innerFunc, messages) { - if (!Array.isArray(messages)) { - messages = [messages]; - } - const warnings = testHelpers.captureWarnings(innerFunc); - chai.assert.lengthOf(warnings, messages.length); - messages.forEach((message, i) => { - chai.assert.match(warnings[i], message); - }); -} -exports.assertWarnings = assertWarnings; - -/** - * Asserts that the given function logs no warning messages. - * @param {function()} innerFunc The function to call. - */ -function assertNoWarnings(innerFunc) { - assertWarnings(innerFunc, []); -} -exports.assertNoWarnings = assertNoWarnings; - -/** - * Stubs Blockly.utils.deprecation.warn call. - * @return {!SinonStub} The created stub. - */ -function createDeprecationWarningStub() { - return sinon.stub(Blockly.utils.deprecation, 'warn'); -} -exports.createDeprecationWarningStub = createDeprecationWarningStub; - -/** - * Asserts whether the given deprecation warning stub or call was called with - * the expected functionName. - * @param {!SinonSpy|!SinonSpyCall} spyOrSpyCall The spy or spy call to use. - * @param {string} functionName The function name to check that the given spy or - * spy call was called with. - */ -function assertDeprecationWarningCall(spyOrSpyCall, functionName) { - sinon.assert.calledWith(spyOrSpyCall, functionName); -} -exports.assertDeprecationWarningCall = assertDeprecationWarningCall; - -/** - * Asserts that there was a single deprecation warning call with the given - * functionName passed. - * @param {!SinonSpy} spy The spy to use. - * @param {string} functionName The function name to check that the given spy - * was called with. - */ -function assertSingleDeprecationWarningCall(spy, functionName) { - sinon.assert.calledOnce(spy); - assertDeprecationWarningCall(spy.getCall(0), functionName); -} -exports.assertSingleDeprecationWarningCall = assertSingleDeprecationWarningCall; - -/** - * Safely disposes of Blockly workspace, logging any errors. - * Assumes that sharedTestSetup has also been called. This should be called - * using workspaceTeardown.call(this). - * @param {!Blockly.Workspace} workspace The workspace to dispose. - */ -function workspaceTeardown(workspace) { - try { - this.clock.runAll(); // Run all queued setTimeout calls. - workspace.dispose(); - this.clock.runAll(); // Run all remaining queued setTimeout calls. - } catch (e) { - const testRef = this.currentTest || this.test; - console.error(testRef.fullTitle() + '\n', e); - } -} -exports.workspaceTeardown = workspaceTeardown; - -/** - * Creates stub for Blockly.Events.fire that advances the clock forward after - * the event fires so it is processed immediately instead of on a timeout. - * @param {!SinonClock} clock The sinon clock. - * @return {!SinonStub} The created stub. - * @private - */ -function createEventsFireStubFireImmediately_(clock) { - const stub = sinon.stub(eventUtils, 'fire'); - stub.callsFake(function(event) { - // Call original method. - stub.wrappedMethod.call(this, ...arguments); - // Advance clock forward to run any queued events. - clock.runAll(); - }); - return stub; -} - -/** - * Adds message to shared cleanup object so that it is cleaned from - * Blockly.Messages global in sharedTestTeardown. - * @param {!Object} sharedCleanupObj The shared cleanup object created in - * sharedTestSetup. - * @param {string} message The message to add to shared cleanup object. - */ -function addMessageToCleanup(sharedCleanupObj, message) { - sharedCleanupObj.messagesCleanup_.push(message); -} -exports.addMessageToCleanup = addMessageToCleanup; - -/** - * Adds block type to shared cleanup object so that it is cleaned from - * Blockly.Blocks global in sharedTestTeardown. - * @param {!Object} sharedCleanupObj The shared cleanup object created in - * sharedTestSetup. - * @param {string} blockType The block type to add to shared cleanup object. - */ -function addBlockTypeToCleanup(sharedCleanupObj, blockType) { - sharedCleanupObj.blockTypesCleanup_.push(blockType); -} -exports.addBlockTypeToCleanup = addBlockTypeToCleanup; - -/** - * Wraps Blockly.defineBlocksWithJsonArray using stub in order to keep track of - * block types passed in to method on shared cleanup object so they are cleaned - * from Blockly.Blocks global in sharedTestTeardown. - * @param {!Object} sharedCleanupObj The shared cleanup object created in - * sharedTestSetup. - * @private - */ -function wrapDefineBlocksWithJsonArrayWithCleanup_(sharedCleanupObj) { - const stub = sinon.stub(Blockly, 'defineBlocksWithJsonArray'); - stub.callsFake(function(jsonArray) { - if (jsonArray) { - jsonArray.forEach((jsonBlock) => { - if (jsonBlock) { - addBlockTypeToCleanup(sharedCleanupObj, jsonBlock['type']); - } - }); - } - // Calls original method. - stub.wrappedMethod.call(this, ...arguments); - }); -} - -/** - * Shared setup method that sets up fake timer for clock so that pending - * setTimeout calls can be cleared in test teardown along with other common - * stubs. Should be called in setup of outermost suite using - * sharedTestSetup.call(this). - * The sinon fake timer defined on this.clock_ should not be reset in tests to - * avoid causing issues with cleanup in sharedTestTeardown. - * - * Stubs created in this setup (unless disabled by options passed): - * - Blockly.Events.fire - this.eventsFireStub - wraps fire event to trigger - * fireNow_ call immediately, rather than on timeout - * - Blockly.defineBlocksWithJsonArray - thin wrapper that adds logic to keep - * track of block types defined so that they can be undefined in - * sharedTestTeardown and calls original method. - * - * @param {Object} options Options to enable/disable setup - * of certain stubs. - */ -function sharedTestSetup(options = {}) { - this.sharedSetupCalled_ = true; - // Sandbox created for greater control when certain stubs are cleared. - this.sharedSetupSandbox_ = sinon.createSandbox(); - this.clock = this.sharedSetupSandbox_.useFakeTimers(); - if (options['fireEventsNow'] === undefined || options['fireEventsNow']) { - // Stubs event firing unless passed option "fireEventsNow: false" - this.eventsFireStub = createEventsFireStubFireImmediately_(this.clock); - } - this.sharedCleanup = { - blockTypesCleanup_: [], - messagesCleanup_: [], - }; - this.blockTypesCleanup_ = this.sharedCleanup.blockTypesCleanup_; - this.messagesCleanup_ = this.sharedCleanup.messagesCleanup_; - wrapDefineBlocksWithJsonArrayWithCleanup_(this.sharedCleanup); -} -exports.sharedTestSetup = sharedTestSetup; - -/** - * Shared cleanup method that clears up pending setTimeout calls, disposes of - * workspace, and resets global variables. Should be called in setup of - * outermost suite using sharedTestTeardown.call(this). - */ -function sharedTestTeardown() { - const testRef = this.currentTest || this.test; - if (!this.sharedSetupCalled_) { - console.error('"' + testRef.fullTitle() + '" did not call sharedTestSetup'); - } - - try { - if (this.workspace) { - workspaceTeardown.call(this, this.workspace); - this.workspace = null; - } else { - this.clock.runAll(); // Run all queued setTimeout calls. - } - } catch (e) { - console.error(testRef.fullTitle() + '\n', e); - } finally { - // Clear Blockly.Event state. - eventUtils.setGroup(false); - while (!eventUtils.isEnabled()) { - eventUtils.enable(); - } - eventUtils.setRecordUndo(true); - if (eventUtils.TEST_ONLY.FIRE_QUEUE.length) { - // If this happens, it may mean that some previous test is missing cleanup - // (i.e. a previous test added an event to the queue on a timeout that - // did not use a stubbed clock). - eventUtils.TEST_ONLY.FIRE_QUEUE.length = 0; - console.warn('"' + testRef.fullTitle() + - '" needed cleanup of Blockly.Events.TEST_ONLY.FIRE_QUEUE. This may ' + - 'indicate leakage from an earlier test'); - } - - // Restore all stubbed methods. - this.sharedSetupSandbox_.restore(); - sinon.restore(); - - const blockTypes = this.sharedCleanup.blockTypesCleanup_; - for (let i = 0; i < blockTypes.length; i++) { - delete Blocks[blockTypes[i]]; - } - const messages = this.sharedCleanup.messagesCleanup_; - for (let i = 0; i < messages.length; i++) { - delete Blockly.Msg[messages[i]]; - } - - Blockly.WidgetDiv.testOnly_setDiv(null); - } -} -exports.sharedTestTeardown = sharedTestTeardown; - -/** - * Creates stub for Blockly.utils.genUid that returns the provided id or ids. - * Recommended to also assert that the stub is called the expected number of - * times. - * @param {string|!Array} returnIds The return values to use for the - * created stub. If a single value is passed, then the stub always returns - * that value. - * @return {!SinonStub} The created stub. - */ -function createGenUidStubWithReturns(returnIds) { - const stub = sinon.stub(Blockly.utils.idGenerator.TEST_ONLY, "genUid"); - if (Array.isArray(returnIds)) { - for (let i = 0; i < returnIds.length; i++) { - stub.onCall(i).returns(returnIds[i]); - } - } else { - stub.returns(returnIds); - } - return stub; -} -exports.createGenUidStubWithReturns = createGenUidStubWithReturns; - -/** - * Creates spy for workspace fireChangeListener - * @param {!Blockly.Workspace} workspace The workspace to spy fireChangeListener - * calls on. - * @return {!SinonSpy} The created spy. - */ -function createFireChangeListenerSpy(workspace) { - return sinon.spy(workspace, 'fireChangeListener'); -} -exports.createFireChangeListenerSpy = createFireChangeListenerSpy; - -/** - * Asserts whether the given xml property has the expected property. - * @param {!Node} xmlValue The xml value to check. - * @param {!Node|string} expectedValue The expected value. - * @param {string=} message Optional message to use in assert message. - * @private - */ -function assertXmlPropertyEqual_(xmlValue, expectedValue, message) { - const value = Blockly.Xml.domToText(xmlValue); - if (expectedValue instanceof Node) { - expectedValue = Blockly.Xml.domToText(expectedValue); - } - chai.assert.equal(value, expectedValue, message); -} - -/** - * Asserts that the given object has the expected xml properties. - * @param {Object} obj The object to check. - * @param {Object} expectedXmlProperties The expected xml - * properties. - * @private - */ -function assertXmlProperties_(obj, expectedXmlProperties) { - Object.keys(expectedXmlProperties).map((key) => { - const value = obj[key]; - const expectedValue = expectedXmlProperties[key]; - if (expectedValue === undefined) { - chai.assert.isUndefined(value, - 'Expected ' + key + ' property to be undefined'); - return; - } - chai.assert.exists(value, 'Expected ' + key + ' property to exist'); - assertXmlPropertyEqual_(value, expectedValue, 'Checking property ' + key); - }); -} - -/** - * Whether given key indicates that the property is xml. - * @param {string} key The key to check. - * @return {boolean} Whether the given key is for xml property. - * @private - */ -function isXmlProperty_(key) { - return key.toLowerCase().endsWith('xml'); -} - -/** - * Asserts that the given event has the expected values. - * @param {!Blockly.Events.Abstract} event The event to check. - * @param {string} expectedType Expected type of event fired. - * @param {string} expectedWorkspaceId Expected workspace id of event fired. - * @param {?string} expectedBlockId Expected block id of event fired. - * @param {!Object} expectedProperties Map of of additional expected - * properties to check on fired event. - * @param {boolean=} [isUiEvent=false] Whether the event is a UI event. - * @param {string=} message Optional message to prepend assert messages. - */ -function assertEventEquals(event, expectedType, - expectedWorkspaceId, expectedBlockId, expectedProperties, isUiEvent = false, message) { - let prependMessage = message ? message + ' ' : ''; - prependMessage += 'Event fired '; - chai.assert.equal(event.type, expectedType, - prependMessage + 'type'); - chai.assert.equal(event.workspaceId, expectedWorkspaceId, - prependMessage + 'workspace id'); - chai.assert.equal(event.blockId, expectedBlockId, - prependMessage + 'block id'); - Object.keys(expectedProperties).map((key) => { - const value = event[key]; - const expectedValue = expectedProperties[key]; - if (expectedValue === undefined) { - chai.assert.isUndefined(value, prependMessage + key); - return; - } - chai.assert.exists(value, prependMessage + key); - if (isXmlProperty_(key)) { - assertXmlPropertyEqual_(value, expectedValue, - prependMessage + key); - } else { - chai.assert.equal(value, expectedValue, - prependMessage + key); - } - }); - if (isUiEvent) { - chai.assert.isTrue(event.isUiEvent); - } else { - chai.assert.isFalse(event.isUiEvent); - } -} -exports.assertEventEquals = assertEventEquals; - -/** - * Asserts that an event with the given values was fired. - * @param {!SinonSpy|!SinonSpyCall} spy The spy or spy call to use. - * @param {function(new:Blockly.Events.Abstract)} instanceType Expected instance - * type of event fired. - * @param {!Object} expectedProperties Map of of expected properties - * to check on fired event. - * @param {string} expectedWorkspaceId Expected workspace id of event fired. - * @param {?string=} expectedBlockId Expected block id of event fired. - */ -function assertEventFired(spy, instanceType, expectedProperties, - expectedWorkspaceId, expectedBlockId) { - expectedProperties = Object.assign({ - type: instanceType.prototype.type, - workspaceId: expectedWorkspaceId, - blockId: expectedBlockId, - }, expectedProperties); - const expectedEvent = - sinon.match.instanceOf(instanceType).and(sinon.match(expectedProperties)); - sinon.assert.calledWith(spy, expectedEvent); -} -exports.assertEventFired = assertEventFired; - -/** - * Asserts that an event with the given values was not fired. - * @param {!SpyCall} spy The spy to use. - * @param {function(new:Blockly.Events.Abstract)} instanceType Expected instance - * type of event fired. - * @param {!Object} expectedProperties Map of of expected properties - * to check on fired event. - * @param {string=} expectedWorkspaceId Expected workspace id of event fired. - * @param {?string=} expectedBlockId Expected block id of event fired. - */ -function assertEventNotFired(spy, instanceType, expectedProperties, - expectedWorkspaceId, expectedBlockId) { - expectedProperties.type = instanceType.prototype.type; - if (expectedWorkspaceId !== undefined) { - expectedProperties.workspaceId = expectedWorkspaceId; - } - if (expectedBlockId !== undefined) { - expectedProperties.blockId = expectedBlockId; - } - const expectedEvent = - sinon.match.instanceOf(instanceType).and(sinon.match(expectedProperties)); - sinon.assert.neverCalledWith(spy, expectedEvent); -} -exports.assertEventNotFired = assertEventNotFired; - -/** - * Filters out xml properties from given object based on key. - * @param {Object} properties The properties to filter. - * @return {Array>} A list containing split non - * xml properties and xml properties. [Object, Object] - * @private - */ -function splitByXmlProperties_(properties) { - const xmlProperties = {}; - const nonXmlProperties = {}; - Object.keys(properties).forEach((key) => { - if (isXmlProperty_(key)) { - xmlProperties[key] = properties[key]; - return false; - } else { - nonXmlProperties[key] = properties[key]; - } - }); - return [nonXmlProperties, xmlProperties]; -} - -/** - * Asserts that the event passed to the nth call of the given spy has the - * expected values. Assumes that the event is passed as the first argument. - * @param {!SinonSpy} spy The spy to use. - * @param {number} n Which call to check. - * @param {function(new:Blockly.Events.Abstract)} instanceType Expected instance - * type of event fired. - * @param {Object} expectedProperties Map of of expected properties - * to check on fired event. - * @param {string} expectedWorkspaceId Expected workspace id of event fired. - * @param {?string=} expectedBlockId Expected block id of event fired. - */ -function assertNthCallEventArgEquals(spy, n, instanceType, expectedProperties, - expectedWorkspaceId, expectedBlockId) { - const nthCall = spy.getCall(n); - const splitProperties = splitByXmlProperties_(expectedProperties); - const nonXmlProperties = splitProperties[0]; - const xmlProperties = splitProperties[1]; - - assertEventFired(nthCall, instanceType, nonXmlProperties, expectedWorkspaceId, - expectedBlockId); - const eventArg = nthCall.firstArg; - assertXmlProperties_(eventArg, xmlProperties); -} -exports.assertNthCallEventArgEquals = assertNthCallEventArgEquals; - -function defineStackBlock(name = 'stack_block') { - Blockly.defineBlocksWithJsonArray([{ - "type": name, - "message0": "", - "previousStatement": null, - "nextStatement": null, - }]); -} -exports.defineStackBlock = defineStackBlock; - -function defineRowBlock(name = 'row_block') { - Blockly.defineBlocksWithJsonArray([{ - "type": name, - "message0": "%1", - "args0": [ - { - "type": "input_value", - "name": "INPUT", - }, - ], - "output": null, - }]); -} -exports.defineRowBlock = defineRowBlock; - -function defineStatementBlock(name = 'statement_block') { - Blockly.defineBlocksWithJsonArray([{ - "type": name, - "message0": "%1", - "args0": [ - { - "type": "input_statement", - "name": "NAME", - }, - ], - "previousStatement": null, - "nextStatement": null, - "colour": 230, - "tooltip": "", - "helpUrl": "", - }]); -} -exports.defineStatementBlock = defineStatementBlock; - -function defineBasicBlockWithField(name = 'test_field_block') { - Blockly.defineBlocksWithJsonArray([{ - "type": name, - "message0": "%1", - "args0": [ - { - "type": "field_input", - "name": "NAME", - }, - ], - "output": null, - }]); -} -exports.defineBasicBlockWithField = defineBasicBlockWithField; - -function defineMutatorBlocks() { - Blockly.defineBlocksWithJsonArray([ - { - 'type': 'xml_block', - 'mutator': 'xml_mutator', - }, - { - 'type': 'jso_block', - 'mutator': 'jso_mutator', - }, - { - 'type': 'checkbox_block', - 'message0': '%1', - 'args0': [ - { - 'type': 'field_checkbox', - 'name': 'CHECK', - }, - ], - }, - ]); - - const xmlMutator = { - hasInput: false, - - mutationToDom: function() { - const mutation = Blockly.utils.xml.createElement('mutation'); - mutation.setAttribute('hasInput', this.hasInput); - return mutation; - }, - - domToMutation: function(mutation) { - this.hasInput = mutation.getAttribute('hasInput') == 'true'; - this.updateShape(); - }, - - decompose: function(workspace) { - const topBlock = workspace.newBlock('checkbox_block', 'check_block'); - topBlock.initSvg(); - topBlock.render(); - return topBlock; - }, - - compose: function(topBlock) { - this.hasInput = topBlock.getFieldValue('CHECK') == 'TRUE'; - this.updateShape(); - }, - - updateShape: function() { - if (this.hasInput && !this.getInput('INPUT')) { - this.appendValueInput('INPUT'); - } else if (!this.hasInput && this.getInput('INPUT')) { - this.removeInput('INPUT'); - } - }, - }; - Blockly.Extensions.registerMutator('xml_mutator', xmlMutator); - - const jsoMutator = { - hasInput: false, - - saveExtraState: function() { - return {hasInput: this.hasInput}; - }, - - loadExtraState: function(state) { - this.hasInput = state.hasInput || false; - this.updateShape(); - }, - - decompose: function(workspace) { - const topBlock = workspace.newBlock('checkbox_block', 'check_block'); - topBlock.initSvg(); - topBlock.render(); - return topBlock; - }, - - compose: function(topBlock) { - this.hasInput = topBlock.getFieldValue('CHECK') == 'TRUE'; - this.updateShape(); - }, - - updateShape: function() { - if (this.hasInput && !this.getInput('INPUT')) { - this.appendValueInput('INPUT'); - } else if (!this.hasInput && this.getInput('INPUT')) { - this.removeInput('INPUT'); - } - }, - }; - Blockly.Extensions.registerMutator('jso_mutator', jsoMutator); -} -exports.defineMutatorBlocks = defineMutatorBlocks; - -function createTestBlock() { - return { - 'id': 'test', - 'rendered': false, - 'workspace': { - 'rendered': false, - }, - 'isShadow': function() { - return false; - }, - 'renameVarById': Blockly.Block.prototype.renameVarById, - 'updateVarName': Blockly.Block.prototype.updateVarName, - }; -} -exports.createTestBlock = createTestBlock; - -function createRenderedBlock(workspaceSvg, type) { - const block = workspaceSvg.newBlock(type); - block.initSvg(); - block.render(); - return block; -} -exports.createRenderedBlock = createRenderedBlock; - -/** - * Triggers pointer event on target. - * @param {!EventTarget} target The object receiving the event. - * @param {string} type The type of mouse event (eg: mousedown, mouseup, - * click). - * @param {Object=} properties Properties to pass into event - * constructor. - */ -function dispatchPointerEvent(target, type, properties) { - const eventInitDict = { - cancelable: true, - bubbles: true, - isPrimary: true, - pressure: 0.5, - clientX: 10, - clientY: 10, - }; - if (properties) { - Object.assign(eventInitDict, properties); - } - const event = new PointerEvent(type, eventInitDict); - target.dispatchEvent(event); -} -exports.dispatchPointerEvent = dispatchPointerEvent; - -/** - * Creates a key down event used for testing. - * @param {number} keyCode The keycode for the event. Use Blockly.utils.KeyCodes enum. - * @param {!Array=} modifiers A list of modifiers. Use Blockly.utils.KeyCodes enum. - * @return {!KeyboardEvent} The mocked keydown event. - */ -function createKeyDownEvent(keyCode, modifiers) { - const event = { - keyCode: keyCode, - }; - if (modifiers && modifiers.length > 0) { - event.altKey = modifiers.indexOf(KeyCodes.ALT) > -1; - event.ctrlKey = modifiers.indexOf(KeyCodes.CTRL) > -1; - event.metaKey = modifiers.indexOf(KeyCodes.META) > -1; - event.shiftKey = modifiers.indexOf(KeyCodes.SHIFT) > -1; - } - return new KeyboardEvent('keydown', event); -} -exports.createKeyDownEvent = createKeyDownEvent; - -/** - * Simulates mouse click by triggering relevant mouse events. - * @param {!EventTarget} target The object receiving the event. - * @param {Object=} properties Properties to pass into event - * constructor. - */ -function simulateClick(target, properties) { - dispatchPointerEvent(target, 'pointerdown', properties); - dispatchPointerEvent(target, 'pointerup', properties); - dispatchPointerEvent(target, 'click', properties); -} -exports.simulateClick = simulateClick; diff --git a/tests/mocha/test_helpers/block_definitions.js b/tests/mocha/test_helpers/block_definitions.js new file mode 100644 index 000000000..a30d7defe --- /dev/null +++ b/tests/mocha/test_helpers/block_definitions.js @@ -0,0 +1,185 @@ +/** + * @license + * Copyright 2019 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +goog.module('Blockly.test.helpers.blockDefinitions'); + + +function defineStackBlock(name = 'stack_block') { + Blockly.defineBlocksWithJsonArray([{ + "type": name, + "message0": "", + "previousStatement": null, + "nextStatement": null, + }]); +} +exports.defineStackBlock = defineStackBlock; + +function defineRowBlock(name = 'row_block') { + Blockly.defineBlocksWithJsonArray([{ + "type": name, + "message0": "%1", + "args0": [ + { + "type": "input_value", + "name": "INPUT", + }, + ], + "output": null, + }]); +} +exports.defineRowBlock = defineRowBlock; + +function defineStatementBlock(name = 'statement_block') { + Blockly.defineBlocksWithJsonArray([{ + "type": name, + "message0": "%1", + "args0": [ + { + "type": "input_statement", + "name": "NAME", + }, + ], + "previousStatement": null, + "nextStatement": null, + "colour": 230, + "tooltip": "", + "helpUrl": "", + }]); +} +exports.defineStatementBlock = defineStatementBlock; + +function defineBasicBlockWithField(name = 'test_field_block') { + Blockly.defineBlocksWithJsonArray([{ + "type": name, + "message0": "%1", + "args0": [ + { + "type": "field_input", + "name": "NAME", + }, + ], + "output": null, + }]); +} +exports.defineBasicBlockWithField = defineBasicBlockWithField; + +function defineMutatorBlocks() { + Blockly.defineBlocksWithJsonArray([ + { + 'type': 'xml_block', + 'mutator': 'xml_mutator', + }, + { + 'type': 'jso_block', + 'mutator': 'jso_mutator', + }, + { + 'type': 'checkbox_block', + 'message0': '%1', + 'args0': [ + { + 'type': 'field_checkbox', + 'name': 'CHECK', + }, + ], + }, + ]); + + const xmlMutator = { + hasInput: false, + + mutationToDom: function() { + const mutation = Blockly.utils.xml.createElement('mutation'); + mutation.setAttribute('hasInput', this.hasInput); + return mutation; + }, + + domToMutation: function(mutation) { + this.hasInput = mutation.getAttribute('hasInput') == 'true'; + this.updateShape(); + }, + + decompose: function(workspace) { + const topBlock = workspace.newBlock('checkbox_block', 'check_block'); + topBlock.initSvg(); + topBlock.render(); + return topBlock; + }, + + compose: function(topBlock) { + this.hasInput = topBlock.getFieldValue('CHECK') == 'TRUE'; + this.updateShape(); + }, + + updateShape: function() { + if (this.hasInput && !this.getInput('INPUT')) { + this.appendValueInput('INPUT'); + } else if (!this.hasInput && this.getInput('INPUT')) { + this.removeInput('INPUT'); + } + }, + }; + Blockly.Extensions.registerMutator('xml_mutator', xmlMutator); + + const jsoMutator = { + hasInput: false, + + saveExtraState: function() { + return {hasInput: this.hasInput}; + }, + + loadExtraState: function(state) { + this.hasInput = state.hasInput || false; + this.updateShape(); + }, + + decompose: function(workspace) { + const topBlock = workspace.newBlock('checkbox_block', 'check_block'); + topBlock.initSvg(); + topBlock.render(); + return topBlock; + }, + + compose: function(topBlock) { + this.hasInput = topBlock.getFieldValue('CHECK') == 'TRUE'; + this.updateShape(); + }, + + updateShape: function() { + if (this.hasInput && !this.getInput('INPUT')) { + this.appendValueInput('INPUT'); + } else if (!this.hasInput && this.getInput('INPUT')) { + this.removeInput('INPUT'); + } + }, + }; + Blockly.Extensions.registerMutator('jso_mutator', jsoMutator); +} +exports.defineMutatorBlocks = defineMutatorBlocks; + +function createTestBlock() { + return { + 'id': 'test', + 'rendered': false, + 'workspace': { + 'rendered': false, + }, + 'isShadow': function() { + return false; + }, + 'renameVarById': Blockly.Block.prototype.renameVarById, + 'updateVarName': Blockly.Block.prototype.updateVarName, + }; +} +exports.createTestBlock = createTestBlock; + +function createRenderedBlock(workspaceSvg, type) { + const block = workspaceSvg.newBlock(type); + block.initSvg(); + block.render(); + return block; +} +exports.createRenderedBlock = createRenderedBlock; diff --git a/tests/mocha/test_helpers/code_generation.js b/tests/mocha/test_helpers/code_generation.js new file mode 100644 index 000000000..72d520c7f --- /dev/null +++ b/tests/mocha/test_helpers/code_generation.js @@ -0,0 +1,118 @@ +/* eslint-disable valid-jsdoc */ +/** + * @license + * Copyright 2020 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +goog.module('Blockly.test.helpers.codeGeneration'); + +const {runTestSuites} = goog.require('Blockly.test.helpers.common'); + + +/** + * Code generation test case configuration. + * @implements {TestCase} + * @record + */ +class CodeGenerationTestCase { + /** + * Class for a code generation test case. + */ + constructor() { + /** + * @type {string} The expected code. + */ + this.expectedCode; + /** + * @type {boolean|undefined} Whether to use workspaceToCode instead of + * blockToCode for test. + */ + this.useWorkspaceToCode; + /** + * @type {number|undefined} The expected inner order. + */ + this.expectedInnerOrder; + } + + /** + * Creates the block to use for this test case. + * @param {!Blockly.Workspace} workspace The workspace context for this + * test. + * @return {!Blockly.Block} The block to use for the test case. + */ + createBlock(workspace) {} +} +exports.CodeGenerationTestCase = CodeGenerationTestCase; + +/** + * Code generation test suite. + * @extends {TestSuite} + * @record + */ +class CodeGenerationTestSuite { + /** + * Class for a code generation test suite. + */ + constructor() { + /** + * @type {!Blockly.Generator} The generator to use for running test cases. + */ + this.generator; + } +} +exports.CodeGenerationTestSuite = CodeGenerationTestSuite; + +/** + * Returns mocha test callback for code generation based on provided + * generator. + * @param {!Blockly.Generator} generator The generator to use in test. + * @return {function(!CodeGenerationTestCase):!Function} Function that + * returns mocha test callback based on test case. + * @private + */ +const createCodeGenerationTestFn_ = (generator) => { + return (testCase) => { + return function() { + const block = testCase.createBlock(this.workspace); + let code; + let innerOrder; + if (testCase.useWorkspaceToCode) { + code = generator.workspaceToCode(this.workspace); + } else { + generator.init(this.workspace); + code = generator.blockToCode(block); + if (Array.isArray(code)) { + innerOrder = code[1]; + code = code[0]; + } + } + const assertFunc = (typeof testCase.expectedCode === 'string') ? + chai.assert.equal : chai.assert.match; + assertFunc(code, testCase.expectedCode); + if (!testCase.useWorkspaceToCode && + testCase.expectedInnerOrder !== undefined) { + chai.assert.equal(innerOrder, testCase.expectedInnerOrder); + } + }; + }; +}; + +/** + * Runs blockToCode test suites. + * @param {!Array} testSuites The test suites to run. + */ +const runCodeGenerationTestSuites = (testSuites) => { + /** + * Creates function used to generate mocha test callback. + * @param {!CodeGenerationTestSuite} suiteInfo The test suite information. + * @return {function(!CodeGenerationTestCase):!Function} Function that + * creates mocha test callback. + */ + const createTestFn = (suiteInfo) => { + return createCodeGenerationTestFn_(suiteInfo.generator); + }; + + runTestSuites(testSuites, createTestFn); +}; +exports.runCodeGenerationTestSuites = runCodeGenerationTestSuites; diff --git a/tests/mocha/test_helpers/common.js b/tests/mocha/test_helpers/common.js new file mode 100644 index 000000000..fcb61021a --- /dev/null +++ b/tests/mocha/test_helpers/common.js @@ -0,0 +1,112 @@ +/** + * @license + * Copyright 2020 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +goog.module('Blockly.test.helpers.common'); + +/** + * Test case configuration. + * @record + */ +class TestCase { + /** + * Class for a test case configuration. + */ + constructor() { + /** + * @type {string} The title for the test case. + */ + this.title; + /** + * @type {boolean|undefined} Whether this test case should be skipped. + * Used to skip buggy test case and should have an associated bug. + */ + this.skip; + /** + * @type {boolean|undefined} Whether this test case should be called as + * only. Used for debugging. + */ + this.only; + } +} +exports.TestCase = TestCase; + +/** + * Test suite configuration. + * @record + * @template {TestCase} T + * @template {TestSuite} U + */ +class TestSuite { + /** + * Class for a test suite configuration. + */ + constructor() { + /** + * @type {string} The title for the test case. + */ + this.title; + /** + * @type {?Array} The associated test cases. + */ + this.testCases; + /** + * @type {?Array} List of nested inner test suites. + */ + this.testSuites; + /** + * @type {boolean|undefined} Whether this test suite should be skipped. + * Used to skip buggy test case and should have an associated bug. + */ + this.skip; + /** + * @type {boolean|undefined} Whether this test suite should be called as + * only. Used for debugging. + */ + this.only; + } +} +exports.TestSuite = TestSuite; + +/** + * Runs provided test cases. + * @template {TestCase} T + * @param {!Array} testCases The test cases to run. + * @param {function(T):Function} createTestCallback Creates test + * callback using given test case. + */ +function runTestCases(testCases, createTestCallback) { + testCases.forEach((testCase) => { + let testCall = (testCase.skip ? test.skip : test); + testCall = (testCase.only ? test.only : testCall); + testCall(testCase.title, createTestCallback(testCase)); + }); +} +exports.runTestCases = runTestCases; + +/** + * Runs provided test suite. + * @template {TestCase} T + * @template {TestSuite} U + * @param {Array} testSuites The test suites to run. + * @param {function(!U):(function(T):!Function) + * } createTestCaseCallback Creates test case callback using given test + * suite. + */ +function runTestSuites(testSuites, createTestCaseCallback) { + testSuites.forEach((testSuite) => { + let suiteCall = (testSuite.skip ? suite.skip : suite); + suiteCall = (testSuite.only ? suite.only : suiteCall); + suiteCall(testSuite.title, function() { + if (testSuite.testSuites && testSuite.testSuites.length) { + runTestSuites(testSuite.testSuites, createTestCaseCallback); + } + if (testSuite.testCases && testSuite.testCases.length) { + runTestCases(testSuite.testCases, createTestCaseCallback(testSuite)); + } + }); + }); +} +exports.runTestSuites = runTestSuites; diff --git a/tests/mocha/test_helpers/events.js b/tests/mocha/test_helpers/events.js new file mode 100644 index 000000000..e69573318 --- /dev/null +++ b/tests/mocha/test_helpers/events.js @@ -0,0 +1,204 @@ +/** + * @license + * Copyright 2019 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +goog.module('Blockly.test.helpers.events'); + + +/** + * Creates spy for workspace fireChangeListener + * @param {!Blockly.Workspace} workspace The workspace to spy fireChangeListener + * calls on. + * @return {!SinonSpy} The created spy. + */ +function createFireChangeListenerSpy(workspace) { + return sinon.spy(workspace, 'fireChangeListener'); +} +exports.createFireChangeListenerSpy = createFireChangeListenerSpy; + +/** + * Asserts whether the given xml property has the expected property. + * @param {!Node} xmlValue The xml value to check. + * @param {!Node|string} expectedValue The expected value. + * @param {string=} message Optional message to use in assert message. + * @private + */ +function assertXmlPropertyEqual_(xmlValue, expectedValue, message) { + const value = Blockly.Xml.domToText(xmlValue); + if (expectedValue instanceof Node) { + expectedValue = Blockly.Xml.domToText(expectedValue); + } + chai.assert.equal(value, expectedValue, message); +} + +/** + * Asserts that the given object has the expected xml properties. + * @param {Object} obj The object to check. + * @param {Object} expectedXmlProperties The expected xml + * properties. + * @private + */ +function assertXmlProperties_(obj, expectedXmlProperties) { + Object.keys(expectedXmlProperties).map((key) => { + const value = obj[key]; + const expectedValue = expectedXmlProperties[key]; + if (expectedValue === undefined) { + chai.assert.isUndefined(value, + 'Expected ' + key + ' property to be undefined'); + return; + } + chai.assert.exists(value, 'Expected ' + key + ' property to exist'); + assertXmlPropertyEqual_(value, expectedValue, 'Checking property ' + key); + }); +} + +/** + * Whether given key indicates that the property is xml. + * @param {string} key The key to check. + * @return {boolean} Whether the given key is for xml property. + * @private + */ +function isXmlProperty_(key) { + return key.toLowerCase().endsWith('xml'); +} + +/** + * Asserts that the given event has the expected values. + * @param {!Blockly.Events.Abstract} event The event to check. + * @param {string} expectedType Expected type of event fired. + * @param {string} expectedWorkspaceId Expected workspace id of event fired. + * @param {?string} expectedBlockId Expected block id of event fired. + * @param {!Object} expectedProperties Map of of additional expected + * properties to check on fired event. + * @param {boolean=} [isUiEvent=false] Whether the event is a UI event. + * @param {string=} message Optional message to prepend assert messages. + */ +function assertEventEquals(event, expectedType, + expectedWorkspaceId, expectedBlockId, expectedProperties, isUiEvent = false, message) { + let prependMessage = message ? message + ' ' : ''; + prependMessage += 'Event fired '; + chai.assert.equal(event.type, expectedType, + prependMessage + 'type'); + chai.assert.equal(event.workspaceId, expectedWorkspaceId, + prependMessage + 'workspace id'); + chai.assert.equal(event.blockId, expectedBlockId, + prependMessage + 'block id'); + Object.keys(expectedProperties).map((key) => { + const value = event[key]; + const expectedValue = expectedProperties[key]; + if (expectedValue === undefined) { + chai.assert.isUndefined(value, prependMessage + key); + return; + } + chai.assert.exists(value, prependMessage + key); + if (isXmlProperty_(key)) { + assertXmlPropertyEqual_(value, expectedValue, + prependMessage + key); + } else { + chai.assert.equal(value, expectedValue, + prependMessage + key); + } + }); + if (isUiEvent) { + chai.assert.isTrue(event.isUiEvent); + } else { + chai.assert.isFalse(event.isUiEvent); + } +} +exports.assertEventEquals = assertEventEquals; + +/** + * Asserts that an event with the given values was fired. + * @param {!SinonSpy|!SinonSpyCall} spy The spy or spy call to use. + * @param {function(new:Blockly.Events.Abstract)} instanceType Expected instance + * type of event fired. + * @param {!Object} expectedProperties Map of of expected properties + * to check on fired event. + * @param {string} expectedWorkspaceId Expected workspace id of event fired. + * @param {?string=} expectedBlockId Expected block id of event fired. + */ +function assertEventFired(spy, instanceType, expectedProperties, + expectedWorkspaceId, expectedBlockId) { + expectedProperties = Object.assign({ + workspaceId: expectedWorkspaceId, + blockId: expectedBlockId, + }, expectedProperties); + const expectedEvent = + sinon.match.instanceOf(instanceType).and(sinon.match(expectedProperties)); + sinon.assert.calledWith(spy, expectedEvent); +} +exports.assertEventFired = assertEventFired; + +/** + * Asserts that an event with the given values was not fired. + * @param {!SpyCall} spy The spy to use. + * @param {function(new:Blockly.Events.Abstract)} instanceType Expected instance + * type of event fired. + * @param {!Object} expectedProperties Map of of expected properties + * to check on fired event. + * @param {string=} expectedWorkspaceId Expected workspace id of event fired. + * @param {?string=} expectedBlockId Expected block id of event fired. + */ +function assertEventNotFired(spy, instanceType, expectedProperties, + expectedWorkspaceId, expectedBlockId) { + expectedProperties.type = instanceType.prototype.type; + if (expectedWorkspaceId !== undefined) { + expectedProperties.workspaceId = expectedWorkspaceId; + } + if (expectedBlockId !== undefined) { + expectedProperties.blockId = expectedBlockId; + } + const expectedEvent = + sinon.match.instanceOf(instanceType).and(sinon.match(expectedProperties)); + sinon.assert.neverCalledWith(spy, expectedEvent); +} +exports.assertEventNotFired = assertEventNotFired; + +/** + * Filters out xml properties from given object based on key. + * @param {Object} properties The properties to filter. + * @return {Array>} A list containing split non + * xml properties and xml properties. [Object, Object] + * @private + */ +function splitByXmlProperties_(properties) { + const xmlProperties = {}; + const nonXmlProperties = {}; + Object.keys(properties).forEach((key) => { + if (isXmlProperty_(key)) { + xmlProperties[key] = properties[key]; + return false; + } else { + nonXmlProperties[key] = properties[key]; + } + }); + return [nonXmlProperties, xmlProperties]; +} + +/** + * Asserts that the event passed to the nth call of the given spy has the + * expected values. Assumes that the event is passed as the first argument. + * @param {!SinonSpy} spy The spy to use. + * @param {number} n Which call to check. + * @param {function(new:Blockly.Events.Abstract)} instanceType Expected instance + * type of event fired. + * @param {Object} expectedProperties Map of of expected properties + * to check on fired event. + * @param {string} expectedWorkspaceId Expected workspace id of event fired. + * @param {?string=} expectedBlockId Expected block id of event fired. + */ +function assertNthCallEventArgEquals(spy, n, instanceType, expectedProperties, + expectedWorkspaceId, expectedBlockId) { + const nthCall = spy.getCall(n); + const splitProperties = splitByXmlProperties_(expectedProperties); + const nonXmlProperties = splitProperties[0]; + const xmlProperties = splitProperties[1]; + + assertEventFired(nthCall, instanceType, nonXmlProperties, expectedWorkspaceId, + expectedBlockId); + const eventArg = nthCall.firstArg; + assertXmlProperties_(eventArg, xmlProperties); +} +exports.assertNthCallEventArgEquals = assertNthCallEventArgEquals; diff --git a/tests/mocha/test_helpers/fields.js b/tests/mocha/test_helpers/fields.js new file mode 100644 index 000000000..dd89e55fa --- /dev/null +++ b/tests/mocha/test_helpers/fields.js @@ -0,0 +1,282 @@ +/** + * @license + * Copyright 2020 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +goog.module('Blockly.test.helpers.fields'); + +const {runTestCases, TestCase} = goog.require('Blockly.test.helpers.common'); + + +/** + * Field value test case. + * @implements {TestCase} + * @record + */ +class FieldValueTestCase { + /** + * Class for a a field value test case. + */ + constructor() { + /** + * @type {*} The value to use in test. + */ + this.value; + /** + * @type {*} The expected value. + */ + this.expectedValue; + /** + * @type {string|undefined} The expected text value. Provided if different + * from String(expectedValue). + */ + this.expectedText; + /** + * @type {!RegExp|string|undefined} The optional error message matcher. + * Provided if test case is expected to throw. + */ + this.errMsgMatcher; + } +} +exports.FieldValueTestCase = FieldValueTestCase; + +/** + * Field creation test case. + * @extends {FieldValueTestCase} + * @record + */ +class FieldCreationTestCase { + /** + * Class for a field creation test case. + */ + constructor() { + /** + * @type {Array<*>} The arguments to pass to field constructor. + */ + this.args; + /** + * @type {string} The json to use in field creation. + */ + this.json; + } +} +exports.FieldCreationTestCase = FieldCreationTestCase; + +/** + * Assert a field's value is the same as the expected value. + * @param {!Blockly.Field} field The field. + * @param {*} expectedValue The expected value. + * @param {string=} expectedText The expected text. + */ +function assertFieldValue(field, expectedValue, expectedText = undefined) { + const actualValue = field.getValue(); + const actualText = field.getText(); + if (expectedText === undefined) { + expectedText = String(expectedValue); + } + chai.assert.equal(actualValue, expectedValue, 'Value'); + chai.assert.equal(actualText, expectedText, 'Text'); +} +exports.assertFieldValue = assertFieldValue; + +/** + * Runs provided creation test cases. + * @param {!Array} testCases The test cases to run. + * @param {function(!Blockly.Field, !FieldCreationTestCase)} assertion The + * assertion to use. + * @param {function(new:Blockly.Field,!FieldCreationTestCase):Blockly.Field + * } creation A function that returns an instance of the field based on the + * provided test case. + * @private + */ +function runCreationTests_(testCases, assertion, creation) { + /** + * Creates test callback for creation test. + * @param {FieldCreationTestCase} testCase The test case to use. + * @return {Function} The test callback. + */ + const createTestFn = (testCase) => { + return function() { + const field = creation.call(this, testCase); + assertion(field, testCase); + }; + }; + runTestCases(testCases, createTestFn); +} + +/** + * Runs provided creation test cases. + * @param {!Array} testCases The test cases to run. + * @param {function(new:Blockly.Field,!FieldCreationTestCase):Blockly.Field + * } creation A function that returns an instance of the field based on the + * provided test case. + * @private + */ +function runCreationTestsAssertThrows_(testCases, creation) { + /** + * Creates test callback for creation test. + * @param {!FieldCreationTestCase} testCase The test case to use. + * @return {!Function} The test callback. + */ + const createTestFn = (testCase) => { + return function() { + chai.assert.throws(function() { + creation.call(this, testCase); + }, testCase.errMsgMatcher); + }; + }; + runTestCases(testCases, createTestFn); +} + +/** + * Runs suite of tests for constructor for the specified field. + * @param {function(new:Blockly.Field, *=)} TestedField The class of the field + * being tested. + * @param {Array} validValueTestCases Test cases with + * valid values for given field. + * @param {Array} invalidValueTestCases Test cases with + * invalid values for given field. + * @param {function(!Blockly.Field, !FieldCreationTestCase) + * } validRunAssertField Asserts that field has expected values. + * @param {function(!Blockly.Field)=} assertFieldDefault Asserts that field has + * default values. If undefined, tests will check that field throws when + * invalid value is passed rather than asserting default. + * @param {function(!FieldCreationTestCase=)=} customCreateWithJs Custom + * creation function to use in tests. + */ +function runConstructorSuiteTests(TestedField, validValueTestCases, + invalidValueTestCases, validRunAssertField, assertFieldDefault, + customCreateWithJs) { + suite('Constructor', function() { + if (assertFieldDefault) { + test('Empty', function() { + const field = customCreateWithJs ? customCreateWithJs.call(this) : + new TestedField(); + assertFieldDefault(field); + }); + } else { + test('Empty', function() { + chai.assert.throws(function() { + customCreateWithJs ? customCreateWithJs.call(this) : + new TestedField(); + }); + }); + } + + /** + * Creates a field using its constructor and the provided test case. + * @param {!FieldCreationTestCase} testCase The test case information. + * @return {!Blockly.Field} The instantiated field. + */ + const createWithJs = function(testCase) { + return customCreateWithJs ? customCreateWithJs.call(this, testCase) : + new TestedField(...testCase.args); + }; + if (assertFieldDefault) { + runCreationTests_( + invalidValueTestCases, assertFieldDefault, createWithJs); + } else { + runCreationTestsAssertThrows_(invalidValueTestCases, createWithJs); + } + runCreationTests_(validValueTestCases, validRunAssertField, createWithJs); + }); +} +exports.runConstructorSuiteTests = runConstructorSuiteTests; + +/** + * Runs suite of tests for fromJson creation of specified field. + * @param {function(new:Blockly.Field, *=)} TestedField The class of the field + * being tested. + * @param {!Array} validValueTestCases Test cases with + * valid values for given field. + * @param {!Array} invalidValueTestCases Test cases with + * invalid values for given field. + * @param {function(!Blockly.Field, !FieldValueTestCase) + * } validRunAssertField Asserts that field has expected values. + * @param {function(!Blockly.Field)=} assertFieldDefault Asserts that field has + * default values. If undefined, tests will check that field throws when + * invalid value is passed rather than asserting default. + * @param {function(!FieldCreationTestCase=)=} customCreateWithJson Custom + * creation function to use in tests. + */ +function runFromJsonSuiteTests(TestedField, validValueTestCases, + invalidValueTestCases, validRunAssertField, assertFieldDefault, + customCreateWithJson) { + suite('fromJson', function() { + if (assertFieldDefault) { + test('Empty', function() { + const field = customCreateWithJson ? customCreateWithJson.call(this) : + TestedField.fromJson({}); + assertFieldDefault(field); + }); + } else { + test('Empty', function() { + chai.assert.throws(function() { + customCreateWithJson ? customCreateWithJson.call(this) : + TestedField.fromJson({}); + }); + }); + } + + /** + * Creates a field using fromJson and the provided test case. + * @param {!FieldCreationTestCase} testCase The test case information. + * @return {!Blockly.Field} The instantiated field. + */ + const createWithJson = function(testCase) { + return customCreateWithJson ? customCreateWithJson.call(this, testCase) : + TestedField.fromJson(testCase.json); + }; + if (assertFieldDefault) { + runCreationTests_( + invalidValueTestCases, assertFieldDefault, createWithJson); + } else { + runCreationTestsAssertThrows_(invalidValueTestCases, createWithJson); + } + runCreationTests_(validValueTestCases, validRunAssertField, createWithJson); + }); +} +exports.runFromJsonSuiteTests = runFromJsonSuiteTests; + +/** + * Runs tests for setValue calls. + * @param {!Array} validValueTestCases Test cases with + * valid values. + * @param {!Array} invalidValueTestCases Test cases with + * invalid values. + * @param {*} invalidRunExpectedValue Expected value for field after invalid + * call to setValue. + * @param {string=} invalidRunExpectedText Expected text for field after invalid + * call to setValue. + */ +function runSetValueTests(validValueTestCases, invalidValueTestCases, + invalidRunExpectedValue, invalidRunExpectedText) { + /** + * Creates test callback for invalid setValue test. + * @param {!FieldValueTestCase} testCase The test case information. + * @return {!Function} The test callback. + */ + const createInvalidSetValueTestCallback = (testCase) => { + return function() { + this.field.setValue(testCase.value); + assertFieldValue( + this.field, invalidRunExpectedValue, invalidRunExpectedText); + }; + }; + /** + * Creates test callback for valid setValue test. + * @param {!FieldValueTestCase} testCase The test case information. + * @return {!Function} The test callback. + */ + const createValidSetValueTestCallback = (testCase) => { + return function() { + this.field.setValue(testCase.value); + assertFieldValue( + this.field, testCase.expectedValue, testCase.expectedText); + }; + }; + runTestCases(invalidValueTestCases, createInvalidSetValueTestCallback); + runTestCases(validValueTestCases, createValidSetValueTestCallback); +} +exports.runSetValueTests = runSetValueTests; diff --git a/tests/mocha/procedures_test_helpers.js b/tests/mocha/test_helpers/procedures.js similarity index 93% rename from tests/mocha/procedures_test_helpers.js rename to tests/mocha/test_helpers/procedures.js index deb75a5ba..23843c9f6 100644 --- a/tests/mocha/procedures_test_helpers.js +++ b/tests/mocha/test_helpers/procedures.js @@ -3,7 +3,7 @@ * Copyright 2020 Google LLC * SPDX-License-Identifier: Apache-2.0 */ -goog.module('Blockly.test.procedureHelpers'); +goog.module('Blockly.test.helpers.procedures'); const {ConnectionType} = goog.require('Blockly.ConnectionType'); @@ -85,13 +85,15 @@ function assertDefBlockStructure(defBlock, hasReturn = false, exports.assertDefBlockStructure = assertDefBlockStructure; /** - * Asserts that the procedure definition block has the expected inputs and + * Asserts that the procedure call block has the expected inputs and * fields. * @param {!Blockly.Block} callBlock The procedure call block. * @param {Array=} args An array of argument names. * @param {Array=} varIds An array of variable ids. + * @param {string=} name The name we expect the caller to have. */ -function assertCallBlockStructure(callBlock, args = [], varIds = []) { +function assertCallBlockStructure( + callBlock, args = [], varIds = [], name = undefined) { if (args.length) { chai.assert.include(callBlock.toString(), 'with'); } else { @@ -100,6 +102,9 @@ function assertCallBlockStructure(callBlock, args = [], varIds = []) { assertCallBlockArgsStructure(callBlock, args); assertBlockVarModels(callBlock, varIds); + if (name !== undefined) { + chai.assert(callBlock.getFieldValue('NAME'), name); + } } exports.assertCallBlockStructure = assertCallBlockStructure; diff --git a/tests/mocha/test_helpers/serialization.js b/tests/mocha/test_helpers/serialization.js new file mode 100644 index 000000000..c8b90ba7f --- /dev/null +++ b/tests/mocha/test_helpers/serialization.js @@ -0,0 +1,132 @@ +/** + * @license + * Copyright 2020 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +goog.module('Blockly.test.helpers.serialization'); + +const {runTestCases} = goog.require('Blockly.test.helpers.common'); + +/** + * Serialization test case. + * @implements {TestCase} + * @record + */ +class SerializationTestCase { + /** + * Class for a block serialization test case. + */ + constructor() { + /** + * @type {string} The block xml to use for test. Do not provide if json is + * provided. + */ + this.xml; + /** + * @type {string|undefined} The expected xml after round trip. Provided if + * it different from xml that was passed in. + */ + this.expectedXml; + /** + * @type {string} The block json to use for test. Do not provide if xml is + * provided. + */ + this.json; + /** + * @type {string|undefined} The expected json after round trip. Provided if + * it is different from json that was passed in. + */ + this.expectedJson; + } + /** + * Asserts that the block created from xml has the expected structure. + * @param {!Blockly.Block} block The block to check. + */ + assertBlockStructure(block) {} +} +exports.SerializationTestCase = SerializationTestCase; + +/** + * Runs serialization test suite. + * @param {!Array} testCases The test cases to run. + */ +const runSerializationTestSuite = (testCases) => { + /** + * Creates test callback for xmlToBlock test. + * @param {!SerializationTestCase} testCase The test case information. + * @return {!Function} The test callback. + */ + const createSerializedDataToBlockTestCallback = (testCase) => { + return function() { + let block; + if (testCase.json) { + block = Blockly.serialization.blocks.append( + testCase.json, this.workspace); + } else { + block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom( + testCase.xml), this.workspace); + } + testCase.assertBlockStructure(block); + }; + }; + /** + * Creates test callback for xml round trip test. + * @param {!SerializationTestCase} testCase The test case information. + * @return {!Function} The test callback. + */ + const createRoundTripTestCallback = (testCase) => { + return function() { + if (testCase.json) { + const block = Blockly.serialization.blocks.append( + testCase.json, this.workspace); + const generatedJson = Blockly.serialization.blocks.save(block); + const expectedJson = testCase.expectedJson || testCase.json; + chai.assert.deepEqual(generatedJson, expectedJson); + } else { + const block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom( + testCase.xml), this.workspace); + const generatedXml = + Blockly.Xml.domToPrettyText( + Blockly.Xml.blockToDom(block)); + const expectedXml = testCase.expectedXml || testCase.xml; + chai.assert.equal(generatedXml, expectedXml); + } + }; + }; + suite('Serialization', function() { + suite('xmlToBlock', function() { + runTestCases(testCases, createSerializedDataToBlockTestCallback); + }); + suite('xml round-trip', function() { + setup(function() { + // The genUid is undergoing change as part of the 2021Q3 + // goog.module migration: + // + // - It is being moved from Blockly.utils to + // Blockly.utils.idGenerator (which itself is being renamed + // from IdGenerator). + // - For compatibility with changes to the module system (from + // goog.provide to goog.module and in future to ES modules), + // .genUid is now a wrapper around .TEST_ONLY.genUid, which + // can be safely stubbed by sinon or other similar + // frameworks in a way that will continue to work. + if (Blockly.utils.idGenerator && + Blockly.utils.idGenerator.TEST_ONLY) { + sinon.stub(Blockly.utils.idGenerator.TEST_ONLY, 'genUid') + .returns('1'); + } else { + // Fall back to stubbing original version on Blockly.utils. + sinon.stub(Blockly.utils, 'genUid').returns('1'); + } + }); + + teardown(function() { + sinon.restore(); + }); + + runTestCases(testCases, createRoundTripTestCallback); + }); + }); +}; +exports.runSerializationTestSuite = runSerializationTestSuite; diff --git a/tests/mocha/test_helpers/setup_teardown.js b/tests/mocha/test_helpers/setup_teardown.js new file mode 100644 index 000000000..e1b085be1 --- /dev/null +++ b/tests/mocha/test_helpers/setup_teardown.js @@ -0,0 +1,208 @@ +/** + * @license + * Copyright 2019 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +goog.module('Blockly.test.helpers.setupTeardown'); + +const eventUtils = goog.require('Blockly.Events.utils'); +const {Blocks} = goog.require('Blockly.blocks'); + + +/** + * Safely disposes of Blockly workspace, logging any errors. + * Assumes that sharedTestSetup has also been called. This should be called + * using workspaceTeardown.call(this). + * @param {!Blockly.Workspace} workspace The workspace to dispose. + */ +function workspaceTeardown(workspace) { + try { + this.clock.runAll(); // Run all queued setTimeout calls. + workspace.dispose(); + this.clock.runAll(); // Run all remaining queued setTimeout calls. + } catch (e) { + const testRef = this.currentTest || this.test; + console.error(testRef.fullTitle() + '\n', e); + } +} +exports.workspaceTeardown = workspaceTeardown; + +/** + * Creates stub for Blockly.Events.fire that advances the clock forward after + * the event fires so it is processed immediately instead of on a timeout. + * @param {!SinonClock} clock The sinon clock. + * @return {!SinonStub} The created stub. + * @private + */ +function createEventsFireStubFireImmediately_(clock) { + const stub = sinon.stub(eventUtils, 'fire'); + stub.callsFake(function(event) { + // Call original method. + stub.wrappedMethod.call(this, ...arguments); + // Advance clock forward to run any queued events. + clock.runAll(); + }); + return stub; +} + +/** + * Adds message to shared cleanup object so that it is cleaned from + * Blockly.Messages global in sharedTestTeardown. + * @param {!Object} sharedCleanupObj The shared cleanup object created in + * sharedTestSetup. + * @param {string} message The message to add to shared cleanup object. + */ +function addMessageToCleanup(sharedCleanupObj, message) { + sharedCleanupObj.messagesCleanup_.push(message); +} +exports.addMessageToCleanup = addMessageToCleanup; + +/** + * Adds block type to shared cleanup object so that it is cleaned from + * Blockly.Blocks global in sharedTestTeardown. + * @param {!Object} sharedCleanupObj The shared cleanup object created in + * sharedTestSetup. + * @param {string} blockType The block type to add to shared cleanup object. + */ +function addBlockTypeToCleanup(sharedCleanupObj, blockType) { + sharedCleanupObj.blockTypesCleanup_.push(blockType); +} +exports.addBlockTypeToCleanup = addBlockTypeToCleanup; + +/** + * Wraps Blockly.defineBlocksWithJsonArray using stub in order to keep track of + * block types passed in to method on shared cleanup object so they are cleaned + * from Blockly.Blocks global in sharedTestTeardown. + * @param {!Object} sharedCleanupObj The shared cleanup object created in + * sharedTestSetup. + * @private + */ +function wrapDefineBlocksWithJsonArrayWithCleanup_(sharedCleanupObj) { + const stub = sinon.stub(Blockly, 'defineBlocksWithJsonArray'); + stub.callsFake(function(jsonArray) { + if (jsonArray) { + jsonArray.forEach((jsonBlock) => { + if (jsonBlock) { + addBlockTypeToCleanup(sharedCleanupObj, jsonBlock['type']); + } + }); + } + // Calls original method. + stub.wrappedMethod.call(this, ...arguments); + }); +} + +/** + * Shared setup method that sets up fake timer for clock so that pending + * setTimeout calls can be cleared in test teardown along with other common + * stubs. Should be called in setup of outermost suite using + * sharedTestSetup.call(this). + * The sinon fake timer defined on this.clock_ should not be reset in tests to + * avoid causing issues with cleanup in sharedTestTeardown. + * + * Stubs created in this setup (unless disabled by options passed): + * - Blockly.Events.fire - this.eventsFireStub - wraps fire event to trigger + * fireNow_ call immediately, rather than on timeout + * - Blockly.defineBlocksWithJsonArray - thin wrapper that adds logic to keep + * track of block types defined so that they can be undefined in + * sharedTestTeardown and calls original method. + * + * @param {Object} options Options to enable/disable setup + * of certain stubs. + */ +function sharedTestSetup(options = {}) { + this.sharedSetupCalled_ = true; + // Sandbox created for greater control when certain stubs are cleared. + this.sharedSetupSandbox_ = sinon.createSandbox(); + this.clock = this.sharedSetupSandbox_.useFakeTimers(); + if (options['fireEventsNow'] === undefined || options['fireEventsNow']) { + // Stubs event firing unless passed option "fireEventsNow: false" + this.eventsFireStub = createEventsFireStubFireImmediately_(this.clock); + } + this.sharedCleanup = { + blockTypesCleanup_: [], + messagesCleanup_: [], + }; + this.blockTypesCleanup_ = this.sharedCleanup.blockTypesCleanup_; + this.messagesCleanup_ = this.sharedCleanup.messagesCleanup_; + wrapDefineBlocksWithJsonArrayWithCleanup_(this.sharedCleanup); +} +exports.sharedTestSetup = sharedTestSetup; + +/** + * Shared cleanup method that clears up pending setTimeout calls, disposes of + * workspace, and resets global variables. Should be called in setup of + * outermost suite using sharedTestTeardown.call(this). + */ +function sharedTestTeardown() { + const testRef = this.currentTest || this.test; + if (!this.sharedSetupCalled_) { + console.error('"' + testRef.fullTitle() + '" did not call sharedTestSetup'); + } + + try { + if (this.workspace) { + workspaceTeardown.call(this, this.workspace); + this.workspace = null; + } else { + this.clock.runAll(); // Run all queued setTimeout calls. + } + } catch (e) { + console.error(testRef.fullTitle() + '\n', e); + } finally { + // Clear Blockly.Event state. + eventUtils.setGroup(false); + while (!eventUtils.isEnabled()) { + eventUtils.enable(); + } + eventUtils.setRecordUndo(true); + if (eventUtils.TEST_ONLY.FIRE_QUEUE.length) { + // If this happens, it may mean that some previous test is missing cleanup + // (i.e. a previous test added an event to the queue on a timeout that + // did not use a stubbed clock). + eventUtils.TEST_ONLY.FIRE_QUEUE.length = 0; + console.warn('"' + testRef.fullTitle() + + '" needed cleanup of Blockly.Events.TEST_ONLY.FIRE_QUEUE. This may ' + + 'indicate leakage from an earlier test'); + } + + // Restore all stubbed methods. + this.sharedSetupSandbox_.restore(); + sinon.restore(); + + const blockTypes = this.sharedCleanup.blockTypesCleanup_; + for (let i = 0; i < blockTypes.length; i++) { + delete Blocks[blockTypes[i]]; + } + const messages = this.sharedCleanup.messagesCleanup_; + for (let i = 0; i < messages.length; i++) { + delete Blockly.Msg[messages[i]]; + } + + Blockly.WidgetDiv.testOnly_setDiv(null); + } +} +exports.sharedTestTeardown = sharedTestTeardown; + +/** + * Creates stub for Blockly.utils.genUid that returns the provided id or ids. + * Recommended to also assert that the stub is called the expected number of + * times. + * @param {string|!Array} returnIds The return values to use for the + * created stub. If a single value is passed, then the stub always returns + * that value. + * @return {!SinonStub} The created stub. + */ +function createGenUidStubWithReturns(returnIds) { + const stub = sinon.stub(Blockly.utils.idGenerator.TEST_ONLY, "genUid"); + if (Array.isArray(returnIds)) { + for (let i = 0; i < returnIds.length; i++) { + stub.onCall(i).returns(returnIds[i]); + } + } else { + stub.returns(returnIds); + } + return stub; +} +exports.createGenUidStubWithReturns = createGenUidStubWithReturns; diff --git a/tests/mocha/toolbox_helper.js b/tests/mocha/test_helpers/toolbox_definitions.js similarity index 99% rename from tests/mocha/toolbox_helper.js rename to tests/mocha/test_helpers/toolbox_definitions.js index bc62d8384..ee7ff576f 100644 --- a/tests/mocha/toolbox_helper.js +++ b/tests/mocha/test_helpers/toolbox_definitions.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -goog.module('Blockly.test.toolboxHelpers'); +goog.module('Blockly.test.helpers.toolboxDefinitions'); /** diff --git a/tests/mocha/test_helpers/user_input.js b/tests/mocha/test_helpers/user_input.js new file mode 100644 index 000000000..9482a7909 --- /dev/null +++ b/tests/mocha/test_helpers/user_input.js @@ -0,0 +1,68 @@ +/** + * @license + * Copyright 2019 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +goog.module('Blockly.test.helpers.userInput'); + +const {KeyCodes} = goog.require('Blockly.utils.KeyCodes'); + + +/** + * Triggers pointer event on target. + * @param {!EventTarget} target The object receiving the event. + * @param {string} type The type of mouse event (eg: mousedown, mouseup, + * click). + * @param {Object=} properties Properties to pass into event + * constructor. + */ +function dispatchPointerEvent(target, type, properties) { + const eventInitDict = { + cancelable: true, + bubbles: true, + isPrimary: true, + pressure: 0.5, + clientX: 10, + clientY: 10, + }; + if (properties) { + Object.assign(eventInitDict, properties); + } + const event = new PointerEvent(type, eventInitDict); + target.dispatchEvent(event); +} +exports.dispatchPointerEvent = dispatchPointerEvent; + +/** + * Creates a key down event used for testing. + * @param {number} keyCode The keycode for the event. Use Blockly.utils.KeyCodes enum. + * @param {!Array=} modifiers A list of modifiers. Use Blockly.utils.KeyCodes enum. + * @return {!KeyboardEvent} The mocked keydown event. + */ +function createKeyDownEvent(keyCode, modifiers) { + const event = { + keyCode: keyCode, + }; + if (modifiers && modifiers.length > 0) { + event.altKey = modifiers.indexOf(KeyCodes.ALT) > -1; + event.ctrlKey = modifiers.indexOf(KeyCodes.CTRL) > -1; + event.metaKey = modifiers.indexOf(KeyCodes.META) > -1; + event.shiftKey = modifiers.indexOf(KeyCodes.SHIFT) > -1; + } + return new KeyboardEvent('keydown', event); +} +exports.createKeyDownEvent = createKeyDownEvent; + +/** + * Simulates mouse click by triggering relevant mouse events. + * @param {!EventTarget} target The object receiving the event. + * @param {Object=} properties Properties to pass into event + * constructor. + */ +function simulateClick(target, properties) { + dispatchPointerEvent(target, 'pointerdown', properties); + dispatchPointerEvent(target, 'pointerup', properties); + dispatchPointerEvent(target, 'click', properties); +} +exports.simulateClick = simulateClick; diff --git a/tests/mocha/test_helpers/variables.js b/tests/mocha/test_helpers/variables.js new file mode 100644 index 000000000..f550e960b --- /dev/null +++ b/tests/mocha/test_helpers/variables.js @@ -0,0 +1,25 @@ +/** + * @license + * Copyright 2019 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +goog.module('Blockly.test.helpers.variables'); + + +/** + * Check if a variable with the given values exists. + * @param {Blockly.Workspace|Blockly.VariableMap} container The workspace or + * variableMap the checked variable belongs to. + * @param {!string} name The expected name of the variable. + * @param {!string} type The expected type of the variable. + * @param {!string} id The expected id of the variable. + */ +function assertVariableValues(container, name, type, id) { + const variable = container.getVariableById(id); + chai.assert.isDefined(variable); + chai.assert.equal(variable.name, name); + chai.assert.equal(variable.type, type); + chai.assert.equal(variable.getId(), id); +} +exports.assertVariableValues = assertVariableValues; diff --git a/tests/mocha/test_helpers/warnings.js b/tests/mocha/test_helpers/warnings.js new file mode 100644 index 000000000..0457395cc --- /dev/null +++ b/tests/mocha/test_helpers/warnings.js @@ -0,0 +1,90 @@ +/** + * @license + * Copyright 2019 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +goog.module('Blockly.test.helpers.warnings'); + + +/** + * Captures the strings sent to console.warn() when calling a function. + * Copies from core. + * @param {Function} innerFunc The function where warnings may called. + * @return {Array} The warning messages (only the first arguments). + */ +function captureWarnings(innerFunc) { + const msgs = []; + const nativeConsoleWarn = console.warn; + try { + console.warn = function(msg) { + msgs.push(msg); + }; + innerFunc(); + } finally { + console.warn = nativeConsoleWarn; + } + return msgs; +} +exports.captureWarnings = captureWarnings; + +/** + * Asserts that the given function logs the provided warning messages. + * @param {function()} innerFunc The function to call. + * @param {Array|!RegExp} messages A list of regex for the expected + * messages (in the expected order). + */ +function assertWarnings(innerFunc, messages) { + if (!Array.isArray(messages)) { + messages = [messages]; + } + const warnings = captureWarnings(innerFunc); + chai.assert.lengthOf(warnings, messages.length); + messages.forEach((message, i) => { + chai.assert.match(warnings[i], message); + }); +} +exports.assertWarnings = assertWarnings; + +/** + * Asserts that the given function logs no warning messages. + * @param {function()} innerFunc The function to call. + */ +function assertNoWarnings(innerFunc) { + assertWarnings(innerFunc, []); +} +exports.assertNoWarnings = assertNoWarnings; + +/** + * Stubs Blockly.utils.deprecation.warn call. + * @return {!SinonStub} The created stub. + */ +function createDeprecationWarningStub() { + return sinon.stub(Blockly.utils.deprecation, 'warn'); +} +exports.createDeprecationWarningStub = createDeprecationWarningStub; + +/** + * Asserts whether the given deprecation warning stub or call was called with + * the expected functionName. + * @param {!SinonSpy|!SinonSpyCall} spyOrSpyCall The spy or spy call to use. + * @param {string} functionName The function name to check that the given spy or + * spy call was called with. + */ +function assertDeprecationWarningCall(spyOrSpyCall, functionName) { + sinon.assert.calledWith(spyOrSpyCall, functionName); +} +exports.assertDeprecationWarningCall = assertDeprecationWarningCall; + +/** + * Asserts that there was a single deprecation warning call with the given + * functionName passed. + * @param {!SinonSpy} spy The spy to use. + * @param {string} functionName The function name to check that the given spy + * was called with. + */ +function assertSingleDeprecationWarningCall(spy, functionName) { + sinon.assert.calledOnce(spy); + assertDeprecationWarningCall(spy.getCall(0), functionName); +} +exports.assertSingleDeprecationWarningCall = assertSingleDeprecationWarningCall; diff --git a/tests/mocha/workspace_helpers.js b/tests/mocha/test_helpers/workspace.js similarity index 99% rename from tests/mocha/workspace_helpers.js rename to tests/mocha/test_helpers/workspace.js index 04aa9939d..204a46df6 100644 --- a/tests/mocha/workspace_helpers.js +++ b/tests/mocha/test_helpers/workspace.js @@ -4,10 +4,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -goog.module('Blockly.test.workspaceHelpers'); +goog.module('Blockly.test.helpers.workspace'); -const {assertVariableValues, assertWarnings, workspaceTeardown} = goog.require('Blockly.test.helpers'); +const {assertVariableValues} = goog.require('Blockly.test.helpers.variables'); +const {assertWarnings} = goog.require('Blockly.test.helpers.warnings'); const eventUtils = goog.require('Blockly.Events.utils'); +const {workspaceTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); function testAWorkspace() { diff --git a/tests/mocha/theme_test.js b/tests/mocha/theme_test.js index 5c8d5d24e..95d61ce0d 100644 --- a/tests/mocha/theme_test.js +++ b/tests/mocha/theme_test.js @@ -6,7 +6,9 @@ goog.module('Blockly.test.theme'); -const {assertEventFired, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers'); +const {assertEventFired} = goog.require('Blockly.test.helpers.events'); +const eventUtils = goog.require('Blockly.Events.utils'); +const {sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('Theme', function() { @@ -146,7 +148,7 @@ suite('Theme', function() { assertEventFired( this.eventsFireStub, Blockly.Events.ThemeChange, - {themeName: 'themeName'}, workspace.id); + {themeName: 'themeName', type: eventUtils.THEME_CHANGE}, workspace.id); } finally { workspaceTeardown.call(this, workspace); } diff --git a/tests/mocha/toolbox_test.js b/tests/mocha/toolbox_test.js index b717060d4..65acef5b4 100644 --- a/tests/mocha/toolbox_test.js +++ b/tests/mocha/toolbox_test.js @@ -6,8 +6,9 @@ goog.module('Blockly.test.toolbox'); -const {defineStackBlock, sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); -const {getBasicToolbox, getCategoryJSON, getChildItem, getCollapsibleItem, getDeeplyNestedJSON, getInjectedToolbox, getNonCollapsibleItem, getSeparator, getSimpleJson, getXmlArray} = goog.require('Blockly.test.toolboxHelpers'); +const {defineStackBlock} = goog.require('Blockly.test.helpers.blockDefinitions'); +const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); +const {getBasicToolbox, getCategoryJSON, getChildItem, getCollapsibleItem, getDeeplyNestedJSON, getInjectedToolbox, getNonCollapsibleItem, getSeparator, getSimpleJson, getXmlArray} = goog.require('Blockly.test.helpers.toolboxDefinitions'); suite('Toolbox', function() { diff --git a/tests/mocha/tooltip_test.js b/tests/mocha/tooltip_test.js index a2c5bb216..5483ea390 100644 --- a/tests/mocha/tooltip_test.js +++ b/tests/mocha/tooltip_test.js @@ -6,39 +6,70 @@ goog.module('Blockly.test.tooltip'); -const {sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers'); +const {sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('Tooltip', function() { setup(function() { sharedTestSetup.call(this); this.workspace = new Blockly.Workspace(); + + Blockly.defineBlocksWithJsonArray([ + { + 'type': 'test_block', + 'message0': '%1', + 'args0': [ + { + 'type': 'field_input', + 'name': 'FIELD', + }, + ], + }, + ]); }); teardown(function() { + delete Blockly.Blocks['test_block']; sharedTestTeardown.call(this); }); - suite('set/getTooltip', function() { + suite('Custom Tooltip', function() { setup(function() { - Blockly.defineBlocksWithJsonArray([ - { - "type": "test_block", - "message0": "%1", - "args0": [ - { - "type": "field_input", - "name": "FIELD", - }, - ], - }, - ]); + this.renderedWorkspace = Blockly.inject('blocklyDiv', {}); }); teardown(function() { - delete Blockly.Blocks["test_block"]; + workspaceTeardown.call(this, this.renderedWorkspace); }); + test('Custom function is called', function() { + // Custom tooltip function is registered and should be called when mouse + // events are fired. + let wasCalled = false; + const customFn = function() { + wasCalled = true; + }; + Blockly.Tooltip.setCustomTooltip(customFn); + + this.block = this.renderedWorkspace.newBlock('test_block'); + this.block.setTooltip('Test Tooltip'); + + // Fire pointer events directly on the relevant SVG. + // Note the 'pointerover', due to the events registered through + // Blockly.browserEvents.bind being registered as pointer events rather + // than mouse events. Mousemove event is registered directly on the + // element rather than through browserEvents. + this.block.pathObject.svgPath.dispatchEvent( + new MouseEvent('pointerover')); + this.block.pathObject.svgPath.dispatchEvent(new MouseEvent('mousemove')); + this.clock.runAll(); + + chai.assert.isTrue( + wasCalled, 'Expected custom tooltip function to have been called'); + }); + }); + + suite('set/getTooltip', function() { const tooltipText = 'testTooltip'; function assertTooltip(obj) { @@ -97,7 +128,8 @@ suite('Tooltip', function() { test('Function returning object', function() { setFunctionReturningObjectTooltip(this.block); - chai.assert.throws(this.block.getTooltip.bind(this.block), + chai.assert.throws( + this.block.getTooltip.bind(this.block), 'Tooltip function must return a string.'); }); @@ -136,7 +168,8 @@ suite('Tooltip', function() { test('Function returning object', function() { setFunctionReturningObjectTooltip(this.block); - chai.assert.throws(this.block.getTooltip.bind(this.block), + chai.assert.throws( + this.block.getTooltip.bind(this.block), 'Tooltip function must return a string.'); }); @@ -169,7 +202,8 @@ suite('Tooltip', function() { test('Function returning object', function() { setFunctionReturningObjectTooltip(this.field); - chai.assert.throws(this.field.getTooltip.bind(this.field), + chai.assert.throws( + this.field.getTooltip.bind(this.field), 'Tooltip function must return a string.'); }); @@ -215,7 +249,8 @@ suite('Tooltip', function() { test('Function returning object', function() { setFunctionReturningObjectTooltip(this.field); - chai.assert.throws(this.field.getTooltip.bind(this.field), + chai.assert.throws( + this.field.getTooltip.bind(this.field), 'Tooltip function must return a string.'); }); diff --git a/tests/mocha/trashcan_test.js b/tests/mocha/trashcan_test.js index fc4550b77..c57d6cb6f 100644 --- a/tests/mocha/trashcan_test.js +++ b/tests/mocha/trashcan_test.js @@ -6,8 +6,11 @@ goog.module('Blockly.test.trashcan'); -const {assertEventFired, assertEventNotFired, defineBasicBlockWithField, defineRowBlock, defineStatementBlock, defineStackBlock, defineMutatorBlocks, sharedTestSetup, sharedTestTeardown, simulateClick} = goog.require('Blockly.test.helpers'); +const {assertEventFired, assertEventNotFired} = goog.require('Blockly.test.helpers.events'); +const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); +const {defineBasicBlockWithField, defineMutatorBlocks, defineRowBlock, defineStackBlock, defineStatementBlock} = goog.require('Blockly.test.helpers.blockDefinitions'); const eventUtils = goog.require('Blockly.Events.utils'); +const {simulateClick} = goog.require('Blockly.test.helpers.userInput'); suite("Trashcan", function() { @@ -77,9 +80,9 @@ suite("Trashcan", function() { simulateClick(this.trashcan.svgGroup_); assertEventNotFired( - this.eventsFireStub, Blockly.Events.TrashcanOpen, {}); + this.eventsFireStub, Blockly.Events.TrashcanOpen, {type: eventUtils.CLICK}); assertEventFired( - this.eventsFireStub, Blockly.Events.Click, {targetType: 'workspace'}, + this.eventsFireStub, Blockly.Events.Click, {targetType: 'workspace', type: eventUtils.CLICK}, this.workspace.id, null); }); test("Click with contents - fires trashcanOpen", function() { @@ -94,9 +97,9 @@ suite("Trashcan", function() { assertEventFired( this.eventsFireStub, Blockly.Events.TrashcanOpen, - {isOpen: true}, this.workspace.id); + {isOpen: true, type: eventUtils.TRASHCAN_OPEN}, this.workspace.id); assertEventNotFired( - this.eventsFireStub, Blockly.Events.Click, {}); + this.eventsFireStub, Blockly.Events.Click, {type: eventUtils.TRASHCAN_OPEN}); }); test("Click outside trashcan - fires trashcanClose", function() { sinon.stub(this.trashcan.flyout, 'isVisible').returns(true); @@ -109,9 +112,9 @@ suite("Trashcan", function() { assertEventFired( this.eventsFireStub, Blockly.Events.TrashcanOpen, - {isOpen: false}, this.workspace.id); + {isOpen: false, type: eventUtils.TRASHCAN_OPEN}, this.workspace.id); assertEventFired( - this.eventsFireStub, Blockly.Events.Click, {targetType: 'workspace'}, + this.eventsFireStub, Blockly.Events.Click, {targetType: 'workspace', type: eventUtils.CLICK}, this.workspace.id, null); }); }); diff --git a/tests/mocha/utils_test.js b/tests/mocha/utils_test.js index 36ade70a7..57ce83f10 100644 --- a/tests/mocha/utils_test.js +++ b/tests/mocha/utils_test.js @@ -6,7 +6,7 @@ goog.module('Blockly.test.utils'); -const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('Utils', function() { diff --git a/tests/mocha/variable_map_test.js b/tests/mocha/variable_map_test.js index b86d4e4fd..14bc0168e 100644 --- a/tests/mocha/variable_map_test.js +++ b/tests/mocha/variable_map_test.js @@ -6,7 +6,8 @@ goog.module('Blockly.test.variableMap'); -const {assertVariableValues, createGenUidStubWithReturns, sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {assertVariableValues} = goog.require('Blockly.test.helpers.variables'); +const {createGenUidStubWithReturns, sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('Variable Map', function() { diff --git a/tests/mocha/variable_model_test.js b/tests/mocha/variable_model_test.js index 272248cca..08a134642 100644 --- a/tests/mocha/variable_model_test.js +++ b/tests/mocha/variable_model_test.js @@ -6,7 +6,7 @@ goog.module('Blockly.test.variableModel'); -const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('Variable Model', function() { diff --git a/tests/mocha/variables_test.js b/tests/mocha/variables_test.js index fb27ec14a..e128576d0 100644 --- a/tests/mocha/variables_test.js +++ b/tests/mocha/variables_test.js @@ -6,7 +6,7 @@ goog.module('Blockly.test.variables'); -const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('Variables', function() { diff --git a/tests/mocha/widget_div_test.js b/tests/mocha/widget_div_test.js index 8e91545bd..dade02878 100644 --- a/tests/mocha/widget_div_test.js +++ b/tests/mocha/widget_div_test.js @@ -6,7 +6,7 @@ goog.module('Blockly.test.widgetDiv'); -const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('WidgetDiv', function() { diff --git a/tests/mocha/workspace_comment_test.js b/tests/mocha/workspace_comment_test.js index c48e70ee2..233e42e39 100644 --- a/tests/mocha/workspace_comment_test.js +++ b/tests/mocha/workspace_comment_test.js @@ -7,7 +7,7 @@ goog.module('Blockly.test.workspaceComment'); goog.require('Blockly.WorkspaceComment'); -const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers'); +const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); suite('Workspace comment', function() { diff --git a/tests/mocha/workspace_svg_test.js b/tests/mocha/workspace_svg_test.js index c6af088ec..f5fe4cd70 100644 --- a/tests/mocha/workspace_svg_test.js +++ b/tests/mocha/workspace_svg_test.js @@ -6,8 +6,12 @@ goog.module('Blockly.test.workspaceSvg'); -const {assertEventFired, assertEventNotFired, assertVariableValues, createFireChangeListenerSpy, defineStackBlock, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers'); -const {testAWorkspace} = goog.require('Blockly.test.workspaceHelpers'); +const {assertEventFired, assertEventNotFired, createFireChangeListenerSpy} = goog.require('Blockly.test.helpers.events'); +const {assertVariableValues} = goog.require('Blockly.test.helpers.variables'); +const {defineStackBlock} = goog.require('Blockly.test.helpers.blockDefinitions'); +const eventUtils = goog.require('Blockly.Events.utils'); +const {sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); +const {testAWorkspace} = goog.require('Blockly.test.helpers.workspace'); suite('WorkspaceSvg', function() { @@ -182,6 +186,7 @@ suite('WorkspaceSvg', function() { oldScale: 1, viewTop: metrics.viewTop, viewLeft: metrics.viewLeft, + type: eventUtils.VIEWPORT_CHANGE, }; assertSpyFiredViewportEvent( eventsFireStub, workspace, expectedProperties); @@ -294,9 +299,10 @@ suite('WorkspaceSvg', function() { ''), this.workspace); this.clock.runAll(); assertEventNotFired( - this.eventsFireStub, Blockly.Events.ViewportChange, {}); + this.eventsFireStub, Blockly.Events.ViewportChange, {type: eventUtils.VIEWPORT_CHANGE}); assertEventNotFired( - this.changeListenerSpy, Blockly.Events.ViewportChange, {}); + this.changeListenerSpy, Blockly.Events.ViewportChange, + {type: eventUtils.VIEWPORT_CHANGE}); }); test('domToWorkspace at 0,0 that doesn\'t trigger scroll', function() { // 4 blocks with space in center. @@ -317,9 +323,11 @@ suite('WorkspaceSvg', function() { Blockly.Xml.domToWorkspace(xmlDom, this.workspace); this.clock.runAll(); assertEventNotFired( - this.eventsFireStub, Blockly.Events.ViewportChange, {}); + this.eventsFireStub, Blockly.Events.ViewportChange, + {type: eventUtils.VIEWPORT_CHANGE}); assertEventNotFired( - this.changeListenerSpy, Blockly.Events.ViewportChange, {}); + this.changeListenerSpy, Blockly.Events.ViewportChange, + {type: eventUtils.VIEWPORT_CHANGE}); }); test.skip('domToWorkspace multiple blocks triggers one viewport event', function() { // TODO: Un-skip after adding filtering for consecutive viewport events. diff --git a/tests/mocha/workspace_test.js b/tests/mocha/workspace_test.js index b221f6310..8153518b0 100644 --- a/tests/mocha/workspace_test.js +++ b/tests/mocha/workspace_test.js @@ -6,8 +6,9 @@ goog.module('Blockly.test.workspace'); -const {assertVariableValues, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers'); -const {testAWorkspace} = goog.require('Blockly.test.workspaceHelpers'); +const {assertVariableValues} = goog.require('Blockly.test.helpers.variables'); +const {sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); +const {testAWorkspace} = goog.require('Blockly.test.helpers.workspace'); suite('Workspace', function() { diff --git a/tests/mocha/xml_test.js b/tests/mocha/xml_test.js index 7354d6370..78ddeeeef 100644 --- a/tests/mocha/xml_test.js +++ b/tests/mocha/xml_test.js @@ -6,7 +6,8 @@ goog.module('Blockly.test.xml'); -const {addBlockTypeToCleanup, assertVariableValues, createGenUidStubWithReturns, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers'); +const {addBlockTypeToCleanup, createGenUidStubWithReturns, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); +const {assertVariableValues} = goog.require('Blockly.test.helpers.variables'); suite('XML', function() { diff --git a/tests/mocha/zoom_controls_test.js b/tests/mocha/zoom_controls_test.js index e17bccddb..573e8b8ff 100644 --- a/tests/mocha/zoom_controls_test.js +++ b/tests/mocha/zoom_controls_test.js @@ -6,7 +6,10 @@ goog.module('Blockly.test.zoomControls'); -const {assertEventFired, assertEventNotFired, sharedTestSetup, sharedTestTeardown, simulateClick} = goog.require('Blockly.test.helpers'); +const {assertEventFired, assertEventNotFired} = goog.require('Blockly.test.helpers.events'); +const eventUtils = goog.require('Blockly.Events.utils'); +const {sharedTestSetup, sharedTestTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); +const {simulateClick} = goog.require('Blockly.test.helpers.userInput'); suite("Zoom Controls", function() { @@ -31,10 +34,10 @@ suite("Zoom Controls", function() { assertEventFired( this.eventsFireStub, Blockly.Events.Click, - {targetType: 'zoom_controls'}, this.workspace.id, null); + {targetType: 'zoom_controls', type: eventUtils.CLICK}, this.workspace.id, null); assertEventNotFired( this.eventsFireStub, Blockly.Events.Click, - {targetType: 'workspace'}); + {targetType: 'workspace', type: eventUtils.CLICK}); chai.assert.closeTo(this.workspace.getScale(), 1.2, 0.05); }); test("Zoom out", function() { @@ -42,10 +45,10 @@ suite("Zoom Controls", function() { assertEventFired( this.eventsFireStub, Blockly.Events.Click, - {targetType: 'zoom_controls'}, this.workspace.id, null); + {targetType: 'zoom_controls', type: eventUtils.CLICK}, this.workspace.id, null); assertEventNotFired( this.eventsFireStub, Blockly.Events.Click, - {targetType: 'workspace'}); + {targetType: 'workspace', type: eventUtils.CLICK}); chai.assert.closeTo(this.workspace.getScale(), 0.8, 0.05); }); test("Reset zoom", function() { @@ -53,10 +56,10 @@ suite("Zoom Controls", function() { assertEventFired( this.eventsFireStub, Blockly.Events.Click, - {targetType: 'zoom_controls'}, this.workspace.id, null); + {targetType: 'zoom_controls', type: eventUtils.CLICK}, this.workspace.id, null); assertEventNotFired( this.eventsFireStub, Blockly.Events.Click, - {targetType: 'workspace'}); + {targetType: 'workspace', type: eventUtils.CLICK}); chai.assert.equal(this.workspace.getScale(), 1); }); }); diff --git a/tests/multi_playground.html b/tests/multi_playground.html index 0cabadb6d..da48e2c85 100644 --- a/tests/multi_playground.html +++ b/tests/multi_playground.html @@ -3,15 +3,16 @@ Multi-toolbox Playground - - - + + + - +

Blockly Playground

-

Show - - Hide - - Advanced

+

+ - + - + Advanced +

- - - + + +
- - - - - + + + + +
- +

Stress test:   - - - + + +

  • - +
  • - +
diff --git a/tests/playgrounds/advanced_playground.html b/tests/playgrounds/advanced_playground.html index e5279d74c..1510555b7 100644 --- a/tests/playgrounds/advanced_playground.html +++ b/tests/playgrounds/advanced_playground.html @@ -3,29 +3,14 @@ Advanced Blockly Playground - - - + + - - - +
diff --git a/tests/playgrounds/blockly.mjs b/tests/playgrounds/blockly.mjs new file mode 100644 index 000000000..cb836f539 --- /dev/null +++ b/tests/playgrounds/blockly.mjs @@ -0,0 +1,39 @@ +/** + * @license + * Copyright 2022 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Finishes loading Blockly and exports it as this + * module's default export. + * + * It is exported as the default export to avoid having to + * re-export each property on Blockly individually, because you + * can't do: + * + * export * from ; // SYNTAX ERROR + * + * You must use a - - - - - - - - - - - - - - - - -
- -

Blockly Playground

- -

Show - - Hide - - Advanced

- -
- - -
-

- - - -
- - - - - -
- -

- -

- Stress test:   - - - -

-
    -
  • - - -
  • -
  • - - -
  • -
- - - - - - - - - - - - - - diff --git a/tests/playgrounds/load_all.js b/tests/playgrounds/load_all.js new file mode 100644 index 000000000..5eaa65e03 --- /dev/null +++ b/tests/playgrounds/load_all.js @@ -0,0 +1,53 @@ +/** + * @license + * Copyright 2021 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Loads uncompressed Blockly when running locally. Loads + * compressed otherwise. + */ +'use strict'; + + +/** + * Loads all the compressed or uncompressed dependencies necessary to run the + * playground. This is necessary since the goog.module conversion. Please see + * issue #5557 for more information. + */ +(function() { +const isIe = navigator.userAgent.indexOf('MSIE') !== -1 || + navigator.appVersion.indexOf('Trident/') > -1; + +if ((location.hostname === 'localhost' || location.hostname === '127.0.0.1' || + location.hostname === '[::1]') && + !isIe) { + document.write( + ``); + document.write(``); + document.write(``); + document.write( + ``); + document.write(``); +} else { + document.write( + ``); + document.write(``); + document.write(``); + document.write(``); + document.write(``); + document.write(``); + document.write(``); + document.write(``); +} +})(); diff --git a/tests/playgrounds/multi_compressed.html b/tests/playgrounds/multi_compressed.html deleted file mode 100644 index 29f0ae383..000000000 --- a/tests/playgrounds/multi_compressed.html +++ /dev/null @@ -1,470 +0,0 @@ - - - - -Multi-toolbox Compressed Playground - - - - - - - - -

Blockly Multi Playground

- -
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
LTR, Vertical, StartRTL, Vertical, Start
LTR, Vertical, EndRTL, Vertical, End
LTR, Horizontal, StartRTL, Horizontal, Start
LTR, Horizontal, EndRTL, Horizontal, End
- - - - - - diff --git a/tests/playgrounds/prepare.js b/tests/playgrounds/prepare.js new file mode 100644 index 000000000..d428b9524 --- /dev/null +++ b/tests/playgrounds/prepare.js @@ -0,0 +1,99 @@ +/** + * @license + * Copyright 2021 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Load this file in a '); + // Load dependency graph info from test/deps.js. To update + // deps.js, run `npm run build:deps`. + document.write(''); + + // Msg loading kludge. This should go away once #5409 and/or + // #1895 are fixed. + + // Load messages into a temporary Blockly.Msg object, deleting it + // afterwards (after saving the messages!) + window.Blockly = {Msg: Object.create(null)}; + document.write(''); + document.write(` + `); + + document.write(` + `); + } else { + // The below code is necessary for a few reasons: + // - We need an absolute path instead of relative path because the + // advanced_playground the and regular playground are in different folders. + // - We need to get the root directory for blockly because it is + // different for github.io, appspot and local. + const files = [ + 'blockly_compressed.js', + 'msg/messages.js', + 'blocks_compressed.js', + 'dart_compressed.js', + 'javascript_compressed.js', + 'lua_compressed.js', + 'php_compressed.js', + 'python_compressed.js', + ]; + + // We need to load Blockly in compiled mode. + const hostName = window.location.host.replaceAll('.', '\\.'); + const matches = new RegExp(hostName + '\\/(.*)tests').exec(window.location.href); + const root = matches && matches[1] ? matches[1] : ''; + + // Load blockly_compressed.js et al. using '); + } + } +})(); diff --git a/tests/playgrounds/screenshot.js b/tests/playgrounds/screenshot.js index a1ee9a204..59d0594e2 100644 --- a/tests/playgrounds/screenshot.js +++ b/tests/playgrounds/screenshot.js @@ -95,7 +95,7 @@ function workspaceToSvg_(workspace, callback, customCss) { * Download a screenshot of the blocks on a Blockly workspace. * @param {!Blockly.WorkspaceSvg} workspace The Blockly workspace. */ -Blockly.downloadScreenshot = function(workspace) { +function downloadScreenshot(workspace) { workspaceToSvg_(workspace, function(datauri) { const a = document.createElement('a'); a.download = 'screenshot.png'; @@ -105,4 +105,4 @@ Blockly.downloadScreenshot = function(workspace) { a.click(); a.parentNode.removeChild(a); }); -}; +} diff --git a/tests/run_all_tests.sh b/tests/run_all_tests.sh index 9318ede6a..1d169be4f 100755 --- a/tests/run_all_tests.sh +++ b/tests/run_all_tests.sh @@ -62,6 +62,9 @@ run_test_command "build-debug" "npm run build-debug" # TODO(5621): Re-enable this test once typings generation is fixed. # run_test_command "typings" "npm run typings" +# Run renaming validation test. +run_test_command "renamings" "tests/migration/validate-renamings.js" + # Check the sizes of built files for unexpected growth. run_test_command "metadata" "tests/scripts/check_metadata.sh" @@ -80,7 +83,6 @@ run_test_command "node" "./node_modules/.bin/mocha tests/node --config tests/nod # Attempt advanced compilation of a Blockly app. run_test_command "advanced_compile" "npm run test:compile:advanced" - # End of tests. popd echo "=======================================" diff --git a/tests/scripts/check_metadata.sh b/tests/scripts/check_metadata.sh index 02195205c..7bf9e38e2 100755 --- a/tests/scripts/check_metadata.sh +++ b/tests/scripts/check_metadata.sh @@ -28,7 +28,8 @@ readonly BUILD_DIR='build' # Q3 2021 6.20210701.0 731695 (mid-quarter goog.module conversion) # Q3 2021 6.20210701.0 808807 (late-quarter goog.module conversion) # Q4 2021 7.20211209.0-beta.0 920002 -readonly BLOCKLY_SIZE_EXPECTED=920002 +# Q4 2021 7.20211209.0 929665 +readonly BLOCKLY_SIZE_EXPECTED=929665 # Size of blocks_compressed.js # Q2 2019 2.20190722.0 75618 @@ -42,7 +43,8 @@ readonly BLOCKLY_SIZE_EXPECTED=920002 # Q2 2021 6.20210701.0 76669 # Q3 2021 6.20210701.0 76669 # Q4 2021 7.20211209.0-beta.0 82054 -readonly BLOCKS_SIZE_EXPECTED=82054 +# Q4 2021 7.20211209.0 86966 +readonly BLOCKS_SIZE_EXPECTED=86966 # Size of blockly_compressed.js.gz # Q2 2019 2.20190722.0 180925 @@ -57,7 +59,8 @@ readonly BLOCKS_SIZE_EXPECTED=82054 # Q3 2021 6.20210701.0 147476 (mid-quarter goog.module conversion) # Q3 2021 6.20210701.0 152025 (late-quarter goog.module conversion) # Q4 2021 7.20211209.0-beta.0 169863 -readonly BLOCKLY_GZ_SIZE_EXPECTED=169863 +# Q4 2021 7.20211209.0 171759 +readonly BLOCKLY_GZ_SIZE_EXPECTED=171759 # Size of blocks_compressed.js.gz # Q2 2019 2.20190722.0 14552 @@ -71,7 +74,8 @@ readonly BLOCKLY_GZ_SIZE_EXPECTED=169863 # Q2 2021 6.20210701.0 15275 # Q3 2021 6.20210701.0 15284 # Q4 2021 7.20211209.0-beta.0 16616 -readonly BLOCKS_GZ_SIZE_EXPECTED=16616 +# Q4 2021 7.20211209.0 15760 +readonly BLOCKS_GZ_SIZE_EXPECTED=15760 # ANSI colors readonly BOLD_GREEN='\033[1;32m' diff --git a/tests/themes/test_themes.js b/tests/themes/test_themes.js index 30792ae9e..ad42d50af 100644 --- a/tests/themes/test_themes.js +++ b/tests/themes/test_themes.js @@ -5,24 +5,22 @@ */ 'use strict'; -goog.provide('Blockly.TestThemes'); - /** * A theme with classic colours but enables start hats. */ -Blockly.Themes.TestHats = Blockly.Theme.defineTheme('testhats', { +const TestHatsTheme = Blockly.Theme.defineTheme('testhats', { 'base': Blockly.Themes.Classic }); -Blockly.Themes.TestHats.setStartHats(true); +TestHatsTheme.setStartHats(true); /** * A theme with classic colours but a different font. */ -Blockly.Themes.TestFont = Blockly.Theme.defineTheme('testfont', { +const TestFontTheme = Blockly.Theme.defineTheme('testfont', { 'base': Blockly.Themes.Classic }); -Blockly.Themes.TestFont.setFontStyle({ +TestFontTheme.setFontStyle({ 'family': '"Times New Roman", Times, serif', 'weight': null, // Use default font-weight 'size': 16 @@ -33,9 +31,9 @@ Blockly.Themes.TestFont.setFontStyle({ * @type {!Object} * @private */ -Blockly.Themes.testThemes_ = { - 'Test Hats': Blockly.Themes.TestHats, - 'Test Font': Blockly.Themes.TestFont +const testThemes_ = { + 'Test Hats': TestHatsTheme, + 'Test Font': TestFontTheme }; /** @@ -45,7 +43,7 @@ Blockly.Themes.testThemes_ = { * @package */ function getTestTheme(value) { - return Blockly.Themes.testThemes_[value]; + return testThemes_[value]; } /** @@ -54,7 +52,7 @@ function getTestTheme(value) { */ function populateTestThemes() { var themeChanger = document.getElementById('themeChanger'); - var keys = Object.keys(Blockly.Themes.testThemes_); + var keys = Object.keys(testThemes_); for (var i = 0, key; (key = keys[i]); i++) { var option = document.createElement('option'); option.setAttribute('value', key); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..88c95700b --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,25 @@ +{ + "include": [ + "core/**/*", + "closure/goog/base_minimal.js", + ], + "compilerOptions": { + // Tells TypeScript to read JS files, as + // normally they are ignored as source files + "allowJs": true, + + // Enable the next few options for type declarations. + // Generate d.ts files + //"declaration": true, + // Types should go into this directory. + // Removing this would place the .d.ts files + // next to the .js files + //"declarationDir": "build/ts/declarations", + + "outDir": "build/ts", + "module": "ES2015", + "moduleResolution": "node", + "target": "ES2020", + "strict": true, + } +} diff --git a/typings/blockly.d.ts b/typings/blockly.d.ts index 291f472fd..b2fe86e59 100644 --- a/typings/blockly.d.ts +++ b/typings/blockly.d.ts @@ -1,111 +1,282 @@ -declare module "utils/idgenerator" { - namespace internal { - /** - * Generate a random unique ID. This should be globally unique. - * 87 characters ^ 20 length > 128 bits (better than a UUID). - * @return {string} A globally unique ID string. - */ - function genUid(): string; - } +declare module "core/utils/colour" { /** - * Generate the next unique element IDs. - * IDs are compatible with the HTML4 id attribute restrictions: - * Use only ASCII letters, digits, '_', '-' and '.' + * Get the richness of block colours, regardless of the hue. + * @alias Blockly.utils.colour.getHsvSaturation + * @return {number} The current richness. + * @package + */ + export function getHsvSaturation(): number; + /** + * Set the richness of block colours, regardless of the hue. + * @param {number} newSaturation The new richness, in the range of 0 + * (inclusive) to 1 (exclusive) + * @alias Blockly.utils.colour.setHsvSaturation + * @package + */ + export function setHsvSaturation(newSaturation: number): void; + /** + * Get the intensity of block colours, regardless of the hue. + * @alias Blockly.utils.colour.getHsvValue + * @return {number} The current intensity. + * @package + */ + export function getHsvValue(): number; + /** + * Set the intensity of block colours, regardless of the hue. + * @param {number} newValue The new intensity, in the range of 0 + * (inclusive) to 1 (exclusive) + * @alias Blockly.utils.colour.setHsvValue + * @package + */ + export function setHsvValue(newValue: number): void; + /** + * Parses a colour from a string. + * .parse('red') -> '#ff0000' + * .parse('#f00') -> '#ff0000' + * .parse('#ff0000') -> '#ff0000' + * .parse('0xff0000') -> '#ff0000' + * .parse('rgb(255, 0, 0)') -> '#ff0000' + * @param {string|number} str Colour in some CSS format. + * @return {?string} A string containing a hex representation of the colour, + * or null if can't be parsed. + * @alias Blockly.utils.colour.parse + */ + export function parse(str: string | number): string | null; + /** + * Converts a colour from RGB to hex representation. + * @param {number} r Amount of red, int between 0 and 255. + * @param {number} g Amount of green, int between 0 and 255. + * @param {number} b Amount of blue, int between 0 and 255. + * @return {string} Hex representation of the colour. + * @alias Blockly.utils.colour.rgbToHex + */ + export function rgbToHex(r: number, g: number, b: number): string; + /** + * Converts a colour to RGB. + * @param {string} colour String representing colour in any + * colour format ('#ff0000', 'red', '0xff000', etc). + * @return {!Array} RGB representation of the colour. + * @alias Blockly.utils.colour.hexToRgb + */ + export function hexToRgb(colour: string): Array; + /** + * Converts an HSV triplet to hex representation. + * @param {number} h Hue value in [0, 360]. + * @param {number} s Saturation value in [0, 1]. + * @param {number} v Brightness in [0, 255]. + * @return {string} Hex representation of the colour. + * @alias Blockly.utils.colour.hsvToHex + */ + export function hsvToHex(h: number, s: number, v: number): string; + /** + * Blend two colours together, using the specified factor to indicate the + * weight given to the first colour. + * @param {string} colour1 First colour. + * @param {string} colour2 Second colour. + * @param {number} factor The weight to be given to colour1 over colour2. + * Values should be in the range [0, 1]. + * @return {?string} Combined colour represented in hex. + * @alias Blockly.utils.colour.blend + */ + export function blend(colour1: string, colour2: string, factor: number): string | null; + /** + * A map that contains the 16 basic colour keywords as defined by W3C: + * https://www.w3.org/TR/2018/REC-css-color-3-20180619/#html4 + * The keys of this map are the lowercase "readable" names of the colours, + * while the values are the "hex" values. * - * For UUIDs use genUid (below) instead; this ID generator should - * primarily be used for IDs that end up in the DOM. - * - * @return {string} The next unique identifier. - * @alias Blockly.utils.idGenerator.getNextUniqueId + * @type {!Object} + * @alias Blockly.utils.colour.names */ - export function getNextUniqueId(): string; + export const names: { + [x: string]: string; + }; /** - * Generate a random unique ID. - * @see internal.genUid - * @return {string} A globally unique ID string. - * @alias Blockly.utils.idGenerator.genUid + * Convert a hue (HSV model) into an RGB hex triplet. + * @param {number} hue Hue on a colour wheel (0-360). + * @return {string} RGB code, e.g. '#5ba65b'. + * @alias Blockly.utils.colour.hueToHex */ - export function genUid(): string; - export { internal as TEST_ONLY }; + export function hueToHex(hue: number): string; } -declare module "utils/object" { +declare module "core/utils/string" { /** - * @license - * Copyright 2019 Google LLC - * SPDX-License-Identifier: Apache-2.0 + * Fast prefix-checker. + * Copied from Closure's goog.string.startsWith. + * @param {string} str The string to check. + * @param {string} prefix A string to look for at the start of `str`. + * @return {boolean} True if `str` begins with `prefix`. + * @alias Blockly.utils.string.startsWith */ + export function startsWith(str: string, prefix: string): boolean; /** - * @fileoverview Utility methods for objects. + * Given an array of strings, return the length of the shortest one. + * @param {!Array} array Array of strings. + * @return {number} Length of shortest string. + * @alias Blockly.utils.string.shortestStringLength */ + export function shortestStringLength(array: Array): number; /** - * Utility methods for objects. - * @namespace Blockly.utils.object + * Given an array of strings, return the length of the common prefix. + * Words may not be split. Any space after a word is included in the length. + * @param {!Array} array Array of strings. + * @param {number=} opt_shortest Length of shortest string. + * @return {number} Length of common prefix. + * @alias Blockly.utils.string.commonWordPrefix */ + export function commonWordPrefix(array: Array, opt_shortest?: number | undefined): number; /** - * Inherit the prototype methods from one constructor into another. - * @param {!Function} childCtor Child class. - * @param {!Function} parentCtor Parent class. - * @suppress {strictMissingProperties} superClass_ is not defined on Function. - * @alias Blockly.utils.object.inherits + * Given an array of strings, return the length of the common suffix. + * Words may not be split. Any space after a word is included in the length. + * @param {!Array} array Array of strings. + * @param {number=} opt_shortest Length of shortest string. + * @return {number} Length of common suffix. + * @alias Blockly.utils.string.commonWordSuffix */ - export function inherits(childCtor: Function, parentCtor: Function): void; + export function commonWordSuffix(array: Array, opt_shortest?: number | undefined): number; /** - * Copies all the members of a source object to a target object. - * @param {!Object} target Target. - * @param {!Object} source Source. - * @alias Blockly.utils.object.mixin + * Wrap text to the specified width. + * @param {string} text Text to wrap. + * @param {number} limit Width to wrap each line. + * @return {string} Wrapped text. + * @alias Blockly.utils.string.wrap */ - export function mixin(target: any, source: any): void; + export function wrap(text: string, limit: number): string; /** - * Complete a deep merge of all members of a source object with a target object. - * @param {!Object} target Target. - * @param {!Object} source Source. - * @return {!Object} The resulting object. - * @alias Blockly.utils.object.deepMerge + * Is the given string a number (includes negative and decimals). + * @param {string} str Input string. + * @return {boolean} True if number, false otherwise. + * @alias Blockly.utils.string.isNumber */ - export function deepMerge(target: any, source: any): any; - /** - * Returns an array of a given object's own enumerable property values. - * @param {!Object} obj Object containing values. - * @return {!Array} Array of values. - * @alias Blockly.utils.object.values - */ - export function values(obj: any): any[]; + export function isNumber(str: string): boolean; } -declare module "connection_type" { +declare module "core/msg" { + /** + * A dictionary of localised messages. + * @type {!Object} + */ + export const Msg: any; +} +declare module "core/utils/parsing" { + /** + * Parse a string with any number of interpolation tokens (%1, %2, ...). + * It will also replace string table references (e.g., %{bky_my_msg} and + * %{BKY_MY_MSG} will both be replaced with the value in + * Msg['MY_MSG']). Percentage sign characters '%' may be self-escaped + * (e.g., '%%'). + * @param {string} message Text which might contain string table references and + * interpolation tokens. + * @return {!Array} Array of strings and numbers. + * @alias Blockly.utils.parsing.tokenizeInterpolation + */ + export function tokenizeInterpolation(message: string): Array; + /** + * Replaces string table references in a message, if the message is a string. + * For example, "%{bky_my_msg}" and "%{BKY_MY_MSG}" will both be replaced with + * the value in Msg['MY_MSG']. + * @param {string|?} message Message, which may be a string that contains + * string table references. + * @return {string} String with message references replaced. + * @alias Blockly.utils.parsing.replaceMessageReferences + */ + export function replaceMessageReferences(message: string | unknown): string; + /** + * Validates that any %{MSG_KEY} references in the message refer to keys of + * the Msg string table. + * @param {string} message Text which might contain string table references. + * @return {boolean} True if all message references have matching values. + * Otherwise, false. + * @alias Blockly.utils.parsing.checkMessageReferences + */ + export function checkMessageReferences(message: string): boolean; + /** + * Parse a block colour from a number or string, as provided in a block + * definition. + * @param {number|string} colour HSV hue value (0 to 360), #RRGGBB string, + * or a message reference string pointing to one of those two values. + * @return {{hue: ?number, hex: string}} An object containing the colour as + * a #RRGGBB string, and the hue if the input was an HSV hue value. + * @throws {Error} If the colour cannot be parsed. + * @alias Blockly.utils.parsing.parseBlockColour + */ + export function parseBlockColour(colour: number | string): { + hue: number | null; + hex: string; + }; +} +declare module "core/utils/aria" { /** * * */ - export type ConnectionType = number; - export namespace ConnectionType { - const INPUT_VALUE: number; - const OUTPUT_VALUE: number; - const NEXT_STATEMENT: number; - const PREVIOUS_STATEMENT: number; + export type Role = string; + export namespace Role { + const GRID: string; + const GRIDCELL: string; + const GROUP: string; + const LISTBOX: string; + const MENU: string; + const MENUITEM: string; + const MENUITEMCHECKBOX: string; + const OPTION: string; + const PRESENTATION: string; + const ROW: string; + const TREE: string; + const TREEITEM: string; } + /** + * * + */ + export type State = string; + export namespace State { + const ACTIVEDESCENDANT: string; + const COLCOUNT: string; + const DISABLED: string; + const EXPANDED: string; + const INVALID: string; + const LABEL: string; + const LABELLEDBY: string; + const LEVEL: string; + const ORIENTATION: string; + const POSINSET: string; + const ROWCOUNT: string; + const SELECTED: string; + const SETSIZE: string; + const VALUEMAX: string; + const VALUEMIN: string; + } + /** + * Sets the role of an element. + * + * Similar to Closure's goog.a11y.aria + * + * @param {!Element} element DOM node to set role of. + * @param {!Role} roleName Role name. + * @alias Blockly.utils.aria.setRole + */ + export function setRole(element: Element, roleName: Role): void; + /** + * Sets the state or property of an element. + * Copied from Closure's goog.a11y.aria + * @param {!Element} element DOM node where we set state. + * @param {!State} stateName State attribute being set. + * Automatically adds prefix 'aria-' to the state name if the attribute is + * not an extra attribute. + * @param {string|boolean|number|!Array} value Value + * for the state attribute. + * @alias Blockly.utils.aria.setState + */ + export function setState(element: Element, stateName: State, value: string | boolean | number | Array): void; } -declare module "utils/global" { - /** - * @license - * Copyright 2019 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - /** - * @fileoverview Provides a reference to the global object. - */ - /** - * Provides a reference to the global object. - * @namespace Blockly.utils.global - */ +declare module "core/utils/global" { /** * Reference to the global object. * * More info on this implementation here: * https://docs.google.com/document/d/1NAeW4Wk7I7FV0Y2tcUFvQdGMc89k2vdgSXInw8_nvCI */ - export const globalThis: any; + export var globalThis: any; } -declare module "utils/useragent" { +declare module "core/utils/useragent" { /** * The raw useragent string. * @type {string} @@ -139,31 +310,18 @@ declare module "utils/useragent" { let isMobile: boolean; export { rawUserAgent as raw, isIe as IE, isEdge as EDGE, isJavaFx as JavaFx, isChrome as CHROME, isWebKit as WEBKIT, isGecko as GECKO, isAndroid as ANDROID, isIPad as IPAD, isIPod as IPOD, isIPhone as IPHONE, isMac as MAC, isTablet as TABLET, isMobile as MOBILE }; } -declare module "utils/svg" { - /** - * @license - * Copyright 2020 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - /** - * @fileoverview Defines the Svg class. Its constants enumerate - * all SVG tag names used by Blockly. - */ - /** - * Defines the Svg class. Its constants enumerate - * all SVG tag names used by Blockly. - * @class - */ +declare module "core/utils/svg" { /** * A name with the type of the SVG element stored in the generic. - * @param {string} tagName The SVG element tag name. - * @constructor * @template T - * @private * @alias Blockly.utils.Svg */ export class Svg { - constructor(tagName: any); + /** + * @param {string} tagName The SVG element tag name. + * @package + */ + constructor(tagName: string); /** * @type {string} * @private @@ -172,7 +330,6 @@ declare module "utils/svg" { /** * Returns the SVG element tag name. * @return {string} The name. - * @override */ toString(): string; } @@ -202,7 +359,7 @@ declare module "utils/svg" { const TSPAN: Svg; } } -declare module "utils/dom" { +declare module "core/utils/dom" { /** * Required name space for SVG elements. * @const @@ -241,7 +398,7 @@ declare module "utils/dom" { * @template T * @alias Blockly.utils.dom.createSvgElement */ - export function createSvgElement(name: string | Svg, attrs: any, opt_parent?: Element | undefined): T; + export function createSvgElement(name: string | Svg, attrs: Object, opt_parent?: Element | undefined): T; /** * Add a CSS class to a element. * Similar to Closure's goog.dom.classes.add, except it handles SVG elements. @@ -367,434 +524,60 @@ declare module "utils/dom" { height: number; baseline: number; }; - import { Svg } from "utils/svg"; + import { Svg } from "core/utils/svg"; } -declare module "utils/xml" { +declare module "core/blocks" { /** - * Namespace for Blockly's XML. - * @alias Blockly.utils.xml.NAME_SPACE + * A block definition. For now this very lose, but it can potentially + * be refined e.g. by replacing this typedef with a class definition. */ - export const NAME_SPACE: "https://developers.google.com/blockly/xml"; + export type BlockDefinition = Object; /** - * Get the document object to use for XML serialization. - * @return {!Document} The document object. - * @alias Blockly.utils.xml.getDocument - */ - export function getDocument(): Document; - /** - * Get the document object to use for XML serialization. - * @param {!Document} document The document object to use. - * @alias Blockly.utils.xml.setDocument - */ - export function setDocument(document: Document): void; - /** - * Create DOM element for XML. - * @param {string} tagName Name of DOM element. - * @return {!Element} New DOM element. - * @alias Blockly.utils.xml.createElement - */ - export function createElement(tagName: string): Element; - /** - * Create text element for XML. - * @param {string} text Text content. - * @return {!Text} New DOM text node. - * @alias Blockly.utils.xml.createTextNode - */ - export function createTextNode(text: string): Text; - /** - * Converts an XML string into a DOM tree. - * @param {string} text XML string. - * @return {Document} The DOM document. - * @throws if XML doesn't parse. - * @alias Blockly.utils.xml.textToDomDocument - */ - export function textToDomDocument(text: string): Document; - /** - * Converts a DOM structure into plain text. - * Currently the text format is fairly ugly: all one line with no whitespace. - * @param {!Node} dom A tree of XML nodes. - * @return {string} Text representation. - * @alias Blockly.utils.xml.domToText - */ - export function domToText(dom: Node): string; -} -declare module "utils/string" { - /** - * @license - * Copyright 2019 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - /** - * @fileoverview Utility methods for string manipulation. - * These methods are not specific to Blockly, and could be factored out into - * a JavaScript framework such as Closure. - */ - /** - * Utility methods for string manipulation. - * These methods are not specific to Blockly, and could be factored out into - * a JavaScript framework such as Closure. - * @namespace Blockly.utils.string - */ - /** - * Fast prefix-checker. - * Copied from Closure's goog.string.startsWith. - * @param {string} str The string to check. - * @param {string} prefix A string to look for at the start of `str`. - * @return {boolean} True if `str` begins with `prefix`. - * @alias Blockly.utils.string.startsWith - */ - export function startsWith(str: string, prefix: string): boolean; - /** - * Given an array of strings, return the length of the shortest one. - * @param {!Array} array Array of strings. - * @return {number} Length of shortest string. - * @alias Blockly.utils.string.shortestStringLength - */ - export function shortestStringLength(array: Array): number; - /** - * Given an array of strings, return the length of the common prefix. - * Words may not be split. Any space after a word is included in the length. - * @param {!Array} array Array of strings. - * @param {number=} opt_shortest Length of shortest string. - * @return {number} Length of common prefix. - * @alias Blockly.utils.string.commonWordPrefix - */ - export function commonWordPrefix(array: Array, opt_shortest?: number | undefined): number; - /** - * Given an array of strings, return the length of the common suffix. - * Words may not be split. Any space after a word is included in the length. - * @param {!Array} array Array of strings. - * @param {number=} opt_shortest Length of shortest string. - * @return {number} Length of common suffix. - * @alias Blockly.utils.string.commonWordSuffix - */ - export function commonWordSuffix(array: Array, opt_shortest?: number | undefined): number; - /** - * Wrap text to the specified width. - * @param {string} text Text to wrap. - * @param {number} limit Width to wrap each line. - * @return {string} Wrapped text. - * @alias Blockly.utils.string.wrap - */ - export function wrap(text: string, limit: number): string; - /** - * Is the given string a number (includes negative and decimals). - * @param {string} str Input string. - * @return {boolean} True if number, false otherwise. - * @alias Blockly.utils.string.isNumber - */ - export function isNumber(str: string): boolean; -} -declare module "internal_constants" { - /** - * The multiplier for scroll wheel deltas using the line delta mode. - * @type {number} - * @alias Blockly.internalConstants.LINE_MODE_MULTIPLIER - */ - export const LINE_MODE_MULTIPLIER: number; - /** - * The multiplier for scroll wheel deltas using the page delta mode. - * @type {number} - * @alias Blockly.internalConstants.PAGE_MODE_MULTIPLIER - */ - export const PAGE_MODE_MULTIPLIER: number; - /** - * Number of pixels the mouse must move before a drag starts. - * @alias Blockly.internalConstants.DRAG_RADIUS - */ - export const DRAG_RADIUS: 5; - /** - * Number of pixels the mouse must move before a drag/scroll starts from the - * flyout. Because the drag-intention is determined when this is reached, it is - * larger than DRAG_RADIUS so that the drag-direction is clearer. - * @alias Blockly.internalConstants.FLYOUT_DRAG_RADIUS - */ - export const FLYOUT_DRAG_RADIUS: 10; - /** - * Maximum misalignment between connections for them to snap together. - * @alias Blockly.internalConstants.SNAP_RADIUS - */ - export const SNAP_RADIUS: 28; - /** - * Maximum misalignment between connections for them to snap together, - * when a connection is already highlighted. - * @alias Blockly.internalConstants.CONNECTING_SNAP_RADIUS - */ - export const CONNECTING_SNAP_RADIUS: 28; - /** - * How much to prefer staying connected to the current connection over moving to - * a new connection. The current previewed connection is considered to be this - * much closer to the matching connection on the block than it actually is. - * @alias Blockly.internalConstants.CURRENT_CONNECTION_PREFERENCE - */ - export const CURRENT_CONNECTION_PREFERENCE: 8; - /** - * Delay in ms between trigger and bumping unconnected block out of alignment. - * @alias Blockly.internalConstants.BUMP_DELAY - */ - export const BUMP_DELAY: 250; - /** - * Maximum randomness in workspace units for bumping a block. - * @alias Blockly.internalConstants.BUMP_RANDOMNESS - */ - export const BUMP_RANDOMNESS: 10; - /** - * Number of characters to truncate a collapsed block to. - * @alias Blockly.internalConstants.COLLAPSE_CHARS - */ - export const COLLAPSE_CHARS: 30; - /** - * Length in ms for a touch to become a long press. - * @alias Blockly.internalConstants.LONGPRESS - */ - export const LONGPRESS: 750; - /** - * Prevent a sound from playing if another sound preceded it within this many - * milliseconds. - * @alias Blockly.internalConstants.SOUND_LIMIT - */ - export const SOUND_LIMIT: 100; - /** - * When dragging a block out of a stack, split the stack in two (true), or drag - * out the block healing the stack (false). - * @alias Blockly.internalConstants.DRAG_STACK - */ - export const DRAG_STACK: true; - /** - * The richness of block colours, regardless of the hue. - * Must be in the range of 0 (inclusive) to 1 (exclusive). - * @alias Blockly.internalConstants.HSV_SATURATION - */ - export const HSV_SATURATION: 0.45; - /** - * The intensity of block colours, regardless of the hue. - * Must be in the range of 0 (inclusive) to 1 (exclusive). - * @alias Blockly.internalConstants.HSV_VALUE - */ - export const HSV_VALUE: 0.65; - export namespace SPRITE { - const width: number; - const height: number; - const url: string; - } - /** - * ENUM for no drag operation. - * @const - * @alias Blockly.internalConstants.DRAG_NONE - */ - export const DRAG_NONE: 0; - /** - * ENUM for inside the sticky DRAG_RADIUS. - * @const - * @alias Blockly.internalConstants.DRAG_STICKY - */ - export const DRAG_STICKY: 1; - /** - * ENUM for inside the non-sticky DRAG_RADIUS, for differentiating between - * clicks and drags. - * @const - * @alias Blockly.internalConstants.DRAG_BEGIN - */ - export const DRAG_BEGIN: 1; - /** - * ENUM for freely draggable (outside the DRAG_RADIUS, if one applies). - * @const - * @alias Blockly.internalConstants.DRAG_FREE - */ - export const DRAG_FREE: 2; - /** - * Lookup table for determining the opposite type of a connection. - * @const - * @alias Blockly.internalConstants.OPPOSITE_TYPE - */ - export const OPPOSITE_TYPE: any[]; - /** - * 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} - * @alias Blockly.internalConstants.VARIABLE_CATEGORY_NAME - */ - export const 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} - * @alias Blockly.internalConstants.VARIABLE_DYNAMIC_CATEGORY_NAME - */ - export const VARIABLE_DYNAMIC_CATEGORY_NAME: "VARIABLE_DYNAMIC"; - /** - * String for use in the "custom" attribute of a category in toolbox XML. - * This string indicates that the category should be dynamically populated with - * procedure blocks. - * @const {string} - * @alias Blockly.internalConstants.PROCEDURE_CATEGORY_NAME - */ - export const PROCEDURE_CATEGORY_NAME: "PROCEDURE"; - /** - * String for use in the dropdown created in field_variable. - * This string indicates that this option in the dropdown is 'Rename - * variable...' and if selected, should trigger the prompt to rename a variable. - * @const {string} - * @alias Blockly.internalConstants.RENAME_VARIABLE_ID - */ - export const RENAME_VARIABLE_ID: "RENAME_VARIABLE_ID"; - /** - * String for use in the dropdown created in field_variable. - * This string indicates that this option in the dropdown is 'Delete the "%1" - * variable' and if selected, should trigger the prompt to delete a variable. - * @const {string} - * @alias Blockly.internalConstants.DELETE_VARIABLE_ID - */ - export const DELETE_VARIABLE_ID: "DELETE_VARIABLE_ID"; -} -declare module "msg" { - export {}; -} -declare module "blocks" { - /** - * @license - * Copyright 2013 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - /** - * @fileoverview A mapping of block type names to block prototype objects. + * A block definition. For now this very lose, but it can potentially + * be refined e.g. by replacing this typedef with a class definition. + * @typedef {!Object} */ + export let BlockDefinition: any; /** * A mapping of block type names to block prototype objects. - * @namespace Blockly.blocks - */ - /** - * A mapping of block type names to block prototype objects. - * @type {!Object} + * @type {!Object} * @alias Blockly.blocks.Blocks */ export const Blocks: { - [x: string]: any; + [x: string]: BlockDefinition; }; } -declare module "interfaces/i_deletable" { - /** - * @license - * Copyright 2019 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - /** - * @fileoverview The interface for an object that is deletable. - */ - /** - * The interface for an object that is deletable. - * @namespace Blockly.IDeletable - */ - /** - * The interface for an object that can be deleted. - * @interface - * @alias Blockly.IDeletable - */ - export class IDeletable { - } -} -declare module "interfaces/i_movable" { - /** - * @license - * Copyright 2019 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - /** - * @fileoverview The interface for an object that is movable. - */ - /** - * The interface for an object that is movable. - * @namespace Blockly.IMovable - */ - /** - * The interface for an object that is movable. - * @interface - * @alias Blockly.IMovable - */ - export class IMovable { - } -} -declare module "interfaces/i_selectable" { - /** - * The interface for an object that is selectable. - * @extends {IDeletable} - * @extends {IMovable} - * @interface - * @alias Blockly.ISelectable - */ - export class ISelectable { +declare module "core/utils/idgenerator" { + namespace internal { /** - * @type {string} + * Generate a random unique ID. This should be globally unique. + * 87 characters ^ 20 length > 128 bits (better than a UUID). + * @return {string} A globally unique ID string. */ - id: string; + function genUid(): string; } + /** + * Generate the next unique element IDs. + * IDs are compatible with the HTML4 id attribute restrictions: + * Use only ASCII letters, digits, '_', '-' and '.' + * + * For UUIDs use genUid (below) instead; this ID generator should + * primarily be used for IDs that end up in the DOM. + * + * @return {string} The next unique identifier. + * @alias Blockly.utils.idGenerator.getNextUniqueId + */ + export function getNextUniqueId(): string; + /** + * Generate a random unique ID. + * @see internal.genUid + * @return {string} A globally unique ID string. + * @alias Blockly.utils.idGenerator.genUid + */ + export function genUid(): string; + export { internal as TEST_ONLY }; } -declare module "dialog" { - /** - * Wrapper to window.alert() that app developers may override via setAlert to - * provide alternatives to the modal browser window. - * @param {string} message The message to display to the user. - * @param {function()=} opt_callback The callback when the alert is dismissed. - * @alias Blockly.dialog.alert - */ - export function alert(message: string, opt_callback?: (() => any) | undefined): void; - /** - * Sets the function to be run when Blockly.dialog.alert() is called. - * @param {!function(string, function()=)} alertFunction The function to be run. - * @see Blockly.dialog.alert - * @alias Blockly.dialog.setAlert - */ - export function setAlert(alertFunction: (arg0: string, arg1: (() => any) | undefined) => any): void; - /** - * Wrapper to window.confirm() that app developers may override via setConfirm - * to provide alternatives to the modal browser window. - * @param {string} message The message to display to the user. - * @param {!function(boolean)} callback The callback for handling user response. - * @alias Blockly.dialog.confirm - */ - export function confirm(message: string, callback: (arg0: boolean) => any): void; - /** - * Sets the function to be run when Blockly.dialog.confirm() is called. - * @param {!function(string, !function(boolean))} confirmFunction The function - * to be run. - * @see Blockly.dialog.confirm - * @alias Blockly.dialog.setConfirm - */ - export function setConfirm(confirmFunction: (arg0: string, arg1: (arg0: boolean) => any) => any): void; - /** - * Wrapper to window.prompt() that app developers may override via setPrompt to - * provide alternatives to the modal browser window. Built-in browser prompts - * are often used for better text input experience on mobile device. We strongly - * recommend testing mobile when overriding this. - * @param {string} message The message to display to the user. - * @param {string} defaultValue The value to initialize the prompt with. - * @param {!function(?string)} callback The callback for handling user response. - * @alias Blockly.dialog.prompt - */ - export function prompt(message: string, defaultValue: string, callback: (arg0: string | null) => any): void; - /** - * Sets the function to be run when Blockly.dialog.prompt() is called. - * @param {!function(string, string, !function(?string))} promptFunction The - * function to be run. - * @see Blockly.dialog.prompt - * @alias Blockly.dialog.setPrompt - */ - export function setPrompt(promptFunction: (arg0: string, arg1: string, arg2: (arg0: string | null) => any) => any): void; -} -declare module "utils/array" { - /** - * @license - * Copyright 2021 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - /** - * @fileoverview Utility methods related to arrays. - */ - /** - * @namespace Blockly.utils.array - */ +declare module "core/utils/array" { /** * Removes the first occurrence of a particular value from an array. * @param {!Array} arr Array from which to remove value. @@ -805,23 +588,7 @@ declare module "utils/array" { */ export function removeElem(arr: any[], value: any): boolean; } -declare module "utils/math" { - /** - * @license - * Copyright 2019 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - /** - * @fileoverview Utility methods for math. - * These methods are not specific to Blockly, and could be factored out into - * a JavaScript framework such as Closure. - */ - /** - * Utility methods for math. - * These methods are not specific to Blockly, and could be factored out into - * a JavaScript framework such as Closure. - * @namespace Blockly.utils.math - */ +declare module "core/utils/math" { /** * Converts degrees to radians. * Copied from Closure's goog.math.toRadians. @@ -848,23 +615,7 @@ declare module "utils/math" { */ export function clamp(lowerBound: number, number: number, upperBound: number): number; } -declare module "serialization/priorities" { - /** - * @license - * Copyright 2021 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - /** - * @fileoverview The top level namespace for priorities of plugin serializers. - * Includes constants for the priorities of different plugin - * serializers. Higher priorities are deserialized first. - */ - /** - * The top level namespace for priorities of plugin serializers. - * Includes constants for the priorities of different plugin serializers. Higher - * priorities are deserialized first. - * @namespace Blockly.serialization.priorities - */ +declare module "core/serialization/priorities" { /** * The priority for deserializing variables. * @type {number} @@ -880,7 +631,7 @@ declare module "serialization/priorities" { */ export const BLOCKS: number; } -declare module "interfaces/i_serializer" { +declare module "core/interfaces/i_serializer" { /** * Serializes and deserializes a plugin or system. * @interface @@ -920,9 +671,9 @@ declare module "interfaces/i_serializer" { */ clear(workspace: Workspace): void; } - import { Workspace } from "workspace"; + import { Workspace } from "core/workspace"; } -declare module "serialization/registry" { +declare module "core/serialization/registry" { /** * Registers the given serializer so that it can be used for serialization and * deserialization. @@ -937,9 +688,9 @@ declare module "serialization/registry" { * @alias Blockly.serialization.registry.unregister */ export function unregister(name: string): void; - import { ISerializer } from "interfaces/i_serializer"; + import { ISerializer } from "core/interfaces/i_serializer"; } -declare module "serialization/exceptions" { +declare module "core/serialization/exceptions" { /** * @alias Blockly.serialization.exceptions.DeserializationError */ @@ -1034,34 +785,60 @@ declare module "serialization/exceptions" { */ state: any; } - import { Block } from "block"; + import { Block } from "core/block"; } -declare module "utils/size" { +declare module "core/utils/rect" { /** - * @license - * Copyright 2019 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - /** - * @fileoverview Utility methods for size calculation. - * These methods are not specific to Blockly, and could be factored out into - * a JavaScript framework such as Closure. - */ - /** - * Utility methods for size calculation. - * These methods are not specific to Blockly, and could be factored out into - * a JavaScript framework such as Closure. - * @class + * Class for representing rectangular regions. + * @alias Blockly.utils.Rect */ + export const Rect: { + new (top: number, bottom: number, left: number, right: number): { + /** @type {number} */ + top: number; + /** @type {number} */ + bottom: number; + /** @type {number} */ + left: number; + /** @type {number} */ + right: number; + /** + * Tests whether this rectangle contains a x/y coordinate. + * + * @param {number} x The x coordinate to test for containment. + * @param {number} y The y coordinate to test for containment. + * @return {boolean} Whether this rectangle contains given coordinate. + */ + contains(x: number, y: number): boolean; + /** + * Tests whether this rectangle intersects the provided rectangle. + * Assumes that the coordinate system increases going down and left. + * @param {!Rect} other The other rectangle to check for + * intersection with. + * @return {boolean} Whether this rectangle intersects the provided rectangle. + */ + intersects(other: any): boolean; + }; + }; +} +declare module "core/utils/size" { /** * Class for representing sizes consisting of a width and height. - * @param {number} width Width. - * @param {number} height Height. - * @struct - * @constructor * @alias Blockly.utils.Size */ - export class Size { + export const Size: { + new (width: number, height: number): { + /** + * Width + * @type {number} + */ + width: number; + /** + * Height + * @type {number} + */ + height: number; + }; /** * Compares sizes for equality. * @param {?Size} a A Size. @@ -1069,3964 +846,170 @@ declare module "utils/size" { * @return {boolean} True iff the sizes have equal widths and equal * heights, or if both are null. */ - static equals(a: Size | null, b: Size | null): boolean; - constructor(width: any, height: any); - /** - * Width - * @type {number} - */ - width: number; - /** - * Height - * @type {number} - */ - height: number; - } -} -declare module "input_types" { - /** - * * - */ - export type inputTypes = number; - export namespace inputTypes { - const VALUE: number; - const STATEMENT: number; - const DUMMY: number; - } -} -declare module "serialization/blocks" { - /** - * Represents the state of a connection. - */ - export type ConnectionState = { - shadow: (any | undefined); - block: (any | undefined); - }; - /** - * Represents the state of a connection. - * @typedef {{ - * shadow: (!State|undefined), - * block: (!State|undefined) - * }} - * @alias Blockly.serialization.blocks.ConnectionState - */ - export let ConnectionState: any; - /** - * Represents the state of a given block. - * @typedef {{ - * type: string, - * id: (string|undefined), - * x: (number|undefined), - * y: (number|undefined), - * collapsed: (boolean|undefined), - * enabled: (boolean|undefined), - * inline: (boolean|undefined), - * data: (string|undefined), - * extra-state: (*|undefined), - * icons: (!Object|undefined), - * fields: (!Object|undefined), - * inputs: (!Object|undefined), - * next: (!ConnectionState|undefined) - * }} - * @alias Blockly.serialization.blocks.State - */ - export let State: any; - /** - * Returns the state of the given block as a plain JavaScript object. - * @param {!Block} block The block to serialize. - * @param {{addCoordinates: (boolean|undefined), addInputBlocks: - * (boolean|undefined), addNextBlocks: (boolean|undefined), - * doFullSerialization: (boolean|undefined)}=} param1 - * addCoordinates: If true, the coordinates of the block are added to the - * serialized state. False by default. - * addinputBlocks: If true, children of the block which are connected to - * inputs will be serialized. True by default. - * addNextBlocks: If true, children of the block which are connected to the - * block's next connection (if it exists) will be serialized. - * True by default. - * doFullSerialization: If true, fields that normally just save a reference - * to some external state (eg variables) will instead serialize all of the - * info about that state. This supports deserializing the block into a - * workspace where that state doesn't yet exist. True by default. - * @return {?State} The serialized state of the block, or null if the block - * could not be serialied (eg it was an insertion marker). - * @alias Blockly.serialization.blocks.save - */ - export function save(block: Block, { addCoordinates, addInputBlocks, addNextBlocks, doFullSerialization, }?: { - addCoordinates: (boolean | undefined); - addInputBlocks: (boolean | undefined); - addNextBlocks: (boolean | undefined); - doFullSerialization: (boolean | undefined); - } | undefined): any | null; - /** - * Loads the block represented by the given state into the given workspace. - * @param {!State} state The state of a block to deserialize into the workspace. - * @param {!Workspace} workspace The workspace to add the block to. - * @param {{recordUndo: (boolean|undefined)}=} param1 - * recordUndo: If true, events triggered by this function will be undo-able - * by the user. False by default. - * @return {!Block} The block that was just loaded. - * @alias Blockly.serialization.blocks.append - */ - export function append(state: any, workspace: Workspace, { recordUndo }?: { - recordUndo: (boolean | undefined); - } | undefined): Block; - /** - * Loads the block represented by the given state into the given workspace. - * This is defined internally so that the extra parameters don't clutter our - * external API. - * But it is exported so that other places within Blockly can call it directly - * with the extra parameters. - * @param {!State} state The state of a block to deserialize into the workspace. - * @param {!Workspace} workspace The workspace to add the block to. - * @param {{parentConnection: (!Connection|undefined), isShadow: - * (boolean|undefined), recordUndo: (boolean|undefined)}=} param1 - * parentConnection: If provided, the system will attempt to connect the - * block to this connection after it is created. Undefined by default. - * isShadow: If true, the block will be set to a shadow block after it is - * created. False by default. - * recordUndo: If true, events triggered by this function will be undo-able - * by the user. False by default. - * @return {!Block} The block that was just appended. - * @alias Blockly.serialization.blocks.appendInternal - * @package - */ - export function appendInternal(state: any, workspace: Workspace, { parentConnection, isShadow, recordUndo, }?: { - parentConnection: (Connection | undefined); - isShadow: (boolean | undefined); - recordUndo: (boolean | undefined); - } | undefined): Block; - import { Block } from "block"; - import { Workspace } from "workspace"; - import { Connection } from "connection"; -} -declare module "utils/deprecation" { - /** - * @license - * Copyright 2020 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - /** - * @fileoverview Helper function for warning developers about deprecations. - * This method is not specific to Blockly. - */ - /** - * Helper function for warning developers about deprecations. - * This method is not specific to Blockly. - * @namespace Blockly.utils.deprecation - */ - /** - * Warn developers that a function or property is deprecated. - * @param {string} name The name of the function or property. - * @param {string} deprecationDate The date of deprecation. - * Prefer 'month yyyy' or 'quarter yyyy' format. - * @param {string} deletionDate The date of deletion, in the same format as the - * deprecation date. - * @param {string=} opt_use The name of a function or property to use instead, - * if any. - * @alias Blockly.utils.deprecation.warn - * @package - */ - export function warn(name: string, deprecationDate: string, deletionDate: string, opt_use?: string | undefined): void; -} -declare module "css" { - /** - * Add some CSS to the blob that will be injected later. Allows optional - * components such as fields and the toolbox to store separate CSS. - * @param {string|!Array} cssContent Multiline CSS string or an array of - * single lines of CSS. - * @alias Blockly.Css.register - */ - export function register(cssContent: string | Array): void; - /** - * Inject the CSS into the DOM. This is preferable over using a regular CSS - * file since: - * a) It loads synchronously and doesn't force a redraw later. - * b) It speeds up loading by not blocking on a separate HTTP transfer. - * c) The CSS content may be made dynamic depending on init options. - * @param {boolean} hasCss If false, don't inject CSS - * (providing CSS becomes the document's responsibility). - * @param {string} pathToMedia Path from page to the Blockly media directory. - * @alias Blockly.Css.inject - */ - export function inject(hasCss: boolean, pathToMedia: string): void; - /** - * The CSS content for Blockly. - * @alias Blockly.Css.content - */ - export let content: string; -} -declare module "utils/aria" { - /** - * * - */ - export type Role = string; - export namespace Role { - const GRID: string; - const GRIDCELL: string; - const GROUP: string; - const LISTBOX: string; - const MENU: string; - const MENUITEM: string; - const MENUITEMCHECKBOX: string; - const OPTION: string; - const PRESENTATION: string; - const ROW: string; - const TREE: string; - const TREEITEM: string; - } - /** - * * - */ - export type State = string; - export namespace State { - const ACTIVEDESCENDANT: string; - const COLCOUNT: string; - const DISABLED: string; - const EXPANDED: string; - const INVALID: string; - const LABEL: string; - const LABELLEDBY: string; - const LEVEL: string; - const ORIENTATION: string; - const POSINSET: string; - const ROWCOUNT: string; - const SELECTED: string; - const SETSIZE: string; - const VALUEMAX: string; - const VALUEMIN: string; - } - /** - * Sets the role of an element. - * - * Similar to Closure's goog.a11y.aria - * - * @param {!Element} element DOM node to set role of. - * @param {!Role} roleName Role name. - * @alias Blockly.utils.aria.setRole - */ - export function setRole(element: Element, roleName: Role): void; - /** - * Sets the state or property of an element. - * Copied from Closure's goog.a11y.aria - * @param {!Element} element DOM node where we set state. - * @param {!State} stateName State attribute being set. - * Automatically adds prefix 'aria-' to the state name if the attribute is - * not an extra attribute. - * @param {string|boolean|number|!Array} value Value - * for the state attribute. - * @alias Blockly.utils.aria.setState - */ - export function setState(element: Element, stateName: State, value: string | boolean | number | Array): void; -} -declare module "utils/colour" { - /** - * Parses a colour from a string. - * .parse('red') -> '#ff0000' - * .parse('#f00') -> '#ff0000' - * .parse('#ff0000') -> '#ff0000' - * .parse('0xff0000') -> '#ff0000' - * .parse('rgb(255, 0, 0)') -> '#ff0000' - * @param {string|number} str Colour in some CSS format. - * @return {?string} A string containing a hex representation of the colour, - * or null if can't be parsed. - * @alias Blockly.utils.colour.parse - */ - export function parse(str: string | number): string | null; - /** - * Converts a colour from RGB to hex representation. - * @param {number} r Amount of red, int between 0 and 255. - * @param {number} g Amount of green, int between 0 and 255. - * @param {number} b Amount of blue, int between 0 and 255. - * @return {string} Hex representation of the colour. - * @alias Blockly.utils.colour.rgbToHex - */ - export function rgbToHex(r: number, g: number, b: number): string; - /** - * Converts a colour to RGB. - * @param {string} colour String representing colour in any - * colour format ('#ff0000', 'red', '0xff000', etc). - * @return {!Array} RGB representation of the colour. - * @alias Blockly.utils.colour.hexToRgb - */ - export function hexToRgb(colour: string): Array; - /** - * Converts an HSV triplet to hex representation. - * @param {number} h Hue value in [0, 360]. - * @param {number} s Saturation value in [0, 1]. - * @param {number} v Brightness in [0, 255]. - * @return {string} Hex representation of the colour. - * @alias Blockly.utils.colour.hsvToHex - */ - export function hsvToHex(h: number, s: number, v: number): string; - /** - * Blend two colours together, using the specified factor to indicate the - * weight given to the first colour. - * @param {string} colour1 First colour. - * @param {string} colour2 Second colour. - * @param {number} factor The weight to be given to colour1 over colour2. - * Values should be in the range [0, 1]. - * @return {?string} Combined colour represented in hex. - * @alias Blockly.utils.colour.blend - */ - export function blend(colour1: string, colour2: string, factor: number): string | null; - /** - * A map that contains the 16 basic colour keywords as defined by W3C: - * https://www.w3.org/TR/2018/REC-css-color-3-20180619/#html4 - * The keys of this map are the lowercase "readable" names of the colours, - * while the values are the "hex" values. - * - * @type {!Object} - * @alias Blockly.utils.colour.names - */ - export const names: { - [x: string]: string; - }; - /** - * Convert a hue (HSV model) into an RGB hex triplet. - * @param {number} hue Hue on a colour wheel (0-360). - * @return {string} RGB code, e.g. '#5ba65b'. - * @alias Blockly.utils.colour.hueToHex - */ - export function hueToHex(hue: number): string; -} -declare module "utils/parsing" { - /** - * Parse a string with any number of interpolation tokens (%1, %2, ...). - * It will also replace string table references (e.g., %{bky_my_msg} and - * %{BKY_MY_MSG} will both be replaced with the value in - * Msg['MY_MSG']). Percentage sign characters '%' may be self-escaped - * (e.g., '%%'). - * @param {string} message Text which might contain string table references and - * interpolation tokens. - * @return {!Array} Array of strings and numbers. - * @alias Blockly.parsing.tokenizeInterpolation - */ - export function tokenizeInterpolation(message: string): Array; - /** - * Replaces string table references in a message, if the message is a string. - * For example, "%{bky_my_msg}" and "%{BKY_MY_MSG}" will both be replaced with - * the value in Msg['MY_MSG']. - * @param {string|?} message Message, which may be a string that contains - * string table references. - * @return {string} String with message references replaced. - * @alias Blockly.parsing.replaceMessageReferences - */ - export function replaceMessageReferences(message: string | unknown): string; - /** - * Validates that any %{MSG_KEY} references in the message refer to keys of - * the Msg string table. - * @param {string} message Text which might contain string table references. - * @return {boolean} True if all message references have matching values. - * Otherwise, false. - * @alias Blockly.parsing.checkMessageReferences - */ - export function checkMessageReferences(message: string): boolean; - /** - * Parse a block colour from a number or string, as provided in a block - * definition. - * @param {number|string} colour HSV hue value (0 to 360), #RRGGBB string, - * or a message reference string pointing to one of those two values. - * @return {{hue: ?number, hex: string}} An object containing the colour as - * a #RRGGBB string, and the hue if the input was an HSV hue value. - * @throws {Error} If the colour cannot be parsed. - * @alias Blockly.parsing.parseBlockColour - */ - export function parseBlockColour(colour: number | string): { - hue: number | null; - hex: string; + equals(a: { + /** + * Width + * @type {number} + */ + width: number; + /** + * Height + * @type {number} + */ + height: number; + } | null, b: { + /** + * Width + * @type {number} + */ + width: number; + /** + * Height + * @type {number} + */ + height: number; + } | null): boolean; }; } -declare module "interfaces/i_toolbox_item" { +declare module "core/dialog" { /** - * @license - * Copyright 2020 Google LLC - * SPDX-License-Identifier: Apache-2.0 + * Wrapper to window.alert() that app developers may override via setAlert to + * provide alternatives to the modal browser window. + * @param {string} message The message to display to the user. + * @param {function()=} opt_callback The callback when the alert is dismissed. + * @alias Blockly.dialog.alert */ + export function alert(message: string, opt_callback?: (() => any) | undefined): void; /** - * @fileoverview The interface for a toolbox item. + * Sets the function to be run when Blockly.dialog.alert() is called. + * @param {!function(string, function()=)} alertFunction The function to be run. + * @see Blockly.dialog.alert + * @alias Blockly.dialog.setAlert */ + export function setAlert(alertFunction: (arg0: string, arg1: (() => any) | undefined) => any): void; /** - * The interface for a toolbox item. - * @namespace Blockly.IToolboxItem + * Wrapper to window.confirm() that app developers may override via setConfirm + * to provide alternatives to the modal browser window. + * @param {string} message The message to display to the user. + * @param {!function(boolean)} callback The callback for handling user response. + * @alias Blockly.dialog.confirm */ + export function confirm(message: string, callback: (arg0: boolean) => any): void; /** - * Interface for an item in the toolbox. - * @interface - * @alias Blockly.IToolboxItem + * Sets the function to be run when Blockly.dialog.confirm() is called. + * @param {!function(string, !function(boolean))} confirmFunction The function + * to be run. + * @see Blockly.dialog.confirm + * @alias Blockly.dialog.setConfirm */ - export class IToolboxItem { - } + export function setConfirm(confirmFunction: (arg0: string, arg1: (arg0: boolean) => any) => any): void; + /** + * Wrapper to window.prompt() that app developers may override via setPrompt to + * provide alternatives to the modal browser window. Built-in browser prompts + * are often used for better text input experience on mobile device. We strongly + * recommend testing mobile when overriding this. + * @param {string} message The message to display to the user. + * @param {string} defaultValue The value to initialize the prompt with. + * @param {!function(?string)} callback The callback for handling user response. + * @alias Blockly.dialog.prompt + */ + export function prompt(message: string, defaultValue: string, callback: (arg0: string | null) => any): void; + /** + * Sets the function to be run when Blockly.dialog.prompt() is called. + * @param {!function(string, string, !function(?string))} promptFunction The + * function to be run. + * @see Blockly.dialog.prompt + * @alias Blockly.dialog.setPrompt + */ + export function setPrompt(promptFunction: (arg0: string, arg1: string, arg2: (arg0: string | null) => any) => any): void; } -declare module "interfaces/i_selectable_toolbox_item" { +declare module "core/utils/xml" { /** - * Interface for an item in the toolbox that can be selected. - * @extends {IToolboxItem} - * @interface - * @alias Blockly.ISelectableToolboxItem + * Namespace for Blockly's XML. + * @alias Blockly.utils.xml.NAME_SPACE */ - export class ISelectableToolboxItem { - } + export const NAME_SPACE: "https://developers.google.com/blockly/xml"; + /** + * Get the document object to use for XML serialization. + * @return {!Document} The document object. + * @alias Blockly.utils.xml.getDocument + */ + export function getDocument(): Document; + /** + * Get the document object to use for XML serialization. + * @param {!Document} document The document object to use. + * @alias Blockly.utils.xml.setDocument + */ + export function setDocument(document: Document): void; + /** + * Create DOM element for XML. + * @param {string} tagName Name of DOM element. + * @return {!Element} New DOM element. + * @alias Blockly.utils.xml.createElement + */ + export function createElement(tagName: string): Element; + /** + * Create text element for XML. + * @param {string} text Text content. + * @return {!Text} New DOM text node. + * @alias Blockly.utils.xml.createTextNode + */ + export function createTextNode(text: string): Text; + /** + * Converts an XML string into a DOM tree. + * @param {string} text XML string. + * @return {Document} The DOM document. + * @throws if XML doesn't parse. + * @alias Blockly.utils.xml.textToDomDocument + */ + export function textToDomDocument(text: string): Document; + /** + * Converts a DOM structure into plain text. + * Currently the text format is fairly ugly: all one line with no whitespace. + * @param {!Node} dom A tree of XML nodes. + * @return {string} Text representation. + * @alias Blockly.utils.xml.domToText + */ + export function domToText(dom: Node): string; } -declare module "interfaces/i_collapsible_toolbox_item" { - /** - * Interface for an item in the toolbox that can be collapsed. - * @extends {ISelectableToolboxItem} - * @interface - * @alias Blockly.ICollapsibleToolboxItem - */ - export class ICollapsibleToolboxItem { - } -} -declare module "utils/coordinate" { - /** - * @license - * Copyright 2019 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - /** - * @fileoverview Utility methods for coordinate manipulation. - * These methods are not specific to Blockly, and could be factored out into - * a JavaScript framework such as Closure. - */ - /** - * Utility methods for coordinate manipulation. - * These methods are not specific to Blockly, and could be factored out into - * a JavaScript framework such as Closure. - * @class - */ - /** - * Class for representing coordinates and positions. - * @param {number} x Left. - * @param {number} y Top. - * @struct - * @constructor - * @alias Blockly.utils.Coordinate - */ - export class Coordinate { - /** - * Compares coordinates for equality. - * @param {?Coordinate} a A Coordinate. - * @param {?Coordinate} b A Coordinate. - * @return {boolean} True iff the coordinates are equal, or if both are null. - */ - static equals(a: Coordinate | null, b: Coordinate | null): boolean; - /** - * Returns the distance between two coordinates. - * @param {!Coordinate} a A Coordinate. - * @param {!Coordinate} b A Coordinate. - * @return {number} The distance between `a` and `b`. - */ - static distance(a: Coordinate, b: Coordinate): number; - /** - * Returns the magnitude of a coordinate. - * @param {!Coordinate} a A Coordinate. - * @return {number} The distance between the origin and `a`. - */ - static magnitude(a: Coordinate): number; - /** - * Returns the difference between two coordinates as a new - * Coordinate. - * @param {!Coordinate|!SVGPoint} a An x/y coordinate. - * @param {!Coordinate|!SVGPoint} b An x/y coordinate. - * @return {!Coordinate} A Coordinate representing the difference - * between `a` and `b`. - */ - static difference(a: Coordinate | SVGPoint, b: Coordinate | SVGPoint): Coordinate; - /** - * Returns the sum of two coordinates as a new Coordinate. - * @param {!Coordinate|!SVGPoint} a An x/y coordinate. - * @param {!Coordinate|!SVGPoint} b An x/y coordinate. - * @return {!Coordinate} A Coordinate representing the sum of - * the two coordinates. - */ - static sum(a: Coordinate | SVGPoint, b: Coordinate | SVGPoint): Coordinate; - constructor(x: any, y: any); - /** - * X-value - * @type {number} - */ - x: number; - /** - * Y-value - * @type {number} - */ - y: number; - /** - * Creates a new copy of this coordinate. - * @return {!Coordinate} A copy of this coordinate. - */ - clone(): Coordinate; - /** - * Scales this coordinate by the given scale factor. - * @param {number} s The scale factor to use for both x and y dimensions. - * @return {!Coordinate} This coordinate after scaling. - */ - scale(s: number): Coordinate; - /** - * Translates this coordinate by the given offsets. - * respectively. - * @param {number} tx The value to translate x by. - * @param {number} ty The value to translate y by. - * @return {!Coordinate} This coordinate after translating. - */ - translate(tx: number, ty: number): Coordinate; - } -} -declare module "interfaces/i_registrable" { - /** - * @license - * Copyright 2020 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - /** - * @fileoverview The interface for a Blockly component that can be registered. - * (Ex. Toolbox, Fields, Renderers) - */ - /** - * The interface for a Blockly component that can be registered. - * (Ex. Toolbox, Fields, Renderers) - * @namespace Blockly.IRegistrable - */ - /** - * The interface for a Blockly component that can be registered. - * @interface - * @alias Blockly.IRegistrable - */ - export class IRegistrable { - } -} -declare module "interfaces/i_flyout" { - /** - * Interface for a flyout. - * @extends {IRegistrable} - * @interface - * @alias Blockly.IFlyout - */ - export class IFlyout { - /** - * Whether the flyout is laid out horizontally or not. - * @type {boolean} - */ - horizontalLayout: boolean; - /** - * Is RTL vs LTR. - * @type {boolean} - */ - RTL: boolean; - /** - * The target workspace - * @type {?WorkspaceSvg} - */ - targetWorkspace: WorkspaceSvg | null; - /** - * Margin around the edges of the blocks in the flyout. - * @type {number} - * @const - */ - MARGIN: number; - /** - * Does the flyout automatically close when a block is created? - * @type {boolean} - */ - autoClose: boolean; - /** - * Corner radius of the flyout background. - * @type {number} - * @const - */ - CORNER_RADIUS: number; - } - import { WorkspaceSvg } from "workspace_svg"; -} -declare module "interfaces/i_toolbox" { - /** - * Interface for a toolbox. - * @extends {IRegistrable} - * @interface - * @alias Blockly.IToolbox - */ - export class IToolbox { - } -} -declare module "toolbox/toolbox_item" { - /** - * Class for an item in the toolbox. - * @param {!toolbox.ToolboxItemInfo} toolboxItemDef The JSON defining the - * toolbox item. - * @param {!IToolbox} toolbox The toolbox that holds the toolbox item. - * @param {ICollapsibleToolboxItem=} opt_parent The parent toolbox item - * or null if the category does not have a parent. - * @constructor - * @implements {IToolboxItem} - * @alias Blockly.ToolboxItem - */ - export class ToolboxItem implements IToolboxItem { - constructor(toolboxItemDef: any, toolbox: any, opt_parent: any); - /** - * The id for the category. - * @type {string} - * @protected - */ - protected id_: string; - /** - * The parent of the category. - * @type {?ICollapsibleToolboxItem} - * @protected - */ - protected parent_: ICollapsibleToolboxItem | null; - /** - * The level that the category is nested at. - * @type {number} - * @protected - */ - protected level_: number; - /** - * The JSON definition of the toolbox item. - * @type {!toolbox.ToolboxItemInfo} - * @protected - */ - protected toolboxItemDef_: any; - /** - * The toolbox this category belongs to. - * @type {!IToolbox} - * @protected - */ - protected parentToolbox_: IToolbox; - /** - * The workspace of the parent toolbox. - * @type {!WorkspaceSvg} - * @protected - */ - protected workspace_: WorkspaceSvg; - /** - * Initializes the toolbox item. - * This includes creating the DOM and updating the state of any items based - * on the info object. - * @public - */ - public init(): void; - /** - * Gets the div for the toolbox item. - * @return {?Element} The div for the toolbox item. - * @public - */ - public getDiv(): Element | null; - /** - * Gets a unique identifier for this toolbox item. - * @return {string} The ID for the toolbox item. - * @public - */ - public getId(): string; - /** - * Gets the parent if the toolbox item is nested. - * @return {?IToolboxItem} The parent toolbox item, or null if - * this toolbox item is not nested. - * @public - */ - public getParent(): IToolboxItem | null; - /** - * Gets the nested level of the category. - * @return {number} The nested level of the category. - * @package - */ - getLevel(): number; - /** - * Whether the toolbox item is selectable. - * @return {boolean} True if the toolbox item can be selected. - * @public - */ - public isSelectable(): boolean; - /** - * Whether the toolbox item is collapsible. - * @return {boolean} True if the toolbox item is collapsible. - * @public - */ - public isCollapsible(): boolean; - /** - * Dispose of this toolbox item. No-op by default. - * @public - */ - public dispose(): void; - } - import { IToolboxItem } from "interfaces/i_toolbox_item"; - import { ICollapsibleToolboxItem } from "interfaces/i_collapsible_toolbox_item"; - import { IToolbox } from "interfaces/i_toolbox"; - import { WorkspaceSvg } from "workspace_svg"; -} -declare module "toolbox/category" { - import { ToolboxItem } from "toolbox/toolbox_item"; - export class ToolboxCategory extends ToolboxItem { - /** - * Class for a category in a toolbox. - * @param {!toolbox.CategoryInfo} categoryDef The information needed - * to create a category in the toolbox. - * @param {!IToolbox} toolbox The parent toolbox for the category. - * @param {ICollapsibleToolboxItem=} opt_parent The parent category or null if - * the category does not have a parent. - * @constructor - * @extends {ToolboxItem} - * @implements {ISelectableToolboxItem} - * @alias Blockly.ToolboxCategory - */ - constructor(categoryDef: toolbox.CategoryInfo, toolbox: IToolbox, opt_parent?: ICollapsibleToolboxItem | undefined); - /** - * The name that will be displayed on the category. - * @type {string} - * @protected - */ - protected name_: string; - /** - * The colour of the category. - * @type {string} - * @protected - */ - protected colour_: string; - /** - * The html container for the category. - * @type {?Element} - * @protected - */ - protected htmlDiv_: Element | null; - /** - * The html element for the category row. - * @type {?Element} - * @protected - */ - protected rowDiv_: Element | null; - /** - * The html element that holds children elements of the category row. - * @type {?Element} - * @protected - */ - protected rowContents_: Element | null; - /** - * The html element for the toolbox icon. - * @type {?Element} - * @protected - */ - protected iconDom_: Element | null; - /** - * The html element for the toolbox label. - * @type {?Element} - * @protected - */ - protected labelDom_: Element | null; - /** - * All the css class names that are used to create a category. - * @type {!ToolboxCategory.CssConfig} - * @protected - */ - protected cssConfig_: ToolboxCategory.CssConfig; - /** - * True if the category is meant to be hidden, false otherwise. - * @type {boolean} - * @protected - */ - protected isHidden_: boolean; - /** - * True if this category is disabled, false otherwise. - * @type {boolean} - * @protected - */ - protected isDisabled_: boolean; - /** - * The flyout items for this category. - * @type {string|!toolbox.FlyoutItemInfoArray} - * @protected - */ - protected flyoutItems_: string | toolbox.FlyoutItemInfoArray; - /** - * Creates an object holding the default classes for a category. - * @return {!ToolboxCategory.CssConfig} The configuration object holding - * all the CSS classes for a category. - * @protected - */ - protected makeDefaultCssConfig_(): ToolboxCategory.CssConfig; - /** - * Parses the contents array depending on if the category is a dynamic category, - * or if its contents are meant to be shown in the flyout. - * @param {!toolbox.CategoryInfo} categoryDef The information needed - * to create a category. - * @protected - */ - protected parseContents_(categoryDef: toolbox.CategoryInfo): void; - /** - * @override - */ - override init(): void; - /** - * Creates the DOM for the category. - * @return {!Element} The parent element for the category. - * @protected - */ - protected createDom_(): Element; - /** - * Creates the container that holds the row and any subcategories. - * @return {!Element} The div that holds the icon and the label. - * @protected - */ - protected createContainer_(): Element; - /** - * Creates the parent of the contents container. All clicks will happen on this - * div. - * @return {!Element} The div that holds the contents container. - * @protected - */ - protected createRowContainer_(): Element; - /** - * Creates the container for the label and icon. - * This is necessary so we can set all subcategory pointer events to none. - * @return {!Element} The div that holds the icon and the label. - * @protected - */ - protected createRowContentsContainer_(): Element; - /** - * Creates the span that holds the category icon. - * @return {!Element} The span that holds the category icon. - * @protected - */ - protected createIconDom_(): Element; - /** - * Creates the span that holds the category label. - * This should have an ID for accessibility purposes. - * @param {string} name The name of the category. - * @return {!Element} The span that holds the category label. - * @protected - */ - protected createLabelDom_(name: string): Element; - /** - * Updates the colour for this category. - * @public - */ - public refreshTheme(): void; - /** - * Add the strip of colour to the toolbox category. - * @param {string} colour The category colour. - * @protected - */ - protected addColourBorder_(colour: string): void; - /** - * Gets either the colour or the style for a category. - * @param {!toolbox.CategoryInfo} categoryDef The object holding - * information on the category. - * @return {string} The hex colour for the category. - * @protected - */ - protected getColour_(categoryDef: toolbox.CategoryInfo): string; - /** - * Sets the colour for the category using the style name and returns the new - * colour as a hex string. - * @param {string} styleName Name of the style. - * @return {string} The hex colour for the category. - * @private - */ - private getColourfromStyle_; - /** - * Gets the HTML element that is clickable. - * The parent toolbox element receives clicks. The parent toolbox will add an ID - * to this element so it can pass the onClick event to the correct toolboxItem. - * @return {!Element} The HTML element that receives clicks. - * @public - */ - public getClickTarget(): Element; - /** - * Parses the colour on the category. - * @param {number|string} colourValue HSV hue value (0 to 360), #RRGGBB string, - * or a message reference string pointing to one of those two values. - * @return {string} The hex colour for the category. - * @private - */ - private parseColour_; - /** - * Adds appropriate classes to display an open icon. - * @param {?Element} iconDiv The div that holds the icon. - * @protected - */ - protected openIcon_(iconDiv: Element | null): void; - /** - * Adds appropriate classes to display a closed icon. - * @param {?Element} iconDiv The div that holds the icon. - * @protected - */ - protected closeIcon_(iconDiv: Element | null): void; - /** - * Sets whether the category is visible or not. - * For a category to be visible its parent category must also be expanded. - * @param {boolean} isVisible True if category should be visible. - * @protected - */ - protected setVisible_(isVisible: boolean): void; - /** - * Hide the category. - */ - hide(): void; - /** - * Show the category. Category will only appear if its parent category is also - * expanded. - */ - show(): void; - /** - * Whether the category is visible. - * A category is only visible if all of its ancestors are expanded and isHidden_ - * is false. - * @return {boolean} True if the category is visible, false otherwise. - * @public - */ - public isVisible(): boolean; - /** - * Whether all ancestors of a category (parent and parent's parent, etc.) are - * expanded. - * @return {boolean} True only if every ancestor is expanded - * @protected - */ - protected allAncestorsExpanded_(): boolean; - /** - * @override - */ - override isSelectable(): boolean; - /** - * Handles when the toolbox item is clicked. - * @param {!Event} _e Click event to handle. - * @public - */ - public onClick(_e: Event): void; - /** - * Sets the current category as selected. - * @param {boolean} isSelected True if this category is selected, false - * otherwise. - * @public - */ - public setSelected(isSelected: boolean): void; - /** - * Sets whether the category is disabled. - * @param {boolean} isDisabled True to disable the category, false otherwise. - */ - setDisabled(isDisabled: boolean): void; - /** - * Gets the name of the category. Used for emitting events. - * @return {string} The name of the toolbox item. - * @public - */ - public getName(): string; - /** - * @override - */ - override getParent(): any; - /** - * @override - */ - override getDiv(): Element; - /** - * Gets the contents of the category. These are items that are meant to be - * displayed in the flyout. - * @return {!toolbox.FlyoutItemInfoArray|string} The definition - * of items to be displayed in the flyout. - * @public - */ - public getContents(): any | string; - /** - * Updates the contents to be displayed in the flyout. - * If the flyout is open when the contents are updated, refreshSelection on the - * toolbox must also be called. - * @param {!toolbox.FlyoutDefinition|string} contents The contents - * to be displayed in the flyout. A string can be supplied to create a - * dynamic category. - * @public - */ - public updateFlyoutContents(contents: toolbox.FlyoutDefinition | string): void; - /** - * @override - */ - override dispose(): void; - } - export namespace ToolboxCategory { - const registrationName: string; - const nestedPadding: number; - const borderWidth: number; - const defaultBackgroundColour: string; - /** - * All the CSS class names that are used to create a category. - */ - type CssConfig = { - container: (string | undefined); - row: (string | undefined); - rowcontentcontainer: (string | undefined); - icon: (string | undefined); - label: (string | undefined); - selected: (string | undefined); - openicon: (string | undefined); - closedicon: (string | undefined); - }; - } - import * as toolbox from "utils/toolbox"; - import { IToolbox } from "interfaces/i_toolbox"; - import { ICollapsibleToolboxItem } from "interfaces/i_collapsible_toolbox_item"; -} -declare module "toolbox/separator" { - import { ToolboxItem } from "toolbox/toolbox_item"; - /** - * Class for a toolbox separator. This is the thin visual line that appears on - * the toolbox. This item is not interactable. - * @param {!toolbox.SeparatorInfo} separatorDef The information - * needed to create a separator. - * @param {!IToolbox} toolbox The parent toolbox for the separator. - * @constructor - * @extends {ToolboxItem} - * @alias Blockly.ToolboxSeparator - */ - export class ToolboxSeparator extends ToolboxItem { - constructor(separatorDef: any, toolbox: any); - /** - * All the CSS class names that are used to create a separator. - * @type {!ToolboxSeparator.CssConfig} - * @protected - */ - protected cssConfig_: ToolboxSeparator.CssConfig; - /** - * @override - */ - override init(): void; - /** - * Creates the DOM for a separator. - * @return {!Element} The parent element for the separator. - * @protected - */ - protected createDom_(): Element; - htmlDiv_: HTMLDivElement; - /** - * @override - */ - override getDiv(): HTMLDivElement; - /** - * @override - */ - override dispose(): void; - } - export namespace ToolboxSeparator { - const registrationName: string; - /** - * All the CSS class names that are used to create a separator. - */ - type CssConfig = { - container: (string | undefined); - }; - } -} -declare module "utils/toolbox" { -/** - * The information needed to create a block in the toolbox. - * Note that disabled has a different type for backwards compatibility. - * @typedef {{ - * kind:string, - * blockxml:(string|!Node|undefined), - * type:(string|undefined), - * gap:(string|number|undefined), - * disabled: (string|boolean|undefined), - * enabled: (boolean|undefined), - * id: (string|undefined), - * x: (number|undefined), - * y: (number|undefined), - * collapsed: (boolean|undefined), - * inline: (boolean|undefined), - * data: (string|undefined), - * extra-state: (*|undefined), - * icons: (!Object|undefined), - * fields: (!Object|undefined), - * inputs: (!Object|undefined), - * next: (!ConnectionState|undefined) - * }} - * @alias Blockly.utils.toolbox.BlockInfo - */ -export type BlockInfo = any; - /** - * An array holding flyout items. - * @typedef { - * Array - * } - * @alias Blockly.utils.toolbox.FlyoutItemInfoArray - */ - export type FlyoutItemInfoArray = any; - - /** - * The information needed to create a separator in the toolbox. - */ - export type SeparatorInfo = { - kind: string; - id: (string | undefined); - gap: (number | undefined); - cssconfig: (ToolboxSeparator.CssConfig | undefined); - }; - /** - * The information needed to create a separator in the toolbox. - * @typedef {{ - * kind:string, - * id:(string|undefined), - * gap:(number|undefined), - * cssconfig:(!ToolboxSeparator.CssConfig|undefined) - * }} - * @alias Blockly.utils.toolbox.SeparatorInfo - */ - export let SeparatorInfo: any; - /** - * The information needed to create a button in the toolbox. - */ - export type ButtonInfo = { - kind: string; - text: string; - callbackkey: string; - }; - /** - * The information needed to create a button in the toolbox. - * @typedef {{ - * kind:string, - * text:string, - * callbackkey:string - * }} - * @alias Blockly.utils.toolbox.ButtonInfo - */ - export let ButtonInfo: any; - /** - * The information needed to create a label in the toolbox. - */ - export type LabelInfo = { - kind: string; - text: string; - id: (string | undefined); - }; - /** - * The information needed to create a label in the toolbox. - * @typedef {{ - * kind:string, - * text:string, - * id:(string|undefined) - * }} - * @alias Blockly.utils.toolbox.LabelInfo - */ - export let LabelInfo: any; - /** - * The information needed to create either a button or a label in the flyout. - */ - export type ButtonOrLabelInfo = ButtonInfo | LabelInfo; - /** - * The information needed to create either a button or a label in the flyout. - * @typedef {ButtonInfo| - * LabelInfo} - * @alias Blockly.utils.toolbox.ButtonOrLabelInfo - */ - export let ButtonOrLabelInfo: any; - /** - * The information needed to create a category in the toolbox. - */ - export type StaticCategoryInfo = { - kind: string; - name: string; - contents: Array; - id: (string | undefined); - categorystyle: (string | undefined); - colour: (string | undefined); - cssconfig: (ToolboxCategory.CssConfig | undefined); - hidden: (string | undefined); - }; - /** - * The information needed to create a category in the toolbox. - * @typedef {{ - * kind:string, - * name:string, - * contents:!Array, - * id:(string|undefined), - * categorystyle:(string|undefined), - * colour:(string|undefined), - * cssconfig:(!ToolboxCategory.CssConfig|undefined), - * hidden:(string|undefined) - * }} - * @alias Blockly.utils.toolbox.StaticCategoryInfo - */ - export let StaticCategoryInfo: any; - /** - * The information needed to create a custom category. - */ - export type DynamicCategoryInfo = { - kind: string; - custom: string; - id: (string | undefined); - categorystyle: (string | undefined); - colour: (string | undefined); - cssconfig: (ToolboxCategory.CssConfig | undefined); - hidden: (string | undefined); - }; - /** - * The information needed to create a custom category. - * @typedef {{ - * kind:string, - * custom:string, - * id:(string|undefined), - * categorystyle:(string|undefined), - * colour:(string|undefined), - * cssconfig:(!ToolboxCategory.CssConfig|undefined), - * hidden:(string|undefined) - * }} - * @alias Blockly.utils.toolbox.DynamicCategoryInfo - */ - export let DynamicCategoryInfo: any; - /** - * The information needed to create either a dynamic or static category. - */ - export type CategoryInfo = StaticCategoryInfo | DynamicCategoryInfo; - /** - * The information needed to create either a dynamic or static category. - * @typedef {StaticCategoryInfo| - * DynamicCategoryInfo} - * @alias Blockly.utils.toolbox.CategoryInfo - */ - export let CategoryInfo: any; - /** - * Any information that can be used to create an item in the toolbox. - */ - export type ToolboxItemInfo = FlyoutItemInfo | StaticCategoryInfo; - /** - * Any information that can be used to create an item in the toolbox. - * @typedef {FlyoutItemInfo| - * StaticCategoryInfo} - * @alias Blockly.utils.toolbox.ToolboxItemInfo - */ - export let ToolboxItemInfo: any; - /** - * All the different types that can be displayed in a flyout. - */ - export type FlyoutItemInfo = any | SeparatorInfo | ButtonInfo | LabelInfo | DynamicCategoryInfo; - /** - * All the different types that can be displayed in a flyout. - * @typedef {BlockInfo| - * SeparatorInfo| - * ButtonInfo| - * LabelInfo| - * DynamicCategoryInfo} - * @alias Blockly.utils.toolbox.FlyoutItemInfo - */ - export let FlyoutItemInfo: any; - /** - * The JSON definition of a toolbox. - */ - export type ToolboxInfo = { - kind: (string | undefined); - contents: Array; - }; - /** - * The JSON definition of a toolbox. - * @typedef {{ - * kind:(string|undefined), - * contents:!Array - * }} - * @alias Blockly.utils.toolbox.ToolboxInfo - */ - export let ToolboxInfo: any; - /** - * An array holding flyout items. - * @typedef { - * Array - * } - * @alias Blockly.utils.toolbox.FlyoutItemInfoArray - */ - export let FlyoutItemInfoArray: any; - /** - * All of the different types that can create a toolbox. - */ - export type ToolboxDefinition = Node | ToolboxInfo | string; - /** - * All of the different types that can create a toolbox. - * @typedef {Node| - * ToolboxInfo| - * string} - * @alias Blockly.utils.toolbox.ToolboxDefinition - */ - export let ToolboxDefinition: any; - /** - * All of the different types that can be used to show items in a flyout. - */ - export type FlyoutDefinition = any | NodeList | ToolboxInfo | Array; - /** - * All of the different types that can be used to show items in a flyout. - * @typedef {FlyoutItemInfoArray| - * NodeList| - * ToolboxInfo| - * Array} - * @alias Blockly.utils.toolbox.FlyoutDefinition - */ - export let FlyoutDefinition: any; - /** - * * - */ - export type Position = number; - export namespace Position { - const TOP: number; - const BOTTOM: number; - const LEFT: number; - const RIGHT: number; - } - /** - * Converts the toolbox definition into toolbox JSON. - * @param {?ToolboxDefinition} toolboxDef The definition - * of the toolbox in one of its many forms. - * @return {?ToolboxInfo} Object holding information - * for creating a toolbox. - * @alias Blockly.utils.toolbox.convertToolboxDefToJson - * @package - */ - export function convertToolboxDefToJson(toolboxDef: ToolboxDefinition | null): ToolboxInfo | null; - /** - * Converts the flyout definition into a list of flyout items. - * @param {?FlyoutDefinition} flyoutDef The definition of - * the flyout in one of its many forms. - * @return {!FlyoutItemInfoArray} A list of flyout items. - * @alias Blockly.utils.toolbox.convertFlyoutDefToJsonArray - * @package - */ - export function convertFlyoutDefToJsonArray(flyoutDef: FlyoutDefinition | null): any; - /** - * Whether or not the toolbox definition has categories. - * @param {?ToolboxInfo} toolboxJson Object holding - * information for creating a toolbox. - * @return {boolean} True if the toolbox has categories. - * @alias Blockly.utils.toolbox.hasCategories - * @package - */ - export function hasCategories(toolboxJson: ToolboxInfo | null): boolean; - /** - * Whether or not the category is collapsible. - * @param {!CategoryInfo} categoryInfo Object holing - * information for creating a category. - * @return {boolean} True if the category has subcategories. - * @alias Blockly.utils.toolbox.isCategoryCollapsible - * @package - */ - export function isCategoryCollapsible(categoryInfo: CategoryInfo): boolean; - /** - * Parse the provided toolbox tree into a consistent DOM format. - * @param {?Node|?string} toolboxDef DOM tree of blocks, or text representation - * of same. - * @return {?Node} DOM tree of blocks, or null. - * @alias Blockly.utils.toolbox.parseToolboxTree - */ - export function parseToolboxTree(toolboxDef: (Node | (string | null)) | null): Node | null; - import { ToolboxSeparator } from "toolbox/separator"; - import { ToolboxCategory } from "toolbox/category"; -} -declare module "blockly_options" { - /** - * @license - * Copyright 2016 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - /** - * @fileoverview Object that defines user-specified options for the workspace. - */ - /** - * Object that defines user-specified options for the workspace. - * @namespace Blockly.BlocklyOptions - */ - /** - * Blockly options. - * This interface is further described in - * `typings/parts/blockly-interfaces.d.ts`. - * @interface - * @alias Blockly.BlocklyOptions - */ - export class BlocklyOptions { - } -} -declare module "utils/svg_paths" { - /** - * @license - * Copyright 2019 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - /** - * @fileoverview Methods for creating parts of SVG path strings. See - * developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths - */ - /** - * Methods for creating parts of SVG path strings. See - * @namespace Blockly.utils.svgPaths - */ - /** - * Create a string representing the given x, y pair. It does not matter whether - * the coordinate is relative or absolute. The result has leading - * and trailing spaces, and separates the x and y coordinates with a comma but - * no space. - * @param {number} x The x coordinate. - * @param {number} y The y coordinate. - * @return {string} A string of the format ' x,y ' - * @alias Blockly.utils.svgPaths.point - */ - export function point(x: number, y: number): string; - /** - * Draw a cubic or quadratic curve. See - * developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#Cubic_B%C3%A9zier_Curve - * These coordinates are unitless and hence in the user coordinate system. - * @param {string} command The command to use. - * Should be one of: c, C, s, S, q, Q. - * @param {!Array} points An array containing all of the points to pass - * to the curve command, in order. The points are represented as strings of - * the format ' x, y '. - * @return {string} A string defining one or more Bezier curves. See the MDN - * documentation for exact format. - * @alias Blockly.utils.svgPaths.curve - */ - export function curve(command: string, points: Array): string; - /** - * Move the cursor to the given position without drawing a line. - * The coordinates are absolute. - * These coordinates are unitless and hence in the user coordinate system. - * See developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths#Line_commands - * @param {number} x The absolute x coordinate. - * @param {number} y The absolute y coordinate. - * @return {string} A string of the format ' M x,y ' - * @alias Blockly.utils.svgPaths.moveTo - */ - export function moveTo(x: number, y: number): string; - /** - * Move the cursor to the given position without drawing a line. - * Coordinates are relative. - * These coordinates are unitless and hence in the user coordinate system. - * See developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths#Line_commands - * @param {number} dx The relative x coordinate. - * @param {number} dy The relative y coordinate. - * @return {string} A string of the format ' m dx,dy ' - * @alias Blockly.utils.svgPaths.moveBy - */ - export function moveBy(dx: number, dy: number): string; - /** - * Draw a line from the current point to the end point, which is the current - * point shifted by dx along the x-axis and dy along the y-axis. - * These coordinates are unitless and hence in the user coordinate system. - * See developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths#Line_commands - * @param {number} dx The relative x coordinate. - * @param {number} dy The relative y coordinate. - * @return {string} A string of the format ' l dx,dy ' - * @alias Blockly.utils.svgPaths.lineTo - */ - export function lineTo(dx: number, dy: number): string; - /** - * Draw multiple lines connecting all of the given points in order. This is - * equivalent to a series of 'l' commands. - * These coordinates are unitless and hence in the user coordinate system. - * See developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths#Line_commands - * @param {!Array} points An array containing all of the points to - * draw lines to, in order. The points are represented as strings of the - * format ' dx,dy '. - * @return {string} A string of the format ' l (dx,dy)+ ' - * @alias Blockly.utils.svgPaths.line - */ - export function line(points: Array): string; - /** - * Draw a horizontal or vertical line. - * The first argument specifies the direction and whether the given position is - * relative or absolute. - * These coordinates are unitless and hence in the user coordinate system. - * See developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#LineTo_path_commands - * @param {string} command The command to prepend to the coordinate. This - * should be one of: V, v, H, h. - * @param {number} val The coordinate to pass to the command. It may be - * absolute or relative. - * @return {string} A string of the format ' command val ' - * @alias Blockly.utils.svgPaths.lineOnAxis - */ - export function lineOnAxis(command: string, val: number): string; - /** - * Draw an elliptical arc curve. - * These coordinates are unitless and hence in the user coordinate system. - * See developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#Elliptical_Arc_Curve - * @param {string} command The command string. Either 'a' or 'A'. - * @param {string} flags The flag string. See the MDN documentation for a - * description and examples. - * @param {number} radius The radius of the arc to draw. - * @param {string} point The point to move the cursor to after drawing the arc, - * specified either in absolute or relative coordinates depending on the - * command. - * @return {string} A string of the format 'command radius radius flags point' - * @alias Blockly.utils.svgPaths.arc - */ - export function arc(command: string, flags: string, radius: number, point: string): string; -} -declare module "utils/style" { - /** - * Gets the height and width of an element. - * Similar to Closure's goog.style.getSize - * @param {!Element} element Element to get size of. - * @return {!Size} Object with width/height properties. - * @alias Blockly.utils.style.getSize - */ - export function getSize(element: Element): Size; - /** - * Retrieves a computed style value of a node. It returns empty string if the - * value cannot be computed (which will be the case in Internet Explorer) or - * "none" if the property requested is an SVG one and it has not been - * explicitly set (firefox and webkit). - * - * Copied from Closure's goog.style.getComputedStyle - * - * @param {!Element} element Element to get style of. - * @param {string} property Property to get (camel-case). - * @return {string} Style value. - * @alias Blockly.utils.style.getComputedStyle - */ - export function getComputedStyle(element: Element, property: string): string; - /** - * Gets the cascaded style value of a node, or null if the value cannot be - * computed (only Internet Explorer can do this). - * - * Copied from Closure's goog.style.getCascadedStyle - * - * @param {!Element} element Element to get style of. - * @param {string} style Property to get (camel-case). - * @return {string} Style value. - * @alias Blockly.utils.style.getCascadedStyle - */ - export function getCascadedStyle(element: Element, style: string): string; - /** - * Returns a Coordinate object relative to the top-left of the HTML document. - * Similar to Closure's goog.style.getPageOffset - * @param {!Element} el Element to get the page offset for. - * @return {!Coordinate} The page offset. - * @alias Blockly.utils.style.getPageOffset - */ - export function getPageOffset(el: Element): Coordinate; - /** - * Calculates the viewport coordinates relative to the document. - * Similar to Closure's goog.style.getViewportPageOffset - * @return {!Coordinate} The page offset of the viewport. - * @alias Blockly.utils.style.getViewportPageOffset - */ - export function getViewportPageOffset(): Coordinate; - /** - * Shows or hides an element from the page. Hiding the element is done by - * setting the display property to "none", removing the element from the - * rendering hierarchy so it takes up no space. To show the element, the default - * inherited display property is restored (defined either in stylesheets or by - * the browser's default style rules). - * Copied from Closure's goog.style.getViewportPageOffset - * - * @param {!Element} el Element to show or hide. - * @param {*} isShown True to render the element in its default style, - * false to disable rendering the element. - * @alias Blockly.utils.style.setElementShown - */ - export function setElementShown(el: Element, isShown: any): void; - /** - * Returns true if the element is using right to left (RTL) direction. - * Copied from Closure's goog.style.isRightToLeft - * - * @param {!Element} el The element to test. - * @return {boolean} True for right to left, false for left to right. - * @alias Blockly.utils.style.isRightToLeft - */ - export function isRightToLeft(el: Element): boolean; - /** - * Gets the computed border widths (on all sides) in pixels - * Copied from Closure's goog.style.getBorderBox - * @param {!Element} element The element to get the border widths for. - * @return {!Object} The computed border widths. - * @alias Blockly.utils.style.getBorderBox - */ - export function getBorderBox(element: Element): any; - /** - * Changes the scroll position of `container` with the minimum amount so - * that the content and the borders of the given `element` become visible. - * If the element is bigger than the container, its top left corner will be - * aligned as close to the container's top left corner as possible. - * Copied from Closure's goog.style.scrollIntoContainerView - * - * @param {!Element} element The element to make visible. - * @param {!Element} container The container to scroll. If not set, then the - * document scroll element will be used. - * @param {boolean=} opt_center Whether to center the element in the container. - * Defaults to false. - * @alias Blockly.utils.style.scrollIntoContainerView - */ - export function scrollIntoContainerView(element: Element, container: Element, opt_center?: boolean | undefined): void; - /** - * Calculate the scroll position of `container` with the minimum amount so - * that the content and the borders of the given `element` become visible. - * If the element is bigger than the container, its top left corner will be - * aligned as close to the container's top left corner as possible. - * Copied from Closure's goog.style.getContainerOffsetToScrollInto - * - * @param {!Element} element The element to make visible. - * @param {!Element} container The container to scroll. If not set, then the - * document scroll element will be used. - * @param {boolean=} opt_center Whether to center the element in the container. - * Defaults to false. - * @return {!Coordinate} The new scroll position of the container, - * in form of goog.math.Coordinate(scrollLeft, scrollTop). - * @alias Blockly.utils.style.getContainerOffsetToScrollInto - */ - export function getContainerOffsetToScrollInto(element: Element, container: Element, opt_center?: boolean | undefined): Coordinate; - import { Size } from "utils/size"; - import { Coordinate } from "utils/coordinate"; -} -declare module "utils/rect" { - /** - * @license - * Copyright 2019 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - /** - * @fileoverview Utility methods for rectangle manipulation. - * These methods are not specific to Blockly, and could be factored out into - * a JavaScript framework such as Closure. - */ - /** - * Utility methods for rectangle manipulation. - * These methods are not specific to Blockly, and could be factored out into - * a JavaScript framework such as Closure. - * @class - */ - /** - * Class for representing rectangular regions. - * @param {number} top Top. - * @param {number} bottom Bottom. - * @param {number} left Left. - * @param {number} right Right. - * @struct - * @constructor - * @alias Blockly.utils.Rect - */ - export class Rect { - constructor(top: any, bottom: any, left: any, right: any); - /** @type {number} */ - top: number; - /** @type {number} */ - bottom: number; - /** @type {number} */ - left: number; - /** @type {number} */ - right: number; - /** - * Tests whether this rectangle contains a x/y coordinate. - * - * @param {number} x The x coordinate to test for containment. - * @param {number} y The y coordinate to test for containment. - * @return {boolean} Whether this rectangle contains given coordinate. - */ - contains(x: number, y: number): boolean; - /** - * Tests whether this rectangle intersects the provided rectangle. - * Assumes that the coordinate system increases going down and left. - * @param {!Rect} other The other rectangle to check for - * intersection with. - * @return {boolean} Whether this rectangle intersects the provided rectangle. - */ - intersects(other: Rect): boolean; - } -} -declare module "utils/svg_math" { - export namespace TEST_ONLY { - export { XY_REGEX }; - export { XY_STYLE_REGEX }; - } - /** - * Return the coordinates of the top-left corner of this element relative to - * its parent. Only for SVG elements and children (e.g. rect, g, path). - * @param {!Element} element SVG element to find the coordinates of. - * @return {!Coordinate} Object with .x and .y properties. - * @alias Blockly.svgMath.getRelativeXY - */ - export function getRelativeXY(element: Element): Coordinate; - /** - * Return the coordinates of the top-left corner of this element relative to - * the div Blockly was injected into. - * @param {!Element} element SVG element to find the coordinates of. If this is - * not a child of the div Blockly was injected into, the behaviour is - * undefined. - * @return {!Coordinate} Object with .x and .y properties. - * @alias Blockly.svgMath.getInjectionDivXY - */ - export function getInjectionDivXY(element: Element): Coordinate; - /** - * Check if 3D transforms are supported by adding an element - * and attempting to set the property. - * @return {boolean} True if 3D transforms are supported. - * @alias Blockly.svgMath.is3dSupported - */ - export function is3dSupported(): boolean; - /** - * Get the position of the current viewport in window coordinates. This takes - * scroll into account. - * @return {!Rect} An object containing window width, height, and - * scroll position in window coordinates. - * @alias Blockly.svgMath.getViewportBBox - * @package - */ - export function getViewportBBox(): Rect; - /** - * Gets the document scroll distance as a coordinate object. - * Copied from Closure's goog.dom.getDocumentScroll. - * @return {!Coordinate} Object with values 'x' and 'y'. - * @alias Blockly.svgMath.getDocumentScroll - */ - export function getDocumentScroll(): Coordinate; - /** - * Converts screen coordinates to workspace coordinates. - * @param {!WorkspaceSvg} ws The workspace to find the coordinates on. - * @param {!Coordinate} screenCoordinates The screen coordinates to - * be converted to workspace coordinates - * @return {!Coordinate} The workspace coordinates. - * @alias Blockly.svgMath.screenToWsCoordinates - */ - export function screenToWsCoordinates(ws: WorkspaceSvg, screenCoordinates: Coordinate): Coordinate; - /** - * Returns the dimensions of the specified SVG image. - * @param {!SVGElement} svg SVG image. - * @return {!Size} Contains width and height properties. - * @deprecated Use workspace.getCachedParentSvgSize. (2021 March 5) - * @alias Blockly.utils.svgMath.svgSize - */ - export function svgSize(svg: SVGElement): Size; - /** - * Static regex to pull the x,y values out of an SVG translate() directive. - * Note that Firefox and IE (9,10) return 'translate(12)' instead of - * 'translate(12, 0)'. - * Note that IE (9,10) returns 'translate(16 8)' instead of 'translate(16, 8)'. - * Note that IE has been reported to return scientific notation (0.123456e-42). - * @type {!RegExp} - */ - const XY_REGEX: RegExp; - /** - * Static regex to pull the x,y values out of a translate() or translate3d() - * style property. - * Accounts for same exceptions as XY_REGEX. - * @type {!RegExp} - */ - const XY_STYLE_REGEX: RegExp; - import { Coordinate } from "utils/coordinate"; - import { Rect } from "utils/rect"; - import { WorkspaceSvg } from "workspace_svg"; - import { Size } from "utils/size"; - export {}; -} -declare module "rendered_connection" { - export class RenderedConnection extends Connection { - /** - * Class for a connection between blocks that may be rendered on screen. - * @param {!BlockSvg} source The block establishing this connection. - * @param {number} type The type of the connection. - * @extends {Connection} - * @constructor - * @alias Blockly.RenderedConnection - */ - constructor(source: BlockSvg, type: number); - /** - * Connection database for connections of this type on the current workspace. - * @const {!ConnectionDB} - * @private - */ - private db_; - /** - * Connection database for connections compatible with this type on the - * current workspace. - * @const {!ConnectionDB} - * @private - */ - private dbOpposite_; - /** - * Workspace units, (0, 0) is top left of block. - * @type {!Coordinate} - * @private - */ - private offsetInBlock_; - /** - * Describes the state of this connection's tracked-ness. - * @type {RenderedConnection.TrackedState} - * @private - */ - private trackedState_; - /** - * Connection this connection connects to. Null if not connected. - * @type {RenderedConnection} - */ - targetConnection: RenderedConnection; - /** - * Dispose of this connection. Remove it from the database (if it is - * tracked) and call the super-function to deal with connected blocks. - * @override - * @package - */ - override dispose(): void; - /** - * Get the source block for this connection. - * @return {!BlockSvg} The source block. - * @override - */ - override getSourceBlock(): BlockSvg; - /** - * Returns the block that this connection connects to. - * @return {?BlockSvg} The connected block or null if none is connected. - * @override - */ - override targetBlock(): BlockSvg | null; - /** - * Returns the distance between this connection and another connection in - * workspace units. - * @param {!Connection} otherConnection The other connection to measure - * the distance to. - * @return {number} The distance between connections, in workspace units. - */ - distanceFrom(otherConnection: Connection): number; - /** - * Move the block(s) belonging to the connection to a point where they don't - * visually interfere with the specified connection. - * @param {!Connection} staticConnection The connection to move away - * from. - * @package - */ - bumpAwayFrom(staticConnection: Connection): void; - /** - * Change the connection's coordinates. - * @param {number} x New absolute x coordinate, in workspace coordinates. - * @param {number} y New absolute y coordinate, in workspace coordinates. - */ - moveTo(x: number, y: number): void; - x: number; - y: number; - /** - * Change the connection's coordinates. - * @param {number} dx Change to x coordinate, in workspace units. - * @param {number} dy Change to y coordinate, in workspace units. - */ - moveBy(dx: number, dy: number): void; - /** - * Move this connection to the location given by its offset within the block and - * the location of the block's top left corner. - * @param {!Coordinate} blockTL The location of the top left - * corner of the block, in workspace coordinates. - */ - moveToOffset(blockTL: Coordinate): void; - /** - * Set the offset of this connection relative to the top left of its block. - * @param {number} x The new relative x, in workspace units. - * @param {number} y The new relative y, in workspace units. - */ - setOffsetInBlock(x: number, y: number): void; - /** - * Get the offset of this connection relative to the top left of its block. - * @return {!Coordinate} The offset of the connection. - * @package - */ - getOffsetInBlock(): Coordinate; - /** - * Move the blocks on either side of this connection right next to each other. - * @package - */ - tighten(): void; - /** - * Find the closest compatible connection to this connection. - * All parameters are in workspace units. - * @param {number} maxLimit The maximum radius to another connection. - * @param {!Coordinate} dxy Offset between this connection's location - * in the database and the current location (as a result of dragging). - * @return {!{connection: ?Connection, radius: number}} Contains two - * properties: 'connection' which is either another connection or null, - * and 'radius' which is the distance. - */ - closest(maxLimit: number, dxy: Coordinate): { - connection: Connection | null; - radius: number; - }; - /** - * Add highlighting around this connection. - */ - highlight(): void; - /** - * Remove the highlighting around this connection. - */ - unhighlight(): void; - /** - * Set whether this connections is tracked in the database or not. - * @param {boolean} doTracking If true, start tracking. If false, stop tracking. - * @package - */ - setTracking(doTracking: boolean): void; - /** - * Stop tracking this connection, as well as all down-stream connections on - * any block attached to this connection. This happens when a block is - * collapsed. - * - * Also closes down-stream icons/bubbles. - * @package - */ - stopTrackingAll(): void; - /** - * Start tracking this connection, as well as all down-stream connections on - * any block attached to this connection. This happens when a block is expanded. - * @return {!Array} List of blocks to render. - */ - startTrackingAll(): Array; - /** - * Check if the two connections can be dragged to connect to each other. - * @param {!Connection} candidate A nearby connection to check. - * @param {number=} maxRadius The maximum radius allowed for connections, in - * workspace units. - * @return {boolean} True if the connection is allowed, false otherwise. - * @deprecated July 2020 - */ - isConnectionAllowed(candidate: Connection, maxRadius?: number | undefined): boolean; - /** - * Behavior after a connection attempt fails. - * Bumps this connection away from the other connection. Called when an - * attempted connection fails. - * @param {!Connection} otherConnection Connection that this connection - * failed to connect to. - * @package - */ - onFailedConnect(otherConnection: Connection): void; - /** - * Disconnect two blocks that are connected by this connection. - * @param {!Block} parentBlock The superior block. - * @param {!Block} childBlock The inferior block. - * @protected - * @override - */ - protected override disconnectInternal_(parentBlock: Block, childBlock: Block): void; - /** - * Respawn the shadow block if there was one connected to the this connection. - * Render/rerender blocks as needed. - * @protected - * @override - */ - protected override respawnShadow_(): void; - /** - * Find all nearby compatible connections to this connection. - * Type checking does not apply, since this function is used for bumping. - * @param {number} maxLimit The maximum radius to another connection, in - * workspace units. - * @return {!Array} List of connections. - * @package - */ - neighbours(maxLimit: number): Array; - /** - * Connect two connections together. This is the connection on the superior - * block. Rerender blocks as needed. - * @param {!Connection} childConnection Connection on inferior block. - * @protected - */ - protected connect_(childConnection: Connection): void; - /** - * Function to be called when this connection's compatible types have changed. - * @protected - */ - protected onCheckChanged_(): void; - } - export namespace RenderedConnection { - namespace TrackedState { - const WILL_TRACK: number; - const UNTRACKED: number; - const TRACKED: number; - } - /** - * Enum for different kinds of tracked states. - * - * WILL_TRACK means that this connection will add itself to - * the db on the next moveTo call it receives. - * - * UNTRACKED means that this connection will not add - * itself to the database until setTracking(true) is explicitly called. - * - * TRACKED means that this connection is currently being tracked. - */ - type TrackedState = number; - } - import { BlockSvg } from "block_svg"; - import { Connection } from "connection"; - import { Coordinate } from "utils/coordinate"; - import { Block } from "block"; -} -declare module "interfaces/i_connection_checker" { - /** - * Class for connection type checking logic. - * @interface - * @alias Blockly.IConnectionChecker - */ - export class IConnectionChecker { - } -} -declare module "constants" { - /** - * * - */ - export type ALIGN = number; - export namespace ALIGN { - const LEFT: number; - const CENTRE: number; - const RIGHT: number; - } - /** - * The language-neutral ID given to the collapsed input. - * @const {string} - * @alias Blockly.constants.COLLAPSED_INPUT_NAME - */ - export const COLLAPSED_INPUT_NAME: "_TEMP_COLLAPSED_INPUT"; - /** - * The language-neutral ID given to the collapsed field. - * @const {string} - * @alias Blockly.constants.COLLAPSED_FIELD_NAME - */ - export const COLLAPSED_FIELD_NAME: "_TEMP_COLLAPSED_FIELD"; -} -declare module "connection_db" { - export class ConnectionDB { - /** - * Initialize a set of connection DBs for a workspace. - * @param {!IConnectionChecker} checker The workspace's - * connection checker, used to decide if connections are valid during a - * drag. - * @return {!Array} Array of databases. - */ - static init(checker: IConnectionChecker): Array; - /** - * Database of connections. - * Connections are stored in order of their vertical component. This way - * connections in an area may be looked up quickly using a binary search. - * @param {!IConnectionChecker} checker The workspace's - * connection type checker, used to decide if connections are valid during a - * drag. - * @constructor - * @alias Blockly.ConnectionDB - */ - constructor(checker: IConnectionChecker); - /** - * Array of connections sorted by y position in workspace units. - * @type {!Array} - * @private - */ - private connections_; - /** - * The workspace's connection type checker, used to decide if connections are - * valid during a drag. - * @type {!IConnectionChecker} - * @private - */ - private connectionChecker_; - /** - * Add a connection to the database. Should not already exist in the database. - * @param {!RenderedConnection} connection The connection to be added. - * @param {number} yPos The y position used to decide where to insert the - * connection. - * @package - */ - addConnection(connection: RenderedConnection, yPos: number): void; - /** - * Finds the index of the given connection. - * - * Starts by doing a binary search to find the approximate location, then - * linearly searches nearby for the exact connection. - * @param {!RenderedConnection} conn The connection to find. - * @param {number} yPos The y position used to find the index of the connection. - * @return {number} The index of the connection, or -1 if the connection was - * not found. - * @private - */ - private findIndexOfConnection_; - /** - * Finds the correct index for the given y position. - * @param {number} yPos The y position used to decide where to - * insert the connection. - * @return {number} The candidate index. - * @private - */ - private calculateIndexForYPos_; - /** - * Remove a connection from the database. Must already exist in DB. - * @param {!RenderedConnection} connection The connection to be removed. - * @param {number} yPos The y position used to find the index of the connection. - * @throws {Error} If the connection cannot be found in the database. - */ - removeConnection(connection: RenderedConnection, yPos: number): void; - /** - * Find all nearby connections to the given connection. - * Type checking does not apply, since this function is used for bumping. - * @param {!RenderedConnection} connection The connection whose - * neighbours should be returned. - * @param {number} maxRadius The maximum radius to another connection. - * @return {!Array} List of connections. - */ - getNeighbours(connection: RenderedConnection, maxRadius: number): Array; - /** - * Is the candidate connection close to the reference connection. - * Extremely fast; only looks at Y distance. - * @param {number} index Index in database of candidate connection. - * @param {number} baseY Reference connection's Y value. - * @param {number} maxRadius The maximum radius to another connection. - * @return {boolean} True if connection is in range. - * @private - */ - private isInYRange_; - /** - * Find the closest compatible connection to this connection. - * @param {!RenderedConnection} conn The connection searching for a compatible - * mate. - * @param {number} maxRadius The maximum radius to another connection. - * @param {!Coordinate} dxy Offset between this connection's - * location in the database and the current location (as a result of - * dragging). - * @return {!{connection: RenderedConnection, radius: number}} - * Contains two properties: 'connection' which is either another - * connection or null, and 'radius' which is the distance. - */ - searchForClosest(conn: RenderedConnection, maxRadius: number, dxy: Coordinate): { - connection: RenderedConnection; - radius: number; - }; - } - import { RenderedConnection } from "rendered_connection"; - import { Coordinate } from "utils/coordinate"; - import { IConnectionChecker } from "interfaces/i_connection_checker"; -} -declare module "interfaces/i_ast_node_location" { - /** - * @license - * Copyright 2020 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - /** - * @fileoverview The interface for an AST node location. - */ - /** - * The interface for an AST node location. - * @namespace Blockly.IASTNodeLocation - */ - /** - * An AST node location interface. - * @interface - * @alias Blockly.IASTNodeLocation - */ - export class IASTNodeLocation { - } -} -declare module "theme" { - /** - * Class for a theme. - * @param {string} name Theme name. - * @param {!Object=} opt_blockStyles A map - * from style names (strings) to objects with style attributes for blocks. - * @param {!Object=} opt_categoryStyles A - * map from style names (strings) to objects with style attributes for - * categories. - * @param {!Theme.ComponentStyle=} opt_componentStyles A map of Blockly - * component names to style value. - * @constructor - * @alias Blockly.Theme - */ - export class Theme { - /** - * Define a new Blockly theme. - * @param {string} name The name of the theme. - * @param {!Object} themeObj An object containing theme properties. - * @return {!Theme} A new Blockly theme. - */ - static defineTheme(name: string, themeObj: any): Theme; - constructor(name: any, opt_blockStyles: any, opt_categoryStyles: any, opt_componentStyles: any); - /** - * The theme name. This can be used to reference a specific theme in CSS. - * @type {string} - */ - name: string; - /** - * The block styles map. - * @type {!Object} - * @package - */ - blockStyles: { - [x: string]: Theme.BlockStyle; - }; - /** - * The category styles map. - * @type {!Object} - * @package - */ - categoryStyles: { - [x: string]: Theme.CategoryStyle; - }; - /** - * The UI components styles map. - * @type {!Theme.ComponentStyle} - * @package - */ - componentStyles: Theme.ComponentStyle; - /** - * The font style. - * @type {!Theme.FontStyle} - * @package - */ - fontStyle: Theme.FontStyle; - /** - * Whether or not to add a 'hat' on top of all blocks with no previous or - * output connections. - * @type {?boolean} - * @package - */ - startHats: boolean | null; - /** - * Gets the class name that identifies this theme. - * @return {string} The CSS class name. - * @package - */ - getClassName(): string; - /** - * Overrides or adds a style to the blockStyles map. - * @param {string} blockStyleName The name of the block style. - * @param {Theme.BlockStyle} blockStyle The block style. - */ - setBlockStyle(blockStyleName: string, blockStyle: Theme.BlockStyle): void; - /** - * Overrides or adds a style to the categoryStyles map. - * @param {string} categoryStyleName The name of the category style. - * @param {Theme.CategoryStyle} categoryStyle The category style. - */ - setCategoryStyle(categoryStyleName: string, categoryStyle: Theme.CategoryStyle): void; - /** - * Gets the style for a given Blockly UI component. If the style value is a - * string, we attempt to find the value of any named references. - * @param {string} componentName The name of the component. - * @return {?string} The style value. - */ - getComponentStyle(componentName: string): string | null; - /** - * Configure a specific Blockly UI component with a style value. - * @param {string} componentName The name of the component. - * @param {*} styleValue The style value. - */ - setComponentStyle(componentName: string, styleValue: any): void; - /** - * Configure a theme's font style. - * @param {Theme.FontStyle} fontStyle The font style. - */ - setFontStyle(fontStyle: Theme.FontStyle): void; - /** - * Configure a theme's start hats. - * @param {boolean} startHats True if the theme enables start hats, false - * otherwise. - */ - setStartHats(startHats: boolean): void; - } - export namespace Theme { - /** - * A block style. - */ - type BlockStyle = { - colourPrimary: string; - colourSecondary: string; - colourTertiary: string; - hat: string; - }; - /** - * A category style. - */ - type CategoryStyle = { - colour: string; - }; - /** - * A component style. - */ - type ComponentStyle = { - workspaceBackgroundColour: string | null; - toolboxBackgroundColour: string | null; - toolboxForegroundColour: string | null; - flyoutBackgroundColour: string | null; - flyoutForegroundColour: string | null; - flyoutOpacity: number | null; - scrollbarColour: string | null; - scrollbarOpacity: number | null; - insertionMarkerColour: string | null; - insertionMarkerOpacity: number | null; - markerColour: string | null; - cursorColour: string | null; - selectedGlowColour: string | null; - selectedGlowOpacity: number | null; - replacementGlowColour: string | null; - replacementGlowOpacity: number | null; - }; - /** - * A font style. - */ - type FontStyle = { - family: string | null; - weight: string | null; - size: number | null; - }; - } -} -declare module "theme/classic" { - /** - * Classic theme. - * Contains multi-coloured border to create shadow effect. - * @type {Theme} - * @alias Blockly.Themes.Classic - */ - export const Classic: Theme; - import { Theme } from "theme"; -} -declare module "utils/metrics" { - /** - * @license - * Copyright 2020 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - /** - * @fileoverview Workspace metrics definitions. - */ - /** - * Workspace metrics definitions. - * @namespace Blockly.utils.Metrics - */ - /** - * @record - * @alias Blockly.utils.Metrics - */ - export class Metrics { - /** - * Height of the visible portion of the workspace. - * @type {number} - */ - viewHeight: number; - /** - * Width of the visible portion of the workspace. - * @type {number} - */ - viewWidth: number; - /** - * Height of the content. - * @type {number} - */ - contentHeight: number; - /** - * Width of the content. - * @type {number} - */ - contentWidth: number; - /** - * Height of the scroll area. - * @type {number} - */ - scrollHeight: number; - /** - * Width of the scroll area. - * @type {number} - */ - scrollWidth: number; - /** - * Top-edge of the visible portion of the workspace, relative to the workspace - * origin. - * @type {number} - */ - viewTop: number; - /** - * Left-edge of the visible portion of the workspace, relative to the workspace - * origin. - * @type {number} - */ - viewLeft: number; - /** - * Top-edge of the content, relative to the workspace origin. - * @type {number} - */ - contentTop: number; - /** - * Left-edge of the content relative to the workspace origin. - * @type {number} - */ - contentLeft: number; - /** - * Top-edge of the scroll area, relative to the workspace origin. - * @type {number} - */ - scrollTop: number; - /** - * Left-edge of the scroll area relative to the workspace origin. - * @type {number} - */ - scrollLeft: number; - /** - * Top-edge of the visible portion of the workspace, relative to the blocklyDiv. - * @type {number} - */ - absoluteTop: number; - /** - * Left-edge of the visible portion of the workspace, relative to the - * blocklyDiv. - * @type {number} - */ - absoluteLeft: number; - /** - * Height of the Blockly div (the view + the toolbox, simple of otherwise). - * @type {number} - */ - svgHeight: number; - /** - * Width of the Blockly div (the view + the toolbox, simple or otherwise). - * @type {number} - */ - svgWidth: number; - /** - * Width of the toolbox, if it exists. Otherwise zero. - * @type {number} - */ - toolboxWidth: number; - /** - * Height of the toolbox, if it exists. Otherwise zero. - * @type {number} - */ - toolboxHeight: number; - /** - * Top, bottom, left or right. Use TOOLBOX_AT constants to compare. - * @type {number} - */ - toolboxPosition: number; - /** - * Width of the flyout if it is always open. Otherwise zero. - * @type {number} - */ - flyoutWidth: number; - /** - * Height of the flyout if it is always open. Otherwise zero. - * @type {number} - */ - flyoutHeight: number; - } -} -declare module "options" { - export class Options { - /** - * Parse the user-specified move options, using reasonable defaults where - * behaviour is unspecified. - * @param {!Object} options Dictionary of options. - * @param {boolean} hasCategories Whether the workspace has categories or not. - * @return {!Options.MoveOptions} Normalized move options. - * @private - */ - private static parseMoveOptions_; - /** - * Parse the user-specified zoom options, using reasonable defaults where - * behaviour is unspecified. See zoom documentation: - * https://developers.google.com/blockly/guides/configure/web/zoom - * @param {!Object} options Dictionary of options. - * @return {!Options.ZoomOptions} Normalized zoom options. - * @private - */ - private static parseZoomOptions_; - /** - * Parse the user-specified grid options, using reasonable defaults where - * behaviour is unspecified. See grid documentation: - * https://developers.google.com/blockly/guides/configure/web/grid - * @param {!Object} options Dictionary of options. - * @return {!Options.GridOptions} Normalized grid options. - * @private - */ - private static parseGridOptions_; - /** - * Parse the user-specified theme options, using the classic theme as a default. - * https://developers.google.com/blockly/guides/configure/web/themes - * @param {!Object} options Dictionary of options. - * @return {!Theme} A Blockly Theme. - * @private - */ - private static parseThemeOptions_; - /** - * Parse the user-specified options, using reasonable defaults where behaviour - * is unspecified. - * @param {!BlocklyOptions} options Dictionary of options. - * Specification: - * https://developers.google.com/blockly/guides/get-started/web#configuration - * @constructor - * @alias Blockly.Options - */ - constructor(options: BlocklyOptions); - /** @type {boolean} */ - RTL: boolean; - /** @type {boolean} */ - oneBasedIndex: boolean; - /** @type {boolean} */ - collapse: boolean; - /** @type {boolean} */ - comments: boolean; - /** @type {boolean} */ - disable: boolean; - /** @type {boolean} */ - readOnly: boolean; - /** @type {number} */ - maxBlocks: number; - /** @type {?Object} */ - maxInstances: { - [x: string]: number; - } | null; - /** @type {string} */ - pathToMedia: string; - /** @type {boolean} */ - hasCategories: boolean; - /** @type {!Options.MoveOptions} */ - moveOptions: Options.MoveOptions; - /** @deprecated January 2019 */ - hasScrollbars: boolean; - /** @type {boolean} */ - hasTrashcan: boolean; - /** @type {number} */ - maxTrashcanContents: number; - /** @type {boolean} */ - hasSounds: boolean; - /** @type {boolean} */ - hasCss: boolean; - /** @type {boolean} */ - horizontalLayout: boolean; - /** @type {?toolbox.ToolboxInfo} */ - languageTree: toolbox.ToolboxInfo | null; - /** @type {!Options.GridOptions} */ - gridOptions: Options.GridOptions; - /** @type {!Options.ZoomOptions} */ - zoomOptions: Options.ZoomOptions; - /** @type {!toolbox.Position} */ - toolboxPosition: toolbox.Position; - /** @type {!Theme} */ - theme: Theme; - /** @type {string} */ - renderer: string; - /** @type {?Object} */ - rendererOverrides: any | null; - /** - * The SVG element for the grid pattern. - * Created during injection. - * @type {?SVGElement} - */ - gridPattern: SVGElement | null; - /** - * The parent of the current workspace, or null if there is no parent - * workspace. We can assert that this is of type WorkspaceSvg as opposed to - * Workspace as this is only used in a rendered workspace. - * @type {WorkspaceSvg} - */ - parentWorkspace: WorkspaceSvg; - /** - * Map of plugin type to name of registered plugin or plugin class. - * @type {!Object} - */ - plugins: { - [x: string]: ((new (...args: unknown[]) => unknown) | string); - }; - } - export namespace Options { - /** - * Grid Options. - */ - type GridOptions = { - colour: string; - length: number; - snap: boolean; - spacing: number; - }; - /** - * Move Options. - */ - type MoveOptions = { - drag: boolean; - scrollbars: (boolean | Options.ScrollbarOptions); - wheel: boolean; - }; - /** - * Scrollbar Options. - */ - type ScrollbarOptions = { - horizontal: boolean; - vertical: boolean; - }; - /** - * Zoom Options. - */ - type ZoomOptions = { - controls: boolean; - maxScale: number; - minScale: number; - pinch: boolean; - scaleSpeed: number; - startScale: number; - wheel: boolean; - }; - } - import * as toolbox from "utils/toolbox"; - import { Theme } from "theme"; - import { WorkspaceSvg } from "workspace_svg"; - import { BlocklyOptions } from "blockly_options"; -} -declare module "names" { - export class Names { - /** - * Do the given two entity names refer to the same entity? - * Blockly names are case-insensitive. - * @param {string} name1 First name. - * @param {string} name2 Second name. - * @return {boolean} True if names are the same. - */ - static equals(name1: string, name2: string): boolean; - /** - * Class for a database of entity names (variables, procedures, etc). - * @param {string} reservedWords A comma-separated string of words that are - * illegal for use as names in a language (e.g. 'new,if,this,...'). - * @param {string=} opt_variablePrefix Some languages need a '$' or a namespace - * before all variable names (but not procedure names). - * @constructor - * @alias Blockly.Names - */ - constructor(reservedWords: string, opt_variablePrefix?: string | undefined); - variablePrefix_: string; - reservedDict_: any; - /** - * When JavaScript (or most other languages) is generated, variable 'foo' and - * procedure 'foo' would collide. However, Blockly has no such problems since - * variable get 'foo' and procedure call 'foo' are unambiguous. - * Therefore, Blockly keeps a separate realm name to disambiguate. - * getName('foo', 'VARIABLE') -> 'foo' - * getName('foo', 'PROCEDURE') -> 'foo2' - */ - /** - * Empty the database and start from scratch. The reserved words are kept. - */ - reset(): void; - db_: any; - dbReverse_: any; - variableMap_: VariableMap; - /** - * Set the variable map that maps from variable name to variable object. - * @param {!VariableMap} map The map to track. - */ - setVariableMap(map: VariableMap): void; - /** - * Get the name for a user-defined variable, based on its ID. - * This should only be used for variables of realm - * internalConstants.VARIABLE_CATEGORY_NAME. - * @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 - */ - private getNameForUserVariable_; - /** - * Generate names for user variables, but only ones that are being used. - * @param {!Workspace} workspace Workspace to generate variables from. - */ - populateVariables(workspace: Workspace): void; - /** - * Generate names for procedures. - * @param {!Workspace} workspace Workspace to generate procedures from. - */ - populateProcedures(workspace: Workspace): void; - /** - * Convert a Blockly entity name to a legal exportable entity name. - * @param {string} nameOrId The Blockly entity name (no constraints) or - * variable ID. - * @param {string} realm The realm of entity in Blockly - * ('VARIABLE', 'PROCEDURE', 'DEVELOPER_VARIABLE', etc...). - * @return {string} An entity name that is legal in the exported language. - */ - getName(nameOrId: string, realm: string): string; - /** - * Return a list of all known user-created names in a specified realm. - * @param {string} realm The realm of entity in Blockly - * ('VARIABLE', 'PROCEDURE', 'DEVELOPER_VARIABLE', etc...). - * @return {!Array} A list of Blockly entity names (no constraints). - */ - getUserNames(realm: string): Array; - /** - * Convert a Blockly entity name to a legal exportable entity name. - * Ensure that this is a new name not overlapping any previously defined name. - * Also check against list of reserved words for the current language and - * ensure name doesn't collide. - * @param {string} name The Blockly entity name (no constraints). - * @param {string} realm The realm of entity in Blockly - * ('VARIABLE', 'PROCEDURE', 'DEVELOPER_VARIABLE', etc...). - * @return {string} An entity name that is legal in the exported language. - */ - getDistinctName(name: string, realm: string): string; - /** - * Given a proposed entity name, generate a name that conforms to the - * [_A-Za-z][_A-Za-z0-9]* format that most languages consider legal for - * variable and function names. - * @param {string} name Potentially illegal entity name. - * @return {string} Safe entity name. - * @private - */ - private safeName_; - } - export namespace Names { - const DEVELOPER_VARIABLE_TYPE: string; - } - import { VariableMap } from "variable_map"; - import { Workspace } from "workspace"; -} -declare module "events/events_var_base" { +declare module "core/events/events_var_base" { /** * Abstract class for a variable event. - * @param {!VariableModel=} opt_variable The variable this event - * corresponds to. Undefined for a blank event. - * @extends {Abstract} - * @constructor + * @extends {AbstractEvent} * @alias Blockly.Events.VarBase */ - export class VarBase { - constructor(opt_variable: any); - isBlank: boolean; + export class VarBase extends AbstractEvent { + /** + * @param {!VariableModel=} opt_variable The variable this event + * corresponds to. Undefined for a blank event. + */ + constructor(opt_variable?: VariableModel | undefined); /** * The variable id for the variable this event pertains to. * @type {string} */ varId: string; - /** - * The workspace identifier for this event. - * @type {string} - */ - workspaceId: string; - /** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ - toJson(): any; - /** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ - fromJson(json: any): void; } + import { Abstract as AbstractEvent } from "core/events/events_abstract"; + import { VariableModel } from "core/variable_model"; } -declare module "events/events_var_delete" { - /** - * Class for a variable deletion event. - * @param {!VariableModel=} opt_variable The deleted variable. Undefined - * for a blank event. - * @extends {VarBase} - * @constructor - * @alias Blockly.Events.VarDelete - */ - export class VarDelete { - constructor(opt_variable: any); - varType: any; - varName: any; - /** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ - toJson(): any; - /** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ - fromJson(json: any): void; - /** - * Run a variable deletion event. - * @param {boolean} forward True if run forward, false if run backward (undo). - */ - run(forward: boolean): void; - /** - * Type of this event. - * @type {string} - */ - type: string; - } -} -declare module "events/events_var_rename" { - /** - * Class for a variable rename event. - * @param {!VariableModel=} opt_variable The renamed variable. Undefined - * for a blank event. - * @param {string=} newName The new name the variable will be changed to. - * @extends {VarBase} - * @constructor - * @alias Blockly.Events.VarRename - */ - export class VarRename { - constructor(opt_variable: any, newName: any); - oldName: any; - newName: any; - /** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ - toJson(): any; - /** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ - fromJson(json: any): void; - /** - * Run a variable rename event. - * @param {boolean} forward True if run forward, false if run backward (undo). - */ - run(forward: boolean): void; - /** - * Type of this event. - * @type {string} - */ - type: string; - } -} -declare module "variable_map" { - export class VariableMap { - /** - * Class for a variable map. This contains a dictionary data structure with - * variable types as keys and lists of variables as values. The list of - * variables are the type indicated by the key. - * @param {!Workspace} workspace The workspace this map belongs to. - * @constructor - * @alias Blockly.VariableMap - */ - constructor(workspace: 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. - * @type {!Object>} - * @private - */ - private variableMap_; - /** - * The workspace this map belongs to. - * @type {!Workspace} - */ - workspace: Workspace; - /** - * Clear the variable map. - */ - clear(): void; - /** - * Rename the given variable by updating its name in the variable map. - * @param {!VariableModel} variable Variable to rename. - * @param {string} newName New variable name. - * @package - */ - renameVariable(variable: VariableModel, newName: string): void; - /** - * 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. - */ - renameVariableById(id: string, newName: string): void; - /** - * 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 {!VariableModel} variable Variable to rename. - * @param {string} newName New variable name. - * @param {!Array} blocks The list of all blocks in the - * workspace. - * @private - */ - private renameVariableAndUses_; - /** - * 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 {!VariableModel} variable Variable to rename. - * @param {string} newName New variable name. - * @param {!VariableModel} conflictVar The variable that was already - * using newName. - * @param {!Array} blocks The list of all blocks in the - * workspace. - * @private - */ - private renameVariableWithConflict_; - /** - * Create a variable with a given name, optional type, and optional ID. - * @param {string} name The name of the variable. This must be unique across - * variables and procedures. - * @param {?string=} opt_type The type of the variable like 'int' or 'string'. - * Does not need to be unique. Field_variable can filter variables based on - * their type. This will default to '' which is a specific type. - * @param {?string=} opt_id The unique ID of the variable. This will default to - * a UUID. - * @return {!VariableModel} The newly created variable. - */ - createVariable(name: string, opt_type?: (string | null) | undefined, opt_id?: (string | null) | undefined): VariableModel; - /** - * Delete a variable. - * @param {!VariableModel} variable Variable to delete. - */ - deleteVariable(variable: VariableModel): void; - /** - * 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. - */ - deleteVariableById(id: string): void; - /** - * Deletes a variable and all of its uses from this workspace without asking the - * user for confirmation. - * @param {!VariableModel} variable Variable to delete. - * @param {!Array} uses An array of uses of the variable. - * @package - */ - deleteVariableInternal(variable: VariableModel, uses: Array): void; - /** - * 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 {?VariableModel} The variable with the given name, or null if - * it was not found. - */ - getVariable(name: string, opt_type?: (string | null) | undefined): VariableModel | null; - /** - * Find the variable by the given ID and return it. Return null if not found. - * @param {string} id The ID to check for. - * @return {?VariableModel} The variable with the given ID. - */ - getVariableById(id: string): VariableModel | null; - /** - * Get a list containing all of the variables of a specified type. If type is - * null, return list of variables with empty string type. - * @param {?string} type Type of the variables to find. - * @return {!Array} The sought after variables of the - * passed in type. An empty array if none are found. - */ - getVariablesOfType(type: string | null): Array; - /** - * Return all variable and potential variable types. This list always contains - * the empty string. - * @param {?Workspace} ws The workspace used to look for potential - * variables. This can be different than the workspace stored on this object - * if the passed in ws is a flyout workspace. - * @return {!Array} List of variable types. - * @package - */ - getVariableTypes(ws: Workspace | null): Array; - /** - * Return all variables of all types. - * @return {!Array} List of variable models. - */ - getAllVariables(): Array; - /** - * Returns all of the variable names of all types. - * @return {!Array} All of the variable names of all types. - */ - getAllVariableNames(): Array; - /** - * Find all the uses of a named variable. - * @param {string} id ID of the variable to find. - * @return {!Array} Array of block usages. - */ - getVariableUsesById(id: string): Array; - } - import { Workspace } from "workspace"; - import { VariableModel } from "variable_model"; - import { Block } from "block"; -} -declare module "events/events_comment_create" { - /** - * Class for a comment creation event. - * @param {!WorkspaceComment=} opt_comment The created comment. - * Undefined for a blank event. - * @extends {CommentBase} - * @constructor - * @alias Blockly.Events.CommentCreate - */ - export class CommentCreate { - constructor(opt_comment: any); - xml: any; - /** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ - toJson(): any; - /** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ - fromJson(json: any): void; - /** - * Run a creation event. - * @param {boolean} forward True if run forward, false if run backward (undo). - */ - run(forward: boolean): void; - /** - * Type of this event. - * @type {string} - */ - type: string; - } -} -declare module "events/events_comment_delete" { - /** - * Class for a comment deletion event. - * @param {!WorkspaceComment=} opt_comment The deleted comment. - * Undefined for a blank event. - * @extends {CommentBase} - * @constructor - * @alias Blockly.Events.CommentDelete - */ - export class CommentDelete { - constructor(opt_comment: any); - xml: any; - /** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ - toJson(): any; - /** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ - fromJson(json: any): void; - /** - * Run a creation event. - * @param {boolean} forward True if run forward, false if run backward (undo). - */ - run(forward: boolean): void; - /** - * Type of this event. - * @type {string} - */ - type: string; - } -} -declare module "events/events_comment_base" { - /** - * Abstract class for a comment event. - * @param {!WorkspaceComment=} opt_comment The comment this event - * corresponds to. Undefined for a blank event. - * @extends {AbstractEvents} - * @constructor - * @alias Blockly.Events.CommentBase - */ - export class CommentBase { - /** - * Helper function for Comment[Create|Delete] - * @param {!CommentCreate|!CommentDelete} event - * The event to run. - * @param {boolean} create if True then Create, if False then Delete - */ - static CommentCreateDeleteHelper(event: CommentCreate | CommentDelete, create: boolean): void; - constructor(opt_comment: any); - /** - * Whether or not an event is blank. - * @type {boolean} - */ - isBlank: boolean; - /** - * The ID of the comment this event pertains to. - * @type {string} - */ - commentId: string; - /** - * The workspace identifier for this event. - * @type {string} - */ - workspaceId: string; - /** - * The event group id for the group this event belongs to. Groups define - * events that should be treated as an single action from the user's - * perspective, and should be undone together. - * @type {string} - */ - group: string; - /** - * Sets whether the event should be added to the undo stack. - * @type {boolean} - */ - recordUndo: boolean; - /** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ - toJson(): any; - /** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ - fromJson(json: any): void; - } - import { CommentCreate } from "events/events_comment_create"; - import { CommentDelete } from "events/events_comment_delete"; -} -declare module "events/events_comment_change" { - /** - * Class for a comment change event. - * @param {!WorkspaceComment=} opt_comment The comment that is being - * changed. Undefined for a blank event. - * @param {string=} opt_oldContents Previous contents of the comment. - * @param {string=} opt_newContents New contents of the comment. - * @extends {CommentBase} - * @constructor - * @alias Blockly.Events.CommentChange - */ - export class CommentChange { - constructor(opt_comment: any, opt_oldContents: any, opt_newContents: any); - oldContents_: any; - newContents_: any; - /** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ - toJson(): any; - /** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ - fromJson(json: any): void; - /** - * Does this event record any change of state? - * @return {boolean} False if something changed. - */ - isNull(): boolean; - /** - * Run a change event. - * @param {boolean} forward True if run forward, false if run backward (undo). - */ - run(forward: boolean): void; - /** - * Type of this event. - * @type {string} - */ - type: string; - } -} -declare module "events/events_comment_move" { - /** - * Class for a comment move event. Created before the move. - * @param {!WorkspaceComment=} opt_comment The comment that is being - * moved. Undefined for a blank event. - * @extends {CommentBase} - * @constructor - * @alias Blockly.Events.CommentMove - */ - export class CommentMove { - constructor(opt_comment: any); - /** - * The comment that is being moved. Will be cleared after recording the new - * location. - * @type {WorkspaceComment} - */ - comment_: WorkspaceComment; - /** - * The location before the move, in workspace coordinates. - * @type {!Coordinate} - */ - oldCoordinate_: Coordinate; - /** - * The location after the move, in workspace coordinates. - * @type {Coordinate} - */ - newCoordinate_: Coordinate; - /** - * Record the comment's new location. Called after the move. Can only be - * called once. - */ - recordNew(): void; - /** - * Override the location before the move. Use this if you don't create the - * event until the end of the move, but you know the original location. - * @param {!Coordinate} xy The location before the move, - * in workspace coordinates. - */ - setOldCoordinate(xy: Coordinate): void; - /** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ - toJson(): any; - /** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ - fromJson(json: any): void; - /** - * Does this event record any change of state? - * @return {boolean} False if something changed. - */ - isNull(): boolean; - /** - * Run a move event. - * @param {boolean} forward True if run forward, false if run backward (undo). - */ - run(forward: boolean): void; - /** - * Type of this event. - * @type {string} - */ - type: string; - } - import { WorkspaceComment } from "workspace_comment"; - import { Coordinate } from "utils/coordinate"; -} -declare module "workspace_comment" { - /** - * Class for a workspace comment. - * @param {!Workspace} workspace The block's workspace. - * @param {string} content The content of this workspace comment. - * @param {number} height Height of the comment. - * @param {number} width Width of the comment. - * @param {string=} opt_id Optional ID. Use this ID if provided, otherwise - * create a new ID. - * @constructor - * @alias Blockly.WorkspaceComment - */ - export class WorkspaceComment { - /** - * Fire a create event for the given workspace comment, if comments are enabled. - * @param {!WorkspaceComment} comment The comment that was just created. - * @package - */ - static fireCreateEvent(comment: WorkspaceComment): void; - /** - * Decode an XML comment tag and create a comment on the workspace. - * @param {!Element} xmlComment XML comment element. - * @param {!Workspace} workspace The workspace. - * @return {!WorkspaceComment} The created workspace comment. - * @package - */ - static fromXml(xmlComment: Element, workspace: Workspace): WorkspaceComment; - /** - * Decode an XML comment tag and return the results in an object. - * @param {!Element} xml XML comment element. - * @return {{w: number, h: number, x: number, y: number, content: string}} An - * object containing the id, size, position, and comment string. - * @package - */ - static parseAttributes(xml: Element): { - w: number; - h: number; - x: number; - y: number; - content: string; - }; - constructor(workspace: any, content: any, height: any, width: any, opt_id: any); - /** @type {string} */ - id: string; - /** - * The comment's position in workspace units. (0, 0) is at the workspace's - * origin; scale does not change this value. - * @type {!Coordinate} - * @protected - */ - protected xy_: Coordinate; - /** - * The comment's height in workspace units. Scale does not change this value. - * @type {number} - * @protected - */ - protected height_: number; - /** - * The comment's width in workspace units. Scale does not change this value. - * @type {number} - * @protected - */ - protected width_: number; - /** - * @type {!Workspace} - */ - workspace: Workspace; - /** - * @protected - * @type {boolean} - */ - protected RTL: boolean; - /** - * @type {boolean} - * @private - */ - private deletable_; - /** - * @type {boolean} - * @private - */ - private movable_; - /** - * @type {boolean} - * @private - */ - private editable_; - /** - * @protected - * @type {string} - */ - protected content_: string; - /** - * Whether this comment has been disposed. - * @protected - * @type {boolean} - */ - protected disposed_: boolean; - /** - * @package - * @type {boolean} - */ - isComment: boolean; - /** - * Dispose of this comment. - * @package - */ - dispose(): void; - /** - * Get comment height. - * @return {number} Comment height. - * @package - */ - getHeight(): number; - /** - * Set comment height. - * @param {number} height Comment height. - * @package - */ - setHeight(height: number): void; - /** - * Get comment width. - * @return {number} Comment width. - * @package - */ - getWidth(): number; - /** - * Set comment width. - * @param {number} width comment width. - * @package - */ - setWidth(width: number): void; - /** - * Get stored location. - * @return {!Coordinate} The comment's stored location. - * This is not valid if the comment is currently being dragged. - * @package - */ - getXY(): Coordinate; - /** - * Move a comment by a relative offset. - * @param {number} dx Horizontal offset, in workspace units. - * @param {number} dy Vertical offset, in workspace units. - * @package - */ - moveBy(dx: number, dy: number): void; - /** - * Get whether this comment is deletable or not. - * @return {boolean} True if deletable. - * @package - */ - isDeletable(): boolean; - /** - * Set whether this comment is deletable or not. - * @param {boolean} deletable True if deletable. - * @package - */ - setDeletable(deletable: boolean): void; - /** - * Get whether this comment is movable or not. - * @return {boolean} True if movable. - * @package - */ - isMovable(): boolean; - /** - * Set whether this comment is movable or not. - * @param {boolean} movable True if movable. - * @package - */ - setMovable(movable: boolean): void; - /** - * Get whether this comment is editable or not. - * @return {boolean} True if editable. - */ - isEditable(): boolean; - /** - * Set whether this comment is editable or not. - * @param {boolean} editable True if editable. - */ - setEditable(editable: boolean): void; - /** - * Returns this comment's text. - * @return {string} Comment text. - * @package - */ - getContent(): string; - /** - * Set this comment's content. - * @param {string} content Comment content. - * @package - */ - setContent(content: string): void; - /** - * Encode a comment subtree as XML with XY coordinates. - * @param {boolean=} opt_noId True if the encoder should skip the comment ID. - * @return {!Element} Tree of XML elements. - * @package - */ - toXmlWithXY(opt_noId?: boolean | undefined): Element; - /** - * Encode a comment subtree as XML, but don't serialize the XY coordinates. - * This method avoids some expensive metrics-related calls that are made in - * toXmlWithXY(). - * @param {boolean=} opt_noId True if the encoder should skip the comment ID. - * @return {!Element} Tree of XML elements. - * @package - */ - toXml(opt_noId?: boolean | undefined): Element; - } - import { Coordinate } from "utils/coordinate"; - import { Workspace } from "workspace"; -} -declare module "connection_checker" { - /** - * Class for connection type checking logic. - * @implements {IConnectionChecker} - * @constructor - * @alias Blockly.ConnectionChecker - */ - export class ConnectionChecker implements IConnectionChecker { - /** - * Check whether the current connection can connect with the target - * connection. - * @param {Connection} a Connection to check compatibility with. - * @param {Connection} b Connection to check compatibility with. - * @param {boolean} isDragging True if the connection is being made by dragging - * a block. - * @param {number=} opt_distance The max allowable distance between the - * connections for drag checks. - * @return {boolean} Whether the connection is legal. - * @public - */ - public canConnect(a: Connection, b: Connection, isDragging: boolean, opt_distance?: number | undefined): boolean; - /** - * Checks whether the current connection can connect with the target - * connection, and return an error code if there are problems. - * @param {Connection} a Connection to check compatibility with. - * @param {Connection} b Connection to check compatibility with. - * @param {boolean} isDragging True if the connection is being made by dragging - * a block. - * @param {number=} opt_distance The max allowable distance between the - * connections for drag checks. - * @return {number} Connection.CAN_CONNECT if the connection is legal, - * an error code otherwise. - * @public - */ - public canConnectWithReason(a: Connection, b: Connection, isDragging: boolean, opt_distance?: number | undefined): number; - /** - * Helper method that translates a connection error code into a string. - * @param {number} errorCode The error code. - * @param {Connection} a One of the two connections being checked. - * @param {Connection} b The second of the two connections being - * checked. - * @return {string} A developer-readable error string. - * @public - */ - public getErrorMessage(errorCode: number, a: Connection, b: Connection): string; - /** - * Check that connecting the given connections is safe, meaning that it would - * not break any of Blockly's basic assumptions (e.g. no self connections). - * @param {Connection} a The first of the connections to check. - * @param {Connection} b The second of the connections to check. - * @return {number} An enum with the reason this connection is safe or unsafe. - * @public - */ - public doSafetyChecks(a: Connection, b: Connection): number; - /** - * Check whether this connection is compatible with another connection with - * respect to the value type system. E.g. square_root("Hello") is not - * compatible. - * @param {!Connection} a Connection to compare. - * @param {!Connection} b Connection to compare against. - * @return {boolean} True if the connections share a type. - * @public - */ - public doTypeChecks(a: Connection, b: Connection): boolean; - /** - * Check whether this connection can be made by dragging. - * @param {!RenderedConnection} a Connection to compare. - * @param {!RenderedConnection} b Connection to compare against. - * @param {number} distance The maximum allowable distance between connections. - * @return {boolean} True if the connection is allowed during a drag. - * @public - */ - public doDragChecks(a: RenderedConnection, b: RenderedConnection, distance: number): boolean; - /** - * Helper function for drag checking. - * @param {!Connection} a The connection to check, which must be a - * statement input or next connection. - * @param {!Connection} b A nearby connection to check, which - * must be a previous connection. - * @return {boolean} True if the connection is allowed, false otherwise. - * @protected - */ - protected canConnectToPrevious_(a: Connection, b: Connection): boolean; - } - import { IConnectionChecker } from "interfaces/i_connection_checker"; - import { Connection } from "connection"; - import { RenderedConnection } from "rendered_connection"; -} -declare module "workspace" { - export class Workspace { - /** - * Find the workspace with the specified ID. - * @param {string} id ID of workspace to find. - * @return {?Workspace} The sought after workspace or null if not found. - */ - static getById(id: string): Workspace | null; - /** - * Find all workspaces. - * @return {!Array} Array of workspaces. - */ - static getAll(): Array; - /** - * Class for a workspace. This is a data structure that contains blocks. - * There is no UI, and can be created headlessly. - * @param {!Options=} opt_options Dictionary of options. - * @constructor - * @implements {IASTNodeLocation} - * @alias Blockly.Workspace - */ - constructor(opt_options?: Options | undefined); - /** @type {string} */ - id: string; - /** @type {!Options} */ - options: Options; - /** @type {boolean} */ - RTL: boolean; - /** @type {boolean} */ - horizontalLayout: boolean; - /** @type {toolbox.Position} */ - toolboxPosition: toolbox.Position; - /** - * An object that encapsulates logic for safety, type, and dragging checks. - * @type {!IConnectionChecker} - */ - connectionChecker: IConnectionChecker; - /** - * @type {!Array} - * @private - */ - private topBlocks_; - /** - * @type {!Array} - * @private - */ - private topComments_; - /** - * @type {!Object} - * @private - */ - private commentDB_; - /** - * @type {!Array} - * @private - */ - private listeners_; - /** - * @type {!Array} - * @protected - */ - protected undoStack_: Array; - /** - * @type {!Array} - * @protected - */ - protected redoStack_: Array; - /** - * @type {!Object} - * @private - */ - private blockDB_; - /** - * @type {!Object} - * @private - */ - private typedBlocksDB_; - /** - * 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. - * @type {!VariableMap} - * @private - */ - private variableMap_; - /** - * 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 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 {?VariableMap} - * @private - */ - private potentialVariableMap_; - /** - * Dispose of this workspace. - * Unlink from all DOM elements to prevent memory leaks. - * @suppress {checkTypes} - */ - dispose(): void; - /** - * Compare function for sorting objects (blocks, comments, etc) by position; - * top to bottom (with slight LTR or RTL bias). - * @param {!Block | !WorkspaceComment} a The first object to - * compare. - * @param {!Block | !WorkspaceComment} b The second object to - * compare. - * @return {number} The comparison value. This tells Array.sort() how to change - * object a's index. - * @private - */ - private sortObjects_; - /** - * Adds a block to the list of top blocks. - * @param {!Block} block Block to add. - */ - addTopBlock(block: Block): void; - /** - * Removes a block from the list of top blocks. - * @param {!Block} block Block to remove. - */ - removeTopBlock(block: Block): void; - /** - * Finds the top-level blocks and returns them. Blocks are optionally sorted - * by position; top to bottom (with slight LTR or RTL bias). - * @param {boolean} ordered Sort the list if true. - * @return {!Array} The top-level block objects. - */ - getTopBlocks(ordered: boolean): Array; - /** - * Add a block to the list of blocks keyed by type. - * @param {!Block} block Block to add. - */ - addTypedBlock(block: Block): void; - /** - * Remove a block from the list of blocks keyed by type. - * @param {!Block} block Block to remove. - */ - removeTypedBlock(block: Block): void; - /** - * Finds the blocks with the associated type and returns them. Blocks are - * optionally sorted by position; top to bottom (with slight LTR or RTL bias). - * @param {string} type The type of block to search for. - * @param {boolean} ordered Sort the list if true. - * @return {!Array} The blocks of the given type. - */ - getBlocksByType(type: string, ordered: boolean): Array; - /** - * Adds a comment to the list of top comments. - * @param {!WorkspaceComment} comment comment to add. - * @package - */ - addTopComment(comment: WorkspaceComment): void; - /** - * Removes a comment from the list of top comments. - * @param {!WorkspaceComment} comment comment to remove. - * @package - */ - removeTopComment(comment: WorkspaceComment): void; - /** - * Finds the top-level comments and returns them. Comments are optionally - * sorted by position; top to bottom (with slight LTR or RTL bias). - * @param {boolean} ordered Sort the list if true. - * @return {!Array} The top-level comment objects. - * @package - */ - getTopComments(ordered: boolean): Array; - /** - * Find all blocks in workspace. Blocks are optionally sorted - * by position; top to bottom (with slight LTR or RTL bias). - * @param {boolean} ordered Sort the list if true. - * @return {!Array} Array of blocks. - */ - getAllBlocks(ordered: boolean): Array; - /** - * Dispose of all blocks and comments in workspace. - */ - clear(): void; - isClearing: boolean; - /** - * 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. - */ - renameVariableById(id: string, newName: string): void; - /** - * Create a variable with a given name, optional type, and optional ID. - * @param {string} name The name of the variable. This must be unique across - * variables and procedures. - * @param {?string=} opt_type The type of the variable like 'int' or 'string'. - * Does not need to be unique. Field_variable can filter variables based on - * their type. This will default to '' which is a specific type. - * @param {?string=} opt_id The unique ID of the variable. This will default to - * a UUID. - * @return {!VariableModel} The newly created variable. - */ - createVariable(name: string, opt_type?: (string | null) | undefined, opt_id?: (string | null) | undefined): VariableModel; - /** - * 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. - */ - getVariableUsesById(id: string): Array; - /** - * 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. - */ - deleteVariableById(id: string): void; - /** - * Find the variable by the given name and return it. Return null if 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 {?VariableModel} The variable with the given name. - */ - getVariable(name: string, opt_type?: string | undefined): VariableModel | null; - /** - * Find the variable by the given ID and return it. Return null if not found. - * @param {string} id The ID to check for. - * @return {?VariableModel} The variable with the given ID. - */ - getVariableById(id: string): VariableModel | null; - /** - * 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. - */ - getVariablesOfType(type: string | null): Array; - /** - * Return all variable types. - * @return {!Array} List of variable types. - * @package - */ - getVariableTypes(): Array; - /** - * Return all variables of all types. - * @return {!Array} List of variable models. - */ - getAllVariables(): Array; - /** - * Returns all variable names of all types. - * @return {!Array} List of all variable names of all types. - */ - getAllVariableNames(): Array; - /** - * Returns the horizontal offset of the workspace. - * Intended for LTR/RTL compatibility in XML. - * Not relevant for a headless workspace. - * @return {number} Width. - */ - getWidth(): number; - /** - * Obtain a newly created block. - * @param {!string} prototypeName Name of the language object containing - * type-specific functions for this block. - * @param {string=} opt_id Optional ID. Use this ID if provided, otherwise - * create a new ID. - * @return {!Block} The created block. - */ - newBlock(prototypeName: string, opt_id?: string | undefined): Block; - /** - * The number of blocks that may be added to the workspace before reaching - * the maxBlocks. - * @return {number} Number of blocks left. - */ - remainingCapacity(): number; - /** - * The number of blocks of the given type that may be added to the workspace - * before reaching the maxInstances allowed for that type. - * @param {string} type Type of block to return capacity for. - * @return {number} Number of blocks of type left. - */ - remainingCapacityOfType(type: string): number; - /** - * Check if there is remaining capacity for blocks of the given counts to be - * created. If the total number of blocks represented by the map is more than - * the total remaining capacity, it returns false. If a type count is more - * than the remaining capacity for that type, it returns false. - * @param {!Object} typeCountsMap A map of types to counts (usually representing - * blocks to be created). - * @return {boolean} True if there is capacity for the given map, - * false otherwise. - */ - isCapacityAvailable(typeCountsMap: any): boolean; - /** - * Checks if the workspace has any limits on the maximum number of blocks, - * or the maximum number of blocks of specific types. - * @return {boolean} True if it has block limits, false otherwise. - */ - hasBlockLimits(): boolean; - /** - * Gets the undo stack for workplace. - * @return {!Array} undo stack - * @package - */ - getUndoStack(): Array; - /** - * Gets the redo stack for workplace. - * @return {!Array} redo stack - * @package - */ - getRedoStack(): Array; - /** - * Undo or redo the previous action. - * @param {boolean} redo False if undo, true if redo. - */ - undo(redo: boolean): void; - /** - * Clear the undo/redo stacks. - */ - clearUndo(): void; - /** - * When something in this workspace changes, call a function. - * Note that there may be a few recent events already on the stack. Thus the - * new change listener might be called with events that occurred a few - * milliseconds before the change listener was added. - * @param {!Function} func Function to call. - * @return {!Function} Obsolete return value, ignore. - */ - addChangeListener(func: Function): Function; - /** - * Stop listening for this workspace's changes. - * @param {!Function} func Function to stop calling. - */ - removeChangeListener(func: Function): void; - /** - * Fire a change event. - * @param {!Abstract} event Event to fire. - */ - fireChangeListener(event: typeof Abstract): void; - /** - * Find the block on this workspace with the specified ID. - * @param {string} id ID of block to find. - * @return {?Block} The sought after block, or null if not found. - */ - getBlockById(id: string): Block | null; - /** - * Set a block on this workspace with the specified ID. - * @param {string} id ID of block to set. - * @param {Block} block The block to set. - * @package - */ - setBlockById(id: string, block: Block): void; - /** - * Delete a block off this workspace with the specified ID. - * @param {string} id ID of block to delete. - * @package - */ - removeBlockById(id: string): void; - /** - * Find the comment on this workspace with the specified ID. - * @param {string} id ID of comment to find. - * @return {?WorkspaceComment} The sought after comment, or null if not - * found. - * @package - */ - getCommentById(id: string): WorkspaceComment | null; - /** - * Checks whether all value and statement inputs in the workspace are filled - * with blocks. - * @param {boolean=} opt_shadowBlocksAreFilled An optional argument controlling - * whether shadow blocks are counted as filled. Defaults to true. - * @return {boolean} True if all inputs are filled, false otherwise. - */ - allInputsFilled(opt_shadowBlocksAreFilled?: boolean | undefined): boolean; - /** - * Return the variable map that contains "potential" variables. - * These exist in the flyout but not in the workspace. - * @return {?VariableMap} The potential variable map. - * @package - */ - getPotentialVariableMap(): VariableMap | null; - /** - * Create and store the potential variable map for this workspace. - * @package - */ - createPotentialVariableMap(): void; - /** - * Return the map of all variables on the workspace. - * @return {!VariableMap} The variable map. - */ - getVariableMap(): VariableMap; - /** - * Set the map of all variables on the workspace. - * @param {!VariableMap} variableMap The variable map. - * @package - */ - setVariableMap(variableMap: VariableMap): void; - /** - * Returns `true` if the workspace is visible and `false` if it's headless. - * @type {boolean} - */ - rendered: boolean; - /** - * Maximum number of undo events in stack. `0` turns off undo, `Infinity` sets - * it to unlimited. - * @type {number} - */ - MAX_UNDO: number; - /** - * Set of databases for rapid lookup of connection locations. - * @type {Array} - */ - connectionDBList: Array; - } - export namespace Workspace { - const SCAN_ANGLE: number; - } - import { Options } from "options"; - import * as toolbox from "utils/toolbox"; - import { IConnectionChecker } from "interfaces/i_connection_checker"; - import * as Abstract from "events/events_abstract"; - import { Block } from "block"; - import { WorkspaceComment } from "workspace_comment"; - import { VariableModel } from "variable_model"; - import { VariableMap } from "variable_map"; - import { ConnectionDB } from "connection_db"; -} -declare module "events/events_var_create" { +declare module "core/events/events_var_create" { /** * Class for a variable creation event. - * @param {!VariableModel=} opt_variable The created variable. Undefined - * for a blank event. * @extends {VarBase} - * @constructor * @alias Blockly.Events.VarCreate */ - export class VarCreate { - constructor(opt_variable: any); - varType: any; - varName: any; - /** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ - toJson(): any; - /** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ - fromJson(json: any): void; - /** - * Run a variable creation event. - * @param {boolean} forward True if run forward, false if run backward (undo). - */ - run(forward: boolean): void; - /** - * Type of this event. - * @type {string} - */ - type: string; + export class VarCreate extends VarBase { + varType: string | undefined; + varName: string | undefined; } + import { VarBase } from "core/events/events_var_base"; } -declare module "variable_model" { +declare module "core/variable_model" { /** * Class for a variable model. * Holds information for the variable including name, ID, and type. - * @param {!Workspace} workspace The variable's workspace. - * @param {string} name The name of the variable. This is the user-visible name - * (e.g. 'my var' or '私の変数'), not the generated name. - * @param {string=} opt_type The type of the variable like 'int' or 'string'. - * Does not need to be unique. Field_variable can filter variables based on - * their type. This will default to '' which is a specific type. - * @param {string=} opt_id The unique ID of the variable. This will default to - * a UUID. * @see {Blockly.FieldVariable} - * @constructor * @alias Blockly.VariableModel */ export class VariableModel { @@ -5039,7 +1022,17 @@ declare module "variable_model" { * @package */ static compareByName(var1: VariableModel, var2: VariableModel): number; - constructor(workspace: any, name: any, opt_type: any, opt_id: any); + /** + * @param {!Workspace} workspace The variable's workspace. + * @param {string} name The name of the variable. This is the user-visible + * name (e.g. 'my var' or '私の変数'), not the generated name. + * @param {string=} opt_type The type of the variable like 'int' or 'string'. + * Does not need to be unique. Field_variable can filter variables based + * on their type. This will default to '' which is a specific type. + * @param {string=} opt_id The unique ID of the variable. This will default to + * a UUID. + */ + constructor(workspace: Workspace, name: string, opt_type?: string | undefined, opt_id?: string | undefined); /** * The workspace the variable is in. * @type {!Workspace} @@ -5072,9 +1065,19 @@ declare module "variable_model" { */ getId(): string; } - import { Workspace } from "workspace"; + import { Workspace } from "core/workspace"; } -declare module "variables" { +declare module "core/variables" { + /** + * 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. + * See also Blockly.Procedures.CATEGORY_NAME and + * Blockly.VariablesDynamic.CATEGORY_NAME. + * @const {string} + * @alias Blockly.Variables.CATEGORY_NAME + */ + export const CATEGORY_NAME: "VARIABLE"; /** * Find all user-created variables that are in use in the workspace. * For use by generators. @@ -5100,11 +1103,11 @@ declare module "variables" { /** * Construct the elements (blocks and button) required by the flyout for the * variable category. - * @param {!Workspace} workspace The workspace containing variables. + * @param {!WorkspaceSvg} workspace The workspace containing variables. * @return {!Array} Array of XML elements. * @alias Blockly.Variables.flyoutCategory */ - export function flyoutCategory(workspace: Workspace): Array; + export function flyoutCategory(workspace: WorkspaceSvg): Array; /** * Construct the blocks required by the flyout for the variable category. * @param {!Workspace} workspace The workspace containing variables. @@ -5239,252 +1242,6283 @@ declare module "variables" { * @package */ export function getAddedVariables(workspace: Workspace, originalVariables: Array): Array; - import { Workspace } from "workspace"; - import { VariableModel } from "variable_model"; + import { Workspace } from "core/workspace"; + import { VariableModel } from "core/variable_model"; + import { WorkspaceSvg } from "core/workspace_svg"; } -declare module "events/events_block_base" { +declare module "core/events/events_ui_base" { + /** + * Base class for a UI event. + * UI events are events that don't need to be sent over the wire for multi-user + * editing to work (e.g. scrolling the workspace, zooming, opening toolbox + * categories). + * UI events do not undo or redo. + * @extends {AbstractEvent} + * @alias Blockly.Events.UiBase + */ + export class UiBase extends AbstractEvent { + /** + * @param {string=} opt_workspaceId The workspace identifier for this event. + * Undefined for a blank event. + */ + constructor(opt_workspaceId?: string | undefined); + } + import { Abstract as AbstractEvent } from "core/events/events_abstract"; +} +declare module "core/events/events_bubble_open" { + /** + * Class for a bubble open event. + * @extends {UiBase} + * @alias Blockly.Events.BubbleOpen + */ + export class BubbleOpen extends UiBase { + /** + * @param {BlockSvg} opt_block The associated block. Undefined for a + * blank event. + * @param {boolean=} opt_isOpen Whether the bubble is opening (false if + * closing). Undefined for a blank event. + * @param {string=} opt_bubbleType The type of bubble. One of 'mutator', + * 'comment' + * or 'warning'. Undefined for a blank event. + */ + constructor(opt_block: BlockSvg, opt_isOpen?: boolean | undefined, opt_bubbleType?: string | undefined); + blockId: string | null; + /** + * Whether the bubble is opening (false if closing). + * @type {boolean|undefined} + */ + isOpen: boolean | undefined; + /** + * The type of bubble. One of 'mutator', 'comment', or 'warning'. + * @type {string|undefined} + */ + bubbleType: string | undefined; + } + import { UiBase } from "core/events/events_ui_base"; + import { BlockSvg } from "core/block_svg"; +} +declare module "core/block_animations" { + /** + * Play some UI effects (sound, animation) when disposing of a block. + * @param {!BlockSvg} block The block being disposed of. + * @alias Blockly.blockAnimations.disposeUiEffect + * @package + */ + export function disposeUiEffect(block: BlockSvg): void; + /** + * Play some UI effects (sound, ripple) after a connection has been established. + * @param {!BlockSvg} block The block being connected. + * @alias Blockly.blockAnimations.connectionUiEffect + * @package + */ + export function connectionUiEffect(block: BlockSvg): void; + /** + * Play some UI effects (sound, animation) when disconnecting a block. + * @param {!BlockSvg} block The block being disconnected. + * @alias Blockly.blockAnimations.disconnectUiEffect + * @package + */ + export function disconnectUiEffect(block: BlockSvg): void; + /** + * Stop the disconnect UI animation immediately. + * @alias Blockly.blockAnimations.disconnectUiStop + * @package + */ + export function disconnectUiStop(): void; + import { BlockSvg } from "core/block_svg"; +} +declare module "core/connection_type" { + /** + * * + */ + export type ConnectionType = number; + export namespace ConnectionType { + const INPUT_VALUE: number; + const OUTPUT_VALUE: number; + const NEXT_STATEMENT: number; + const PREVIOUS_STATEMENT: number; + } +} +declare module "core/internal_constants" { + /** + * Number of characters to truncate a collapsed block to. + * @alias Blockly.internalConstants.COLLAPSE_CHARS + */ + export const COLLAPSE_CHARS: 30; + /** + * When dragging a block out of a stack, split the stack in two (true), or drag + * out the block healing the stack (false). + * @alias Blockly.internalConstants.DRAG_STACK + */ + export const DRAG_STACK: true; + /** + * Lookup table for determining the opposite type of a connection. + * @const + * @alias Blockly.internalConstants.OPPOSITE_TYPE + */ + export const OPPOSITE_TYPE: any[]; + /** + * String for use in the dropdown created in field_variable. + * This string indicates that this option in the dropdown is 'Rename + * variable...' and if selected, should trigger the prompt to rename a variable. + * @const {string} + * @alias Blockly.internalConstants.RENAME_VARIABLE_ID + */ + export const RENAME_VARIABLE_ID: "RENAME_VARIABLE_ID"; + /** + * String for use in the dropdown created in field_variable. + * This string indicates that this option in the dropdown is 'Delete the "%1" + * variable' and if selected, should trigger the prompt to delete a variable. + * @const {string} + * @alias Blockly.internalConstants.DELETE_VARIABLE_ID + */ + export const DELETE_VARIABLE_ID: "DELETE_VARIABLE_ID"; +} +declare module "core/utils/deprecation" { + /** + * Warn developers that a function or property is deprecated. + * @param {string} name The name of the function or property. + * @param {string} deprecationDate The date of deprecation. + * Prefer 'month yyyy' or 'quarter yyyy' format. + * @param {string} deletionDate The date of deletion, in the same format as the + * deprecation date. + * @param {string=} opt_use The name of a function or property to use instead, + * if any. + * @alias Blockly.utils.deprecation.warn + * @package + */ + export function warn(name: string, deprecationDate: string, deletionDate: string, opt_use?: string | undefined): void; +} +declare module "core/utils/coordinate" { + /** + * Class for representing coordinates and positions. + * @alias Blockly.utils.Coordinate + */ + export const Coordinate: { + new (x: number, y: number): { + /** + * X-value + * @type {number} + */ + x: number; + /** + * Y-value + * @type {number} + */ + y: number; + /** + * Creates a new copy of this coordinate. + * @return {!Coordinate} A copy of this coordinate. + */ + clone(): any; + /** + * Scales this coordinate by the given scale factor. + * @param {number} s The scale factor to use for both x and y dimensions. + * @return {!Coordinate} This coordinate after scaling. + */ + scale(s: number): any; + /** + * Translates this coordinate by the given offsets. + * respectively. + * @param {number} tx The value to translate x by. + * @param {number} ty The value to translate y by. + * @return {!Coordinate} This coordinate after translating. + */ + translate(tx: number, ty: number): any; + }; + /** + * Compares coordinates for equality. + * @param {?Coordinate} a A Coordinate. + * @param {?Coordinate} b A Coordinate. + * @return {boolean} True iff the coordinates are equal, or if both are null. + */ + equals(a: { + /** + * X-value + * @type {number} + */ + x: number; + /** + * Y-value + * @type {number} + */ + y: number; + /** + * Creates a new copy of this coordinate. + * @return {!Coordinate} A copy of this coordinate. + */ + clone(): any; + /** + * Scales this coordinate by the given scale factor. + * @param {number} s The scale factor to use for both x and y dimensions. + * @return {!Coordinate} This coordinate after scaling. + */ + scale(s: number): any; + /** + * Translates this coordinate by the given offsets. + * respectively. + * @param {number} tx The value to translate x by. + * @param {number} ty The value to translate y by. + * @return {!Coordinate} This coordinate after translating. + */ + translate(tx: number, ty: number): any; + } | null, b: { + /** + * X-value + * @type {number} + */ + x: number; + /** + * Y-value + * @type {number} + */ + y: number; + /** + * Creates a new copy of this coordinate. + * @return {!Coordinate} A copy of this coordinate. + */ + clone(): any; + /** + * Scales this coordinate by the given scale factor. + * @param {number} s The scale factor to use for both x and y dimensions. + * @return {!Coordinate} This coordinate after scaling. + */ + scale(s: number): any; + /** + * Translates this coordinate by the given offsets. + * respectively. + * @param {number} tx The value to translate x by. + * @param {number} ty The value to translate y by. + * @return {!Coordinate} This coordinate after translating. + */ + translate(tx: number, ty: number): any; + } | null): boolean; + /** + * Returns the distance between two coordinates. + * @param {!Coordinate} a A Coordinate. + * @param {!Coordinate} b A Coordinate. + * @return {number} The distance between `a` and `b`. + */ + distance(a: { + /** + * X-value + * @type {number} + */ + x: number; + /** + * Y-value + * @type {number} + */ + y: number; + /** + * Creates a new copy of this coordinate. + * @return {!Coordinate} A copy of this coordinate. + */ + clone(): any; + /** + * Scales this coordinate by the given scale factor. + * @param {number} s The scale factor to use for both x and y dimensions. + * @return {!Coordinate} This coordinate after scaling. + */ + scale(s: number): any; + /** + * Translates this coordinate by the given offsets. + * respectively. + * @param {number} tx The value to translate x by. + * @param {number} ty The value to translate y by. + * @return {!Coordinate} This coordinate after translating. + */ + translate(tx: number, ty: number): any; + }, b: { + /** + * X-value + * @type {number} + */ + x: number; + /** + * Y-value + * @type {number} + */ + y: number; + /** + * Creates a new copy of this coordinate. + * @return {!Coordinate} A copy of this coordinate. + */ + clone(): any; + /** + * Scales this coordinate by the given scale factor. + * @param {number} s The scale factor to use for both x and y dimensions. + * @return {!Coordinate} This coordinate after scaling. + */ + scale(s: number): any; + /** + * Translates this coordinate by the given offsets. + * respectively. + * @param {number} tx The value to translate x by. + * @param {number} ty The value to translate y by. + * @return {!Coordinate} This coordinate after translating. + */ + translate(tx: number, ty: number): any; + }): number; + /** + * Returns the magnitude of a coordinate. + * @param {!Coordinate} a A Coordinate. + * @return {number} The distance between the origin and `a`. + */ + magnitude(a: { + /** + * X-value + * @type {number} + */ + x: number; + /** + * Y-value + * @type {number} + */ + y: number; + /** + * Creates a new copy of this coordinate. + * @return {!Coordinate} A copy of this coordinate. + */ + clone(): any; + /** + * Scales this coordinate by the given scale factor. + * @param {number} s The scale factor to use for both x and y dimensions. + * @return {!Coordinate} This coordinate after scaling. + */ + scale(s: number): any; + /** + * Translates this coordinate by the given offsets. + * respectively. + * @param {number} tx The value to translate x by. + * @param {number} ty The value to translate y by. + * @return {!Coordinate} This coordinate after translating. + */ + translate(tx: number, ty: number): any; + }): number; + /** + * Returns the difference between two coordinates as a new + * Coordinate. + * @param {!Coordinate|!SVGPoint} a An x/y coordinate. + * @param {!Coordinate|!SVGPoint} b An x/y coordinate. + * @return {!Coordinate} A Coordinate representing the difference + * between `a` and `b`. + */ + difference(a: { + /** + * X-value + * @type {number} + */ + x: number; + /** + * Y-value + * @type {number} + */ + y: number; + /** + * Creates a new copy of this coordinate. + * @return {!Coordinate} A copy of this coordinate. + */ + clone(): any; + /** + * Scales this coordinate by the given scale factor. + * @param {number} s The scale factor to use for both x and y dimensions. + * @return {!Coordinate} This coordinate after scaling. + */ + scale(s: number): any; + /** + * Translates this coordinate by the given offsets. + * respectively. + * @param {number} tx The value to translate x by. + * @param {number} ty The value to translate y by. + * @return {!Coordinate} This coordinate after translating. + */ + translate(tx: number, ty: number): any; + } | SVGPoint, b: { + /** + * X-value + * @type {number} + */ + x: number; + /** + * Y-value + * @type {number} + */ + y: number; + /** + * Creates a new copy of this coordinate. + * @return {!Coordinate} A copy of this coordinate. + */ + clone(): any; + /** + * Scales this coordinate by the given scale factor. + * @param {number} s The scale factor to use for both x and y dimensions. + * @return {!Coordinate} This coordinate after scaling. + */ + scale(s: number): any; + /** + * Translates this coordinate by the given offsets. + * respectively. + * @param {number} tx The value to translate x by. + * @param {number} ty The value to translate y by. + * @return {!Coordinate} This coordinate after translating. + */ + translate(tx: number, ty: number): any; + } | SVGPoint): { + /** + * X-value + * @type {number} + */ + x: number; + /** + * Y-value + * @type {number} + */ + y: number; + /** + * Creates a new copy of this coordinate. + * @return {!Coordinate} A copy of this coordinate. + */ + clone(): any; + /** + * Scales this coordinate by the given scale factor. + * @param {number} s The scale factor to use for both x and y dimensions. + * @return {!Coordinate} This coordinate after scaling. + */ + scale(s: number): any; + /** + * Translates this coordinate by the given offsets. + * respectively. + * @param {number} tx The value to translate x by. + * @param {number} ty The value to translate y by. + * @return {!Coordinate} This coordinate after translating. + */ + translate(tx: number, ty: number): any; + }; + /** + * Returns the sum of two coordinates as a new Coordinate. + * @param {!Coordinate|!SVGPoint} a An x/y coordinate. + * @param {!Coordinate|!SVGPoint} b An x/y coordinate. + * @return {!Coordinate} A Coordinate representing the sum of + * the two coordinates. + */ + sum(a: { + /** + * X-value + * @type {number} + */ + x: number; + /** + * Y-value + * @type {number} + */ + y: number; + /** + * Creates a new copy of this coordinate. + * @return {!Coordinate} A copy of this coordinate. + */ + clone(): any; + /** + * Scales this coordinate by the given scale factor. + * @param {number} s The scale factor to use for both x and y dimensions. + * @return {!Coordinate} This coordinate after scaling. + */ + scale(s: number): any; + /** + * Translates this coordinate by the given offsets. + * respectively. + * @param {number} tx The value to translate x by. + * @param {number} ty The value to translate y by. + * @return {!Coordinate} This coordinate after translating. + */ + translate(tx: number, ty: number): any; + } | SVGPoint, b: { + /** + * X-value + * @type {number} + */ + x: number; + /** + * Y-value + * @type {number} + */ + y: number; + /** + * Creates a new copy of this coordinate. + * @return {!Coordinate} A copy of this coordinate. + */ + clone(): any; + /** + * Scales this coordinate by the given scale factor. + * @param {number} s The scale factor to use for both x and y dimensions. + * @return {!Coordinate} This coordinate after scaling. + */ + scale(s: number): any; + /** + * Translates this coordinate by the given offsets. + * respectively. + * @param {number} tx The value to translate x by. + * @param {number} ty The value to translate y by. + * @return {!Coordinate} This coordinate after translating. + */ + translate(tx: number, ty: number): any; + } | SVGPoint): { + /** + * X-value + * @type {number} + */ + x: number; + /** + * Y-value + * @type {number} + */ + y: number; + /** + * Creates a new copy of this coordinate. + * @return {!Coordinate} A copy of this coordinate. + */ + clone(): any; + /** + * Scales this coordinate by the given scale factor. + * @param {number} s The scale factor to use for both x and y dimensions. + * @return {!Coordinate} This coordinate after scaling. + */ + scale(s: number): any; + /** + * Translates this coordinate by the given offsets. + * respectively. + * @param {number} tx The value to translate x by. + * @param {number} ty The value to translate y by. + * @return {!Coordinate} This coordinate after translating. + */ + translate(tx: number, ty: number): any; + }; + }; +} +declare module "core/utils/style" { + /** + * Gets the height and width of an element. + * Similar to Closure's goog.style.getSize + * @param {!Element} element Element to get size of. + * @return {!Size} Object with width/height properties. + * @alias Blockly.utils.style.getSize + */ + export function getSize(element: Element): { + width: number; + height: number; + }; + /** + * Retrieves a computed style value of a node. It returns empty string if the + * value cannot be computed (which will be the case in Internet Explorer) or + * "none" if the property requested is an SVG one and it has not been + * explicitly set (firefox and webkit). + * + * Copied from Closure's goog.style.getComputedStyle + * + * @param {!Element} element Element to get style of. + * @param {string} property Property to get (camel-case). + * @return {string} Style value. + * @alias Blockly.utils.style.getComputedStyle + */ + export function getComputedStyle(element: Element, property: string): string; + /** + * Gets the cascaded style value of a node, or null if the value cannot be + * computed (only Internet Explorer can do this). + * + * Copied from Closure's goog.style.getCascadedStyle + * + * @param {!Element} element Element to get style of. + * @param {string} style Property to get (camel-case). + * @return {string} Style value. + * @alias Blockly.utils.style.getCascadedStyle + */ + export function getCascadedStyle(element: Element, style: string): string; + /** + * Returns a Coordinate object relative to the top-left of the HTML document. + * Similar to Closure's goog.style.getPageOffset + * @param {!Element} el Element to get the page offset for. + * @return {!Coordinate} The page offset. + * @alias Blockly.utils.style.getPageOffset + */ + export function getPageOffset(el: Element): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + /** + * Calculates the viewport coordinates relative to the document. + * Similar to Closure's goog.style.getViewportPageOffset + * @return {!Coordinate} The page offset of the viewport. + * @alias Blockly.utils.style.getViewportPageOffset + */ + export function getViewportPageOffset(): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + /** + * Shows or hides an element from the page. Hiding the element is done by + * setting the display property to "none", removing the element from the + * rendering hierarchy so it takes up no space. To show the element, the default + * inherited display property is restored (defined either in stylesheets or by + * the browser's default style rules). + * Copied from Closure's goog.style.getViewportPageOffset + * + * @param {!Element} el Element to show or hide. + * @param {*} isShown True to render the element in its default style, + * false to disable rendering the element. + * @alias Blockly.utils.style.setElementShown + */ + export function setElementShown(el: Element, isShown: any): void; + /** + * Returns true if the element is using right to left (RTL) direction. + * Copied from Closure's goog.style.isRightToLeft + * + * @param {!Element} el The element to test. + * @return {boolean} True for right to left, false for left to right. + * @alias Blockly.utils.style.isRightToLeft + */ + export function isRightToLeft(el: Element): boolean; + /** + * Gets the computed border widths (on all sides) in pixels + * Copied from Closure's goog.style.getBorderBox + * @param {!Element} element The element to get the border widths for. + * @return {!Object} The computed border widths. + * @alias Blockly.utils.style.getBorderBox + */ + export function getBorderBox(element: Element): Object; + /** + * Changes the scroll position of `container` with the minimum amount so + * that the content and the borders of the given `element` become visible. + * If the element is bigger than the container, its top left corner will be + * aligned as close to the container's top left corner as possible. + * Copied from Closure's goog.style.scrollIntoContainerView + * + * @param {!Element} element The element to make visible. + * @param {!Element} container The container to scroll. If not set, then the + * document scroll element will be used. + * @param {boolean=} opt_center Whether to center the element in the container. + * Defaults to false. + * @alias Blockly.utils.style.scrollIntoContainerView + */ + export function scrollIntoContainerView(element: Element, container: Element, opt_center?: boolean | undefined): void; + /** + * Calculate the scroll position of `container` with the minimum amount so + * that the content and the borders of the given `element` become visible. + * If the element is bigger than the container, its top left corner will be + * aligned as close to the container's top left corner as possible. + * Copied from Closure's goog.style.getContainerOffsetToScrollInto + * + * @param {!Element} element The element to make visible. + * @param {!Element} container The container to scroll. If not set, then the + * document scroll element will be used. + * @param {boolean=} opt_center Whether to center the element in the container. + * Defaults to false. + * @return {!Coordinate} The new scroll position of the container, + * in form of goog.math.Coordinate(scrollLeft, scrollTop). + * @alias Blockly.utils.style.getContainerOffsetToScrollInto + */ + export function getContainerOffsetToScrollInto(element: Element, container: Element, opt_center?: boolean | undefined): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; +} +declare module "core/utils/svg_math" { + export namespace TEST_ONLY { + export { XY_REGEX }; + export { XY_STYLE_REGEX }; + } + /** + * Return the coordinates of the top-left corner of this element relative to + * its parent. Only for SVG elements and children (e.g. rect, g, path). + * @param {!Element} element SVG element to find the coordinates of. + * @return {!Coordinate} Object with .x and .y properties. + * @alias Blockly.utils.svgMath.getRelativeXY + */ + export function getRelativeXY(element: Element): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + /** + * Return the coordinates of the top-left corner of this element relative to + * the div Blockly was injected into. + * @param {!Element} element SVG element to find the coordinates of. If this is + * not a child of the div Blockly was injected into, the behaviour is + * undefined. + * @return {!Coordinate} Object with .x and .y properties. + * @alias Blockly.utils.svgMath.getInjectionDivXY + */ + export function getInjectionDivXY(element: Element): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + /** + * Check if 3D transforms are supported by adding an element + * and attempting to set the property. + * @return {boolean} True if 3D transforms are supported. + * @alias Blockly.utils.svgMath.is3dSupported + */ + export function is3dSupported(): boolean; + /** + * Get the position of the current viewport in window coordinates. This takes + * scroll into account. + * @return {!Rect} An object containing window width, height, and + * scroll position in window coordinates. + * @alias Blockly.utils.svgMath.getViewportBBox + * @package + */ + export function getViewportBBox(): { + top: number; + bottom: number; + left: number; + right: number; + contains(x: number, y: number): boolean; + intersects(other: any): boolean; + }; + /** + * Gets the document scroll distance as a coordinate object. + * Copied from Closure's goog.dom.getDocumentScroll. + * @return {!Coordinate} Object with values 'x' and 'y'. + * @alias Blockly.utils.svgMath.getDocumentScroll + */ + export function getDocumentScroll(): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + /** + * Converts screen coordinates to workspace coordinates. + * @param {!WorkspaceSvg} ws The workspace to find the coordinates on. + * @param {!Coordinate} screenCoordinates The screen coordinates to + * be converted to workspace coordinates + * @return {!Coordinate} The workspace coordinates. + * @alias Blockly.utils.svgMath.screenToWsCoordinates + */ + export function screenToWsCoordinates(ws: WorkspaceSvg, screenCoordinates: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + /** + * Returns the dimensions of the specified SVG image. + * @param {!SVGElement} svg SVG image. + * @return {!Size} Contains width and height properties. + * @deprecated Use workspace.getCachedParentSvgSize. (2021 March 5) + * @alias Blockly.utils.svgMath.svgSize + */ + export function svgSize(svg: SVGElement): { + width: number; + height: number; + }; + /** + * Static regex to pull the x,y values out of an SVG translate() directive. + * Note that Firefox and IE (9,10) return 'translate(12)' instead of + * 'translate(12, 0)'. + * Note that IE (9,10) returns 'translate(16 8)' instead of 'translate(16, 8)'. + * Note that IE has been reported to return scientific notation (0.123456e-42). + * @type {!RegExp} + */ + const XY_REGEX: RegExp; + /** + * Static regex to pull the x,y values out of a translate() or translate3d() + * style property. + * Accounts for same exceptions as XY_REGEX. + * @type {!RegExp} + */ + const XY_STYLE_REGEX: RegExp; + import { WorkspaceSvg } from "core/workspace_svg"; + export {}; +} +declare module "core/block_drag_surface" { + /** + * Class for a drag surface for the currently dragged block. This is a separate + * SVG that contains only the currently moving block, or nothing. + * @alias Blockly.BlockDragSurfaceSvg + */ + export const BlockDragSurfaceSvg: { + new (container: Element): { + /** + * The SVG drag surface. Set once by BlockDragSurfaceSvg.createDom. + * @type {?SVGElement} + * @private + */ + SVG_: SVGElement | null; + /** + * This is where blocks live while they are being dragged if the drag + * surface is enabled. + * @type {?SVGElement} + * @private + */ + dragGroup_: SVGElement | null; + /** + * Containing HTML element; parent of the workspace and the drag surface. + * @type {!Element} + * @private + */ + container_: Element; + /** + * Cached value for the scale of the drag surface. + * Used to set/get the correct translation during and after a drag. + * @type {number} + * @private + */ + scale_: number; + /** + * Cached value for the translation of the drag surface. + * This translation is in pixel units, because the scale is applied to the + * drag group rather than the top-level SVG. + * @type {?Coordinate} + * @private + */ + surfaceXY_: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + } | null; + /** + * Cached value for the translation of the child drag surface in pixel + * units. Since the child drag surface tracks the translation of the + * workspace this is ultimately the translation of the workspace. + * @type {!Coordinate} + * @private + */ + childSurfaceXY_: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + /** + * Create the drag surface and inject it into the container. + */ + createDom(): void; + /** + * Set the SVG blocks on the drag surface's group and show the surface. + * Only one block group should be on the drag surface at a time. + * @param {!SVGElement} blocks Block or group of blocks to place on the drag + * surface. + */ + setBlocksAndShow(blocks: SVGElement): void; + /** + * Translate and scale the entire drag surface group to the given position, to + * keep in sync with the workspace. + * @param {number} x X translation in pixel coordinates. + * @param {number} y Y translation in pixel coordinates. + * @param {number} scale Scale of the group. + */ + translateAndScaleGroup(x: number, y: number, scale: number): void; + /** + * Translate the drag surface's SVG based on its internal state. + * @private + */ + translateSurfaceInternal_(): void; + /** + * Translates the entire surface by a relative offset. + * @param {number} deltaX Horizontal offset in pixel units. + * @param {number} deltaY Vertical offset in pixel units. + */ + translateBy(deltaX: number, deltaY: number): void; + /** + * Translate the entire drag surface during a drag. + * We translate the drag surface instead of the blocks inside the surface + * so that the browser avoids repainting the SVG. + * Because of this, the drag coordinates must be adjusted by scale. + * @param {number} x X translation for the entire surface. + * @param {number} y Y translation for the entire surface. + */ + translateSurface(x: number, y: number): void; + /** + * Reports the surface translation in scaled workspace coordinates. + * Use this when finishing a drag to return blocks to the correct position. + * @return {!Coordinate} Current translation of the surface. + */ + getSurfaceTranslation(): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + /** + * Provide a reference to the drag group (primarily for + * BlockSvg.getRelativeToSurfaceXY). + * @return {?SVGElement} Drag surface group element. + */ + getGroup(): SVGElement | null; + /** + * Returns the SVG drag surface. + * @returns {?SVGElement} The SVG drag surface. + */ + getSvgRoot(): SVGElement | null; + /** + * Get the current blocks on the drag surface, if any (primarily + * for BlockSvg.getRelativeToSurfaceXY). + * @return {?Element} Drag surface block DOM element, or null if no blocks + * exist. + */ + getCurrentBlock(): Element | null; + /** + * Gets the translation of the child block surface + * This surface is in charge of keeping track of how much the workspace has + * moved. + * @return {!Coordinate} The amount the workspace has been moved. + */ + getWsTranslation(): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + /** + * Clear the group and hide the surface; move the blocks off onto the provided + * element. + * If the block is being deleted it doesn't need to go back to the original + * surface, since it would be removed immediately during dispose. + * @param {Element=} opt_newSurface Surface the dragging blocks should be + * moved to, or null if the blocks should be removed from this surface + * without being moved to a different surface. + */ + clearAndHide(opt_newSurface?: Element | undefined): void; + }; + }; +} +declare module "core/events/events_comment_change" { + /** + * Class for a comment change event. + * @extends {CommentBase} + * @alias Blockly.Events.CommentChange + */ + export class CommentChange extends CommentBase { + /** + * @param {!WorkspaceComment=} opt_comment The comment that is being + * changed. Undefined for a blank event. + * @param {string=} opt_oldContents Previous contents of the comment. + * @param {string=} opt_newContents New contents of the comment. + */ + constructor(opt_comment?: WorkspaceComment | undefined, opt_oldContents?: string | undefined, opt_newContents?: string | undefined); + oldContents_: string | undefined; + newContents_: string | undefined; + } + import { CommentBase } from "core/events/events_comment_base"; + import { WorkspaceComment } from "core/workspace_comment"; +} +declare module "core/events/events_comment_delete" { + /** + * Class for a comment deletion event. + * @extends {CommentBase} + * @alias Blockly.Events.CommentDelete + */ + export class CommentDelete extends CommentBase { + xml: Element | undefined; + } + import { CommentBase } from "core/events/events_comment_base"; +} +declare module "core/workspace_comment" { + /** + * Class for a workspace comment. + * @alias Blockly.WorkspaceComment + */ + export class WorkspaceComment { + /** + * Fire a create event for the given workspace comment, if comments are + * enabled. + * @param {!WorkspaceComment} comment The comment that was just created. + * @package + */ + static fireCreateEvent(comment: WorkspaceComment): void; + /** + * Decode an XML comment tag and create a comment on the workspace. + * @param {!Element} xmlComment XML comment element. + * @param {!Workspace} workspace The workspace. + * @return {!WorkspaceComment} The created workspace comment. + * @package + */ + static fromXml(xmlComment: Element, workspace: Workspace): WorkspaceComment; + /** + * Decode an XML comment tag and return the results in an object. + * @param {!Element} xml XML comment element. + * @return {{w: number, h: number, x: number, y: number, content: string}} An + * object containing the id, size, position, and comment string. + * @package + */ + static parseAttributes(xml: Element): { + w: number; + h: number; + x: number; + y: number; + content: string; + }; + /** + * @param {!Workspace} workspace The block's workspace. + * @param {string} content The content of this workspace comment. + * @param {number} height Height of the comment. + * @param {number} width Width of the comment. + * @param {string=} opt_id Optional ID. Use this ID if provided, otherwise + * create a new ID. + */ + constructor(workspace: Workspace, content: string, height: number, width: number, opt_id?: string | undefined); + /** @type {string} */ + id: string; + /** + * The comment's position in workspace units. (0, 0) is at the workspace's + * origin; scale does not change this value. + * @type {!Coordinate} + * @protected + */ + protected xy_: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + /** + * The comment's height in workspace units. Scale does not change this + * value. + * @type {number} + * @protected + */ + protected height_: number; + /** + * The comment's width in workspace units. Scale does not change this + * value. + * @type {number} + * @protected + */ + protected width_: number; + /** + * @type {!Workspace} + */ + workspace: Workspace; + /** + * @protected + * @type {boolean} + */ + protected RTL: boolean; + /** + * @type {boolean} + * @private + */ + private deletable_; + /** + * @type {boolean} + * @private + */ + private movable_; + /** + * @type {boolean} + * @private + */ + private editable_; + /** + * @protected + * @type {string} + */ + protected content_: string; + /** + * Whether this comment has been disposed. + * @protected + * @type {boolean} + */ + protected disposed_: boolean; + /** + * @package + * @type {boolean} + */ + isComment: boolean; + /** + * Dispose of this comment. + * @package + */ + dispose(): void; + /** + * Get comment height. + * @return {number} Comment height. + * @package + */ + getHeight(): number; + /** + * Set comment height. + * @param {number} height Comment height. + * @package + */ + setHeight(height: number): void; + /** + * Get comment width. + * @return {number} Comment width. + * @package + */ + getWidth(): number; + /** + * Set comment width. + * @param {number} width comment width. + * @package + */ + setWidth(width: number): void; + /** + * Get stored location. + * @return {!Coordinate} The comment's stored location. + * This is not valid if the comment is currently being dragged. + * @package + */ + getXY(): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + /** + * Move a comment by a relative offset. + * @param {number} dx Horizontal offset, in workspace units. + * @param {number} dy Vertical offset, in workspace units. + * @package + */ + moveBy(dx: number, dy: number): void; + /** + * Get whether this comment is deletable or not. + * @return {boolean} True if deletable. + * @package + */ + isDeletable(): boolean; + /** + * Set whether this comment is deletable or not. + * @param {boolean} deletable True if deletable. + * @package + */ + setDeletable(deletable: boolean): void; + /** + * Get whether this comment is movable or not. + * @return {boolean} True if movable. + * @package + */ + isMovable(): boolean; + /** + * Set whether this comment is movable or not. + * @param {boolean} movable True if movable. + * @package + */ + setMovable(movable: boolean): void; + /** + * Get whether this comment is editable or not. + * @return {boolean} True if editable. + */ + isEditable(): boolean; + /** + * Set whether this comment is editable or not. + * @param {boolean} editable True if editable. + */ + setEditable(editable: boolean): void; + /** + * Returns this comment's text. + * @return {string} Comment text. + * @package + */ + getContent(): string; + /** + * Set this comment's content. + * @param {string} content Comment content. + * @package + */ + setContent(content: string): void; + /** + * Encode a comment subtree as XML with XY coordinates. + * @param {boolean=} opt_noId True if the encoder should skip the comment ID. + * @return {!Element} Tree of XML elements. + * @package + */ + toXmlWithXY(opt_noId?: boolean | undefined): Element; + /** + * Encode a comment subtree as XML, but don't serialize the XY coordinates. + * This method avoids some expensive metrics-related calls that are made in + * toXmlWithXY(). + * @param {boolean=} opt_noId True if the encoder should skip the comment ID. + * @return {!Element} Tree of XML elements. + * @package + */ + toXml(opt_noId?: boolean | undefined): Element; + } + import { Workspace } from "core/workspace"; +} +declare module "core/events/events_comment_create" { + /** + * Class for a comment creation event. + * @extends {CommentBase} + * @alias Blockly.Events.CommentCreate + */ + export class CommentCreate extends CommentBase { + xml: Element | undefined; + } + import { CommentBase } from "core/events/events_comment_base"; +} +declare module "core/events/events_comment_base" { + /** + * Abstract class for a comment event. + * @extends {AbstractEvent} + * @alias Blockly.Events.CommentBase + */ + export class CommentBase extends AbstractEvent { + /** + * Helper function for Comment[Create|Delete] + * @param {!CommentCreate|!CommentDelete} event + * The event to run. + * @param {boolean} create if True then Create, if False then Delete + */ + static CommentCreateDeleteHelper(event: CommentCreate | CommentDelete, create: boolean): void; + /** + * @param {!WorkspaceComment=} opt_comment The comment this event + * corresponds to. Undefined for a blank event. + */ + constructor(opt_comment?: WorkspaceComment | undefined); + /** + * The ID of the comment this event pertains to. + * @type {string} + */ + commentId: string; + } + import { Abstract as AbstractEvent } from "core/events/events_abstract"; + import { CommentCreate } from "core/events/events_comment_create"; + import { CommentDelete } from "core/events/events_comment_delete"; + import { WorkspaceComment } from "core/workspace_comment"; +} +declare module "core/events/events_comment_move" { + /** + * Class for a comment move event. Created before the move. + * @extends {CommentBase} + * @alias Blockly.Events.CommentMove + */ + export class CommentMove extends CommentBase { + /** + * The comment that is being moved. Will be cleared after recording the new + * location. + * @type {WorkspaceComment} + */ + comment_: WorkspaceComment; + /** + * The location before the move, in workspace coordinates. + * @type {!Coordinate} + */ + oldCoordinate_: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + /** + * The location after the move, in workspace coordinates. + * @type {Coordinate} + */ + newCoordinate_: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + /** + * Record the comment's new location. Called after the move. Can only be + * called once. + */ + recordNew(): void; + /** + * Override the location before the move. Use this if you don't create the + * event until the end of the move, but you know the original location. + * @param {!Coordinate} xy The location before the move, + * in workspace coordinates. + */ + setOldCoordinate(xy: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): void; + } + import { CommentBase } from "core/events/events_comment_base"; + import { WorkspaceComment } from "core/workspace_comment"; +} +declare module "core/interfaces/i_component" { + /** + * The interface for a workspace component that can be registered with the + * ComponentManager. + * @interface + * @alias Blockly.IComponent + */ + export function IComponent(): void; + export namespace IComponent { + const id: string; + } +} +declare module "core/interfaces/i_autohideable" { + /** + * Interface for a component that can be automatically hidden. + * @extends {IComponent} + * @interface + * @alias Blockly.IAutoHideable + */ + export class IAutoHideable {} +} +declare module "core/interfaces/i_deletable" { + /** + * The interface for an object that can be deleted. + * @interface + * @alias Blockly.IDeletable + */ + export class IDeletable {} +} +declare module "core/interfaces/i_draggable" { + /** + * The interface for an object that can be dragged. + * @extends {IDeletable} + * @interface + * @alias Blockly.IDraggable + */ + export class IDraggable {} +} +declare module "core/interfaces/i_drag_target" { + /** + * Interface for a component with custom behaviour when a block or bubble is + * dragged over or dropped on top of it. + * @extends {IComponent} + * @interface + * @alias Blockly.IDragTarget + */ + export class IDragTarget {} +} +declare module "core/interfaces/i_delete_area" { + /** + * Interface for a component that can delete a block or bubble that is dropped + * on top of it. + * @extends {IDragTarget} + * @interface + * @alias Blockly.IDeleteArea + */ + export class IDeleteArea {} +} +declare module "core/interfaces/i_registrable" { + /** + * The interface for a Blockly component that can be registered. + * @interface + * @alias Blockly.IRegistrable + */ + export class IRegistrable {} +} +declare module "core/interfaces/i_flyout" { + export class IFlyout { + /** + * Whether the flyout is laid out horizontally or not. + * @type {boolean} + */ + horizontalLayout: boolean; + /** + * Is RTL vs LTR. + * @type {boolean} + */ + RTL: boolean; + /** + * The target workspace + * @type {?WorkspaceSvg} + */ + targetWorkspace: WorkspaceSvg | null; + /** + * Margin around the edges of the blocks in the flyout. + * @type {number} + * @const + */ + MARGIN: number; + /** + * Does the flyout automatically close when a block is created? + * @type {boolean} + */ + autoClose: boolean; + /** + * Corner radius of the flyout background. + * @type {number} + * @const + */ + CORNER_RADIUS: number; + } + import { WorkspaceSvg } from "core/workspace_svg"; +} +declare module "core/utils/metrics" { + /** + * @record + * @alias Blockly.utils.Metrics + */ + export class Metrics { + /** + * Height of the visible portion of the workspace. + * @type {number} + */ + viewHeight: number; + /** + * Width of the visible portion of the workspace. + * @type {number} + */ + viewWidth: number; + /** + * Height of the content. + * @type {number} + */ + contentHeight: number; + /** + * Width of the content. + * @type {number} + */ + contentWidth: number; + /** + * Height of the scroll area. + * @type {number} + */ + scrollHeight: number; + /** + * Width of the scroll area. + * @type {number} + */ + scrollWidth: number; + /** + * Top-edge of the visible portion of the workspace, relative to the workspace + * origin. + * @type {number} + */ + viewTop: number; + /** + * Left-edge of the visible portion of the workspace, relative to the workspace + * origin. + * @type {number} + */ + viewLeft: number; + /** + * Top-edge of the content, relative to the workspace origin. + * @type {number} + */ + contentTop: number; + /** + * Left-edge of the content relative to the workspace origin. + * @type {number} + */ + contentLeft: number; + /** + * Top-edge of the scroll area, relative to the workspace origin. + * @type {number} + */ + scrollTop: number; + /** + * Left-edge of the scroll area relative to the workspace origin. + * @type {number} + */ + scrollLeft: number; + /** + * Top-edge of the visible portion of the workspace, relative to the blocklyDiv. + * @type {number} + */ + absoluteTop: number; + /** + * Left-edge of the visible portion of the workspace, relative to the + * blocklyDiv. + * @type {number} + */ + absoluteLeft: number; + /** + * Height of the Blockly div (the view + the toolbox, simple of otherwise). + * @type {number} + */ + svgHeight: number; + /** + * Width of the Blockly div (the view + the toolbox, simple or otherwise). + * @type {number} + */ + svgWidth: number; + /** + * Width of the toolbox, if it exists. Otherwise zero. + * @type {number} + */ + toolboxWidth: number; + /** + * Height of the toolbox, if it exists. Otherwise zero. + * @type {number} + */ + toolboxHeight: number; + /** + * Top, bottom, left or right. Use TOOLBOX_AT constants to compare. + * @type {number} + */ + toolboxPosition: number; + /** + * Width of the flyout if it is always open. Otherwise zero. + * @type {number} + */ + flyoutWidth: number; + /** + * Height of the flyout if it is always open. Otherwise zero. + * @type {number} + */ + flyoutHeight: number; + } +} +declare module "core/interfaces/i_metrics_manager" { + /** + * Interface for a metrics manager. + * @interface + * @alias Blockly.IMetricsManager + */ + export class IMetricsManager {} +} +declare module "core/interfaces/i_toolbox_item" { + /** + * Interface for an item in the toolbox. + * @interface + * @alias Blockly.IToolboxItem + */ + export class IToolboxItem {} +} +declare module "core/interfaces/i_toolbox" { + /** + * Interface for a toolbox. + * @extends {IRegistrable} + * @interface + * @alias Blockly.IToolbox + */ + export class IToolbox {} +} +declare module "core/metrics_manager" { + /** + * The manager for all workspace metrics calculations. + * @implements {IMetricsManager} + * @alias Blockly.MetricsManager + */ + export class MetricsManager implements IMetricsManager { + /** + * @param {!WorkspaceSvg} workspace The workspace to calculate metrics + * for. + */ + constructor(workspace: WorkspaceSvg); + /** + * The workspace to calculate metrics for. + * @type {!WorkspaceSvg} + * @protected + */ + protected workspace_: WorkspaceSvg; + /** + * Gets the dimensions of the given workspace component, in pixel coordinates. + * @param {?IToolbox|?IFlyout} elem The element to get the + * dimensions of, or null. It should be a toolbox or flyout, and should + * implement getWidth() and getHeight(). + * @return {!Size} An object containing width and height + * attributes, which will both be zero if elem did not exist. + * @protected + */ + protected getDimensionsPx_(elem: ((() => void) | (IFlyout | null)) | null): { + width: number; + height: number; + }; + /** + * Gets the width and the height of the flyout on the workspace in pixel + * coordinates. Returns 0 for the width and height if the workspace has a + * category toolbox instead of a simple toolbox. + * @param {boolean=} opt_own Whether to only return the workspace's own + * flyout. + * @return {!MetricsManager.ToolboxMetrics} The width and height of the + * flyout. + * @public + */ + public getFlyoutMetrics(opt_own?: boolean | undefined): MetricsManager.ToolboxMetrics; + /** + * Gets the width, height and position of the toolbox on the workspace in + * pixel coordinates. Returns 0 for the width and height if the workspace has + * a simple toolbox instead of a category toolbox. To get the width and height + * of a + * simple toolbox @see {@link getFlyoutMetrics}. + * @return {!MetricsManager.ToolboxMetrics} The object with the width, + * height and position of the toolbox. + * @public + */ + public getToolboxMetrics(): MetricsManager.ToolboxMetrics; + /** + * Gets the width and height of the workspace's parent SVG element in pixel + * coordinates. This area includes the toolbox and the visible workspace area. + * @return {!Size} The width and height of the workspace's parent + * SVG element. + * @public + */ + public getSvgMetrics(): { + width: number; + height: number; + }; + /** + * Gets the absolute left and absolute top in pixel coordinates. + * This is where the visible workspace starts in relation to the SVG + * container. + * @return {!MetricsManager.AbsoluteMetrics} The absolute metrics for + * the workspace. + * @public + */ + public getAbsoluteMetrics(): MetricsManager.AbsoluteMetrics; + /** + * Gets the metrics for the visible workspace in either pixel or workspace + * coordinates. The visible workspace does not include the toolbox or flyout. + * @param {boolean=} opt_getWorkspaceCoordinates True to get the view metrics + * in workspace coordinates, false to get them in pixel coordinates. + * @return {!MetricsManager.ContainerRegion} The width, height, top and + * left of the viewport in either workspace coordinates or pixel + * coordinates. + * @public + */ + public getViewMetrics(opt_getWorkspaceCoordinates?: boolean | undefined): MetricsManager.ContainerRegion; + /** + * Gets content metrics in either pixel or workspace coordinates. + * The content area is a rectangle around all the top bounded elements on the + * workspace (workspace comments and blocks). + * @param {boolean=} opt_getWorkspaceCoordinates True to get the content + * metrics in workspace coordinates, false to get them in pixel + * coordinates. + * @return {!MetricsManager.ContainerRegion} The + * metrics for the content container. + * @public + */ + public getContentMetrics(opt_getWorkspaceCoordinates?: boolean | undefined): MetricsManager.ContainerRegion; + /** + * Returns whether the scroll area has fixed edges. + * @return {boolean} Whether the scroll area has fixed edges. + * @package + */ + hasFixedEdges(): boolean; + /** + * Computes the fixed edges of the scroll area. + * @param {!MetricsManager.ContainerRegion=} opt_viewMetrics The view + * metrics if they have been previously computed. Passing in null may + * cause the view metrics to be computed again, if it is needed. + * @return {!MetricsManager.FixedEdges} The fixed edges of the scroll + * area. + * @protected + */ + protected getComputedFixedEdges_(opt_viewMetrics?: MetricsManager.ContainerRegion | undefined): MetricsManager.FixedEdges; + /** + * Returns the content area with added padding. + * @param {!MetricsManager.ContainerRegion} viewMetrics The view + * metrics. + * @param {!MetricsManager.ContainerRegion} contentMetrics The content + * metrics. + * @return {{top: number, bottom: number, left: number, right: number}} The + * padded content area. + * @protected + */ + protected getPaddedContent_(viewMetrics: MetricsManager.ContainerRegion, contentMetrics: MetricsManager.ContainerRegion): { + top: number; + bottom: number; + left: number; + right: number; + }; + /** + * Returns the metrics for the scroll area of the workspace. + * @param {boolean=} opt_getWorkspaceCoordinates True to get the scroll + * metrics in workspace coordinates, false to get them in pixel + * coordinates. + * @param {!MetricsManager.ContainerRegion=} opt_viewMetrics The view + * metrics if they have been previously computed. Passing in null may + * cause the view metrics to be computed again, if it is needed. + * @param {!MetricsManager.ContainerRegion=} opt_contentMetrics The + * content metrics if they have been previously computed. Passing in null + * may cause the content metrics to be computed again, if it is needed. + * @return {!MetricsManager.ContainerRegion} The metrics for the scroll + * container. + */ + getScrollMetrics(opt_getWorkspaceCoordinates?: boolean | undefined, opt_viewMetrics?: MetricsManager.ContainerRegion | undefined, opt_contentMetrics?: MetricsManager.ContainerRegion | undefined): MetricsManager.ContainerRegion; + /** + * Returns common metrics used by UI elements. + * @return {!MetricsManager.UiMetrics} The UI metrics. + */ + getUiMetrics(): MetricsManager.UiMetrics; + /** + * Returns an object with all the metrics required to size scrollbars for a + * top level workspace. The following properties are computed: + * Coordinate system: pixel coordinates, -left, -up, +right, +down + * .viewHeight: Height of the visible portion of the workspace. + * .viewWidth: Width of the visible portion of the workspace. + * .contentHeight: Height of the content. + * .contentWidth: Width of the content. + * .scrollHeight: Height of the scroll area. + * .scrollWidth: Width of the scroll area. + * .svgHeight: Height of the Blockly div (the view + the toolbox, + * simple or otherwise), + * .svgWidth: Width of the Blockly div (the view + the toolbox, + * simple or otherwise), + * .viewTop: Top-edge of the visible portion of the workspace, relative to + * the workspace origin. + * .viewLeft: Left-edge of the visible portion of the workspace, relative to + * the workspace origin. + * .contentTop: Top-edge of the content, relative to the workspace origin. + * .contentLeft: Left-edge of the content relative to the workspace origin. + * .scrollTop: Top-edge of the scroll area, relative to the workspace origin. + * .scrollLeft: Left-edge of the scroll area relative to the workspace origin. + * .absoluteTop: Top-edge of the visible portion of the workspace, relative + * to the blocklyDiv. + * .absoluteLeft: Left-edge of the visible portion of the workspace, relative + * to the blocklyDiv. + * .toolboxWidth: Width of the toolbox, if it exists. Otherwise zero. + * .toolboxHeight: Height of the toolbox, if it exists. Otherwise zero. + * .flyoutWidth: Width of the flyout if it is always open. Otherwise zero. + * .flyoutHeight: Height of the flyout if it is always open. Otherwise zero. + * .toolboxPosition: Top, bottom, left or right. Use TOOLBOX_AT constants to + * compare. + * @return {!Metrics} Contains size and position metrics of a top + * level workspace. + * @public + */ + public getMetrics(): Metrics; + } + export namespace MetricsManager { + /** + * Describes the width, height and location of the toolbox on the main + * workspace. + */ + type ToolboxMetrics = { + width: number; + height: number; + position: toolboxUtils.Position; + }; + /** + * Describes where the viewport starts in relation to the workspace SVG. + */ + type AbsoluteMetrics = { + left: number; + top: number; + }; + /** + * All the measurements needed to describe the size and location of a + * container. + */ + type ContainerRegion = { + height: number; + width: number; + top: number; + left: number; + }; + /** + * Describes fixed edges of the workspace. + */ + type FixedEdges = { + top: (number | undefined); + bottom: (number | undefined); + left: (number | undefined); + right: (number | undefined); + }; + /** + * Common metrics used for UI elements. + */ + type UiMetrics = { + viewMetrics: MetricsManager.ContainerRegion; + absoluteMetrics: MetricsManager.AbsoluteMetrics; + toolboxMetrics: MetricsManager.ToolboxMetrics; + }; + } + import { IMetricsManager } from "core/interfaces/i_metrics_manager"; + import { WorkspaceSvg } from "core/workspace_svg"; + import { IFlyout } from "core/interfaces/i_flyout"; + import { Metrics } from "core/utils/metrics"; + import * as toolboxUtils from "core/utils/toolbox"; +} +declare module "core/interfaces/i_positionable" { + /** + * Interface for a component that is positioned on top of the workspace. + * @extends {IComponent} + * @interface + * @alias Blockly.IPositionable + */ + export class IPositionable {} +} +declare module "core/component_manager" { + /** + * Manager for all items registered with the workspace. + * @alias Blockly.ComponentManager + */ + export class ComponentManager { + /** + * A map of the components registered with the workspace, mapped to id. + * @type {!Object} + * @private + */ + private componentData_; + /** + * A map of capabilities to component IDs. + * @type {!Object>} + * @private + */ + private capabilityToComponentIds_; + /** + * Adds a component. + * @param {!ComponentManager.ComponentDatum} componentInfo The data for + * the component to register. + * @param {boolean=} opt_allowOverrides True to prevent an error when + * overriding an already registered item. + */ + addComponent(componentInfo: ComponentManager.ComponentDatum, opt_allowOverrides?: boolean | undefined): void; + /** + * Removes a component. + * @param {string} id The ID of the component to remove. + */ + removeComponent(id: string): void; + /** + * Adds a capability to a existing registered component. + * @param {string} id The ID of the component to add the capability to. + * @param {string|!ComponentManager.Capability} capability The + * capability to add. + * @template T + */ + addCapability(id: string, capability: string | { + /** + * @type {string} + * @private + */ + name_: string; + /** + * Returns the name of the capability. + * @return {string} The name. + * @override + */ + toString(): string; + }): void; + /** + * Removes a capability from an existing registered component. + * @param {string} id The ID of the component to remove the capability from. + * @param {string|!ComponentManager.Capability} capability The + * capability to remove. + * @template T + */ + removeCapability(id: string, capability: string | { + /** + * @type {string} + * @private + */ + name_: string; + /** + * Returns the name of the capability. + * @return {string} The name. + * @override + */ + toString(): string; + }): void; + /** + * Returns whether the component with this id has the specified capability. + * @param {string} id The ID of the component to check. + * @param {string|!ComponentManager.Capability} capability The + * capability to check for. + * @return {boolean} Whether the component has the capability. + * @template T + */ + hasCapability(id: string, capability: string | { + /** + * @type {string} + * @private + */ + name_: string; + /** + * Returns the name of the capability. + * @return {string} The name. + * @override + */ + toString(): string; + }): boolean; + /** + * Gets the component with the given ID. + * @param {string} id The ID of the component to get. + * @return {!IComponent|undefined} The component with the given name + * or undefined if not found. + */ + getComponent(id: string): { + (): void; + id: string; + } | undefined; + /** + * Gets all the components with the specified capability. + * @param {string|!ComponentManager.Capability + * } capability The capability of the component. + * @param {boolean} sorted Whether to return list ordered by weights. + * @return {!Array} The components that match the specified capability. + * @template T + */ + getComponents(capability: string | { + /** + * @type {string} + * @private + */ + name_: string; + /** + * Returns the name of the capability. + * @return {string} The name. + * @override + */ + toString(): string; + }, sorted: boolean): T_4[]; + } + export namespace ComponentManager { + export { Capability }; + /** + * An object storing component information. + */ + export type ComponentDatum = { + component: { + (): void; + id: string; + }; + capabilities: (Array); + weight: number; + }; + } + class Capability { + /** + * @param {string} name The name of the component capability. + */ + constructor(name: string); + /** + * @type {string} + * @private + */ + private name_; + /** + * Returns the name of the capability. + * @return {string} The name. + * @override + */ + toString(): string; + } + export {}; +} +declare module "core/interfaces/i_contextmenu" { + /** + * @interface + * @alias Blockly.IContextMenu + */ + export class IContextMenu {} +} +declare module "core/interfaces/i_bubble" { + /** + * A bubble interface. + * @interface + * @extends {IDraggable} + * @extends {IContextMenu} + * @alias Blockly.IBubble + */ + export class IBubble {} +} +declare module "core/css" { + /** + * Add some CSS to the blob that will be injected later. Allows optional + * components such as fields and the toolbox to store separate CSS. + * @param {string|!Array} cssContent Multiline CSS string or an array of + * single lines of CSS. + * @alias Blockly.Css.register + */ + export function register(cssContent: string | Array): void; + /** + * Inject the CSS into the DOM. This is preferable over using a regular CSS + * file since: + * a) It loads synchronously and doesn't force a redraw later. + * b) It speeds up loading by not blocking on a separate HTTP transfer. + * c) The CSS content may be made dynamic depending on init options. + * @param {boolean} hasCss If false, don't inject CSS + * (providing CSS becomes the document's responsibility). + * @param {string} pathToMedia Path from page to the Blockly media directory. + * @alias Blockly.Css.inject + */ + export function inject(hasCss: boolean, pathToMedia: string): void; + /** + * The CSS content for Blockly. + * @alias Blockly.Css.content + */ + export let content: string; +} +declare module "core/interfaces/i_bounded_element" { + /** + * A bounded element interface. + * @interface + * @alias Blockly.IBoundedElement + */ + export class IBoundedElement {} +} +declare module "core/interfaces/i_movable" { + /** + * The interface for an object that is movable. + * @interface + * @alias Blockly.IMovable + */ + export class IMovable {} +} +declare module "core/interfaces/i_selectable" { + export class ISelectable { + /** + * @type {string} + */ + id: string; + } +} +declare module "core/interfaces/i_copyable" { + /** + * @extends {ISelectable} + * @interface + * @alias Blockly.ICopyable + */ + export class ICopyable {} + export namespace ICopyable { + /** + * Copy Metadata. + */ + type CopyData = { + saveInfo: (Object | Element); + source: WorkspaceSvg; + typeCounts: Object | null; + }; + } + import { WorkspaceSvg } from "core/workspace_svg"; +} +declare module "core/events/events_selected" { + /** + * Class for a selected event. + * @extends {UiBase} + * @alias Blockly.Events.Selected + */ + export class Selected extends UiBase { + /** + * @param {?string=} opt_oldElementId The ID of the previously selected + * element. Null if no element last selected. Undefined for a blank event. + * @param {?string=} opt_newElementId The ID of the selected element. Null if + * no element currently selected (deselect). Undefined for a blank event. + * @param {string=} opt_workspaceId The workspace identifier for this event. + * Null if no element previously selected. Undefined for a blank event. + */ + constructor(opt_oldElementId?: (string | null) | undefined, opt_newElementId?: (string | null) | undefined, opt_workspaceId?: string | undefined); + /** + * The id of the last selected element. + * @type {?string|undefined} + */ + oldElementId: (string | undefined) | null; + /** + * The id of the selected element. + * @type {?string|undefined} + */ + newElementId: (string | undefined) | null; + } + import { UiBase } from "core/events/events_ui_base"; +} +declare module "core/workspace_comment_svg" { + /** + * Class for a workspace comment's SVG representation. + * @extends {WorkspaceComment} + * @implements {IBoundedElement} + * @implements {IBubble} + * @implements {ICopyable} + * @alias Blockly.WorkspaceCommentSvg + */ + export class WorkspaceCommentSvg extends WorkspaceComment implements IBoundedElement, IBubble, ICopyable { + /** + * Decode an XML comment tag and create a rendered comment on the workspace. + * @param {!Element} xmlComment XML comment element. + * @param {!WorkspaceSvg} workspace The workspace. + * @param {number=} opt_wsWidth The width of the workspace, which is used to + * position comments correctly in RTL. + * @return {!WorkspaceCommentSvg} The created workspace comment. + * @package + */ + static fromXmlRendered(xmlComment: Element, workspace: WorkspaceSvg, opt_wsWidth?: number | undefined): WorkspaceCommentSvg; + /** + * @param {!WorkspaceSvg} workspace The block's workspace. + * @param {string} content The content of this workspace comment. + * @param {number} height Height of the comment. + * @param {number} width Width of the comment. + * @param {string=} opt_id Optional ID. Use this ID if provided, otherwise + * create a new ID. + */ + constructor(workspace: WorkspaceSvg, content: string, height: number, width: number, opt_id?: string | undefined); + /** + * Mouse up event data. + * @type {?browserEvents.Data} + * @private + */ + private onMouseUpWrapper_; + /** + * Mouse move event data. + * @type {?browserEvents.Data} + * @private + */ + private onMouseMoveWrapper_; + /** + * Whether event handlers have been initialized. + * @type {boolean} + * @private + */ + private eventsInit_; + /** + * @type {?Element} + * @private + */ + private textarea_; + /** + * @type {?SVGRectElement} + * @private + */ + private svgRectTarget_; + /** + * @type {?SVGRectElement} + * @private + */ + private svgHandleTarget_; + /** + * @type {?SVGForeignObjectElement} + * @private + */ + private foreignObject_; + /** + * @type {?SVGGElement} + * @private + */ + private resizeGroup_; + /** + * @type {?SVGGElement} + * @private + */ + private deleteGroup_; + /** + * @type {?SVGCircleElement} + * @private + */ + private deleteIconBorder_; + /** + * @type {boolean} + * @private + */ + private focused_; + /** + * @type {boolean} + * @private + */ + private autoLayout_; + /** + * @type {!SVGElement} + * @private + */ + private svgGroup_; + svgRect_: SVGRectElement; + /** + * Whether the comment is rendered onscreen and is a part of the DOM. + * @type {boolean} + * @private + */ + private rendered_; + /** + * Whether to move the comment to the drag surface when it is dragged. + * True if it should move, false if it should be translated directly. + * @type {boolean} + * @private + */ + private useDragSurface_; + /** + * Create and initialize the SVG representation of a workspace comment. + * May be called more than once. + * + * @param {boolean=} opt_noSelect Text inside text area will be selected if + * false + * + * @package + */ + initSvg(opt_noSelect?: boolean | undefined): void; + /** + * Handle a mouse-down on an SVG comment. + * @param {!Event} e Mouse down event or touch start event. + * @private + */ + private pathMouseDown_; + /** + * Show the context menu for this workspace comment. + * @param {!Event} e Mouse event. + * @package + */ + showContextMenu(e: Event): void; + /** + * Select this comment. Highlight it visually. + * @package + */ + select(): void; + /** + * Unselect this comment. Remove its highlighting. + * @package + */ + unselect(): void; + /** + * Select this comment. Highlight it visually. + * @package + */ + addSelect(): void; + /** + * Unselect this comment. Remove its highlighting. + * @package + */ + removeSelect(): void; + /** + * Focus this comment. Highlight it visually. + * @package + */ + addFocus(): void; + /** + * Unfocus this comment. Remove its highlighting. + * @package + */ + removeFocus(): void; + /** + * Return the coordinates of the top-left corner of this comment relative to + * the drawing surface's origin (0,0), in workspace units. + * If the comment is on the workspace, (0, 0) is the origin of the workspace + * coordinate system. + * This does not change with workspace scale. + * @return {!Coordinate} Object with .x and .y properties in + * workspace coordinates. + * @package + */ + getRelativeToSurfaceXY(): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + /** + * Transforms a comment by setting the translation on the transform attribute + * of the block's SVG. + * @param {number} x The x coordinate of the translation in workspace units. + * @param {number} y The y coordinate of the translation in workspace units. + * @package + */ + translate(x: number, y: number): void; + /** + * Move this comment to its workspace's drag surface, accounting for + * positioning. Generally should be called at the same time as + * setDragging(true). Does nothing if useDragSurface_ is false. + * @package + */ + moveToDragSurface(): void; + /** + * Move this comment during a drag, taking into account whether we are using a + * drag surface to translate blocks. + * @param {BlockDragSurfaceSvg} dragSurface The surface that carries + * rendered items during a drag, or null if no drag surface is in use. + * @param {!Coordinate} newLoc The location to translate to, in + * workspace coordinates. + * @package + */ + moveDuringDrag(dragSurface: { + SVG_: SVGElement | null; + dragGroup_: SVGElement | null; + container_: Element; + scale_: number; + surfaceXY_: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + } | null; + childSurfaceXY_: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + createDom(): void; /** + * @type {?SVGRectElement} + * @private + */ + setBlocksAndShow(blocks: SVGElement): void; /** + * @type {boolean} + * @private + */ + translateAndScaleGroup(x: number, y: number, scale: number): void; + translateSurfaceInternal_(): void; + translateBy(deltaX: number, deltaY: number): void; + translateSurface(x: number, y: number): void; + getSurfaceTranslation(): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + getGroup(): SVGElement | null; + getSvgRoot(): SVGElement | null; + getCurrentBlock(): Element | null; + getWsTranslation(): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + clearAndHide(opt_newSurface?: Element | undefined): void; + }, newLoc: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): void; + /** + * 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 + */ + moveTo(x: number, y: number): void; + /** + * Clear the comment of transform="..." attributes. + * Used when the comment is switching from 3d to 2d transform or vice versa. + * @private + */ + private clearTransformAttributes_; + /** + * Returns the coordinates of a bounding box describing the dimensions of this + * comment. + * Coordinate system: workspace coordinates. + * @return {!Rect} Object with coordinates of the bounding box. + * @package + */ + getBoundingRectangle(): { + top: number; + bottom: number; + left: number; + right: number; + contains(x: number, y: number): boolean; + intersects(other: any): boolean; + }; + /** + * Add or remove the UI indicating if this comment is movable or not. + * @package + */ + updateMovable(): void; + /** + * Recursively adds or removes the dragging class to this node and its + * children. + * @param {boolean} adding True if adding, false if removing. + * @package + */ + setDragging(adding: boolean): void; + /** + * Return the root node of the SVG or null if none exists. + * @return {!SVGElement} The root SVG node (probably a group). + * @package + */ + getSvgRoot(): SVGElement; + /** + * Update the cursor over this comment by adding or removing a class. + * @param {boolean} enable True if the delete cursor should be shown, false + * otherwise. + * @package + */ + setDeleteStyle(enable: boolean): void; + /** + * 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 + */ + setAutoLayout(_enable: boolean): void; + /** + * Encode a comment for copying. + * @return {!ICopyable.CopyData} Copy metadata. + * @package + */ + toCopyData(): ICopyable.CopyData; + /** + * Returns a bounding box describing the dimensions of this comment. + * @return {!{height: number, width: number}} Object with height and width + * properties in workspace units. + * @package + */ + getHeightWidth(): { + height: number; + width: number; + }; + /** + * Renders the workspace comment. + * @package + */ + render(): void; + /** + * Create the text area for the comment. + * @return {!Element} The top-level node of the editor. + * @private + */ + private createEditor_; + /** + * Add the resize icon to the DOM + * @private + */ + private addResizeDom_; + /** + * Add the delete icon to the DOM + * @private + */ + private addDeleteDom_; + /** + * Handle a mouse-down on comment's resize corner. + * @param {!Event} e Mouse down event. + * @private + */ + private resizeMouseDown_; + /** + * Handle a mouse-down on comment's delete icon. + * @param {!Event} e Mouse down event. + * @private + */ + private deleteMouseDown_; + /** + * Handle a mouse-out on comment's delete icon. + * @param {!Event} _e Mouse out event. + * @private + */ + private deleteMouseOut_; + /** + * Handle a mouse-up on comment's delete icon. + * @param {!Event} e Mouse up event. + * @private + */ + private deleteMouseUp_; + /** + * Stop binding to the global mouseup and mousemove events. + * @private + */ + private unbindDragEvents_; + /** + * Handle a mouse-up event while dragging a comment's border or resize handle. + * @param {!Event} _e Mouse up event. + * @private + */ + private resizeMouseUp_; + /** + * Resize this comment to follow the mouse. + * @param {!Event} e Mouse move event. + * @private + */ + private resizeMouseMove_; + /** + * Callback function triggered when the comment has resized. + * Resize the text area accordingly. + * @private + */ + private resizeComment_; + /** + * Set size + * @param {number} width width of the container + * @param {number} height height of the container + * @private + */ + private setSize_; + /** + * Dispose of any rendered comment components. + * @private + */ + private disposeInternal_; + /** + * Set the focus on the text area. + * @package + */ + setFocus(): void; + /** + * Remove focus from the text area. + * @package + */ + blurFocus(): void; + } + export namespace WorkspaceCommentSvg { + const DEFAULT_SIZE: number; + const TOP_OFFSET: number; + } + import { IBoundedElement } from "core/interfaces/i_bounded_element"; + import { IBubble } from "core/interfaces/i_bubble"; + import { ICopyable } from "core/interfaces/i_copyable"; + import { WorkspaceComment } from "core/workspace_comment"; + import { WorkspaceSvg } from "core/workspace_svg"; +} +declare module "core/scrollbar" { + /** + * A note on units: most of the numbers that are in CSS pixels are scaled if the + * scrollbar is in a mutator. + */ + /** + * Class for a pure SVG scrollbar. + * This technique offers a scrollbar that is guaranteed to work, but may not + * look or behave like the system's scrollbars. + * @alias Blockly.Scrollbar + */ + export class Scrollbar { + /** + * @param {!Metrics} first An object containing computed + * measurements of a workspace. + * @param {!Metrics} second Another object containing computed + * measurements of a workspace. + * @return {boolean} Whether the two sets of metrics are equivalent. + * @private + */ + private static metricsAreEquivalent_; + /** + * @param {!WorkspaceSvg} workspace Workspace to bind the scrollbar to. + * @param {boolean} horizontal True if horizontal, false if vertical. + * @param {boolean=} opt_pair True if scrollbar is part of a horiz/vert pair. + * @param {string=} opt_class A class to be applied to this scrollbar. + * @param {number=} opt_margin The margin to apply to this scrollbar. + */ + constructor(workspace: WorkspaceSvg, horizontal: boolean, opt_pair?: boolean | undefined, opt_class?: string | undefined, opt_margin?: number | undefined); + /** + * The workspace this scrollbar is bound to. + * @type {!WorkspaceSvg} + * @private + */ + private workspace_; + /** + * Whether this scrollbar is part of a pair. + * @type {boolean} + * @private + */ + private pair_; + /** + * Whether this is a horizontal scrollbar. + * @type {boolean} + * @private + */ + private horizontal_; + /** + * Margin around the scrollbar (between the scrollbar and the edge of the + * viewport in pixels). + * @type {number} + * @const + * @private + */ + private margin_; + /** + * Previously recorded metrics from the workspace. + * @type {?Metrics} + * @private + */ + private oldHostMetrics_; + /** + * The ratio of handle position offset to workspace content displacement. + * @type {?number} + * @package + */ + ratio: number | null; + /** + * The location of the origin of the workspace that the scrollbar is in, + * measured in CSS pixels relative to the injection div origin. This is + * usually (0, 0). When the scrollbar is in a flyout it may have a + * different origin. + * @type {Coordinate} + * @private + */ + private origin_; + /** + * The position of the mouse along this scrollbar's major axis at the start + * of the most recent drag. Units are CSS pixels, with (0, 0) at the top + * left of the browser window. For a horizontal scrollbar this is the x + * coordinate of the mouse down event; for a vertical scrollbar it's the y + * coordinate of the mouse down event. + * @type {number} + * @private + */ + private startDragMouse_; + /** + * The length of the scrollbars (including the handle and the background), + * in CSS pixels. This is equivalent to scrollbar background length and the + * area within which the scrollbar handle can move. + * @type {number} + * @private + */ + private scrollbarLength_; + /** + * The length of the scrollbar handle in CSS pixels. + * @type {number} + * @private + */ + private handleLength_; + /** + * The offset of the start of the handle from the scrollbar position, in CSS + * pixels. + * @type {number} + * @private + */ + private handlePosition_; + /** + * @type {number} + * @private + */ + private startDragHandle; + /** + * Whether the scrollbar handle is visible. + * @type {boolean} + * @private + */ + private isVisible_; + /** + * Whether the workspace containing this scrollbar is visible. + * @type {boolean} + * @private + */ + private containerVisible_; + /** + * @type {?SVGRectElement} + * @private + */ + private svgBackground_; + /** + * @type {?SVGRectElement} + * @private + */ + private svgHandle_; + /** + * @type {?SVGSVGElement} + * @private + */ + private outerSvg_; + /** + * @type {?SVGGElement} + * @private + */ + private svgGroup_; + /** + * The upper left corner of the scrollbar's SVG group in CSS pixels relative + * to the scrollbar's origin. This is usually relative to the injection div + * origin. + * @type {Coordinate} + * @package + */ + position: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + lengthAttribute_: string; + positionAttribute_: string; + onMouseDownBarWrapper_: any[][]; + onMouseDownHandleWrapper_: any[][]; + /** + * Dispose of this scrollbar. + * Unlink from all DOM elements to prevent memory leaks. + * @suppress {checkTypes} + */ + dispose(): void; + /** + * Constrain the handle's length within the minimum (0) and maximum + * (scrollbar background) values allowed for the scrollbar. + * @param {number} value Value that is potentially out of bounds, in CSS + * pixels. + * @return {number} Constrained value, in CSS pixels. + * @private + */ + private constrainHandleLength_; + /** + * Set the length of the scrollbar's handle and change the SVG attribute + * accordingly. + * @param {number} newLength The new scrollbar handle length in CSS pixels. + * @private + */ + private setHandleLength_; + /** + * Constrain the handle's position within the minimum (0) and maximum values + * allowed for the scrollbar. + * @param {number} value Value that is potentially out of bounds, in CSS + * pixels. + * @return {number} Constrained value, in CSS pixels. + * @private + */ + private constrainHandlePosition_; + /** + * Set the offset of the scrollbar's handle from the scrollbar's position, and + * change the SVG attribute accordingly. + * @param {number} newPosition The new scrollbar handle offset in CSS pixels. + */ + setHandlePosition(newPosition: number): void; + /** + * Set the size of the scrollbar's background and change the SVG attribute + * accordingly. + * @param {number} newSize The new scrollbar background length in CSS pixels. + * @private + */ + private setScrollbarLength_; + /** + * Set the position of the scrollbar's SVG group in CSS pixels relative to the + * scrollbar's origin. This sets the scrollbar's location within the + * workspace. + * @param {number} x The new x coordinate. + * @param {number} y The new y coordinate. + * @package + */ + setPosition(x: number, y: number): void; + /** + * Recalculate the scrollbar's location and its length. + * @param {Metrics=} opt_metrics A data structure of from the + * describing all the required dimensions. If not provided, it will be + * fetched from the host object. + */ + resize(opt_metrics?: Metrics | undefined): void; + /** + * Returns whether the a resizeView is necessary by comparing the passed + * hostMetrics with cached old host metrics. + * @param {!Metrics} hostMetrics A data structure describing all + * the required dimensions, possibly fetched from the host object. + * @return {boolean} Whether a resizeView is necessary. + * @private + */ + private requiresViewResize_; + /** + * Recalculate a horizontal scrollbar's location and length. + * @param {!Metrics} hostMetrics A data structure describing all + * the required dimensions, possibly fetched from the host object. + * @private + */ + private resizeHorizontal_; + /** + * Recalculate a horizontal scrollbar's location on the screen and path + * length. This should be called when the layout or size of the window has + * changed. + * @param {!Metrics} hostMetrics A data structure describing all + * the required dimensions, possibly fetched from the host object. + */ + resizeViewHorizontal(hostMetrics: Metrics): void; + /** + * Recalculate a horizontal scrollbar's location within its path and length. + * This should be called when the contents of the workspace have changed. + * @param {!Metrics} hostMetrics A data structure describing all + * the required dimensions, possibly fetched from the host object. + */ + resizeContentHorizontal(hostMetrics: Metrics): void; + /** + * Recalculate a vertical scrollbar's location and length. + * @param {!Metrics} hostMetrics A data structure describing all + * the required dimensions, possibly fetched from the host object. + * @private + */ + private resizeVertical_; + /** + * Recalculate a vertical scrollbar's location on the screen and path length. + * This should be called when the layout or size of the window has changed. + * @param {!Metrics} hostMetrics A data structure describing all + * the required dimensions, possibly fetched from the host object. + */ + resizeViewVertical(hostMetrics: Metrics): void; + /** + * Recalculate a vertical scrollbar's location within its path and length. + * This should be called when the contents of the workspace have changed. + * @param {!Metrics} hostMetrics A data structure describing all + * the required dimensions, possibly fetched from the host object. + */ + resizeContentVertical(hostMetrics: Metrics): void; + /** + * Create all the DOM elements required for a scrollbar. + * The resulting widget is not sized. + * @param {string=} opt_class A class to be applied to this scrollbar. + * @private + */ + private createDom_; + /** + * Is the scrollbar visible. Non-paired scrollbars disappear when they aren't + * needed. + * @return {boolean} True if visible. + */ + isVisible(): boolean; + /** + * Set whether the scrollbar's container is visible and update + * display accordingly if visibility has changed. + * @param {boolean} visible Whether the container is visible + */ + setContainerVisible(visible: boolean): void; + /** + * Set whether the scrollbar is visible. + * Only applies to non-paired scrollbars. + * @param {boolean} visible True if visible. + */ + setVisible(visible: boolean): void; + /** + * Update visibility of scrollbar based on whether it thinks it should + * be visible and whether its containing workspace is visible. + * We cannot rely on the containing workspace being hidden to hide us + * because it is not necessarily our parent in the DOM. + */ + updateDisplay_(): void; + /** + * Scroll by one pageful. + * Called when scrollbar background is clicked. + * @param {!Event} e Mouse down event. + * @private + */ + private onMouseDownBar_; + /** + * Start a dragging operation. + * Called when scrollbar handle is clicked. + * @param {!Event} e Mouse down event. + * @private + */ + private onMouseDownHandle_; + /** + * Drag the scrollbar's handle. + * @param {!Event} e Mouse up event. + * @private + */ + private onMouseMoveHandle_; + /** + * Release the scrollbar handle and reset state accordingly. + * @private + */ + private onMouseUpHandle_; + /** + * Hide chaff and stop binding to mouseup and mousemove events. Call this to + * wrap up loose ends associated with the scrollbar. + * @private + */ + private cleanUp_; + /** + * Helper to calculate the ratio of handle position to scrollbar view size. + * @return {number} Ratio. + * @package + */ + getRatio_(): number; + /** + * Updates workspace metrics based on new scroll ratio. Called when scrollbar + * is moved. + * @private + */ + private updateMetrics_; + /** + * Set the scrollbar handle's position. + * @param {number} value The content displacement, relative to the view in + * pixels. + * @param {boolean=} updateMetrics Whether to update metrics on this set call. + * Defaults to true. + */ + set(value: number, updateMetrics?: boolean | undefined): void; + /** + * Record the origin of the workspace that the scrollbar is in, in pixels + * relative to the injection div origin. This is for times when the scrollbar + * is used in an object whose origin isn't the same as the main workspace + * (e.g. in a flyout.) + * @param {number} x The x coordinate of the scrollbar's origin, in CSS + * pixels. + * @param {number} y The y coordinate of the scrollbar's origin, in CSS + * pixels. + */ + setOrigin(x: number, y: number): void; + } + export namespace Scrollbar { + const scrollbarThickness: number; + const DEFAULT_SCROLLBAR_MARGIN: number; + } + import { Metrics } from "core/utils/metrics"; + import { WorkspaceSvg } from "core/workspace_svg"; +} +declare module "core/bubble" { + /** + * Class for UI bubble. + * @implements {IBubble} + * @alias Blockly.Bubble + */ + export const Bubble: { + new (workspace: WorkspaceSvg, content: Element, shape: Element, anchorXY: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }, bubbleWidth: number | null, bubbleHeight: number | null): { + workspace_: WorkspaceSvg; + content_: Element; + shape_: Element; + /** + * Flag to stop incremental rendering during construction. + * @type {boolean} + * @private + */ + rendered_: boolean; + /** + * The SVG group containing all parts of the bubble. + * @type {SVGGElement} + * @private + */ + bubbleGroup_: SVGGElement; + /** + * The SVG path for the arrow from the bubble to the icon on the block. + * @type {SVGPathElement} + * @private + */ + bubbleArrow_: SVGPathElement; + /** + * The SVG rect for the main body of the bubble. + * @type {SVGRectElement} + * @private + */ + bubbleBack_: SVGRectElement; + /** + * The SVG group for the resize hash marks on some bubbles. + * @type {SVGGElement} + * @private + */ + resizeGroup_: SVGGElement; + /** + * Absolute coordinate of anchor point, in workspace coordinates. + * @type {Coordinate} + * @private + */ + anchorXY_: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + /** + * Relative X coordinate of bubble with respect to the anchor's centre, + * in workspace units. + * In RTL mode the initial value is negated. + * @type {number} + * @private + */ + relativeLeft_: number; + /** + * Relative Y coordinate of bubble with respect to the anchor's centre, in + * workspace units. + * @type {number} + * @private + */ + relativeTop_: number; + /** + * Width of bubble, in workspace units. + * @type {number} + * @private + */ + width_: number; + /** + * Height of bubble, in workspace units. + * @type {number} + * @private + */ + height_: number; + /** + * Automatically position and reposition the bubble. + * @type {boolean} + * @private + */ + autoLayout_: boolean; + /** + * Method to call on resize of bubble. + * @type {?function()} + * @private + */ + resizeCallback_: (() => any) | null; + /** + * Method to call on move of bubble. + * @type {?function()} + * @private + */ + moveCallback_: (() => any) | null; + /** + * Mouse down on bubbleBack_ event data. + * @type {?browserEvents.Data} + * @private + */ + onMouseDownBubbleWrapper_: any[][] | null; + /** + * Mouse down on resizeGroup_ event data. + * @type {?browserEvents.Data} + * @private + */ + onMouseDownResizeWrapper_: any[][] | null; + /** + * Describes whether this bubble has been disposed of (nodes and event + * listeners removed from the page) or not. + * @type {boolean} + * @package + */ + disposed: boolean; + arrow_radians_: number; + /** + * Create the bubble's DOM. + * @param {!Element} content SVG content for the bubble. + * @param {boolean} hasResize Add diagonal resize gripper if true. + * @return {!SVGElement} The bubble's SVG group. + * @private + */ + createDom_(content: Element, hasResize: boolean): SVGElement; + /** + * Return the root node of the bubble's SVG group. + * @return {!SVGElement} The root SVG node of the bubble's group. + */ + getSvgRoot(): SVGElement; + /** + * Expose the block's ID on the bubble's top-level SVG group. + * @param {string} id ID of block. + */ + setSvgId(id: string): void; + /** + * Handle a mouse-down on bubble's border. + * @param {!Event} e Mouse down event. + * @private + */ + bubbleMouseDown_(e: Event): void; + /** + * Show the context menu for this bubble. + * @param {!Event} _e Mouse event. + * @package + */ + showContextMenu(_e: Event): void; + /** + * Get whether this bubble is deletable or not. + * @return {boolean} True if deletable. + * @package + */ + isDeletable(): boolean; + /** + * Update the style of this bubble when it is dragged over a delete area. + * @param {boolean} _enable True if the bubble is about to be deleted, false + * otherwise. + */ + setDeleteStyle(_enable: boolean): void; + /** + * Handle a mouse-down on bubble's resize corner. + * @param {!Event} e Mouse down event. + * @private + */ + resizeMouseDown_(e: Event): void; + /** + * Resize this bubble to follow the mouse. + * @param {!Event} e Mouse move event. + * @private + */ + resizeMouseMove_(e: Event): void; + /** + * Register a function as a callback event for when the bubble is resized. + * @param {!Function} callback The function to call on resize. + */ + registerResizeEvent(callback: Function): void; + /** + * Register a function as a callback event for when the bubble is moved. + * @param {!Function} callback The function to call on move. + */ + registerMoveEvent(callback: Function): void; + /** + * Move this bubble to the top of the stack. + * @return {boolean} Whether or not the bubble has been moved. + * @package + */ + promote(): boolean; + /** + * Notification that the anchor has moved. + * Update the arrow and bubble accordingly. + * @param {!Coordinate} xy Absolute location. + */ + setAnchorLocation(xy: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): void; + /** + * Position the bubble so that it does not fall off-screen. + * @private + */ + layoutBubble_(): void; + /** + * Calculate the what percentage of the bubble overlaps with the visible + * workspace (what percentage of the bubble is visible). + * @param {!{x: number, y: number}} relativeMin The position of the top-left + * corner of the bubble relative to the anchor point. + * @param {!MetricsManager.ContainerRegion} viewMetrics The view metrics + * of the workspace the bubble will appear in. + * @return {number} The percentage of the bubble that is visible. + * @private + */ + getOverlap_(relativeMin: { + x: number; + y: number; + }, viewMetrics: MetricsManager.ContainerRegion): number; + /** + * Calculate what the optimal horizontal position of the top-left corner of + * the bubble is (relative to the anchor point) so that the most area of the + * bubble is shown. + * @param {!MetricsManager.ContainerRegion} viewMetrics The view metrics + * of the workspace the bubble will appear in. + * @return {number} The optimal horizontal position of the top-left corner + * of the bubble. + * @private + */ + getOptimalRelativeLeft_(viewMetrics: MetricsManager.ContainerRegion): number; + /** + * Calculate what the optimal vertical position of the top-left corner of + * the bubble is (relative to the anchor point) so that the most area of the + * bubble is shown. + * @param {!MetricsManager.ContainerRegion} viewMetrics The view metrics + * of the workspace the bubble will appear in. + * @return {number} The optimal vertical position of the top-left corner + * of the bubble. + * @private + */ + getOptimalRelativeTop_(viewMetrics: MetricsManager.ContainerRegion): number; + /** + * Move the bubble to a location relative to the anchor's centre. + * @private + */ + positionBubble_(): void; + /** + * 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 + */ + moveTo(x: number, y: number): void; + /** + * Triggers a move callback if one exists at the end of a drag. + * @param {boolean} adding True if adding, false if removing. + * @package + */ + setDragging(adding: boolean): void; + /** + * Get the dimensions of this bubble. + * @return {!Size} The height and width of the bubble. + */ + getBubbleSize(): { + width: number; + height: number; + }; + /** + * Size this bubble. + * @param {number} width Width of the bubble. + * @param {number} height Height of the bubble. + */ + setBubbleSize(width: number, height: number): void; + /** + * Draw the arrow between the bubble and the origin. + * @private + */ + renderArrow_(): void; + /** + * Change the colour of a bubble. + * @param {string} hexColour Hex code of colour. + */ + setColour(hexColour: string): void; + /** + * Dispose of this bubble. + */ + dispose(): void; + /** + * Move this bubble during a drag, taking into account whether or not there is + * a drag surface. + * @param {BlockDragSurfaceSvg} dragSurface The surface that carries + * rendered items during a drag, or null if no drag surface is in use. + * @param {!Coordinate} newLoc The location to translate to, in + * workspace coordinates. + * @package + */ + moveDuringDrag(dragSurface: { + SVG_: SVGElement | null; + dragGroup_: SVGElement | null; + container_: Element; + scale_: number; + surfaceXY_: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + } | null; + childSurfaceXY_: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + createDom(): void; + setBlocksAndShow(blocks: SVGElement): void; + translateAndScaleGroup(x: number, y: number, scale: number): void; + translateSurfaceInternal_(): void; + translateBy(deltaX: number, deltaY: number): void; + translateSurface(x: number, y: number): void; + getSurfaceTranslation(): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + getGroup(): SVGElement | null; + getSvgRoot(): SVGElement | null; + getCurrentBlock(): Element | null; + getWsTranslation(): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + clearAndHide(opt_newSurface?: Element | undefined): void; + }, newLoc: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): void; + /** + * 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 {!Coordinate} Object with .x and .y properties. + */ + getRelativeToSurfaceXY(): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + /** + * 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 + */ + setAutoLayout(enable: boolean): void; + }; + /** + * Stop binding to the global mouseup and mousemove events. + * @private + */ + unbindDragEvents_(): void; + /** + * Handle a mouse-up event while dragging a bubble's border or resize handle. + * @param {!Event} _e Mouse up event. + * @private + */ + bubbleMouseUp_(_e: Event): void; + /** + * Create the text for a non editable bubble. + * @param {string} text The text to display. + * @return {!SVGTextElement} The top-level node of the text. + * @package + */ + textToDom(text: string): SVGTextElement; + /** + * Creates a bubble that can not be edited. + * @param {!SVGTextElement} paragraphElement The text element for the non + * editable bubble. + * @param {!BlockSvg} block The block that the bubble is attached to. + * @param {!Coordinate} iconXY The coordinate of the icon. + * @return {!Bubble} The non editable bubble. + * @package + */ + createNonEditableBubble(paragraphElement: SVGTextElement, block: BlockSvg, iconXY: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): { + workspace_: WorkspaceSvg; + content_: Element; + shape_: Element; + /** + * Flag to stop incremental rendering during construction. + * @type {boolean} + * @private + */ + rendered_: boolean; + /** + * The SVG group containing all parts of the bubble. + * @type {SVGGElement} + * @private + */ + bubbleGroup_: SVGGElement; + /** + * The SVG path for the arrow from the bubble to the icon on the block. + * @type {SVGPathElement} + * @private + */ + bubbleArrow_: SVGPathElement; + /** + * The SVG rect for the main body of the bubble. + * @type {SVGRectElement} + * @private + */ + bubbleBack_: SVGRectElement; + /** + * The SVG group for the resize hash marks on some bubbles. + * @type {SVGGElement} + * @private + */ + resizeGroup_: SVGGElement; + /** + * Absolute coordinate of anchor point, in workspace coordinates. + * @type {Coordinate} + * @private + */ + anchorXY_: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + /** + * Relative X coordinate of bubble with respect to the anchor's centre, + * in workspace units. + * In RTL mode the initial value is negated. + * @type {number} + * @private + */ + relativeLeft_: number; + /** + * Relative Y coordinate of bubble with respect to the anchor's centre, in + * workspace units. + * @type {number} + * @private + */ + relativeTop_: number; + /** + * Width of bubble, in workspace units. + * @type {number} + * @private + */ + width_: number; + /** + * Height of bubble, in workspace units. + * @type {number} + * @private + */ + height_: number; + /** + * Automatically position and reposition the bubble. + * @type {boolean} + * @private + */ + autoLayout_: boolean; + /** + * Method to call on resize of bubble. + * @type {?function()} + * @private + */ + resizeCallback_: (() => any) | null; + /** + * Method to call on move of bubble. + * @type {?function()} + * @private + */ + moveCallback_: (() => any) | null; + /** + * Mouse down on bubbleBack_ event data. + * @type {?browserEvents.Data} + * @private + */ + onMouseDownBubbleWrapper_: any[][] | null; + /** + * Mouse down on resizeGroup_ event data. + * @type {?browserEvents.Data} + * @private + */ + onMouseDownResizeWrapper_: any[][] | null; + /** + * Describes whether this bubble has been disposed of (nodes and event + * listeners removed from the page) or not. + * @type {boolean} + * @package + */ + disposed: boolean; + arrow_radians_: number; + /** + * Create the bubble's DOM. + * @param {!Element} content SVG content for the bubble. + * @param {boolean} hasResize Add diagonal resize gripper if true. + * @return {!SVGElement} The bubble's SVG group. + * @private + */ + createDom_(content: Element, hasResize: boolean): SVGElement; + /** + * Return the root node of the bubble's SVG group. + * @return {!SVGElement} The root SVG node of the bubble's group. + */ + getSvgRoot(): SVGElement; + /** + * Expose the block's ID on the bubble's top-level SVG group. + * @param {string} id ID of block. + */ + setSvgId(id: string): void; + /** + * Handle a mouse-down on bubble's border. + * @param {!Event} e Mouse down event. + * @private + */ + bubbleMouseDown_(e: Event): void; + /** + * Show the context menu for this bubble. + * @param {!Event} _e Mouse event. + * @package + */ + showContextMenu(_e: Event): void; + /** + * Get whether this bubble is deletable or not. + * @return {boolean} True if deletable. + * @package + */ + isDeletable(): boolean; + /** + * Update the style of this bubble when it is dragged over a delete area. + * @param {boolean} _enable True if the bubble is about to be deleted, false + * otherwise. + */ + setDeleteStyle(_enable: boolean): void; + /** + * Handle a mouse-down on bubble's resize corner. + * @param {!Event} e Mouse down event. + * @private + */ + resizeMouseDown_(e: Event): void; + /** + * Resize this bubble to follow the mouse. + * @param {!Event} e Mouse move event. + * @private + */ + resizeMouseMove_(e: Event): void; + /** + * Register a function as a callback event for when the bubble is resized. + * @param {!Function} callback The function to call on resize. + */ + registerResizeEvent(callback: Function): void; + /** + * Register a function as a callback event for when the bubble is moved. + * @param {!Function} callback The function to call on move. + */ + registerMoveEvent(callback: Function): void; + /** + * Move this bubble to the top of the stack. + * @return {boolean} Whether or not the bubble has been moved. + * @package + */ + promote(): boolean; + /** + * Notification that the anchor has moved. + * Update the arrow and bubble accordingly. + * @param {!Coordinate} xy Absolute location. + */ + setAnchorLocation(xy: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): void; + /** + * Position the bubble so that it does not fall off-screen. + * @private + */ + layoutBubble_(): void; + /** + * Calculate the what percentage of the bubble overlaps with the visible + * workspace (what percentage of the bubble is visible). + * @param {!{x: number, y: number}} relativeMin The position of the top-left + * corner of the bubble relative to the anchor point. + * @param {!MetricsManager.ContainerRegion} viewMetrics The view metrics + * of the workspace the bubble will appear in. + * @return {number} The percentage of the bubble that is visible. + * @private + */ + getOverlap_(relativeMin: { + x: number; + y: number; + }, viewMetrics: MetricsManager.ContainerRegion): number; + /** + * Calculate what the optimal horizontal position of the top-left corner of + * the bubble is (relative to the anchor point) so that the most area of the + * bubble is shown. + * @param {!MetricsManager.ContainerRegion} viewMetrics The view metrics + * of the workspace the bubble will appear in. + * @return {number} The optimal horizontal position of the top-left corner + * of the bubble. + * @private + */ + getOptimalRelativeLeft_(viewMetrics: MetricsManager.ContainerRegion): number; + /** + * Calculate what the optimal vertical position of the top-left corner of + * the bubble is (relative to the anchor point) so that the most area of the + * bubble is shown. + * @param {!MetricsManager.ContainerRegion} viewMetrics The view metrics + * of the workspace the bubble will appear in. + * @return {number} The optimal vertical position of the top-left corner + * of the bubble. + * @private + */ + getOptimalRelativeTop_(viewMetrics: MetricsManager.ContainerRegion): number; + /** + * Move the bubble to a location relative to the anchor's centre. + * @private + */ + positionBubble_(): void; + /** + * 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 + */ + moveTo(x: number, y: number): void; + /** + * Triggers a move callback if one exists at the end of a drag. + * @param {boolean} adding True if adding, false if removing. + * @package + */ + setDragging(adding: boolean): void; + /** + * Get the dimensions of this bubble. + * @return {!Size} The height and width of the bubble. + */ + getBubbleSize(): { + width: number; + height: number; + }; + /** + * Size this bubble. + * @param {number} width Width of the bubble. + * @param {number} height Height of the bubble. + */ + setBubbleSize(width: number, height: number): void; + /** + * Draw the arrow between the bubble and the origin. + * @private + */ + renderArrow_(): void; + /** + * Change the colour of a bubble. + * @param {string} hexColour Hex code of colour. + */ + setColour(hexColour: string): void; + /** + * Dispose of this bubble. + */ + dispose(): void; + /** + * Move this bubble during a drag, taking into account whether or not there is + * a drag surface. + * @param {BlockDragSurfaceSvg} dragSurface The surface that carries + * rendered items during a drag, or null if no drag surface is in use. + * @param {!Coordinate} newLoc The location to translate to, in + * workspace coordinates. + * @package + */ + moveDuringDrag(dragSurface: { + SVG_: SVGElement | null; + dragGroup_: SVGElement | null; + container_: Element; + scale_: number; + surfaceXY_: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + } | null; + childSurfaceXY_: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + createDom(): void; + setBlocksAndShow(blocks: SVGElement): void; + translateAndScaleGroup(x: number, y: number, scale: number): void; + translateSurfaceInternal_(): void; + translateBy(deltaX: number, deltaY: number): void; + translateSurface(x: number, y: number): void; + getSurfaceTranslation(): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + getGroup(): SVGElement | null; + getSvgRoot(): SVGElement | null; + getCurrentBlock(): Element | null; + getWsTranslation(): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + clearAndHide(opt_newSurface?: Element | undefined): void; + }, newLoc: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): void; + /** + * 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 {!Coordinate} Object with .x and .y properties. + */ + getRelativeToSurfaceXY(): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + /** + * 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 + */ + setAutoLayout(enable: boolean): void; + }; + /** + * Width of the border around the bubble. + */ + BORDER_WIDTH: number; + /** + * Determines the thickness of the base of the arrow in relation to the size + * of the bubble. Higher numbers result in thinner arrows. + */ + ARROW_THICKNESS: number; + /** + * The number of degrees that the arrow bends counter-clockwise. + */ + ARROW_ANGLE: number; + /** + * The sharpness of the arrow's bend. Higher numbers result in smoother arrows. + */ + ARROW_BEND: number; + /** + * Distance between arrow point and anchor point. + */ + ANCHOR_RADIUS: number; + /** + * Mouse up event data. + * @type {?browserEvents.Data} + * @private + */ + onMouseUpWrapper_: any[][] | null; + /** + * Mouse move event data. + * @type {?browserEvents.Data} + * @private + */ + onMouseMoveWrapper_: any[][] | null; + }; + import { WorkspaceSvg } from "core/workspace_svg"; + import { MetricsManager } from "core/metrics_manager"; + import { BlockSvg } from "core/block_svg"; +} +declare module "core/constants" { + /** + * The language-neutral ID given to the collapsed input. + * @const {string} + * @alias Blockly.constants.COLLAPSED_INPUT_NAME + */ + export const COLLAPSED_INPUT_NAME: "_TEMP_COLLAPSED_INPUT"; + /** + * The language-neutral ID given to the collapsed field. + * @const {string} + * @alias Blockly.constants.COLLAPSED_FIELD_NAME + */ + export const COLLAPSED_FIELD_NAME: "_TEMP_COLLAPSED_FIELD"; +} +declare module "core/bubble_dragger" { + /** + * Class for a bubble dragger. It moves things on the bubble canvas around the + * workspace when they are being dragged by a mouse or touch. These can be + * block comments, mutators, warnings, or workspace comments. + * @alias Blockly.BubbleDragger + */ + export const BubbleDragger: { + new (bubble: () => void, workspace: WorkspaceSvg): { + /** + * The item on the bubble canvas that is being dragged. + * @type {!IBubble} + * @private + */ + draggingBubble_: () => void; + /** + * The workspace on which the bubble is being dragged. + * @type {!WorkspaceSvg} + * @private + */ + workspace_: WorkspaceSvg; + /** + * Which drag target the mouse pointer is over, if any. + * @type {?IDragTarget} + * @private + */ + dragTarget_: (() => void) | null; + /** + * Whether the bubble would be deleted if dropped immediately. + * @type {boolean} + * @private + */ + wouldDeleteBubble_: boolean; + /** + * The location of the top left corner of the dragging bubble's body at the + * beginning of the drag, in workspace coordinates. + * @type {!Coordinate} + * @private + */ + startXY_: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + /** + * 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 {BlockDragSurfaceSvg} + * @private + */ + dragSurface_: { + SVG_: SVGElement | null; + dragGroup_: SVGElement | null; + container_: Element; + scale_: number; + surfaceXY_: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + } | null; + childSurfaceXY_: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + createDom(): void; + setBlocksAndShow(blocks: SVGElement): void; + translateAndScaleGroup(x: number, y: number, scale: number): void; + translateSurfaceInternal_(): void; + translateBy(deltaX: number, deltaY: number): void; + translateSurface(x: number, y: number): void; + getSurfaceTranslation(): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + getGroup(): SVGElement | null; + getSvgRoot(): SVGElement | null; + getCurrentBlock(): Element | null; + getWsTranslation(): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + clearAndHide(opt_newSurface?: Element | undefined): void; + }; + /** + * Sever all links from this object. + * @package + * @suppress {checkTypes} + */ + dispose(): void; + /** + * Start dragging a bubble. This includes moving it to the drag surface. + * @package + */ + startBubbleDrag(): void; + /** + * Execute a step of bubble dragging, based on the given event. Update the + * display accordingly. + * @param {!Event} e The most recent move event. + * @param {!Coordinate} currentDragDeltaXY How far the pointer has + * moved from the position at the start of the drag, in pixel units. + * @package + */ + dragBubble(e: Event, currentDragDeltaXY: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): void; + /** + * Whether ending the drag would delete the bubble. + * @param {?IDragTarget} dragTarget The drag target that the bubblee is + * currently over. + * @return {boolean} Whether dropping the bubble immediately would delete the + * block. + * @private + */ + shouldDelete_(dragTarget: (() => void) | null): boolean; + /** + * Update the cursor (and possibly the trash can lid) to reflect whether the + * dragging bubble would be deleted if released immediately. + * @private + */ + updateCursorDuringBubbleDrag_(): void; + /** + * Finish a bubble drag and put the bubble back on the workspace. + * @param {!Event} e The mouseup/touchend event. + * @param {!Coordinate} currentDragDeltaXY How far the pointer has + * moved from the position at the start of the drag, in pixel units. + * @package + */ + endBubbleDrag(e: Event, currentDragDeltaXY: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): void; + /** + * Fire a move event at the end of a bubble drag. + * @private + */ + fireMoveEvent_(): void; + /** + * 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 {!Coordinate} pixelCoord A coordinate with x and y + * values in CSS pixel units. + * @return {!Coordinate} The input coordinate divided by the + * workspace scale. + * @private + */ + pixelsToWorkspaceUnits_(pixelCoord: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + /** + * 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 + */ + moveToDragSurface_(): void; + }; + }; + import { WorkspaceSvg } from "core/workspace_svg"; +} +declare module "core/config" { + /** + * Object holding all the values on Blockly that we expect developers to be + * able to change. + * @type {Config} + */ + export const config: Config; + /** + * All the values that we expect developers to be able to change + * before injecting Blockly. + */ + type Config = { + dragRadius: number; + flyoutDragRadius: number; + snapRadius: number; + currentConnectionPreference: number; + bumpDelay: number; + connectingSnapRadius: number; + }; + /** + * All the values that we expect developers to be able to change + * before injecting Blockly. + * @typedef {{ + * dragRadius: number, + * flyoutDragRadius: number, + * snapRadius: number, + * currentConnectionPreference: number, + * bumpDelay: number, + * connectingSnapRadius: number + * }} + */ + let Config: any; + export {}; +} +declare module "core/interfaces/i_block_dragger" { + /** + * A block dragger interface. + * @interface + * @alias Blockly.IBlockDragger + */ + export function IBlockDragger(): void; +} +declare module "core/workspace_dragger" { + /** + * Class for a workspace dragger. It moves the workspace around when it is + * being dragged by a mouse or touch. + * Note that the workspace itself manages whether or not it has a drag surface + * and how to do translations based on that. This simply passes the right + * commands based on events. + * @alias Blockly.WorkspaceDragger + */ + export class WorkspaceDragger { + /** + * @param {!WorkspaceSvg} workspace The workspace to drag. + */ + constructor(workspace: WorkspaceSvg); + /** + * @type {!WorkspaceSvg} + * @private + */ + private workspace_; + /** + * Whether horizontal scroll is enabled. + * @type {boolean} + * @private + */ + private horizontalScrollEnabled_; + /** + * Whether vertical scroll is enabled. + * @type {boolean} + * @private + */ + private verticalScrollEnabled_; + /** + * The scroll position of the workspace at the beginning of the drag. + * Coordinate system: pixel coordinates. + * @type {!Coordinate} + * @protected + */ + protected startScrollXY_: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + /** + * Sever all links from this object. + * @package + * @suppress {checkTypes} + */ + dispose(): void; + /** + * Start dragging the workspace. + * @package + */ + startDrag(): void; + /** + * Finish dragging the workspace and put everything back where it belongs. + * @param {!Coordinate} currentDragDeltaXY How far the pointer has + * moved from the position at the start of the drag, in pixel coordinates. + * @package + */ + endDrag(currentDragDeltaXY: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): void; + /** + * Move the workspace based on the most recent mouse movements. + * @param {!Coordinate} currentDragDeltaXY How far the pointer has + * moved from the position at the start of the drag, in pixel coordinates. + * @package + */ + drag(currentDragDeltaXY: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): void; + } + import { WorkspaceSvg } from "core/workspace_svg"; +} +declare module "core/events/events_viewport" { + /** + * Class for a viewport change event. + * @extends {UiBase} + * @alias Blockly.Events.ViewportChange + */ + export class ViewportChange extends UiBase { + /** + * @param {number=} opt_top Top-edge of the visible portion of the workspace, + * relative to the workspace origin. Undefined for a blank event. + * @param {number=} opt_left Left-edge of the visible portion of the + * workspace relative to the workspace origin. Undefined for a blank + * event. + * @param {number=} opt_scale The scale of the workspace. Undefined for a + * blank event. + * @param {string=} opt_workspaceId The workspace identifier for this event. + * Undefined for a blank event. + * @param {number=} opt_oldScale The old scale of the workspace. Undefined for + * a blank event. + */ + constructor(opt_top?: number | undefined, opt_left?: number | undefined, opt_scale?: number | undefined, opt_workspaceId?: string | undefined, opt_oldScale?: number | undefined); + /** + * Top-edge of the visible portion of the workspace, relative to the + * workspace origin. + * @type {number|undefined} + */ + viewTop: number | undefined; + /** + * Left-edge of the visible portion of the workspace, relative to the + * workspace origin. + * @type {number|undefined} + */ + viewLeft: number | undefined; + /** + * The scale of the workspace. + * @type {number|undefined} + */ + scale: number | undefined; + /** + * The old scale of the workspace. + * @type {number|undefined} + */ + oldScale: number | undefined; + } + import { UiBase } from "core/events/events_ui_base"; +} +declare module "core/bump_objects" { + /** + * Bumps the given object that has passed out of bounds. + * @param {!WorkspaceSvg} workspace The workspace containing the object. + * @param {!MetricsManager.ContainerRegion} scrollMetrics Scroll metrics + * in workspace coordinates. + * @param {!IBoundedElement} object The object to bump. + * @return {boolean} True if block was bumped. + * @alias Blockly.bumpObjects.bumpIntoBounds + */ + function bumpObjectIntoBounds(workspace: WorkspaceSvg, scrollMetrics: MetricsManager.ContainerRegion, object: () => void): boolean; + /** + * Creates a handler for bumping objects when they cross fixed bounds. + * @param {!WorkspaceSvg} workspace The workspace to handle. + * @return {function(Abstract)} The event handler. + * @alias Blockly.bumpObjects.bumpIntoBoundsHandler + */ + export function bumpIntoBoundsHandler(workspace: WorkspaceSvg): (arg0: Abstract) => any; + /** + * Bumps the top objects in the given workspace into bounds. + * @param {!WorkspaceSvg} workspace The workspace. + * @alias Blockly.bumpObjects.bumpTopObjectsIntoBounds + */ + export function bumpTopObjectsIntoBounds(workspace: WorkspaceSvg): void; + import { WorkspaceSvg } from "core/workspace_svg"; + import { MetricsManager } from "core/metrics_manager"; + import { Abstract } from "core/events/events_abstract"; + export { bumpObjectIntoBounds as bumpIntoBounds }; +} +declare module "core/events/events_block_base" { /** * Abstract class for a block event. - * @param {!Block=} opt_block The block this event corresponds to. - * Undefined for a blank event. - * @extends {Abstract} - * @constructor + * @extends {AbstractEvent} * @alias Blockly.Events.BlockBase */ - export class BlockBase { - constructor(opt_block: any); - isBlank: boolean; + export class BlockBase extends AbstractEvent { + /** + * @param {!Block=} opt_block The block this event corresponds to. + * Undefined for a blank event. + */ + constructor(opt_block?: Block | undefined); /** * The block ID for the block this event pertains to * @type {string} */ blockId: string; - /** - * The workspace identifier for this event. - * @type {string} - */ - workspaceId: string; - /** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ - toJson(): any; - /** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ - fromJson(json: any): void; } + import { Abstract as AbstractEvent } from "core/events/events_abstract"; + import { Block } from "core/block"; } -declare module "events/events_block_change" { - export class BlockChange { +declare module "core/events/events_block_move" { + /** + * Class for a block move event. Created before the move. + * @extends {BlockBase} + * @alias Blockly.Events.BlockMove + */ + export class BlockMove extends BlockBase { + oldParentId: any; + oldInputName: any; + oldCoordinate: any; + newParentId: any; + newInputName: any; + newCoordinate: any; /** - * Returns the extra state of the given block (either as XML or a JSO, depending - * on the block's definition). - * @param {!BlockSvg} block The block to get the extra state of. - * @return {string} A stringified version of the extra state of the given block. + * Record the block's new location. Called after the move. + */ + recordNew(): void; + /** + * Returns the parentId and input if the block is connected, + * or the XY location if disconnected. + * @return {!Object} Collection of location info. + * @private + */ + private currentLocation_; + } + import { BlockBase } from "core/events/events_block_base"; +} +declare module "core/utils/svg_paths" { + /** + * Create a string representing the given x, y pair. It does not matter whether + * the coordinate is relative or absolute. The result has leading + * and trailing spaces, and separates the x and y coordinates with a comma but + * no space. + * @param {number} x The x coordinate. + * @param {number} y The y coordinate. + * @return {string} A string of the format ' x,y ' + * @alias Blockly.utils.svgPaths.point + */ + export function point(x: number, y: number): string; + /** + * Draw a cubic or quadratic curve. See + * developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#Cubic_B%C3%A9zier_Curve + * These coordinates are unitless and hence in the user coordinate system. + * @param {string} command The command to use. + * Should be one of: c, C, s, S, q, Q. + * @param {!Array} points An array containing all of the points to pass + * to the curve command, in order. The points are represented as strings of + * the format ' x, y '. + * @return {string} A string defining one or more Bezier curves. See the MDN + * documentation for exact format. + * @alias Blockly.utils.svgPaths.curve + */ + export function curve(command: string, points: Array): string; + /** + * Move the cursor to the given position without drawing a line. + * The coordinates are absolute. + * These coordinates are unitless and hence in the user coordinate system. + * See developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths#Line_commands + * @param {number} x The absolute x coordinate. + * @param {number} y The absolute y coordinate. + * @return {string} A string of the format ' M x,y ' + * @alias Blockly.utils.svgPaths.moveTo + */ + export function moveTo(x: number, y: number): string; + /** + * Move the cursor to the given position without drawing a line. + * Coordinates are relative. + * These coordinates are unitless and hence in the user coordinate system. + * See developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths#Line_commands + * @param {number} dx The relative x coordinate. + * @param {number} dy The relative y coordinate. + * @return {string} A string of the format ' m dx,dy ' + * @alias Blockly.utils.svgPaths.moveBy + */ + export function moveBy(dx: number, dy: number): string; + /** + * Draw a line from the current point to the end point, which is the current + * point shifted by dx along the x-axis and dy along the y-axis. + * These coordinates are unitless and hence in the user coordinate system. + * See developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths#Line_commands + * @param {number} dx The relative x coordinate. + * @param {number} dy The relative y coordinate. + * @return {string} A string of the format ' l dx,dy ' + * @alias Blockly.utils.svgPaths.lineTo + */ + export function lineTo(dx: number, dy: number): string; + /** + * Draw multiple lines connecting all of the given points in order. This is + * equivalent to a series of 'l' commands. + * These coordinates are unitless and hence in the user coordinate system. + * See developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths#Line_commands + * @param {!Array} points An array containing all of the points to + * draw lines to, in order. The points are represented as strings of the + * format ' dx,dy '. + * @return {string} A string of the format ' l (dx,dy)+ ' + * @alias Blockly.utils.svgPaths.line + */ + export function line(points: Array): string; + /** + * Draw a horizontal or vertical line. + * The first argument specifies the direction and whether the given position is + * relative or absolute. + * These coordinates are unitless and hence in the user coordinate system. + * See developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#LineTo_path_commands + * @param {string} command The command to prepend to the coordinate. This + * should be one of: V, v, H, h. + * @param {number} val The coordinate to pass to the command. It may be + * absolute or relative. + * @return {string} A string of the format ' command val ' + * @alias Blockly.utils.svgPaths.lineOnAxis + */ + export function lineOnAxis(command: string, val: number): string; + /** + * Draw an elliptical arc curve. + * These coordinates are unitless and hence in the user coordinate system. + * See developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#Elliptical_Arc_Curve + * @param {string} command The command string. Either 'a' or 'A'. + * @param {string} flags The flag string. See the MDN documentation for a + * description and examples. + * @param {number} radius The radius of the arc to draw. + * @param {string} point The point to move the cursor to after drawing the arc, + * specified either in absolute or relative coordinates depending on the + * command. + * @return {string} A string of the format 'command radius radius flags point' + * @alias Blockly.utils.svgPaths.arc + */ + export function arc(command: string, flags: string, radius: number, point: string): string; +} +declare module "core/interfaces/i_connection_checker" { + /** + * Class for connection type checking logic. + * @interface + * @alias Blockly.IConnectionChecker + */ + export class IConnectionChecker {} +} +declare module "core/connection_db" { + /** + * Database of connections. + * Connections are stored in order of their vertical component. This way + * connections in an area may be looked up quickly using a binary search. + * @alias Blockly.ConnectionDB + */ + export class ConnectionDB { + /** + * Initialize a set of connection DBs for a workspace. + * @param {!IConnectionChecker} checker The workspace's + * connection checker, used to decide if connections are valid during a + * drag. + * @return {!Array} Array of databases. + */ + static init(checker: () => void): Array; + /** + * @param {!IConnectionChecker} checker The workspace's + * connection type checker, used to decide if connections are valid during + * a drag. + */ + constructor(checker: () => void); + /** + * Array of connections sorted by y position in workspace units. + * @type {!Array} + * @private + */ + private connections_; + /** + * The workspace's connection type checker, used to decide if connections + * are valid during a drag. + * @type {!IConnectionChecker} + * @private + */ + private connectionChecker_; + /** + * Add a connection to the database. Should not already exist in the database. + * @param {!RenderedConnection} connection The connection to be added. + * @param {number} yPos The y position used to decide where to insert the + * connection. * @package */ - static getExtraBlockState_(block: BlockSvg): string; + addConnection(connection: RenderedConnection, yPos: number): void; /** - * Class for a block change event. - * @param {!Block=} opt_block The changed block. Undefined for a blank - * event. - * @param {string=} opt_element One of 'field', 'comment', 'disabled', etc. - * @param {?string=} opt_name Name of input or field affected, or null. - * @param {*=} opt_oldValue Previous value of element. - * @param {*=} opt_newValue New value of element. - * @extends {BlockBase} - * @constructor - * @alias Blockly.Events.BlockChange + * Finds the index of the given connection. + * + * Starts by doing a binary search to find the approximate location, then + * linearly searches nearby for the exact connection. + * @param {!RenderedConnection} conn The connection to find. + * @param {number} yPos The y position used to find the index of the + * connection. + * @return {number} The index of the connection, or -1 if the connection was + * not found. + * @private */ - constructor(opt_block?: Block | undefined, opt_element?: string | undefined, opt_name?: (string | null) | undefined, opt_oldValue?: any | undefined, opt_newValue?: any | undefined); - element: string; - name: string; - oldValue: any; - newValue: any; + private findIndexOfConnection_; /** - * Encode the event as JSON. - * @return {!Object} JSON representation. + * Finds the correct index for the given y position. + * @param {number} yPos The y position used to decide where to + * insert the connection. + * @return {number} The candidate index. + * @private */ - toJson(): any; + private calculateIndexForYPos_; /** - * Decode the JSON event. - * @param {!Object} json JSON representation. + * Remove a connection from the database. Must already exist in DB. + * @param {!RenderedConnection} connection The connection to be removed. + * @param {number} yPos The y position used to find the index of the + * connection. + * @throws {Error} If the connection cannot be found in the database. */ - fromJson(json: any): void; + removeConnection(connection: RenderedConnection, yPos: number): void; /** - * Does this event record any change of state? - * @return {boolean} False if something changed. + * Find all nearby connections to the given connection. + * Type checking does not apply, since this function is used for bumping. + * @param {!RenderedConnection} connection The connection whose + * neighbours should be returned. + * @param {number} maxRadius The maximum radius to another connection. + * @return {!Array} List of connections. */ - isNull(): boolean; + getNeighbours(connection: RenderedConnection, maxRadius: number): Array; /** - * Run a change event. - * @param {boolean} forward True if run forward, false if run backward (undo). + * Is the candidate connection close to the reference connection. + * Extremely fast; only looks at Y distance. + * @param {number} index Index in database of candidate connection. + * @param {number} baseY Reference connection's Y value. + * @param {number} maxRadius The maximum radius to another connection. + * @return {boolean} True if connection is in range. + * @private */ - run(forward: boolean): void; + private isInYRange_; /** - * Type of this event. + * Find the closest compatible connection to this connection. + * @param {!RenderedConnection} conn The connection searching for a compatible + * mate. + * @param {number} maxRadius The maximum radius to another connection. + * @param {!Coordinate} dxy Offset between this connection's + * location in the database and the current location (as a result of + * dragging). + * @return {!{connection: RenderedConnection, radius: number}} + * Contains two properties: 'connection' which is either another + * connection or null, and 'radius' which is the distance. + */ + searchForClosest(conn: RenderedConnection, maxRadius: number, dxy: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): { + connection: RenderedConnection; + radius: number; + }; + } + import { RenderedConnection } from "core/rendered_connection"; +} +declare module "core/rendered_connection" { + /** + * Class for a connection between blocks that may be rendered on screen. + * @extends {Connection} + * @alias Blockly.RenderedConnection + */ + export class RenderedConnection extends Connection { + /** + * @param {!BlockSvg} source The block establishing this connection. + * @param {number} type The type of the connection. + */ + constructor(source: BlockSvg, type: number); + /** + * Connection database for connections of this type on the current + * workspace. + * @const {!ConnectionDB} + * @private + */ + private db_; + /** + * Connection database for connections compatible with this type on the + * current workspace. + * @const {!ConnectionDB} + * @private + */ + private dbOpposite_; + /** + * Workspace units, (0, 0) is top left of block. + * @type {!Coordinate} + * @private + */ + private offsetInBlock_; + /** + * Describes the state of this connection's tracked-ness. + * @type {RenderedConnection.TrackedState} + * @private + */ + private trackedState_; + /** + * Returns the distance between this connection and another connection in + * workspace units. + * @param {!Connection} otherConnection The other connection to measure + * the distance to. + * @return {number} The distance between connections, in workspace units. + */ + distanceFrom(otherConnection: Connection): number; + /** + * Move the block(s) belonging to the connection to a point where they don't + * visually interfere with the specified connection. + * @param {!RenderedConnection} staticConnection The connection to move away + * from. + * @package + */ + bumpAwayFrom(staticConnection: RenderedConnection): void; + /** + * Change the connection's coordinates. + * @param {number} x New absolute x coordinate, in workspace coordinates. + * @param {number} y New absolute y coordinate, in workspace coordinates. + */ + moveTo(x: number, y: number): void; + /** + * Change the connection's coordinates. + * @param {number} dx Change to x coordinate, in workspace units. + * @param {number} dy Change to y coordinate, in workspace units. + */ + moveBy(dx: number, dy: number): void; + /** + * Move this connection to the location given by its offset within the block + * and the location of the block's top left corner. + * @param {!Coordinate} blockTL The location of the top left + * corner of the block, in workspace coordinates. + */ + moveToOffset(blockTL: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): void; + /** + * Set the offset of this connection relative to the top left of its block. + * @param {number} x The new relative x, in workspace units. + * @param {number} y The new relative y, in workspace units. + */ + setOffsetInBlock(x: number, y: number): void; + /** + * Get the offset of this connection relative to the top left of its block. + * @return {!Coordinate} The offset of the connection. + * @package + */ + getOffsetInBlock(): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + /** + * Move the blocks on either side of this connection right next to each other. + * @package + */ + tighten(): void; + /** + * Find the closest compatible connection to this connection. + * All parameters are in workspace units. + * @param {number} maxLimit The maximum radius to another connection. + * @param {!Coordinate} dxy Offset between this connection's location + * in the database and the current location (as a result of dragging). + * @return {!{connection: ?Connection, radius: number}} Contains two + * properties: 'connection' which is either another connection or null, + * and 'radius' which is the distance. + */ + closest(maxLimit: number, dxy: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): { + connection: Connection | null; + radius: number; + }; + /** + * Add highlighting around this connection. + */ + highlight(): void; + /** + * Remove the highlighting around this connection. + */ + unhighlight(): void; + /** + * Set whether this connections is tracked in the database or not. + * @param {boolean} doTracking If true, start tracking. If false, stop + * tracking. + * @package + */ + setTracking(doTracking: boolean): void; + /** + * Stop tracking this connection, as well as all down-stream connections on + * any block attached to this connection. This happens when a block is + * collapsed. + * + * Also closes down-stream icons/bubbles. + * @package + */ + stopTrackingAll(): void; + /** + * Start tracking this connection, as well as all down-stream connections on + * any block attached to this connection. This happens when a block is + * expanded. + * @return {!Array} List of blocks to render. + */ + startTrackingAll(): Array; + } + export namespace RenderedConnection { + namespace TrackedState { + const WILL_TRACK: number; + const UNTRACKED: number; + const TRACKED: number; + } + /** + * Enum for different kinds of tracked states. + * + * WILL_TRACK means that this connection will add itself to + * the db on the next moveTo call it receives. + * + * UNTRACKED means that this connection will not add + * itself to the database until setTracking(true) is explicitly called. + * + * TRACKED means that this connection is currently being tracked. + */ + type TrackedState = number; + } + import { Connection } from "core/connection"; + import { Block } from "core/block"; + import { BlockSvg } from "core/block_svg"; +} +declare module "core/insertion_marker_manager" { + /** + * Class that controls updates to connections during drags. It is primarily + * responsible for finding the closest eligible connection and highlighting or + * unhighlighting it as needed during a drag. + * @alias Blockly.InsertionMarkerManager + */ + export class InsertionMarkerManager { + /** + * @param {!BlockSvg} block The top block in the stack being dragged. + */ + constructor(block: BlockSvg); + /** + * The top block in the stack being dragged. + * Does not change during a drag. + * @type {!BlockSvg} + * @private + */ + private topBlock_; + /** + * The workspace on which these connections are being dragged. + * Does not change during a drag. + * @type {!WorkspaceSvg} + * @private + */ + private workspace_; + /** + * The last connection on the stack, if it's not the last connection on the + * first block. + * Set in initAvailableConnections, if at all. + * @type {RenderedConnection} + * @private + */ + private lastOnStack_; + /** + * The insertion marker corresponding to the last block in the stack, if + * that's not the same as the first block in the stack. + * Set in initAvailableConnections, if at all + * @type {BlockSvg} + * @private + */ + private lastMarker_; + /** + * The insertion marker that shows up between blocks to show where a block + * would go if dropped immediately. + * @type {BlockSvg} + * @private + */ + private firstMarker_; + /** + * The connection that this block would connect to if released immediately. + * Updated on every mouse move. + * This is not on any of the blocks that are being dragged. + * @type {RenderedConnection} + * @private + */ + private closestConnection_; + /** + * The connection that would connect to this.closestConnection_ if this + * block were released immediately. Updated on every mouse move. This is on + * the top block that is being dragged or the last block in the dragging + * stack. + * @type {RenderedConnection} + * @private + */ + private localConnection_; + /** + * Whether the block would be deleted if it were dropped immediately. + * Updated on every mouse move. + * @type {boolean} + * @private + */ + private wouldDeleteBlock_; + /** + * Connection on the insertion marker block that corresponds to + * this.localConnection_ on the currently dragged block. + * @type {RenderedConnection} + * @private + */ + private markerConnection_; + /** + * The block that currently has an input being highlighted, or null. + * @type {BlockSvg} + * @private + */ + private highlightedBlock_; + /** + * The block being faded to indicate replacement, or null. + * @type {BlockSvg} + * @private + */ + private fadedBlock_; + /** + * The connections on the dragging blocks that are available to connect to + * other blocks. This includes all open connections on the top block, as + * well as the last connection on the block stack. Does not change during a + * drag. + * @type {!Array} + * @private + */ + private availableConnections_; + /** + * Sever all links from this object. + * @package + */ + dispose(): void; + /** + * Update the available connections for the top block. These connections can + * change if a block is unplugged and the stack is healed. + * @package + */ + updateAvailableConnections(): void; + /** + * Return whether the block would be deleted if dropped immediately, based on + * information from the most recent move event. + * @return {boolean} True if the block would be deleted if dropped + * immediately. + * @package + */ + wouldDeleteBlock(): boolean; + /** + * Return whether the block would be connected if dropped immediately, based + * on information from the most recent move event. + * @return {boolean} True if the block would be connected if dropped + * immediately. + * @package + */ + wouldConnectBlock(): boolean; + /** + * Connect to the closest connection and render the results. + * This should be called at the end of a drag. + * @package + */ + applyConnections(): void; + /** + * Update connections based on the most recent move location. + * @param {!Coordinate} dxy Position relative to drag start, + * in workspace units. + * @param {?IDragTarget} dragTarget The drag target that the block is + * currently over. + * @package + */ + update(dxy: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }, dragTarget: (() => void) | null): void; + /** + * Create an insertion marker that represents the given block. + * @param {!BlockSvg} sourceBlock The block that the insertion marker + * will represent. + * @return {!BlockSvg} The insertion marker that represents the given + * block. + * @private + */ + private createMarkerBlock_; + /** + * Populate the list of available connections on this block stack. This + * should only be called once, at the beginning of a drag. If the stack has + * more than one block, this function will populate lastOnStack_ and create + * the corresponding insertion marker. + * @return {!Array} A list of available + * connections. + * @private + */ + private initAvailableConnections_; + /** + * Whether the previews (insertion marker and replacement marker) should be + * updated based on the closest candidate and the current drag distance. + * @param {!Object} candidate An object containing a local connection, a + * closest connection, and a radius. Returned by getCandidate_. + * @param {!Coordinate} dxy Position relative to drag start, + * in workspace units. + * @return {boolean} Whether the preview should be updated. + * @private + */ + private shouldUpdatePreviews_; + /** + * Find the nearest valid connection, which may be the same as the current + * closest connection. + * @param {!Coordinate} dxy Position relative to drag start, + * in workspace units. + * @return {!Object} An object containing a local connection, a closest + * connection, and a radius. + * @private + */ + private getCandidate_; + /** + * Decide the radius at which to start searching for the closest connection. + * @return {number} The radius at which to start the search for the closest + * connection. + * @private + */ + private getStartRadius_; + /** + * Whether ending the drag would delete the block. + * @param {!Object} candidate An object containing a local connection, a + * closest + * connection, and a radius. + * @param {?IDragTarget} dragTarget The drag target that the block is + * currently over. + * @return {boolean} Whether dropping the block immediately would delete the + * block. + * @private + */ + private shouldDelete_; + /** + * Show an insertion marker or replacement highlighting during a drag, if + * needed. + * At the beginning of this function, this.localConnection_ and + * this.closestConnection_ should both be null. + * @param {!Object} candidate An object containing a local connection, a + * closest connection, and a radius. + * @private + */ + private maybeShowPreview_; + /** + * A preview should be shown. This function figures out if it should be a + * block highlight or an insertion marker, and shows the appropriate one. + * @private + */ + private showPreview_; + /** + * Show an insertion marker or replacement highlighting during a drag, if + * needed. + * At the end of this function, this.localConnection_ and + * this.closestConnection_ should both be null. + * @param {!Object} candidate An object containing a local connection, a + * closest connection, and a radius. + * @private + */ + private maybeHidePreview_; + /** + * A preview should be hidden. This function figures out if it is a block + * highlight or an insertion marker, and hides the appropriate one. + * @private + */ + private hidePreview_; + /** + * Shows an insertion marker connected to the appropriate blocks (based on + * manager state). + * @private + */ + private showInsertionMarker_; + /** + * Disconnects and hides the current insertion marker. Should return the + * blocks to their original state. + * @private + */ + private hideInsertionMarker_; + /** + * Shows an outline around the input the closest connection belongs to. + * @private + */ + private showInsertionInputOutline_; + /** + * Hides any visible input outlines. + * @private + */ + private hideInsertionInputOutline_; + /** + * Shows a replacement fade affect on the closest connection's target block + * (the block that is currently connected to it). + * @private + */ + private showReplacementFade_; + /** + * Hides/Removes any visible fade affects. + * @private + */ + private hideReplacementFade_; + /** + * Get a list of the insertion markers that currently exist. Drags have 0, 1, + * or 2 insertion markers. + * @return {!Array} A possibly empty list of insertion + * marker blocks. + * @package + */ + getInsertionMarkers(): Array; + } + export namespace InsertionMarkerManager { + namespace PREVIEW_TYPE { + const INSERTION_MARKER: number; + const INPUT_OUTLINE: number; + const REPLACEMENT_FADE: number; + } + /** + * An enum describing different kinds of previews the InsertionMarkerManager + * could display. + */ + type PREVIEW_TYPE = number; + } + import { BlockSvg } from "core/block_svg"; +} +declare module "core/events/events_block_drag" { + /** + * Class for a block drag event. + * @extends {UiBase} + * @alias Blockly.Events.BlockDrag + */ + export class BlockDrag extends UiBase { + /** + * @param {!Block=} opt_block The top block in the stack that is being + * dragged. Undefined for a blank event. + * @param {boolean=} opt_isStart Whether this is the start of a block drag. + * Undefined for a blank event. + * @param {!Array=} opt_blocks The blocks affected by this + * drag. Undefined for a blank event. + */ + constructor(opt_block?: Block | undefined, opt_isStart?: boolean | undefined, opt_blocks?: Array | undefined); + blockId: string | null; + /** + * Whether this is the start of a block drag. + * @type {boolean|undefined} + */ + isStart: boolean | undefined; + /** + * The blocks affected by this drag event. + * @type {!Array|undefined} + */ + blocks: Array | undefined; + } + import { UiBase } from "core/events/events_ui_base"; + import { Block } from "core/block"; +} +declare module "core/block_dragger" { + /** + * Class for a block dragger. It moves blocks around the workspace when they + * are being dragged by a mouse or touch. + * @implements {IBlockDragger} + * @alias Blockly.BlockDragger + */ + export const BlockDragger: { + new (block: BlockSvg, workspace: WorkspaceSvg): { + /** + * The top block in the stack that is being dragged. + * @type {!BlockSvg} + * @protected + */ + draggingBlock_: BlockSvg; + /** + * The workspace on which the block is being dragged. + * @type {!WorkspaceSvg} + * @protected + */ + workspace_: WorkspaceSvg; + /** + * Object that keeps track of connections on dragged blocks. + * @type {!InsertionMarkerManager} + * @protected + */ + draggedConnectionManager_: InsertionMarkerManager; + /** + * Which drag area the mouse pointer is over, if any. + * @type {?IDragTarget} + * @private + */ + dragTarget_: (() => void) | null; + /** + * Whether the block would be deleted if dropped immediately. + * @type {boolean} + * @protected + */ + wouldDeleteBlock_: boolean; + /** + * The location of the top left corner of the dragging block at the + * beginning of the drag in workspace coordinates. + * @type {!Coordinate} + * @protected + */ + startXY_: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + /** + * A list of all of the icons (comment, warning, and mutator) that are + * on this block and its descendants. Moving an icon moves the bubble that + * extends from it if that bubble is open. + * @type {Array} + * @protected + */ + dragIconData_: Array; + /** + * Sever all links from this object. + * @package + */ + dispose(): void; + /** + * Start dragging a block. This includes moving it to the drag surface. + * @param {!Coordinate} currentDragDeltaXY How far the pointer has + * moved from the position at mouse down, in pixel units. + * @param {boolean} healStack Whether or not to heal the stack after + * disconnecting. + * @public + */ + startDrag(currentDragDeltaXY: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }, healStack: boolean): void; + /** + * Whether or not we should disconnect the block when a drag is started. + * @param {boolean} healStack Whether or not to heal the stack after + * disconnecting. + * @return {boolean} True to disconnect the block, false otherwise. + * @protected + */ + shouldDisconnect_(healStack: boolean): boolean; + /** + * Disconnects the block and moves it to a new location. + * @param {boolean} healStack Whether or not to heal the stack after + * disconnecting. + * @param {!Coordinate} currentDragDeltaXY How far the pointer has + * moved from the position at mouse down, in pixel units. + * @protected + */ + disconnectBlock_(healStack: boolean, currentDragDeltaXY: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): void; + /** + * Fire a UI event at the start of a block drag. + * @protected + */ + fireDragStartEvent_(): void; + /** + * Execute a step of block dragging, based on the given event. Update the + * display accordingly. + * @param {!Event} e The most recent move event. + * @param {!Coordinate} currentDragDeltaXY How far the pointer has + * moved from the position at the start of the drag, in pixel units. + * @public + */ + drag(e: Event, currentDragDeltaXY: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): void; + /** + * Finish a block drag and put the block back on the workspace. + * @param {!Event} e The mouseup/touchend event. + * @param {!Coordinate} currentDragDeltaXY How far the pointer has + * moved from the position at the start of the drag, in pixel units. + * @public + */ + endDrag(e: Event, currentDragDeltaXY: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): void; + /** + * Calculates the drag delta and new location values after a block is dragged. + * @param {!Coordinate} currentDragDeltaXY How far the pointer has + * moved from the start of the drag, in pixel units. + * @return {{delta: !Coordinate, newLocation: + * !Coordinate}} New location after drag. delta is in + * workspace units. newLocation is the new coordinate where the block + * should end up. + * @protected + */ + getNewLocationAfterDrag_(currentDragDeltaXY: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): { + delta: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + newLocation: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + }; + /** + * May delete the dragging block, if allowed. If `this.wouldDeleteBlock_` is + * not true, the block will not be deleted. This should be called at the end + * of a block drag. + * @return {boolean} True if the block was deleted. + * @protected + */ + maybeDeleteBlock_(): boolean; + /** + * Updates the necessary information to place a block at a certain location. + * @param {!Coordinate} delta The change in location from where + * the block started the drag to where it ended the drag. + * @protected + */ + updateBlockAfterMove_(delta: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): void; + /** + * Fire a UI event at the end of a block drag. + * @protected + */ + fireDragEndEvent_(): void; + /** + * Adds or removes the style of the cursor for the toolbox. + * This is what changes the cursor to display an x when a deletable block is + * held over the toolbox. + * @param {boolean} isEnd True if we are at the end of a drag, false + * otherwise. + * @protected + */ + updateToolboxStyle_(isEnd: boolean): void; + /** + * Fire a move event at the end of a block drag. + * @protected + */ + fireMoveEvent_(): void; + /** + * Update the cursor (and possibly the trash can lid) to reflect whether the + * dragging block would be deleted if released immediately. + * @protected + */ + updateCursorDuringBlockDrag_(): void; + /** + * 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 {!Coordinate} pixelCoord A coordinate with x and y + * values in CSS pixel units. + * @return {!Coordinate} The input coordinate divided by the + * workspace scale. + * @protected + */ + pixelsToWorkspaceUnits_(pixelCoord: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + /** + * Move all of the icons connected to this drag. + * @param {!Coordinate} dxy How far to move the icons from their + * original positions, in workspace units. + * @protected + */ + dragIcons_(dxy: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): void; + /** + * Get a list of the insertion markers that currently exist. Drags have 0, 1, + * or 2 insertion markers. + * @return {!Array} A possibly empty list of insertion + * marker blocks. + * @public + */ + getInsertionMarkers(): Array; + }; + }; + import { BlockSvg } from "core/block_svg"; + import { WorkspaceSvg } from "core/workspace_svg"; + import { InsertionMarkerManager } from "core/insertion_marker_manager"; +} +declare module "core/events/events_click" { + /** + * Class for a click event. + * @extends {UiBase} + * @alias Blockly.Events.Click + */ + export class Click extends UiBase { + /** + * @param {?Block=} opt_block The affected block. Null for click events + * that do not have an associated block (i.e. workspace click). Undefined + * for a blank event. + * @param {?string=} opt_workspaceId The workspace identifier for this event. + * Not used if block is passed. Undefined for a blank event. + * @param {string=} opt_targetType The type of element targeted by this click + * event. Undefined for a blank event. + */ + constructor(opt_block?: (Block | null) | undefined, opt_workspaceId?: (string | null) | undefined, opt_targetType?: string | undefined); + blockId: string | null; + /** + * The type of element targeted by this click event. + * @type {string|undefined} + */ + targetType: string | undefined; + } + import { UiBase } from "core/events/events_ui_base"; + import { Block } from "core/block"; +} +declare module "core/gesture" { + /** + * Note: In this file "start" refers to touchstart, mousedown, and pointerstart + * events. "End" refers to touchend, mouseup, and pointerend events. + */ + /** + * Class for one gesture. + * @alias Blockly.Gesture + */ + export class Gesture { + /** + * Is a drag or other gesture currently in progress on any workspace? + * @return {boolean} True if gesture is occurring. + */ + static inProgress(): boolean; + /** + * @param {!Event} e The event that kicked off this gesture. + * @param {!WorkspaceSvg} creatorWorkspace The workspace that created + * this gesture and has a reference to it. + */ + constructor(e: Event, creatorWorkspace: WorkspaceSvg); + /** + * The position of the mouse when the gesture started. Units are CSS + * pixels, with (0, 0) at the top left of the browser window (mouseEvent + * clientX/Y). + * @type {Coordinate} + * @private + */ + private mouseDownXY_; + /** + * How far the mouse has moved during this drag, in pixel units. + * (0, 0) is at this.mouseDownXY_. + * @type {!Coordinate} + * @private + */ + private currentDragDeltaXY_; + /** + * The bubble that the gesture started on, or null if it did not start on a + * bubble. + * @type {IBubble} + * @private + */ + private startBubble_; + /** + * The field that the gesture started on, or null if it did not start on a + * field. + * @type {Field} + * @private + */ + private startField_; + /** + * The block that the gesture started on, or null if it did not start on a + * block. + * @type {BlockSvg} + * @private + */ + private startBlock_; + /** + * The block that this gesture targets. If the gesture started on a + * shadow block, this is the first non-shadow parent of the block. If the + * gesture started in the flyout, this is the root block of the block group + * that was clicked or dragged. + * @type {BlockSvg} + * @private + */ + private targetBlock_; + /** + * The workspace that the gesture started on. There may be multiple + * workspaces on a page; this is more accurate than using + * Blockly.common.getMainWorkspace(). + * @type {WorkspaceSvg} + * @protected + */ + protected startWorkspace_: WorkspaceSvg; + /** + * The workspace that created this gesture. This workspace keeps a + * reference to the gesture, which will need to be cleared at deletion. This + * may be different from the start workspace. For instance, a flyout is a + * workspace, but its parent workspace manages gestures for it. + * @type {!WorkspaceSvg} + * @private + */ + private creatorWorkspace_; + /** + * Whether the pointer has at any point moved out of the drag radius. + * A gesture that exceeds the drag radius is a drag even if it ends exactly + * at its start point. + * @type {boolean} + * @private + */ + private hasExceededDragRadius_; + /** + * Whether the workspace is currently being dragged. + * @type {boolean} + * @private + */ + private isDraggingWorkspace_; + /** + * Whether the block is currently being dragged. + * @type {boolean} + * @private + */ + private isDraggingBlock_; + /** + * Whether the bubble is currently being dragged. + * @type {boolean} + * @private + */ + private isDraggingBubble_; + /** + * The event that most recently updated this gesture. + * @type {!Event} + * @private + */ + private mostRecentEvent_; + /** + * A handle to use to unbind a mouse move listener at the end of a drag. + * Opaque data returned from Blockly.bindEventWithChecks_. + * @type {?browserEvents.Data} + * @protected + */ + protected onMoveWrapper_: any[][] | null; + /** + * A handle to use to unbind a mouse up listener at the end of a drag. + * Opaque data returned from Blockly.bindEventWithChecks_. + * @type {?browserEvents.Data} + * @protected + */ + protected onUpWrapper_: any[][] | null; + /** + * The object tracking a bubble drag, or null if none is in progress. + * @type {BubbleDragger} + * @private + */ + private bubbleDragger_; + /** + * The object tracking a block drag, or null if none is in progress. + * @type {?IBlockDragger} + * @private + */ + private blockDragger_; + /** + * The object tracking a workspace or flyout workspace drag, or null if none + * is in progress. + * @type {WorkspaceDragger} + * @private + */ + private workspaceDragger_; + /** + * The flyout a gesture started in, if any. + * @type {IFlyout} + * @private + */ + private flyout_; + /** + * Boolean for sanity-checking that some code is only called once. + * @type {boolean} + * @private + */ + private calledUpdateIsDragging_; + /** + * Boolean for sanity-checking that some code is only called once. + * @type {boolean} + * @private + */ + private hasStarted_; + /** + * Boolean used internally to break a cycle in disposal. + * @type {boolean} + * @protected + */ + protected isEnding_: boolean; + /** + * Boolean used to indicate whether or not to heal the stack after + * disconnecting a block. + * @type {boolean} + * @private + */ + private healStack_; + /** + * Sever all links from this object. + * @package + */ + dispose(): void; + /** + * Update internal state based on an event. + * @param {!Event} e The most recent mouse or touch event. + * @private + */ + private updateFromEvent_; + /** + * DO MATH to set currentDragDeltaXY_ based on the most recent mouse position. + * @param {!Coordinate} currentXY The most recent mouse/pointer + * position, in pixel units, with (0, 0) at the window's top left corner. + * @return {boolean} True if the drag just exceeded the drag radius for the + * first time. + * @private + */ + private updateDragDelta_; + /** + * Update this gesture to record whether a block is being dragged from the + * flyout. + * This function should be called on a mouse/touch move event the first time + * the drag radius is exceeded. It should be called no more than once per + * gesture. If a block should be dragged from the flyout this function creates + * the new block on the main workspace and updates targetBlock_ and + * startWorkspace_. + * @return {boolean} True if a block is being dragged from the flyout. + * @private + */ + private updateIsDraggingFromFlyout_; + /** + * 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 + */ + private updateIsDraggingBubble_; + /** + * Update this gesture to record whether a block is being dragged. + * This function should be called on a mouse/touch move event the first time + * the drag radius is exceeded. It should be called no more than once per + * gesture. If a block should be dragged, either from the flyout or in the + * workspace, this function creates the necessary BlockDragger and starts the + * drag. + * @return {boolean} True if a block is being dragged. + * @private + */ + private updateIsDraggingBlock_; + /** + * Update this gesture to record whether a workspace is being dragged. + * This function should be called on a mouse/touch move event the first time + * the drag radius is exceeded. It should be called no more than once per + * gesture. If a workspace is being dragged this function creates the + * necessary WorkspaceDragger and starts the drag. + * @private + */ + private updateIsDraggingWorkspace_; + /** + * Update this gesture to record whether anything is being dragged. + * This function should be called on a mouse/touch move event the first time + * the drag radius is exceeded. It should be called no more than once per + * gesture. + * @private + */ + private updateIsDragging_; + /** + * Create a block dragger and start dragging the selected block. + * @private + */ + private startDraggingBlock_; + /** + * Create a bubble dragger and start dragging the selected bubble. + * @private + */ + private startDraggingBubble_; + /** + * Start a gesture: update the workspace to indicate that a gesture is in + * progress and bind mousemove and mouseup handlers. + * @param {!Event} e A mouse down or touch start event. + * @package + */ + doStart(e: Event): void; + /** + * Bind gesture events. + * @param {!Event} e A mouse down or touch start event. + * @package + */ + bindMouseEvents(e: Event): void; + /** + * Handle a mouse move or touch move event. + * @param {!Event} e A mouse move or touch move event. + * @package + */ + handleMove(e: Event): void; + /** + * Handle a mouse up or touch end event. + * @param {!Event} e A mouse up or touch end event. + * @package + */ + handleUp(e: Event): void; + /** + * Cancel an in-progress gesture. If a workspace or block drag is in + * progress, end the drag at the most recent location. + * @package + */ + cancel(): void; + /** + * Handle a real or faked right-click event by showing a context menu. + * @param {!Event} e A mouse move or touch move event. + * @package + */ + handleRightClick(e: Event): void; + /** + * Handle a mousedown/touchstart event on a workspace. + * @param {!Event} e A mouse down or touch start event. + * @param {!WorkspaceSvg} ws The workspace the event hit. + * @package + */ + handleWsStart(e: Event, ws: WorkspaceSvg): void; + /** + * Fires a workspace click event. + * @param {!WorkspaceSvg} ws The workspace that a user clicks on. + * @private + */ + private fireWorkspaceClick_; + /** + * Handle a mousedown/touchstart event on a flyout. + * @param {!Event} e A mouse down or touch start event. + * @param {!IFlyout} flyout The flyout the event hit. + * @package + */ + handleFlyoutStart(e: Event, flyout: IFlyout): void; + /** + * Handle a mousedown/touchstart event on a block. + * @param {!Event} e A mouse down or touch start event. + * @param {!BlockSvg} block The block the event hit. + * @package + */ + handleBlockStart(e: Event, block: BlockSvg): void; + /** + * Handle a mousedown/touchstart event on a bubble. + * @param {!Event} e A mouse down or touch start event. + * @param {!IBubble} bubble The bubble the event hit. + * @package + */ + handleBubbleStart(e: Event, bubble: () => void): void; + /** + * Execute a bubble click. + * @private + */ + private doBubbleClick_; + /** + * Execute a field click. + * @private + */ + private doFieldClick_; + /** + * Execute a block click. + * @private + */ + private doBlockClick_; + /** + * Execute a workspace click. When in accessibility mode shift clicking will + * move the cursor. + * @param {!Event} _e A mouse up or touch end event. + * @private + */ + private doWorkspaceClick_; + /** + * Move the dragged/clicked block to the front of the workspace so that it is + * not occluded by other blocks. + * @private + */ + private bringBlockToFront_; + /** + * Record the field that a gesture started on. + * @param {Field} field The field the gesture started on. + * @package + */ + setStartField(field: Field): void; + /** + * Record the bubble that a gesture started on + * @param {IBubble} bubble The bubble the gesture started on. + * @package + */ + setStartBubble(bubble: () => void): void; + /** + * Record the block that a gesture started on, and set the target block + * appropriately. + * @param {BlockSvg} block The block the gesture started on. + * @package + */ + setStartBlock(block: BlockSvg): void; + /** + * Record the block that a gesture targets, meaning the block that will be + * dragged if this turns into a drag. If this block is a shadow, that will be + * its first non-shadow parent. + * @param {BlockSvg} block The block the gesture targets. + * @private + */ + private setTargetBlock_; + /** + * Record the workspace that a gesture started on. + * @param {WorkspaceSvg} ws The workspace the gesture started on. + * @private + */ + private setStartWorkspace_; + /** + * Record the flyout that a gesture started on. + * @param {IFlyout} flyout The flyout the gesture started on. + * @private + */ + private setStartFlyout_; + /** + * 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 + */ + private isBubbleClick_; + /** + * Whether this gesture is a click on a block. This should only be called + * when ending a gesture (mouse up, touch end). + * @return {boolean} Whether this gesture was a click on a block. + * @private + */ + private isBlockClick_; + /** + * Whether this gesture is a click on a field. This should only be called + * when ending a gesture (mouse up, touch end). + * @return {boolean} Whether this gesture was a click on a field. + * @private + */ + private isFieldClick_; + /** + * Whether this gesture is a click on a workspace. This should only be called + * when ending a gesture (mouse up, touch end). + * @return {boolean} Whether this gesture was a click on a workspace. + * @private + */ + private isWorkspaceClick_; + /** + * Whether this gesture is a drag of either a workspace or block. + * This function is called externally to block actions that cannot be taken + * mid-drag (e.g. using the keyboard to delete the selected blocks). + * @return {boolean} True if this gesture is a drag of a workspace or block. + * @package + */ + isDragging(): boolean; + /** + * Whether this gesture has already been started. In theory every mouse down + * has a corresponding mouse up, but in reality it is possible to lose a + * mouse up, leaving an in-process gesture hanging. + * @return {boolean} Whether this gesture was a click on a workspace. + * @package + */ + hasStarted(): boolean; + /** + * Get a list of the insertion markers that currently exist. Block drags have + * 0, 1, or 2 insertion markers. + * @return {!Array} A possibly empty list of insertion + * marker blocks. + * @package + */ + getInsertionMarkers(): Array; + /** + * Gets the current dragger if an item is being dragged. Null if nothing is + * being dragged. + * @return {!WorkspaceDragger|!BubbleDragger|!IBlockDragger|null} + * The dragger that is currently in use or null if no drag is in progress. + */ + getCurrentDragger(): WorkspaceDragger | { + draggingBubble_: () => void; + workspace_: WorkspaceSvg; + dragTarget_: (() => void) | null; + wouldDeleteBubble_: boolean; + startXY_: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + dragSurface_: { + SVG_: SVGElement | null; + dragGroup_: SVGElement | null; + container_: Element; + scale_: number; + surfaceXY_: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + } | null; + childSurfaceXY_: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + createDom(): void; + setBlocksAndShow(blocks: SVGElement): void; + translateAndScaleGroup(x: number, y: number, scale: number): void; + translateSurfaceInternal_(): void; + translateBy(deltaX: number, deltaY: number): void; + translateSurface(x: number, y: number): void; + getSurfaceTranslation(): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + getGroup(): SVGElement | null; + getSvgRoot(): SVGElement | null; + getCurrentBlock(): Element | null; + getWsTranslation(): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + clearAndHide(opt_newSurface?: Element | undefined): void; + }; + dispose(): void; + startBubbleDrag(): void; + dragBubble(e: Event, currentDragDeltaXY: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): void; + shouldDelete_(dragTarget: (() => void) | null): boolean; + updateCursorDuringBubbleDrag_(): void; + endBubbleDrag(e: Event, currentDragDeltaXY: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): void; + fireMoveEvent_(): void; + pixelsToWorkspaceUnits_(pixelCoord: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + moveToDragSurface_(): void; + } | (() => void) | null; + } + import { WorkspaceSvg } from "core/workspace_svg"; + import { IFlyout } from "core/interfaces/i_flyout"; + import { BlockSvg } from "core/block_svg"; + import { Field } from "core/field"; + import { WorkspaceDragger } from "core/workspace_dragger"; +} +declare module "core/touch" { + /** + * Whether touch is enabled in the browser. + * Copied from Closure's goog.events.BrowserFeature.TOUCH_ENABLED + * @const + */ + export const TOUCH_ENABLED: boolean; + /** + * The TOUCH_MAP lookup dictionary specifies additional touch events to fire, + * in conjunction with mouse events. + * @type {Object} + * @alias Blockly.Touch.TOUCH_MAP + */ + export let TOUCH_MAP: Object; + /** + * Context menus on touch devices are activated using a long-press. + * Unfortunately the contextmenu touch event is currently (2015) only supported + * by Chrome. This function is fired on any touchstart event, queues a task, + * which after about a second opens the context menu. The tasks is killed + * if the touch event terminates early. + * @param {!Event} e Touch start event. + * @param {Gesture} gesture The gesture that triggered this longStart. + * @alias Blockly.Touch.longStart + * @package + */ + export function longStart(e: Event, gesture: Gesture): void; + /** + * Nope, that's not a long-press. Either touchend or touchcancel was fired, + * or a drag hath begun. Kill the queued long-press task. + * @alias Blockly.Touch.longStop + * @package + */ + export function longStop(): void; + /** + * Clear the touch identifier that tracks which touch stream to pay attention + * to. This ends the current drag/gesture and allows other pointers to be + * captured. + * @alias Blockly.Touch.clearTouchIdentifier + */ + export function clearTouchIdentifier(): void; + /** + * Decide whether Blockly should handle or ignore this event. + * Mouse and touch events require special checks because we only want to deal + * with one touch stream at a time. All other events should always be handled. + * @param {!Event} e The event to check. + * @return {boolean} True if this event should be passed through to the + * registered handler; false if it should be blocked. + * @alias Blockly.Touch.shouldHandleEvent + */ + export function shouldHandleEvent(e: Event): boolean; + /** + * Get the touch identifier from the given event. If it was a mouse event, the + * identifier is the string 'mouse'. + * @param {!Event} e Mouse event or touch event. + * @return {string} The touch identifier from the first changed touch, if + * defined. Otherwise 'mouse'. + * @alias Blockly.Touch.getTouchIdentifierFromEvent + */ + export function getTouchIdentifierFromEvent(e: Event): string; + /** + * Check whether the touch identifier on the event matches the current saved + * identifier. If there is no identifier, that means it's a mouse event and + * we'll use the identifier "mouse". This means we won't deal well with + * multiple mice being used at the same time. That seems okay. + * If the current identifier was unset, save the identifier from the + * event. This starts a drag/gesture, during which touch events with other + * identifiers will be silently ignored. + * @param {!Event} e Mouse event or touch event. + * @return {boolean} Whether the identifier on the event matches the current + * saved identifier. + * @alias Blockly.Touch.checkTouchIdentifier + */ + export function checkTouchIdentifier(e: Event): boolean; + /** + * Set an event's clientX and clientY from its first changed touch. Use this to + * make a touch event work in a mouse event handler. + * @param {!Event} e A touch event. + * @alias Blockly.Touch.setClientFromTouch + */ + export function setClientFromTouch(e: Event): void; + /** + * Check whether a given event is a mouse or touch event. + * @param {!Event} e An event. + * @return {boolean} True if it is a mouse or touch event; false otherwise. + * @alias Blockly.Touch.isMouseOrTouchEvent + */ + export function isMouseOrTouchEvent(e: Event): boolean; + /** + * 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. + * @alias Blockly.Touch.isTouchEvent + */ + export function isTouchEvent(e: Event): boolean; + /** + * Split an event into an array of events, one per changed touch or mouse + * point. + * @param {!Event} e A mouse event or a touch event with one or more changed + * touches. + * @return {!Array} An array of mouse or touch events. Each touch + * event will have exactly one changed touch. + * @alias Blockly.Touch.splitEventByTouches + */ + export function splitEventByTouches(e: Event): Array; + import { Gesture } from "core/gesture"; +} +declare module "core/browser_events" { + /** + * Blockly opaque event data used to unbind events when using + * `bind` and `conditionalBind`. + */ + export type Data = Array; + /** + * Blockly opaque event data used to unbind events when using + * `bind` and `conditionalBind`. + * @typedef {!Array} + * @alias Blockly.browserEvents.Data + */ + export let Data: any; + /** + * Bind an event handler that can be ignored if it is not part of the active + * touch stream. + * Use this for events that either start or continue a multi-part gesture (e.g. + * mousedown or mousemove, which may be part of a drag or click). + * @param {!EventTarget} node Node upon which to listen. + * @param {string} name Event name to listen to (e.g. 'mousedown'). + * @param {?Object} thisObject The value of 'this' in the function. + * @param {!Function} func Function to call when event is triggered. + * @param {boolean=} opt_noCaptureIdentifier True if triggering on this event + * should not block execution of other event handlers on this touch or + * other simultaneous touches. False by default. + * @param {boolean=} opt_noPreventDefault True if triggering on this event + * should prevent the default handler. False by default. If + * opt_noPreventDefault is provided, opt_noCaptureIdentifier must also be + * provided. + * @return {!Data} Opaque data that can be passed to + * unbindEvent_. + * @alias Blockly.browserEvents.conditionalBind + */ + export function conditionalBind(node: EventTarget, name: string, thisObject: Object | null, func: Function, opt_noCaptureIdentifier?: boolean | undefined, opt_noPreventDefault?: boolean | undefined): any[][]; + /** + * Bind an event handler that should be called regardless of whether it is part + * of the active touch stream. + * Use this for events that are not part of a multi-part gesture (e.g. + * mouseover for tooltips). + * @param {!EventTarget} node Node upon which to listen. + * @param {string} name Event name to listen to (e.g. 'mousedown'). + * @param {?Object} thisObject The value of 'this' in the function. + * @param {!Function} func Function to call when event is triggered. + * @return {!Data} Opaque data that can be passed to + * unbindEvent_. + * @alias Blockly.browserEvents.bind + */ + export function bind(node: EventTarget, name: string, thisObject: Object | null, func: Function): any[][]; + /** + * Unbind one or more events event from a function call. + * @param {!Data} bindData Opaque data from bindEvent_. + * This list is emptied during the course of calling this function. + * @return {!Function} The function call. + * @alias Blockly.browserEvents.unbind + */ + export function unbind(bindData: any[][]): Function; + /** + * Returns true if this event is targeting a text input widget? + * @param {!Event} e An event. + * @return {boolean} True if text input. + * @alias Blockly.browserEvents.isTargetInput + */ + export function isTargetInput(e: Event): boolean; + /** + * Returns true this event is a right-click. + * @param {!Event} e Mouse event. + * @return {boolean} True if right-click. + * @alias Blockly.browserEvents.isRightButton + */ + export function isRightButton(e: Event): boolean; + /** + * Returns the converted coordinates of the given mouse event. + * The origin (0,0) is the top-left corner of the Blockly SVG. + * @param {!Event} e Mouse event. + * @param {!Element} svg SVG element. + * @param {?SVGMatrix} matrix Inverted screen CTM to use. + * @return {!SVGPoint} Object with .x and .y properties. + * @alias Blockly.browserEvents.mouseToSvg + */ + export function mouseToSvg(e: Event, svg: Element, matrix: SVGMatrix | null): SVGPoint; + /** + * Returns the scroll delta of a mouse event in pixel units. + * @param {!Event} e Mouse event. + * @return {{x: number, y: number}} Scroll delta object with .x and .y + * properties. + * @alias Blockly.browserEvents.getScrollDeltaPixels + */ + export function getScrollDeltaPixels(e: Event): { + x: number; + y: number; + }; +} +declare module "core/tooltip" { + /** + * A type which can define a tooltip. + * Either a string, an object containing a tooltip property, or a function which + * returns either a string, or another arbitrarily nested function which + * eventually unwinds to a string. + */ + export type TipInfo = string | { + tooltip: any; + } | (() => (string | Function)); + /** + * A type which can define a tooltip. + * Either a string, an object containing a tooltip property, or a function which + * returns either a string, or another arbitrarily nested function which + * eventually unwinds to a string. + * @typedef {string|{tooltip}|function(): (string|!Function)} + * @alias Blockly.Tooltip.TipInfo + */ + export let TipInfo: any; + /** + * A function that renders custom tooltip UI. + * 1st parameter: the div element to render content into. + * 2nd parameter: the element being moused over (i.e., the element for which the + * tooltip should be shown). + */ + export type CustomTooltip = (arg0: Element, arg1: Element) => any; + /** + * A function that renders custom tooltip UI. + * 1st parameter: the div element to render content into. + * 2nd parameter: the element being moused over (i.e., the element for which the + * tooltip should be shown). + * @typedef {function(!Element, !Element)} + * @alias Blockly.Tooltip.CustomTooltip + */ + export let CustomTooltip: any; + /** + * Sets a custom function that will be called if present instead of the default + * tooltip UI. + * @param {!CustomTooltip} customFn A custom tooltip used to render an alternate + * tooltip UI. + * @alias Blockly.Tooltip.setCustomTooltip + */ + export function setCustomTooltip(customFn: CustomTooltip): void; + /** + * Gets the custom tooltip function. + * @returns {!CustomTooltip|undefined} The custom tooltip function, if defined. + */ + export function getCustomTooltip(): CustomTooltip | undefined; + /** + * Returns whether or not a tooltip is showing + * @returns {boolean} True if a tooltip is showing + * @alias Blockly.Tooltip.isVisible + */ + export function isVisible(): boolean; + /** + * Maximum width (in characters) of a tooltip. + * @alias Blockly.Tooltip.LIMIT + */ + export const LIMIT: 50; + /** + * Horizontal offset between mouse cursor and tooltip. + * @alias Blockly.Tooltip.OFFSET_X + */ + export const OFFSET_X: 0; + /** + * Vertical offset between mouse cursor and tooltip. + * @alias Blockly.Tooltip.OFFSET_Y + */ + export const OFFSET_Y: 10; + /** + * Radius mouse can move before killing tooltip. + * @alias Blockly.Tooltip.RADIUS_OK + */ + export const RADIUS_OK: 10; + /** + * Delay before tooltip appears. + * @alias Blockly.Tooltip.HOVER_MS + */ + export const HOVER_MS: 750; + /** + * Horizontal padding between tooltip and screen edge. + * @alias Blockly.Tooltip.MARGINS + */ + export const MARGINS: 5; + /** + * Returns the HTML tooltip container. + * @returns {?HTMLDivElement} The HTML tooltip container. + * @alias Blockly.Tooltip.getDiv + */ + export function getDiv(): HTMLDivElement | null; + /** + * Returns the tooltip text for the given element. + * @param {?Object} object The object to get the tooltip text of. + * @return {string} The tooltip text of the element. + * @alias Blockly.Tooltip.getTooltipOfObject + */ + export function getTooltipOfObject(object: Object | null): string; + /** + * Create the tooltip div and inject it onto the page. + * @alias Blockly.Tooltip.createDom + */ + export function createDom(): void; + /** + * Binds the required mouse events onto an SVG element. + * @param {!Element} element SVG element onto which tooltip is to be bound. + * @alias Blockly.Tooltip.bindMouseEvents + */ + export function bindMouseEvents(element: Element): void; + /** + * Unbinds tooltip mouse events from the SVG element. + * @param {!Element} element SVG element onto which tooltip is bound. + * @alias Blockly.Tooltip.unbindMouseEvents + */ + export function unbindMouseEvents(element: Element): void; + /** + * Dispose of the tooltip. + * @alias Blockly.Tooltip.dispose + * @package + */ + export function dispose(): void; + /** + * Hide the tooltip. + * @alias Blockly.Tooltip.hide + */ + export function hide(): void; + /** + * Hide any in-progress tooltips and block showing new tooltips until the next + * call to unblock(). + * @alias Blockly.Tooltip.block + * @package + */ + export function block(): void; + /** + * Unblock tooltips: allow them to be scheduled and shown according to their own + * logic. + * @alias Blockly.Tooltip.unblock + * @package + */ + export function unblock(): void; +} +declare module "core/utils/object" { + /** + * Inherit the prototype methods from one constructor into another. + * @param {!Function} childCtor Child class. + * @param {!Function} parentCtor Parent class. + * @suppress {strictMissingProperties} superClass_ is not defined on Function. + * @alias Blockly.utils.object.inherits + */ + export function inherits(childCtor: Function, parentCtor: Function): void; + /** + * Copies all the members of a source object to a target object. + * @param {!Object} target Target. + * @param {!Object} source Source. + * @alias Blockly.utils.object.mixin + */ + export function mixin(target: Object, source: Object): void; + /** + * Complete a deep merge of all members of a source object with a target object. + * @param {!Object} target Target. + * @param {!Object} source Source. + * @return {!Object} The resulting object. + * @alias Blockly.utils.object.deepMerge + */ + export function deepMerge(target: Object, source: Object): Object; + /** + * Returns an array of a given object's own enumerable property values. + * @param {!Object} obj Object containing values. + * @return {!Array} Array of values. + * @alias Blockly.utils.object.values + */ + export function values(obj: Object): any[]; +} +declare module "core/theme" { + /** + * Class for a theme. + * @alias Blockly.Theme + */ + export class Theme { + /** + * Define a new Blockly theme. + * @param {string} name The name of the theme. + * @param {!Object} themeObj An object containing theme properties. + * @return {!Theme} A new Blockly theme. + */ + static defineTheme(name: string, themeObj: Object): Theme; + /** + * @param {string} name Theme name. + * @param {!Object=} opt_blockStyles A map + * from style names (strings) to objects with style attributes for blocks. + * @param {!Object=} opt_categoryStyles A + * map from style names (strings) to objects with style attributes for + * categories. + * @param {!Theme.ComponentStyle=} opt_componentStyles A map of Blockly + * component names to style value. + */ + constructor(name: string, opt_blockStyles?: { + [x: string]: Theme.BlockStyle; + } | undefined, opt_categoryStyles?: { + [x: string]: Theme.CategoryStyle; + } | undefined, opt_componentStyles?: Theme.ComponentStyle | undefined); + /** + * The theme name. This can be used to reference a specific theme in CSS. * @type {string} */ - type: string; + name: string; + /** + * The block styles map. + * @type {!Object} + * @package + */ + blockStyles: { + [x: string]: Theme.BlockStyle; + }; + /** + * The category styles map. + * @type {!Object} + * @package + */ + categoryStyles: { + [x: string]: Theme.CategoryStyle; + }; + /** + * The UI components styles map. + * @type {!Theme.ComponentStyle} + * @package + */ + componentStyles: Theme.ComponentStyle; + /** + * The font style. + * @type {!Theme.FontStyle} + * @package + */ + fontStyle: Theme.FontStyle; + /** + * Whether or not to add a 'hat' on top of all blocks with no previous or + * output connections. + * @type {?boolean} + * @package + */ + startHats: boolean | null; + /** + * Gets the class name that identifies this theme. + * @return {string} The CSS class name. + * @package + */ + getClassName(): string; + /** + * Overrides or adds a style to the blockStyles map. + * @param {string} blockStyleName The name of the block style. + * @param {Theme.BlockStyle} blockStyle The block style. + */ + setBlockStyle(blockStyleName: string, blockStyle: Theme.BlockStyle): void; + /** + * Overrides or adds a style to the categoryStyles map. + * @param {string} categoryStyleName The name of the category style. + * @param {Theme.CategoryStyle} categoryStyle The category style. + */ + setCategoryStyle(categoryStyleName: string, categoryStyle: Theme.CategoryStyle): void; + /** + * Gets the style for a given Blockly UI component. If the style value is a + * string, we attempt to find the value of any named references. + * @param {string} componentName The name of the component. + * @return {?string} The style value. + */ + getComponentStyle(componentName: string): string | null; + /** + * Configure a specific Blockly UI component with a style value. + * @param {string} componentName The name of the component. + * @param {*} styleValue The style value. + */ + setComponentStyle(componentName: string, styleValue: any): void; + /** + * Configure a theme's font style. + * @param {Theme.FontStyle} fontStyle The font style. + */ + setFontStyle(fontStyle: Theme.FontStyle): void; + /** + * Configure a theme's start hats. + * @param {boolean} startHats True if the theme enables start hats, false + * otherwise. + */ + setStartHats(startHats: boolean): void; + } + export namespace Theme { + /** + * A block style. + */ + type BlockStyle = { + colourPrimary: string; + colourSecondary: string; + colourTertiary: string; + hat: string; + }; + /** + * A category style. + */ + type CategoryStyle = { + colour: string; + }; + /** + * A component style. + */ + type ComponentStyle = { + workspaceBackgroundColour: string | null; + toolboxBackgroundColour: string | null; + toolboxForegroundColour: string | null; + flyoutBackgroundColour: string | null; + flyoutForegroundColour: string | null; + flyoutOpacity: number | null; + scrollbarColour: string | null; + scrollbarOpacity: number | null; + insertionMarkerColour: string | null; + insertionMarkerOpacity: number | null; + markerColour: string | null; + cursorColour: string | null; + selectedGlowColour: string | null; + selectedGlowOpacity: number | null; + replacementGlowColour: string | null; + replacementGlowOpacity: number | null; + }; + /** + * A font style. + */ + type FontStyle = { + family: string | null; + weight: string | null; + size: number | null; + }; } - import { BlockSvg } from "block_svg"; - import { Block } from "block"; } -declare module "procedures" { - /** - * The default argument for a procedures_mutatorarg block. - * @type {string} - * @alias Blockly.Procedures.DEFAULT_ARG - */ - export const DEFAULT_ARG: string; - /** - * Procedure block type. - */ - export type ProcedureBlock = { - getProcedureCall: () => string; - renameProcedure: (arg0: string, arg1: string) => any; - getProcedureDef: () => any[]; - }; - /** - * Procedure block type. - * @typedef {{ - * getProcedureCall: function():string, - * renameProcedure: function(string,string), - * getProcedureDef: function():!Array - * }} - * @alias Blockly.Procedures.ProcedureBlock - */ - export let ProcedureBlock: any; - /** - * Find all user-created procedure definitions in a workspace. - * @param {!Workspace} root Root workspace. - * @return {!Array>} Pair of arrays, the - * first contains procedures without return variables, the second with. - * Each procedure is defined by a three-element list of name, parameter - * list, and return value boolean. - * @alias Blockly.Procedures.allProcedures - */ - export function allProcedures(root: Workspace): Array>; - /** - * Ensure two identically-named procedures don't exist. - * Take the proposed procedure name, and return a legal name i.e. one that - * is not empty and doesn't collide with other procedures. - * @param {string} name Proposed procedure name. - * @param {!Block} block Block to disambiguate. - * @return {string} Non-colliding name. - * @alias Blockly.Procedures.findLegalName - */ - export function findLegalName(name: string, block: Block): string; - /** - * Return if the given name is already a procedure name. - * @param {string} name The questionable name. - * @param {!Workspace} workspace The workspace to scan for collisions. - * @param {Block=} opt_exclude Optional block to exclude from - * comparisons (one doesn't want to collide with oneself). - * @return {boolean} True if the name is used, otherwise return false. - * @alias Blockly.Procedures.isNameUsed - */ - export function isNameUsed(name: string, workspace: Workspace, opt_exclude?: Block | undefined): boolean; - /** - * Rename a procedure. Called by the editable field. - * @param {string} name The proposed new name. - * @return {string} The accepted name. - * @this {Field} - * @alias Blockly.Procedures.rename - */ - export function rename(name: string): string; - /** - * Construct the blocks required by the flyout for the procedure category. - * @param {!Workspace} workspace The workspace containing procedures. - * @return {!Array} Array of XML block elements. - * @alias Blockly.Procedures.flyoutCategory - */ - export function flyoutCategory(workspace: Workspace): Array; - /** - * Listens for when a procedure mutator is opened. Then it triggers a flyout - * update and adds a mutator change listener to the mutator workspace. - * @param {!Abstract} e The event that triggered this listener. - * @alias Blockly.Procedures.mutatorOpenListener - * @package - */ - export function mutatorOpenListener(e: typeof Abstract): void; - /** - * Find all the callers of a named procedure. - * @param {string} name Name of procedure. - * @param {!Workspace} workspace The workspace to find callers in. - * @return {!Array} Array of caller blocks. - * @alias Blockly.Procedures.getCallers - */ - export function getCallers(name: string, workspace: Workspace): Array; - /** - * When a procedure definition changes its parameters, find and edit all its - * callers. - * @param {!Block} defBlock Procedure definition block. - * @alias Blockly.Procedures.mutateCallers - */ - export function mutateCallers(defBlock: Block): void; - /** - * Find the definition block for the named procedure. - * @param {string} name Name of procedure. - * @param {!Workspace} workspace The workspace to search. - * @return {?Block} The procedure definition block, or null not found. - * @alias Blockly.Procedures.getDefinition - */ - export function getDefinition(name: string, workspace: Workspace): Block | null; - import { Workspace } from "workspace"; - import { Block } from "block"; - import * as Abstract from "events/events_abstract"; -} -declare module "variables_dynamic" { - function stringButtonClickHandler(button: any): void; - function numberButtonClickHandler(button: any): void; - function colourButtonClickHandler(button: any): void; - /** - * Construct the elements (blocks and button) required by the flyout for the - * variable category. - * @param {!Workspace} workspace The workspace containing variables. - * @return {!Array} Array of XML elements. - * @alias Blockly.VariablesDynamic.flyoutCategory - */ - export function flyoutCategory(workspace: Workspace): Array; - /** - * Construct the blocks required by the flyout for the variable category. - * @param {!Workspace} workspace The workspace containing variables. - * @return {!Array} Array of XML block elements. - * @alias Blockly.VariablesDynamic.flyoutCategoryBlocks - */ - export function flyoutCategoryBlocks(workspace: Workspace): Array; - import { Workspace } from "workspace"; - export { stringButtonClickHandler as onCreateVariableButtonClick_String, numberButtonClickHandler as onCreateVariableButtonClick_Number, colourButtonClickHandler as onCreateVariableButtonClick_Colour }; -} -declare module "renderers/common/debug" { - /** - * Returns whether the debugger is turned on. - * @return {boolean} Whether the debugger is turned on. - * @alias Blockly.blockRendering.debug.isDebuggerEnabled - * @package - */ - export function isDebuggerEnabled(): boolean; - /** - * Turn on the blocks debugger. - * @package - * @alias Blockly.blockRendering.debug.startDebugger - */ - export function startDebugger(): void; - /** - * Turn off the blocks debugger. - * @package - * @alias Blockly.blockRendering.debug.stopDebugger - */ - export function stopDebugger(): void; -} -declare module "renderers/common/constants" { +declare module "core/renderers/common/constants" { /** * An object that provides constants for rendering blocks. - * @constructor - * @package * @alias Blockly.blockRendering.ConstantProvider */ export class ConstantProvider { @@ -5531,8 +7565,8 @@ declare module "renderers/common/constants" { */ TAB_OFFSET_FROM_TOP: number; /** - * Vertical overlap of the puzzle tab, used to make it look more like a puzzle - * piece. + * Vertical overlap of the puzzle tab, used to make it look more like a + * puzzle piece. * @type {number} */ TAB_VERTICAL_OVERLAP: number; @@ -5573,8 +7607,8 @@ declare module "renderers/common/constants" { */ CORNER_RADIUS: number; /** - * Offset from the left side of a block or the inside of a statement input to - * the left side of the notch. + * Offset from the left side of a block or the inside of a statement input + * to the left side of the notch. * @type {number} */ NOTCH_OFFSET_LEFT: number; @@ -5638,9 +7672,9 @@ declare module "renderers/common/constants" { EXTERNAL_VALUE_INPUT_PADDING: number; /** * The height of an empty statement input. Note that in the old rendering - * this varies slightly depending on whether the block has external or inline - * inputs. In the new rendering this is consistent. It seems unlikely that - * the old behaviour was intentional. + * this varies slightly depending on whether the block has external or + * inline inputs. In the new rendering this is consistent. It seems + * unlikely that the old behaviour was intentional. * @type {number} */ EMPTY_STATEMENT_INPUT_HEIGHT: number; @@ -5671,8 +7705,9 @@ declare module "renderers/common/constants" { */ FIELD_TEXT_FONTFAMILY: string; /** - * Height of text. This constant is dynamically set in ``setFontConstants_`` - * to be the height of the text based on the font used. + * Height of text. This constant is dynamically set in + * ``setFontConstants_`` to be the height of the text based on the font + * used. * @type {number} */ FIELD_TEXT_HEIGHT: number; @@ -5912,32 +7947,32 @@ declare module "renderers/common/constants" { * indicators. * @type {!Object} */ - JAGGED_TEETH: any; + JAGGED_TEETH: Object | undefined; /** * An object containing sizing and path information about notches. * @type {!Object} */ - NOTCH: any; + NOTCH: Object | undefined; /** * An object containing sizing and path information about start hats * @type {!Object} */ - START_HAT: any; + START_HAT: Object | undefined; /** * An object containing sizing and path information about puzzle tabs. * @type {!Object} */ - PUZZLE_TAB: any; + PUZZLE_TAB: Object | undefined; /** * An object containing sizing and path information about inside corners * @type {!Object} */ - INSIDE_CORNERS: any; + INSIDE_CORNERS: Object | undefined; /** * An object containing sizing and path information about outside corners. * @type {!Object} */ - OUTSIDE_CORNERS: any; + OUTSIDE_CORNERS: Object | undefined; /** * Refresh constants properties that depend on the theme. * @param {!Theme} theme The current workspace theme. @@ -5951,7 +7986,7 @@ declare module "renderers/common/constants" { */ blockStyles: { [x: string]: Theme.BlockStyle; - }; + } | undefined; /** * Sets dynamic properties that depend on other values or theme properties. * @param {!Theme} theme The current workspace theme. @@ -5971,8 +8006,8 @@ declare module "renderers/common/constants" { */ protected setComponentConstants_(theme: Theme): void; /** - * Get or create a block style based on a single colour value. Generate a name - * for the style based on the colour. + * Get or create a block style based on a single colour value. Generate a + * name for the style based on the colour. * @param {string} colour #RRGGBB colour string. * @return {{style: !Theme.BlockStyle, name: string}} An object * containing the style and an autogenerated name for that style. @@ -6006,7 +8041,7 @@ declare module "renderers/common/constants" { * colourTertiary:(string|undefined), * hat:(string|undefined) * }} blockStyle A full or partial block style object. - + * @return {!Theme.BlockStyle} A full block style object, with all * required properties populated. * @protected @@ -6042,52 +8077,53 @@ declare module "renderers/common/constants" { * collapsed block indicators. * @package */ - makeJaggedTeeth(): any; + makeJaggedTeeth(): Object; /** * @return {!Object} An object containing sizing and path information about * start hats. * @package */ - makeStartHat(): any; + makeStartHat(): Object; /** * @return {!Object} An object containing sizing and path information about * puzzle tabs. * @package */ - makePuzzleTab(): any; + makePuzzleTab(): Object; /** * @return {!Object} An object containing sizing and path information about * notches. * @package */ - makeNotch(): any; + makeNotch(): Object; /** * @return {!Object} An object containing sizing and path information about * inside corners. * @package */ - makeInsideCorners(): any; + makeInsideCorners(): Object; /** * @return {!Object} An object containing sizing and path information about * outside corners. * @package */ - makeOutsideCorners(): any; + makeOutsideCorners(): Object; /** - * Get an object with connection shape and sizing information based on the type - * of the connection. + * Get an object with connection shape and sizing information based on the + * type of the connection. * @param {!RenderedConnection} connection The connection to find a * shape object for * @return {!Object} The shape object for the connection. * @package */ - shapeFor(connection: RenderedConnection): any; + shapeFor(connection: RenderedConnection): Object; /** * Create any DOM elements that this renderer needs (filters, patterns, etc). * @param {!SVGElement} svg The root of the workspace's SVG. * @param {string} tagName The name to use for the CSS style tag. * @param {string} selector The CSS selector to use. - * @suppress {strictModuleDepCheck} Debug renderer only included in playground. + * @suppress {strictModuleDepCheck} Debug renderer only included in + * playground. * @package */ createDom(svg: SVGElement, tagName: string, selector: string): void; @@ -6112,29 +8148,356 @@ declare module "renderers/common/constants" { */ protected getCSS_(selector: string): Array; } - import { Theme } from "theme"; - import { RenderedConnection } from "rendered_connection"; + import { Theme } from "core/theme"; + import { RenderedConnection } from "core/rendered_connection"; } -declare module "renderers/measurables/in_row_spacer" { +declare module "core/interfaces/i_ast_node_location" { /** - * An object containing information about a spacer between two elements on a - * row. - * @param {!ConstantProvider} constants The rendering - * constants provider. - * @param {number} width The width of the spacer. - * @package - * @constructor - * @extends {Measurable} - * @alias Blockly.blockRendering.InRowSpacer + * An AST node location interface. + * @interface + * @alias Blockly.IASTNodeLocation */ - export class InRowSpacer extends Measurable { - constructor(constants: any, width: any); - width: any; - height: any; - } - import { Measurable } from "renderers/common/block_rendering"; + export class IASTNodeLocation {} } -declare module "interfaces/i_registrable_field" { +declare module "core/interfaces/i_ast_node_location_svg" { + /** + * An AST node location SVG interface. + * @interface + * @extends {IASTNodeLocation} + * @alias Blockly.IASTNodeLocationSvg + */ + export class IASTNodeLocationSvg {} +} +declare module "core/interfaces/i_ast_node_location_with_block" { + /** + * An AST node location that has an associated block. + * @interface + * @extends {IASTNodeLocation} + * @alias Blockly.IASTNodeLocationWithBlock + */ + export class IASTNodeLocationWithBlock {} +} +declare module "core/utils/keycodes" { + /** + * * + */ + export type KeyCodes = number; + export namespace KeyCodes { + const WIN_KEY_FF_LINUX: number; + const MAC_ENTER: number; + const BACKSPACE: number; + const TAB: number; + const NUM_CENTER: number; + const ENTER: number; + const SHIFT: number; + const CTRL: number; + const ALT: number; + const PAUSE: number; + const CAPS_LOCK: number; + const ESC: number; + const SPACE: number; + const PAGE_UP: number; + const PAGE_DOWN: number; + const END: number; + const HOME: number; + const LEFT: number; + const UP: number; + const RIGHT: number; + const DOWN: number; + const PLUS_SIGN: number; + const PRINT_SCREEN: number; + const INSERT: number; + const DELETE: number; + const ZERO: number; + const ONE: number; + const TWO: number; + const THREE: number; + const FOUR: number; + const FIVE: number; + const SIX: number; + const SEVEN: number; + const EIGHT: number; + const NINE: number; + const FF_SEMICOLON: number; + const FF_EQUALS: number; + const FF_DASH: number; + const FF_HASH: number; + const QUESTION_MARK: number; + const AT_SIGN: number; + const A: number; + const B: number; + const C: number; + const D: number; + const E: number; + const F: number; + const G: number; + const H: number; + const I: number; + const J: number; + const K: number; + const L: number; + const M: number; + const N: number; + const O: number; + const P: number; + const Q: number; + const R: number; + const S: number; + const T: number; + const U: number; + const V: number; + const W: number; + const X: number; + const Y: number; + const Z: number; + const META: number; + const WIN_KEY_RIGHT: number; + const CONTEXT_MENU: number; + const NUM_ZERO: number; + const NUM_ONE: number; + const NUM_TWO: number; + const NUM_THREE: number; + const NUM_FOUR: number; + const NUM_FIVE: number; + const NUM_SIX: number; + const NUM_SEVEN: number; + const NUM_EIGHT: number; + const NUM_NINE: number; + const NUM_MULTIPLY: number; + const NUM_PLUS: number; + const NUM_MINUS: number; + const NUM_PERIOD: number; + const NUM_DIVISION: number; + const F1: number; + const F2: number; + const F3: number; + const F4: number; + const F5: number; + const F6: number; + const F7: number; + const F8: number; + const F9: number; + const F10: number; + const F11: number; + const F12: number; + const NUMLOCK: number; + const SCROLL_LOCK: number; + const FIRST_MEDIA_KEY: number; + const LAST_MEDIA_KEY: number; + const SEMICOLON: number; + const DASH: number; + const EQUALS: number; + const COMMA: number; + const PERIOD: number; + const SLASH: number; + const APOSTROPHE: number; + const TILDE: number; + const SINGLE_QUOTE: number; + const OPEN_SQUARE_BRACKET: number; + const BACKSLASH: number; + const CLOSE_SQUARE_BRACKET: number; + const WIN_KEY: number; + const MAC_FF_META: number; + const MAC_WK_CMD_LEFT: number; + const MAC_WK_CMD_RIGHT: number; + const WIN_IME: number; + const VK_NONAME: number; + const PHANTOM: number; + } +} +declare module "core/shortcut_registry" { + /** + * Class for the registry of keyboard shortcuts. This is intended to be a + * singleton. You should not create a new instance, and only access this class + * from ShortcutRegistry.registry. + * @alias Blockly.ShortcutRegistry + */ + export class ShortcutRegistry { + /** + * Clear and recreate the registry and keyMap. + */ + reset(): void; + /** + * Registry of all keyboard shortcuts, keyed by name of shortcut. + * @type {!Object} + * @private + */ + private registry_; + /** + * Map of key codes to an array of shortcut names. + * @type {!Object>} + * @private + */ + private keyMap_; + /** + * Registers a keyboard shortcut. + * @param {!ShortcutRegistry.KeyboardShortcut} shortcut The + * shortcut for this key code. + * @param {boolean=} opt_allowOverrides True to prevent a warning when + * overriding an already registered item. + * @throws {Error} if a shortcut with the same name already exists. + * @public + */ + public register(shortcut: ShortcutRegistry.KeyboardShortcut, opt_allowOverrides?: boolean | undefined): void; + /** + * Unregisters a keyboard shortcut registered with the given key code. This + * will also remove any key mappings that reference this shortcut. + * @param {string} shortcutName The name of the shortcut to unregister. + * @return {boolean} True if an item was unregistered, false otherwise. + * @public + */ + public unregister(shortcutName: string): boolean; + /** + * Adds a mapping between a keycode and a keyboard shortcut. + * @param {string|KeyCodes} keyCode The key code for the keyboard + * shortcut. If registering a key code with a modifier (ex: ctrl+c) use + * ShortcutRegistry.registry.createSerializedKey; + * @param {string} shortcutName The name of the shortcut to execute when the + * given keycode is pressed. + * @param {boolean=} opt_allowCollision True to prevent an error when adding a + * shortcut to a key that is already mapped to a shortcut. + * @throws {Error} if the given key code is already mapped to a shortcut. + * @public + */ + public addKeyMapping(keyCode: string | KeyCodes, shortcutName: string, opt_allowCollision?: boolean | undefined): void; + /** + * Removes a mapping between a keycode and a keyboard shortcut. + * @param {string} keyCode The key code for the keyboard shortcut. If + * registering a key code with a modifier (ex: ctrl+c) use + * ShortcutRegistry.registry.createSerializedKey; + * @param {string} shortcutName The name of the shortcut to execute when the + * given keycode is pressed. + * @param {boolean=} opt_quiet True to not console warn when there is no + * shortcut to remove. + * @return {boolean} True if a key mapping was removed, false otherwise. + * @public + */ + public removeKeyMapping(keyCode: string, shortcutName: string, opt_quiet?: boolean | undefined): boolean; + /** + * Removes all the key mappings for a shortcut with the given name. + * Useful when changing the default key mappings and the key codes registered + * to the shortcut are unknown. + * @param {string} shortcutName The name of the shortcut to remove from the + * key map. + * @public + */ + public removeAllKeyMappings(shortcutName: string): void; + /** + * Sets the key map. Setting the key map will override any default key + * mappings. + * @param {!Object>} keyMap The object with key code to + * shortcut names. + * @public + */ + public setKeyMap(keyMap: { + [x: string]: Array; + }): void; + /** + * Gets the current key map. + * @return {!Object>} + * The object holding key codes to ShortcutRegistry.KeyboardShortcut. + * @public + */ + public getKeyMap(): { + [x: string]: Array; + }; + /** + * Gets the registry of keyboard shortcuts. + * @return {!Object} + * The registry of keyboard shortcuts. + * @public + */ + public getRegistry(): { + [x: string]: ShortcutRegistry.KeyboardShortcut; + }; + /** + * Handles key down events. + * @param {!Workspace} workspace The main workspace where the event was + * captured. + * @param {!Event} e The key down event. + * @return {boolean} True if the event was handled, false otherwise. + * @public + */ + public onKeyDown(workspace: Workspace, e: Event): boolean; + /** + * Gets the shortcuts registered to the given key code. + * @param {string} keyCode The serialized key code. + * @return {!Array|undefined} The list of shortcuts to call when the + * given keyCode is used. Undefined if no shortcuts exist. + * @public + */ + public getShortcutNamesByKeyCode(keyCode: string): Array | undefined; + /** + * Gets the serialized key codes that the shortcut with the given name is + * registered under. + * @param {string} shortcutName The name of the shortcut. + * @return {!Array} An array with all the key codes the shortcut is + * registered under. + * @public + */ + public getKeyCodesByShortcutName(shortcutName: string): Array; + /** + * Serializes a key event. + * @param {!Event} e A key down event. + * @return {string} The serialized key code for the given event. + * @private + */ + private serializeKeyEvent_; + /** + * Checks whether any of the given modifiers are not valid. + * @param {!Array} modifiers List of modifiers to be used with the + * key. + * @throws {Error} if the modifier is not in the valid modifiers list. + * @private + */ + private checkModifiers_; + /** + * Creates the serialized key code that will be used in the key map. + * @param {number} keyCode Number code representing the key. + * @param {?Array} modifiers List of modifier key codes to be used + * with the key. All valid modifiers can be found in the + * ShortcutRegistry.modifierKeys. + * @return {string} The serialized key code for the given modifiers and key. + * @public + */ + public createSerializedKey(keyCode: number, modifiers: Array | null): string; + } + export namespace ShortcutRegistry { + export namespace modifierKeys { + const Shift: number; + const Control: number; + const Alt: number; + const Meta: number; + } + /** + * Enum of valid modifiers. + */ + export type modifierKeys = KeyCodes; + export { registry }; + /** + * A keyboard shortcut. + */ + export type KeyboardShortcut = { + callback: (((arg0: Workspace, arg1: Event, arg2: ShortcutRegistry.KeyboardShortcut) => boolean) | undefined); + name: string; + preconditionFn: (((arg0: Workspace) => boolean) | undefined); + metadata: (Object | undefined); + }; + } + import { KeyCodes } from "core/utils/keycodes"; + import { Workspace } from "core/workspace"; + const registry: ShortcutRegistry; + export {}; +} +declare module "core/interfaces/i_keyboard_accessible" { + /** + * An interface for an object that handles keyboard shortcuts. + * @interface + * @alias Blockly.IKeyboardAccessible + */ + export class IKeyboardAccessible {} +} +declare module "core/interfaces/i_registrable_field" { /** * A registrable field. * Note: We are not using an interface here as we are interested in defining the @@ -6144,11 +8507,11 @@ declare module "interfaces/i_registrable_field" { fromJson: IRegistrableField.fromJson; }; export namespace IRegistrableField { - type fromJson = (arg0: any) => Field; + type fromJson = (arg0: Object) => Field; } - import { Field } from "field"; + import { Field } from "core/field"; } -declare module "field_registry" { +declare module "core/field_registry" { /** * Registers a field type. * fieldRegistry.fromJson uses this registry to @@ -6179,22 +8542,33 @@ declare module "field_registry" { * @alias Blockly.fieldRegistry.fromJson * @package */ - export function fromJson(options: any): Field | null; - import { IRegistrableField } from "interfaces/i_registrable_field"; - import { Field } from "field"; + export function fromJson(options: Object): Field | null; + import { IRegistrableField } from "core/interfaces/i_registrable_field"; + import { Field } from "core/field"; } -declare module "field_label" { +declare module "core/input_types" { + /** + * * + */ + export type inputTypes = number; + export namespace inputTypes { + const VALUE: number; + const STATEMENT: number; + const DUMMY: number; + } +} +declare module "core/utils/sentinel" { + /** + * A type used to create flag values. + * @alias Blockly.utils.Sentinel + */ + export class Sentinel { + } +} +declare module "core/field_label" { /** * Class for a non-editable, non-serializable text field. - * @param {string=} opt_value The initial value of the field. Should cast to a - * string. Defaults to an empty string if null or undefined. - * @param {string=} opt_class Optional CSS class for the field's text. - * @param {Object=} opt_config A map of options used to configure the field. - * See the [field creation documentation]{@link - * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/label#creation} - * for a list of properties this parameter supports. * @extends {Field} - * @constructor * @alias Blockly.FieldLabel */ export class FieldLabel extends Field { @@ -6206,61 +8580,57 @@ declare module "field_label" { * @package * @nocollapse */ - static fromJson(options: any): FieldLabel; - constructor(opt_value: any, opt_class: any, opt_config: any); + static fromJson(options: Object): FieldLabel; + /** + * @param {(string|!Sentinel)=} opt_value The initial value of the + * field. Should cast to a string. Defaults to an empty string if null or + * undefined. + * Also accepts Field.SKIP_SETUP if you wish to skip setup (only used by + * subclasses that want to handle configuration and setting the field + * value after their own constructors have run). + * @param {string=} opt_class Optional CSS class for the field's text. + * @param {Object=} opt_config A map of options used to configure the field. + * See the [field creation documentation]{@link + * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/label#creation} + * for a list of properties this parameter supports. + */ + constructor(opt_value?: (string | Sentinel) | undefined, opt_class?: string | undefined, opt_config?: Object | undefined); /** * The html class name to use for this field. * @type {?string} * @private */ private class_; - /** - * @override - */ - override configure_(config: any): void; - /** - * Create block UI for this label. - * @package - */ - initView(): void; - /** - * Ensure that the input value casts to a valid string. - * @param {*=} opt_newValue The input value. - * @return {?string} A valid string, or null if invalid. - * @protected - */ - protected doClassValidation_(opt_newValue?: any | undefined): string | null; /** * Set the CSS class applied to the field's textElement_. * @param {?string} cssClass The new CSS class name, or null to remove. */ setClass(cssClass: string | null): void; - /** - * The default value for this field. - * @type {*} - * @protected - */ - protected DEFAULT_VALUE: any; - /** - * Editable fields usually show some sort of UI indicating they are - * editable. This field should not. - * @type {boolean} - */ - EDITABLE: boolean; } - import { Field } from "field"; + import { Field } from "core/field"; + import { Sentinel } from "core/utils/sentinel"; } -declare module "input" { +declare module "core/input" { + /** + * * + */ + export type Align = number; + export namespace Align { + const LEFT: number; + const CENTRE: number; + const RIGHT: number; + } + /** + * Class for an input with an optional field. + * @alias Blockly.Input + */ export class Input { /** - * Class for an input with an optional field. * @param {number} type The type of the input. - * @param {string} name Language-neutral identifier which may used to find this - * input again. + * @param {string} name Language-neutral identifier which may used to find + * this input again. * @param {!Block} block The block containing this input. * @param {Connection} connection Optional connection for this input. - * @constructor - * @alias Blockly.Input */ constructor(type: number, name: string, block: Block, connection: Connection); /** @type {number} */ @@ -6276,34 +8646,46 @@ declare module "input" { connection: Connection; /** @type {!Array} */ fieldRow: Array; + /** + * Alignment of input's fields (left, right or centre). + * @type {number} + */ + align: number; + /** + * Is the input visible? + * @type {boolean} + * @private + */ + private visible_; /** * Get the source block for this input. * @return {?Block} The source block, or null if there is none. */ getSourceBlock(): Block | null; /** - * Add a field (or label from string), and all prefix and suffix fields, to the - * end of the input's field row. + * Add a field (or label from string), and all prefix and suffix fields, to + * the end of the input's field row. * @param {string|!Field} field Something to add as a field. - * @param {string=} opt_name Language-neutral identifier which may used to find - * this field again. Should be unique to the host block. + * @param {string=} opt_name Language-neutral identifier which may used to + * find this field again. Should be unique to the host block. * @return {!Input} The input being append to (to allow chaining). */ appendField(field: string | Field, opt_name?: string | undefined): Input; /** - * Inserts a field (or label from string), and all prefix and suffix fields, at - * the location of the input's field row. + * Inserts a field (or label from string), and all prefix and suffix fields, + * at the location of the input's field row. * @param {number} index The index at which to insert field. * @param {string|!Field} field Something to add as a field. - * @param {string=} opt_name Language-neutral identifier which may used to find - * this field again. Should be unique to the host block. + * @param {string=} opt_name Language-neutral identifier which may used to + * find this field again. Should be unique to the host block. * @return {number} The index following the last inserted field. */ insertFieldAt(index: number, field: string | Field, opt_name?: string | undefined): number; /** * Remove a field from this input. * @param {string} name The name of the field. - * @param {boolean=} opt_quiet True to prevent an error if field is not present. + * @param {boolean=} opt_quiet True to prevent an error if field is not + * present. * @return {boolean} True if operation succeeds, false if field is not present * and opt_quiet is true. * @throws {Error} if the field is not present and opt_quiet is false. @@ -6322,7 +8704,6 @@ declare module "input" { * @package */ setVisible(visible: boolean): Array; - visible_: any; /** * Mark all fields on this input as dirty. * @package @@ -6337,12 +8718,11 @@ declare module "input" { setCheck(check: string | Array | null): Input; /** * Change the alignment of the connection's field(s). - * @param {number} align One of the values of constants.ALIGN. - * In RTL mode directions are reversed, and ALIGN.RIGHT aligns to the left. + * @param {number} align One of the values of Align + * In RTL mode directions are reversed, and Align.RIGHT aligns to the left. * @return {!Input} The input being modified (to allow chaining). */ setAlign(align: number): Input; - align: number; /** * Changes the connection's shadow block. * @param {?Element} shadow DOM representation of a block or null. @@ -6364,46 +8744,2102 @@ declare module "input" { */ dispose(): void; } - import { Connection } from "connection"; - import { Field } from "field"; - import { Block } from "block"; - import { BlockSvg } from "block_svg"; + import { Connection } from "core/connection"; + import { Field } from "core/field"; + import { Block } from "core/block"; + import { BlockSvg } from "core/block_svg"; } -declare module "renderers/measurables/input_connection" { +declare module "core/keyboard_nav/ast_node" { + /** + * Class for an AST node. + * It is recommended that you use one of the createNode methods instead of + * creating a node directly. + * @alias Blockly.ASTNode + */ + export class ASTNode { + /** + * Whether an AST node of the given type points to a connection. + * @param {string} type The type to check. One of ASTNode.types. + * @return {boolean} True if a node of the given type points to a connection. + * @private + */ + private static isConnectionType_; + /** + * Create an AST node pointing to a field. + * @param {Field} field The location of the AST node. + * @return {ASTNode} An AST node pointing to a field. + */ + static createFieldNode(field: Field): ASTNode; + /** + * Creates an AST node pointing to a connection. If the connection has a + * parent input then create an AST node of type input that will hold the + * connection. + * @param {Connection} connection This is the connection the node will + * point to. + * @return {ASTNode} An AST node pointing to a connection. + */ + static createConnectionNode(connection: Connection): ASTNode; + /** + * Creates an AST node pointing to an input. Stores the input connection as + * the location. + * @param {Input} input The input used to create an AST node. + * @return {ASTNode} An AST node pointing to a input. + */ + static createInputNode(input: Input): ASTNode; + /** + * Creates an AST node pointing to a block. + * @param {Block} block The block used to create an AST node. + * @return {ASTNode} An AST node pointing to a block. + */ + static createBlockNode(block: Block): ASTNode; + /** + * Create an AST node of type stack. A stack, represented by its top block, is + * the set of all blocks connected to a top block, including the top + * block. + * @param {Block} topBlock A top block has no parent and can be found + * in the list returned by workspace.getTopBlocks(). + * @return {ASTNode} An AST node of type stack that points to the top + * block on the stack. + */ + static createStackNode(topBlock: Block): ASTNode; + /** + * Creates an AST node pointing to a workspace. + * @param {!Workspace} workspace The workspace that we are on. + * @param {Coordinate} wsCoordinate The position on the workspace + * for this node. + * @return {ASTNode} An AST node pointing to a workspace and a position + * on the workspace. + */ + static createWorkspaceNode(workspace: Workspace, wsCoordinate: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): ASTNode; + /** + * Creates an AST node for the top position on a block. + * This is either an output connection, previous connection, or block. + * @param {!Block} block The block to find the top most AST node on. + * @return {ASTNode} The AST node holding the top most position on the + * block. + */ + static createTopNode(block: Block): ASTNode; + /** + * @param {string} type The type of the location. + * Must be in ASTNode.types. + * @param {!IASTNodeLocation} location The position in the AST. + * @param {!ASTNode.Params=} opt_params Optional dictionary of options. + * @alias Blockly.ASTNode + */ + constructor(type: string, location: () => void, opt_params?: ASTNode.Params | undefined); + /** + * The type of the location. + * One of ASTNode.types + * @type {string} + * @private + */ + private type_; + /** + * Whether the location points to a connection. + * @type {boolean} + * @private + */ + private isConnection_; + /** + * The location of the AST node. + * @type {!IASTNodeLocation} + * @private + */ + private location_; + /** + * The coordinate on the workspace. + * @type {Coordinate} + * @private + */ + private wsCoordinate_; + /** + * Parse the optional parameters. + * @param {?ASTNode.Params} params The user specified parameters. + * @private + */ + private processParams_; + /** + * Gets the value pointed to by this node. + * It is the callers responsibility to check the node type to figure out what + * type of object they get back from this. + * @return {!IASTNodeLocation} The current field, connection, workspace, or + * block the cursor is on. + */ + getLocation(): () => void; + /** + * The type of the current location. + * One of ASTNode.types + * @return {string} The type of the location. + */ + getType(): string; + /** + * The coordinate on the workspace. + * @return {Coordinate} The workspace coordinate or null if the + * location is not a workspace. + */ + getWsCoordinate(): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + /** + * Whether the node points to a connection. + * @return {boolean} [description] + * @package + */ + isConnection(): boolean; + /** + * Given an input find the next editable field or an input with a non null + * connection in the same block. The current location must be an input + * connection. + * @return {ASTNode} The AST node holding the next field or connection + * or null if there is no editable field or input connection after the + * given input. + * @private + */ + private findNextForInput_; + /** + * Given a field find the next editable field or an input with a non null + * connection in the same block. The current location must be a field. + * @return {ASTNode} The AST node pointing to the next field or + * connection or null if there is no editable field or input connection + * after the given input. + * @private + */ + private findNextForField_; + /** + * Given an input find the previous editable field or an input with a non null + * connection in the same block. The current location must be an input + * connection. + * @return {ASTNode} The AST node holding the previous field or + * connection. + * @private + */ + private findPrevForInput_; + /** + * Given a field find the previous editable field or an input with a non null + * connection in the same block. The current location must be a field. + * @return {ASTNode} The AST node holding the previous input or field. + * @private + */ + private findPrevForField_; + /** + * Navigate between stacks of blocks on the workspace. + * @param {boolean} forward True to go forward. False to go backwards. + * @return {ASTNode} The first block of the next stack or null if there + * are no blocks on the workspace. + * @private + */ + private navigateBetweenStacks_; + /** + * Finds the top most AST node for a given block. + * This is either the previous connection, output connection or block + * depending on what kind of connections the block has. + * @param {!Block} block The block that we want to find the top + * connection on. + * @return {!ASTNode} The AST node containing the top connection. + * @private + */ + private findTopASTNodeForBlock_; + /** + * Get the AST node pointing to the input that the block is nested under or if + * the block is not nested then get the stack AST node. + * @param {Block} block The source block of the current location. + * @return {ASTNode} The AST node pointing to the input connection or + * the top block of the stack this block is in. + * @private + */ + private getOutAstNodeForBlock_; + /** + * Find the first editable field or input with a connection on a given block. + * @param {!Block} block The source block of the current location. + * @return {ASTNode} An AST node pointing to the first field or input. + * Null if there are no editable fields or inputs with connections on the + * block. + * @private + */ + private findFirstFieldOrInput_; + /** + * Finds the source block of the location of this node. + * @return {Block} The source block of the location, or null if the node + * is of type workspace. + */ + getSourceBlock(): Block; + /** + * Find the element to the right of the current element in the AST. + * @return {ASTNode} An AST node that wraps the next field, connection, + * block, or workspace. Or null if there is no node to the right. + */ + next(): ASTNode; + /** + * Find the element one level below and all the way to the left of the current + * location. + * @return {ASTNode} An AST node that wraps the next field, connection, + * workspace, or block. Or null if there is nothing below this node. + */ + in(): ASTNode; + /** + * Find the element to the left of the current element in the AST. + * @return {ASTNode} An AST node that wraps the previous field, + * connection, workspace or block. Or null if no node exists to the left. + * null. + */ + prev(): ASTNode; + /** + * Find the next element that is one position above and all the way to the + * left of the current location. + * @return {ASTNode} An AST node that wraps the next field, connection, + * workspace or block. Or null if we are at the workspace level. + */ + out(): ASTNode; + } + export namespace ASTNode { + namespace types { + const FIELD: string; + const BLOCK: string; + const INPUT: string; + const OUTPUT: string; + const NEXT: string; + const PREVIOUS: string; + const STACK: string; + const WORKSPACE: string; + } + /** + * Object holding different types for an AST node. + */ + type types = string; + const NAVIGATE_ALL_FIELDS: boolean; + const DEFAULT_OFFSET_Y: number; + type Params = { + wsCoordinate: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + }; + } + import { Block } from "core/block"; + import { Field } from "core/field"; + import { Connection } from "core/connection"; + import { Input } from "core/input"; + import { Workspace } from "core/workspace"; +} +declare module "core/events/events_marker_move" { + /** + * Class for a marker move event. + * @extends {UiBase} + * @alias Blockly.Events.MarkerMove + */ + export class MarkerMove extends UiBase { + /** + * @param {?Block=} opt_block The affected block. Null if current node + * is of type workspace. Undefined for a blank event. + * @param {boolean=} isCursor Whether this is a cursor event. Undefined for a + * blank event. + * @param {?ASTNode=} opt_oldNode The old node the marker used to be on. + * Undefined for a blank event. + * @param {!ASTNode=} opt_newNode The new node the marker is now on. + * Undefined for a blank event. + */ + constructor(opt_block?: (Block | null) | undefined, isCursor?: boolean | undefined, opt_oldNode?: (ASTNode | null) | undefined, opt_newNode?: ASTNode | undefined); + /** + * The workspace identifier for this event. + * @type {?string} + */ + blockId: string | null; + /** + * The old node the marker used to be on. + * @type {?ASTNode|undefined} + */ + oldNode: (ASTNode | undefined) | null; + /** + * The new node the marker is now on. + * @type {ASTNode|undefined} + */ + newNode: ASTNode | undefined; + /** + * Whether this is a cursor event. + * @type {boolean|undefined} + */ + isCursor: boolean | undefined; + } + import { UiBase } from "core/events/events_ui_base"; + import { ASTNode } from "core/keyboard_nav/ast_node"; + import { Block } from "core/block"; +} +declare module "core/renderers/common/marker_svg" { + /** + * Class for a marker. + * @alias Blockly.blockRendering.MarkerSvg + */ + export class MarkerSvg { + /** + * @param {!WorkspaceSvg} workspace The workspace the marker belongs to. + * @param {!ConstantProvider} constants The constants for + * the renderer. + * @param {!Marker} marker The marker to draw. + */ + constructor(workspace: WorkspaceSvg, constants: ConstantProvider, marker: Marker); + /** + * The workspace the marker belongs to. + * @type {!WorkspaceSvg} + * @private + */ + private workspace_; + /** + * The marker to draw. + * @type {!Marker} + * @private + */ + private marker_; + /** + * The workspace, field, or block that the marker SVG element should be + * attached to. + * @type {IASTNodeLocationSvg} + * @private + */ + private parent_; + /** + * The constants necessary to draw the marker. + * @type {ConstantProvider} + * @protected + */ + protected constants_: ConstantProvider; + /** + * The current SVG element for the marker. + * @type {Element} + */ + currentMarkerSvg: Element; + /** + * The colour of the marker. + * @type {string} + */ + colour_: string; + /** + * The root SVG group containing the marker. + * @type {SVGGElement} + * @protected + */ + protected markerSvg_: SVGGElement; + /** + * @type {?SVGGElement} + * @protected + */ + protected svgGroup_: SVGGElement | null; + /** + * @type {?SVGPathElement} + * @protected + */ + protected markerBlock_: SVGPathElement | null; + /** + * @type {?SVGPathElement} + * @protected + */ + protected markerInput_: SVGPathElement | null; + /** + * @type {?SVGRectElement} + * @protected + */ + protected markerSvgLine_: SVGRectElement | null; + /** + * @type {?SVGRectElement} + * @protected + */ + protected markerSvgRect_: SVGRectElement | null; + /** + * Return the root node of the SVG or null if none exists. + * @return {SVGElement} The root SVG node. + */ + getSvgRoot(): SVGElement; + /** + * Get the marker. + * @return {!Marker} The marker to draw for. + */ + getMarker(): Marker; + /** + * True if the marker should be drawn as a cursor, false otherwise. + * A cursor is drawn as a flashing line. A marker is drawn as a solid line. + * @return {boolean} True if the marker is a cursor, false otherwise. + */ + isCursor(): boolean; + /** + * Create the DOM element for the marker. + * @return {!SVGElement} The marker controls SVG group. + * @package + */ + createDom(): SVGElement; + /** + * Attaches the SVG root of the marker to the SVG group of the parent. + * @param {!IASTNodeLocationSvg} newParent The workspace, field, or + * block that the marker SVG element should be attached to. + * @protected + */ + protected setParent_(newParent: () => void): void; + /** + * Update the marker. + * @param {ASTNode} oldNode The previous node the marker was on or null. + * @param {ASTNode} curNode The node that we want to draw the marker for. + */ + draw(oldNode: ASTNode, curNode: ASTNode): void; + /** + * Update the marker's visible state based on the type of curNode.. + * @param {!ASTNode} curNode The node that we want to draw the marker for. + * @protected + */ + protected showAtLocation_(curNode: ASTNode): void; + /************************** + * Display + **************************/ + /** + * Show the marker as a combination of the previous connection and block, + * the output connection and block, or just the block. + * @param {!ASTNode} curNode The node to draw the marker for. + * @private + */ + private showWithBlockPrevOutput_; + /** + * Position and display the marker for a block. + * @param {!ASTNode} curNode The node to draw the marker for. + * @protected + */ + protected showWithBlock_(curNode: ASTNode): void; + /** + * Position and display the marker for a previous connection. + * @param {!ASTNode} curNode The node to draw the marker for. + * @protected + */ + protected showWithPrevious_(curNode: ASTNode): void; + /** + * Position and display the marker for an output connection. + * @param {!ASTNode} curNode The node to draw the marker for. + * @protected + */ + protected showWithOutput_(curNode: ASTNode): void; + /** + * Position and display the marker for a workspace coordinate. + * This is a horizontal line. + * @param {!ASTNode} curNode The node to draw the marker for. + * @protected + */ + protected showWithCoordinates_(curNode: ASTNode): void; + /** + * Position and display the marker for a field. + * This is a box around the field. + * @param {!ASTNode} curNode The node to draw the marker for. + * @protected + */ + protected showWithField_(curNode: ASTNode): void; + /** + * Position and display the marker for an input. + * This is a puzzle piece. + * @param {!ASTNode} curNode The node to draw the marker for. + * @protected + */ + protected showWithInput_(curNode: ASTNode): void; + /** + * Position and display the marker for a next connection. + * This is a horizontal line. + * @param {!ASTNode} curNode The node to draw the marker for. + * @protected + */ + protected showWithNext_(curNode: ASTNode): void; + /** + * Position and display the marker for a stack. + * This is a box with extra padding around the entire stack of blocks. + * @param {!ASTNode} curNode The node to draw the marker for. + * @protected + */ + protected showWithStack_(curNode: ASTNode): void; + /** + * Show the current marker. + * @protected + */ + protected showCurrent_(): void; + /************************** + * Position + **************************/ + /** + * Position the marker for a block. + * Displays an outline of the top half of a rectangle around a block. + * @param {number} width The width of the block. + * @param {number} markerOffset The extra padding for around the block. + * @param {number} markerHeight The height of the marker. + * @protected + */ + protected positionBlock_(width: number, markerOffset: number, markerHeight: number): void; + /** + * Position the marker for an input connection. + * Displays a filled in puzzle piece. + * @param {!RenderedConnection} connection The connection to position + * marker around. + * @protected + */ + protected positionInput_(connection: RenderedConnection): void; + /** + * Move and show the marker at the specified coordinate in workspace units. + * Displays a horizontal line. + * @param {number} x The new x, in workspace units. + * @param {number} y The new y, in workspace units. + * @param {number} width The new width, in workspace units. + * @protected + */ + protected positionLine_(x: number, y: number, width: number): void; + /** + * Position the marker for an output connection. + * Displays a puzzle outline and the top and bottom path. + * @param {number} width The width of the block. + * @param {number} height The height of the block. + * @param {!Object} connectionShape The shape object for the connection. + * @protected + */ + protected positionOutput_(width: number, height: number, connectionShape: Object): void; + /** + * Position the marker for a previous connection. + * Displays a half rectangle with a notch in the top to represent the previous + * connection. + * @param {number} width The width of the block. + * @param {number} markerOffset The offset of the marker from around the + * block. + * @param {number} markerHeight The height of the marker. + * @param {!Object} connectionShape The shape object for the connection. + * @protected + */ + protected positionPrevious_(width: number, markerOffset: number, markerHeight: number, connectionShape: Object): void; + /** + * Move and show the marker at the specified coordinate in workspace units. + * Displays a filled in rectangle. + * @param {number} x The new x, in workspace units. + * @param {number} y The new y, in workspace units. + * @param {number} width The new width, in workspace units. + * @param {number} height The new height, in workspace units. + * @protected + */ + protected positionRect_(x: number, y: number, width: number, height: number): void; + /** + * Flip the SVG paths in RTL. + * @param {!SVGElement} markerSvg The marker that we want to flip. + * @private + */ + private flipRtl_; + /** + * Hide the marker. + */ + hide(): void; + /** + * Fire event for the marker or marker. + * @param {ASTNode} oldNode The old node the marker used to be on. + * @param {!ASTNode} curNode The new node the marker is currently on. + * @private + */ + private fireMarkerEvent_; + /** + * Get the properties to make a marker blink. + * @return {!Object} The object holding attributes to make the marker blink. + * @protected + */ + protected getBlinkProperties_(): Object; + /** + * Create the marker SVG. + * @return {Element} The SVG node created. + * @protected + */ + protected createDomInternal_(): Element; + /** + * Apply the marker's colour. + * @param {!ASTNode} _curNode The node that we want to draw the marker + * for. + * @protected + */ + protected applyColour_(_curNode: ASTNode): void; + /** + * Dispose of this marker. + */ + dispose(): void; + } + import { ConstantProvider } from "core/renderers/common/constants"; + import { Marker } from "core/keyboard_nav/marker"; + import { ASTNode } from "core/keyboard_nav/ast_node"; + import { RenderedConnection } from "core/rendered_connection"; + import { WorkspaceSvg } from "core/workspace_svg"; +} +declare module "core/keyboard_nav/marker" { + /** + * Class for a marker. + * This is used in keyboard navigation to save a location in the Blockly AST. + * @alias Blockly.Marker + */ + export class Marker { + /** + * The colour of the marker. + * @type {?string} + */ + colour: string | null; + /** + * The current location of the marker. + * @type {ASTNode} + * @private + */ + private curNode_; + /** + * The object in charge of drawing the visual representation of the current + * node. + * @type {MarkerSvg} + * @private + */ + private drawer_; + /** + * The type of the marker. + * @type {string} + */ + type: string; + /** + * Sets the object in charge of drawing the marker. + * @param {MarkerSvg} drawer The object in charge of + * drawing the marker. + */ + setDrawer(drawer: MarkerSvg): void; + /** + * Get the current drawer for the marker. + * @return {MarkerSvg} The object in charge of drawing + * the marker. + */ + getDrawer(): MarkerSvg; + /** + * Gets the current location of the marker. + * @return {ASTNode} The current field, connection, or block the marker + * is on. + */ + getCurNode(): ASTNode; + /** + * Set the location of the marker and call the update method. + * Setting isStack to true will only work if the newLocation is the top most + * output or previous connection on a stack. + * @param {ASTNode} newNode The new location of the marker. + */ + setCurNode(newNode: ASTNode): void; + /** + * Redraw the current marker. + * @package + */ + draw(): void; + /** + * Hide the marker SVG. + */ + hide(): void; + /** + * Dispose of this marker. + */ + dispose(): void; + } + import { MarkerSvg } from "core/renderers/common/marker_svg"; + import { ASTNode } from "core/keyboard_nav/ast_node"; +} +declare module "core/keyboard_nav/cursor" { + /** + * Class for a cursor. + * A cursor controls how a user navigates the Blockly AST. + * @extends {Marker} + * @alias Blockly.Cursor + */ + export class Cursor extends Marker { + /** + * Find the next connection, field, or block. + * @return {ASTNode} The next element, or null if the current node is + * not set or there is no next value. + * @public + */ + public next(): ASTNode; + /** + * Find the in connection or field. + * @return {ASTNode} The in element, or null if the current node is + * not set or there is no in value. + * @public + */ + public in(): ASTNode; + /** + * Find the previous connection, field, or block. + * @return {ASTNode} The previous element, or null if the current node + * is not set or there is no previous value. + * @public + */ + public prev(): ASTNode; + /** + * Find the out connection, field, or block. + * @return {ASTNode} The out element, or null if the current node is + * not set or there is no out value. + * @public + */ + public out(): ASTNode; + } + import { Marker } from "core/keyboard_nav/marker"; + import { ASTNode } from "core/keyboard_nav/ast_node"; +} +declare module "core/marker_manager" { + /** + * Class to manage the multiple markers and the cursor on a workspace. + * @alias Blockly.MarkerManager + */ + export class MarkerManager { + /** + * @param {!WorkspaceSvg} workspace The workspace for the marker manager. + * @package + */ + constructor(workspace: WorkspaceSvg); + /** + * The cursor. + * @type {?Cursor} + * @private + */ + private cursor_; + /** + * The cursor's SVG element. + * @type {?SVGElement} + * @private + */ + private cursorSvg_; + /** + * The map of markers for the workspace. + * @type {!Object} + * @private + */ + private markers_; + /** + * The workspace this marker manager is associated with. + * @type {!WorkspaceSvg} + * @private + */ + private workspace_; + /** + * The marker's SVG element. + * @type {?SVGElement} + * @private + */ + private markerSvg_; + /** + * Register the marker by adding it to the map of markers. + * @param {string} id A unique identifier for the marker. + * @param {!Marker} marker The marker to register. + */ + registerMarker(id: string, marker: Marker): void; + /** + * Unregister the marker by removing it from the map of markers. + * @param {string} id The ID of the marker to unregister. + */ + unregisterMarker(id: string): void; + /** + * Get the cursor for the workspace. + * @return {?Cursor} The cursor for this workspace. + */ + getCursor(): Cursor | null; + /** + * Get a single marker that corresponds to the given ID. + * @param {string} id A unique identifier for the marker. + * @return {?Marker} The marker that corresponds to the given ID, + * or null if none exists. + */ + getMarker(id: string): Marker | null; + /** + * Sets the cursor and initializes the drawer for use with keyboard + * navigation. + * @param {Cursor} cursor The cursor used to move around this workspace. + */ + setCursor(cursor: Cursor): void; + /** + * Add the cursor SVG to this workspace SVG group. + * @param {?SVGElement} cursorSvg The SVG root of the cursor to be added to + * the workspace SVG group. + * @package + */ + setCursorSvg(cursorSvg: SVGElement | null): void; + /** + * Add the marker SVG to this workspaces SVG group. + * @param {?SVGElement} markerSvg The SVG root of the marker to be added to + * the workspace SVG group. + * @package + */ + setMarkerSvg(markerSvg: SVGElement | null): void; + /** + * Redraw the attached cursor SVG if needed. + * @package + */ + updateMarkers(): void; + /** + * Dispose of the marker manager. + * Go through and delete all markers associated with this marker manager. + * @suppress {checkTypes} + * @package + */ + dispose(): void; + } + export namespace MarkerManager { + const LOCAL_MARKER: string; + } + import { Marker } from "core/keyboard_nav/marker"; + import { Cursor } from "core/keyboard_nav/cursor"; + import { WorkspaceSvg } from "core/workspace_svg"; +} +declare module "core/events/events_block_change" { + /** + * Class for a block change event. + * @extends {BlockBase} + * @alias Blockly.Events.BlockChange + */ + export class BlockChange extends BlockBase { + /** + * Returns the extra state of the given block (either as XML or a JSO, + * depending on the block's definition). + * @param {!BlockSvg} block The block to get the extra state of. + * @return {string} A stringified version of the extra state of the given + * block. + * @package + */ + static getExtraBlockState_(block: BlockSvg): string; + /** + * @param {!Block=} opt_block The changed block. Undefined for a blank + * event. + * @param {string=} opt_element One of 'field', 'comment', 'disabled', etc. + * @param {?string=} opt_name Name of input or field affected, or null. + * @param {*=} opt_oldValue Previous value of element. + * @param {*=} opt_newValue New value of element. + */ + constructor(opt_block?: Block | undefined, opt_element?: string | undefined, opt_name?: (string | null) | undefined, opt_oldValue?: any | undefined, opt_newValue?: any | undefined); + element: string | undefined; + name: string | null | undefined; + oldValue: any; + newValue: any; + } + import { BlockBase } from "core/events/events_block_base"; + import { BlockSvg } from "core/block_svg"; + import { Block } from "core/block"; +} +declare module "core/field" { + /** + * Abstract class for an editable field. + * @implements {IASTNodeLocationSvg} + * @implements {IASTNodeLocationWithBlock} + * @implements {IKeyboardAccessible} + * @implements {IRegistrable} + * @abstract + * @alias Blockly.Field + */ + export class Field implements IASTNodeLocationSvg, IASTNodeLocationWithBlock, IKeyboardAccessible, IRegistrable { + /** + * @param {*} value The initial value of the field. + * Also accepts Field.SKIP_SETUP if you wish to skip setup (only used by + * subclasses that want to handle configuration and setting the field + * value after their own constructors have run). + * @param {?Function=} opt_validator A function that is called to validate + * changes to the field's value. Takes in a value & returns a validated + * value, or null to abort the change. + * @param {Object=} opt_config A map of options used to configure the field. + * Refer to the individual field's documentation for a list of properties + * this parameter supports. + */ + constructor(value: any, opt_validator?: (Function | null) | undefined, opt_config?: Object | undefined); + /** + * Name of field. Unique within each block. + * Static labels are usually unnamed. + * @type {string|undefined} + */ + name: string | undefined; + /** + * A generic value possessed by the field. + * Should generally be non-null, only null when the field is created. + * @type {*} + * @protected + */ + protected value_: any; + /** + * Validation function called when user edits an editable field. + * @type {Function} + * @protected + */ + protected validator_: Function; + /** + * Used to cache the field's tooltip value if setTooltip is called when the + * field is not yet initialized. Is *not* guaranteed to be accurate. + * @type {?Tooltip.TipInfo} + * @private + */ + private tooltip_; + /** + * The size of the area rendered by the field. + * @type {!Size} + * @protected + */ + protected size_: { + width: number; + height: number; + }; + /** + * Holds the cursors svg element when the cursor is attached to the field. + * This is null if there is no cursor on the field. + * @type {SVGElement} + * @private + */ + private cursorSvg_; + /** + * Holds the markers svg element when the marker is attached to the field. + * This is null if there is no marker on the field. + * @type {SVGElement} + * @private + */ + private markerSvg_; + /** + * The rendered field's SVG group element. + * @type {SVGGElement} + * @protected + */ + protected fieldGroup_: SVGGElement; + /** + * The rendered field's SVG border element. + * @type {SVGRectElement} + * @protected + */ + protected borderRect_: SVGRectElement; + /** + * The rendered field's SVG text element. + * @type {SVGTextElement} + * @protected + */ + protected textElement_: SVGTextElement; + /** + * The rendered field's text content element. + * @type {Text} + * @protected + */ + protected textContent_: Text; + /** + * Mouse down event listener data. + * @type {?browserEvents.Data} + * @private + */ + private mouseDownWrapper_; + /** + * Constants associated with the source block's renderer. + * @type {ConstantProvider} + * @protected + */ + protected constants_: ConstantProvider; + /** + * Has this field been disposed of? + * @type {boolean} + * @package + */ + disposed: boolean; + /** + * Maximum characters of text to display before adding an ellipsis. + * @type {number} + */ + maxDisplayLength: number; + /** + * Block this field is attached to. Starts as null, then set in init. + * @type {Block} + * @protected + */ + protected sourceBlock_: Block; + /** + * Does this block need to be re-rendered? + * @type {boolean} + * @protected + */ + protected isDirty_: boolean; + /** + * Is the field visible, or hidden due to the block being collapsed? + * @type {boolean} + * @protected + */ + protected visible_: boolean; + /** + * Can the field value be changed using the editor on an editable block? + * @type {boolean} + * @protected + */ + protected enabled_: boolean; + /** + * The element the click handler is bound to. + * @type {Element} + * @protected + */ + protected clickTarget_: Element; + /** + * The prefix field. + * @type {?string} + * @package + */ + prefixField: string | null; + /** + * The suffix field. + * @type {?string} + * @package + */ + suffixField: string | null; + /** + * Editable fields usually show some sort of UI indicating they are + * editable. They will also be saved by the serializer. + * @type {boolean} + */ + EDITABLE: boolean; + /** + * Serializable fields are saved by the serializer, non-serializable fields + * are not. Editable fields should also be serializable. This is not the + * case by default so that SERIALIZABLE is backwards compatible. + * @type {boolean} + */ + SERIALIZABLE: boolean; + /** + * Mouse cursor style when over the hotspot that initiates the editor. + * @type {string} + */ + CURSOR: string; + /** + * Process the configuration map passed to the field. + * @param {!Object} config A map of options used to configure the field. See + * the individual field's documentation for a list of properties this + * parameter supports. + * @protected + */ + protected configure_(config: Object): void; + /** + * Attach this field to a block. + * @param {!Block} block The block containing this field. + */ + setSourceBlock(block: Block): void; + /** + * Get the renderer constant provider. + * @return {?ConstantProvider} The renderer constant + * provider. + */ + getConstants(): ConstantProvider | null; + /** + * Get the block this field is attached to. + * @return {Block} The block containing this field. + */ + getSourceBlock(): Block; + /** + * Initialize everything to render this field. Override + * methods initModel and initView rather than this method. + * @package + * @final + */ + init(): void; + /** + * Create the block UI for this field. + * @package + */ + initView(): void; + /** + * Initializes the model of the field after it has been installed on a block. + * No-op by default. + * @package + */ + initModel(): void; + /** + * Create a field border rect element. Not to be overridden by subclasses. + * Instead modify the result of the function inside initView, or create a + * separate function to call. + * @protected + */ + protected createBorderRect_(): void; + /** + * Create a field text element. Not to be overridden by subclasses. Instead + * modify the result of the function inside initView, or create a separate + * function to call. + * @protected + */ + protected createTextElement_(): void; + /** + * Bind events to the field. Can be overridden by subclasses if they need to + * do custom input handling. + * @protected + */ + protected bindEvents_(): void; + /** + * Sets the field's value based on the given XML element. Should only be + * called by Blockly.Xml. + * @param {!Element} fieldElement The element containing info about the + * field's state. + * @package + */ + fromXml(fieldElement: Element): void; + /** + * Serializes this field's value to XML. Should only be called by Blockly.Xml. + * @param {!Element} fieldElement The element to populate with info about the + * field's state. + * @return {!Element} The element containing info about the field's state. + * @package + */ + toXml(fieldElement: Element): Element; + /** + * Saves this fields value as something which can be serialized to JSON. + * Should only be called by the serialization system. + * @param {boolean=} _doFullSerialization If true, this signals to the field + * that if it normally just saves a reference to some state (eg variable + * fields) it should instead serialize the full state of the thing being + * referenced. + * @return {*} JSON serializable state. + * @package + */ + saveState(_doFullSerialization?: boolean | undefined): any; + /** + * Sets the field's state based on the given state value. Should only be + * called by the serialization system. + * @param {*} state The state we want to apply to the field. + * @package + */ + loadState(state: any): void; + /** + * Returns a stringified version of the XML state, if it should be used. + * Otherwise this returns null, to signal the field should use its own + * serialization. + * @param {*} callingClass The class calling this method. + * Used to see if `this` has overridden any relevant hooks. + * @return {?string} The stringified version of the XML state, or null. + * @protected + */ + protected saveLegacyState(callingClass: any): string | null; + /** + * Loads the given state using either the old XML hoooks, if they should be + * used. Returns true to indicate loading has been handled, false otherwise. + * @param {*} callingClass The class calling this method. + * Used to see if `this` has overridden any relevant hooks. + * @param {*} state The state to apply to the field. + * @return {boolean} Whether the state was applied or not. + */ + loadLegacyState(callingClass: any, state: any): boolean; + /** + * Dispose of all DOM objects and events belonging to this editable field. + * @package + */ + dispose(): void; + /** + * Add or remove the UI indicating if this field is editable or not. + */ + updateEditable(): void; + /** + * Set whether this field's value can be changed using the editor when the + * source block is editable. + * @param {boolean} enabled True if enabled. + */ + setEnabled(enabled: boolean): void; + /** + * Check whether this field's value can be changed using the editor when the + * source block is editable. + * @return {boolean} Whether this field is enabled. + */ + isEnabled(): boolean; + /** + * Check whether this field defines the showEditor_ function. + * @return {boolean} Whether this field is clickable. + */ + isClickable(): boolean; + /** + * Check whether this field is currently editable. Some fields are never + * EDITABLE (e.g. text labels). Other fields may be EDITABLE but may exist on + * non-editable blocks or be currently disabled. + * @return {boolean} Whether this field is currently enabled, editable and on + * an editable block. + */ + isCurrentlyEditable(): boolean; + /** + * Check whether this field should be serialized by the XML renderer. + * Handles the logic for backwards compatibility and incongruous states. + * @return {boolean} Whether this field should be serialized or not. + */ + isSerializable(): boolean; + /** + * Gets whether this editable field is visible or not. + * @return {boolean} True if visible. + */ + isVisible(): boolean; + /** + * Sets whether this editable field is visible or not. Should only be called + * by input.setVisible. + * @param {boolean} visible True if visible. + * @package + */ + setVisible(visible: boolean): void; + /** + * Sets a new validation function for editable fields, or clears a previously + * set validator. + * + * The validator function takes in the new field value, and returns + * validated value. The validated value could be the input value, a modified + * version of the input value, or null to abort the change. + * + * If the function does not return anything (or returns undefined) the new + * value is accepted as valid. This is to allow for fields using the + * validated function as a field-level change event notification. + * + * @param {Function} handler The validator function + * or null to clear a previous validator. + */ + setValidator(handler: Function): void; + /** + * Gets the validation function for editable fields, or null if not set. + * @return {?Function} Validation function, or null. + */ + getValidator(): Function | null; + /** + * Gets the group element for this editable field. + * Used for measuring the size and for positioning. + * @return {!SVGGElement} The group element. + */ + getSvgRoot(): SVGGElement; + /** + * Updates the field to match the colour/style of the block. Should only be + * called by BlockSvg.applyColour(). + * @package + */ + applyColour(): void; + /** + * Used by getSize() to move/resize any DOM elements, and get the new size. + * + * All rendering that has an effect on the size/shape of the block should be + * done here, and should be triggered by getSize(). + * @protected + */ + protected render_(): void; + /** + * Calls showEditor_ when the field is clicked if the field is clickable. + * Do not override. + * @param {Event=} opt_e Optional mouse event that triggered the field to + * open, or undefined if triggered programmatically. + * @package + * @final + */ + showEditor(opt_e?: Event | undefined): void; + /** + * A developer hook to create an editor for the field. This is no-op by + * default, and must be overriden to create an editor. + * @param {Event=} _e Optional mouse event that triggered the field to + * open, or undefined if triggered programmatically. + * @return {void} + * @protected + */ + protected showEditor_(_e?: Event | undefined): void; + /** + * Updates the size of the field based on the text. + * @param {number=} opt_margin margin to use when positioning the text + * element. + * @protected + */ + protected updateSize_(opt_margin?: number | undefined): void; + /** + * Position a field's text element after a size change. This handles both LTR + * and RTL positioning. + * @param {number} xOffset x offset to use when positioning the text element. + * @param {number} contentWidth The content width. + * @protected + */ + protected positionTextElement_(xOffset: number, contentWidth: number): void; + /** + * Position a field's border rect after a size change. + * @protected + */ + protected positionBorderRect_(): void; + /** + * Returns the height and width of the field. + * + * This should *in general* be the only place render_ gets called from. + * @return {!Size} Height and width. + */ + getSize(): { + width: number; + height: number; + }; + /** + * Returns the bounding box of the rendered field, accounting for workspace + * scaling. + * @return {!Rect} An object with top, bottom, left, and right in + * pixels relative to the top left corner of the page (window + * coordinates). + * @package + */ + getScaledBBox(): { + top: number; + bottom: number; + left: number; + right: number; + contains(x: number, y: number): boolean; + intersects(other: any): boolean; + }; + /** + * Get the text from this field to display on the block. May differ from + * ``getText`` due to ellipsis, and other formatting. + * @return {string} Text to display. + * @protected + */ + protected getDisplayText_(): string; + /** + * Get the text from this field. + * Override getText_ to provide a different behavior than simply casting the + * value to a string. + * @return {string} Current text. + * @final + */ + getText(): string; + /** + * A developer hook to override the returned text of this field. + * Override if the text representation of the value of this field + * is not just a string cast of its value. + * Return null to resort to a string cast. + * @return {?string} Current text or null. + * @protected + */ + protected getText_(): string | null; + /** + * Force a rerender of the block that this field is installed on, which will + * rerender this field and adjust for any sizing changes. + * Other fields on the same block will not rerender, because their sizes have + * already been recorded. + * @package + */ + markDirty(): void; + /** + * Force a rerender of the block that this field is installed on, which will + * rerender this field and adjust for any sizing changes. + * Other fields on the same block will not rerender, because their sizes have + * already been recorded. + * @package + */ + forceRerender(): void; + /** + * Used to change the value of the field. Handles validation and events. + * Subclasses should override doClassValidation_ and doValueUpdate_ rather + * than this method. + * @param {*} newValue New value. + * @final + */ + setValue(newValue: any): void; + /** + * Process the result of validation. + * @param {*} newValue New value. + * @param {*} validatedValue Validated value. + * @return {*} New value, or an Error object. + * @private + */ + private processValidation_; + /** + * Get the current value of the field. + * @return {*} Current value. + */ + getValue(): any; + /** + * Used to validate a value. Returns input by default. Can be overridden by + * subclasses, see FieldDropdown. + * @param {*=} opt_newValue The value to be validated. + * @return {*} The validated value, same as input by default. + * @protected + */ + protected doClassValidation_(opt_newValue?: any | undefined): any; + /** + * Used to update the value of a field. Can be overridden by subclasses to do + * custom storage of values/updating of external things. + * @param {*} newValue The value to be saved. + * @protected + */ + protected doValueUpdate_(newValue: any): void; + /** + * Used to notify the field an invalid value was input. Can be overridden by + * subclasses, see FieldTextInput. + * No-op by default. + * @param {*} _invalidValue The input value that was determined to be invalid. + * @protected + */ + protected doValueInvalid_(_invalidValue: any): void; + /** + * Handle a mouse down event on a field. + * @param {!Event} e Mouse down event. + * @protected + */ + protected onMouseDown_(e: Event): void; + /** + * Sets the tooltip for this field. + * @param {?Tooltip.TipInfo} newTip The + * text for the tooltip, a function that returns the text for the tooltip, + * a parent object whose tooltip will be used, or null to display the tooltip + * of the parent block. To not display a tooltip pass the empty string. + */ + setTooltip(newTip: Tooltip.TipInfo | null): void; + /** + * Returns the tooltip text for this field. + * @return {string} The tooltip text for this field. + */ + getTooltip(): string; + /** + * The element to bind the click handler to. If not set explicitly, defaults + * to the SVG root of the field. When this element is + * clicked on an editable field, the editor will open. + * @return {!Element} Element to bind click handler to. + * @protected + */ + protected getClickTarget_(): Element; + /** + * Return the absolute coordinates of the top-left corner of this field. + * The origin (0,0) is the top-left corner of the page body. + * @return {!Coordinate} Object with .x and .y properties. + * @protected + */ + protected getAbsoluteXY_(): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + /** + * Whether this field references any Blockly variables. If true it may need + * to be handled differently during serialization and deserialization. + * Subclasses may override this. + * @return {boolean} True if this field has any variable references. + * @package + */ + referencesVariables(): boolean; + /** + * Refresh the variable name referenced by this field if this field references + * variables. + * @package + */ + refreshVariableName(): void; + /** + * Search through the list of inputs and their fields in order to find the + * parent input of a field. + * @return {Input} The input that the field belongs to. + * @package + */ + getParentInput(): Input; + /** + * Returns whether or not we should flip the field in RTL. + * @return {boolean} True if we should flip in RTL. + */ + getFlipRtl(): boolean; + /** + * Returns whether or not the field is tab navigable. + * @return {boolean} True if the field is tab navigable. + */ + isTabNavigable(): boolean; + /** + * Handles the given keyboard shortcut. + * @param {!ShortcutRegistry.KeyboardShortcut} _shortcut The shortcut to be + * handled. + * @return {boolean} True if the shortcut has been handled, false otherwise. + * @public + */ + public onShortcut(_shortcut: ShortcutRegistry.KeyboardShortcut): boolean; + /** + * Add the cursor SVG to this fields SVG group. + * @param {SVGElement} cursorSvg The SVG root of the cursor to be added to the + * field group. + * @package + */ + setCursorSvg(cursorSvg: SVGElement): void; + /** + * Add the marker SVG to this fields SVG group. + * @param {SVGElement} markerSvg The SVG root of the marker to be added to the + * field group. + * @package + */ + setMarkerSvg(markerSvg: SVGElement): void; + /** + * Redraw any attached marker or cursor svgs if needed. + * @protected + */ + protected updateMarkers_(): void; + /** + * The default value for this field. + * @type {*} + * @protected + */ + protected DEFAULT_VALUE: any; + } + export namespace Field { + const NBSP: string; + const SKIP_SETUP: Sentinel; + } + import { IASTNodeLocationSvg } from "core/interfaces/i_ast_node_location_svg"; + import { IASTNodeLocationWithBlock } from "core/interfaces/i_ast_node_location_with_block"; + import { IKeyboardAccessible } from "core/interfaces/i_keyboard_accessible"; + import { IRegistrable } from "core/interfaces/i_registrable"; + import { ConstantProvider } from "core/renderers/common/constants"; + import { Block } from "core/block"; + import * as Tooltip from "core/tooltip"; + import { Input } from "core/input"; + import { ShortcutRegistry } from "core/shortcut_registry"; + import { Sentinel } from "core/utils/sentinel"; +} +declare module "core/events/events_var_delete" { + /** + * Class for a variable deletion event. + * @extends {VarBase} + * @alias Blockly.Events.VarDelete + */ + export class VarDelete extends VarBase { + varType: string | undefined; + varName: string | undefined; + } + import { VarBase } from "core/events/events_var_base"; +} +declare module "core/events/events_var_rename" { + /** + * Class for a variable rename event. + * @extends {VarBase} + * @alias Blockly.Events.VarRename + */ + export class VarRename extends VarBase { + /** + * @param {!VariableModel=} opt_variable The renamed variable. Undefined + * for a blank event. + * @param {string=} newName The new name the variable will be changed to. + */ + constructor(opt_variable?: VariableModel | undefined, newName?: string | undefined); + oldName: string | undefined; + newName: string | undefined; + } + import { VarBase } from "core/events/events_var_base"; + import { VariableModel } from "core/variable_model"; +} +declare module "core/variable_map" { + /** + * Class for a variable map. This contains a dictionary data structure with + * variable types as keys and lists of variables as values. The list of + * variables are the type indicated by the key. + * @alias Blockly.VariableMap + */ + export class VariableMap { + /** + * @param {!Workspace} workspace The workspace this map belongs to. + */ + constructor(workspace: 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. + * @type {!Object>} + * @private + */ + private variableMap_; + /** + * The workspace this map belongs to. + * @type {!Workspace} + */ + workspace: Workspace; + /** + * Clear the variable map. + */ + clear(): void; + /** + * Rename the given variable by updating its name in the variable map. + * @param {!VariableModel} variable Variable to rename. + * @param {string} newName New variable name. + * @package + */ + renameVariable(variable: VariableModel, newName: string): void; + /** + * 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. + */ + renameVariableById(id: string, newName: string): void; + /** + * 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 {!VariableModel} variable Variable to rename. + * @param {string} newName New variable name. + * @param {!Array} blocks The list of all blocks in the + * workspace. + * @private + */ + private renameVariableAndUses_; + /** + * 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 {!VariableModel} variable Variable to rename. + * @param {string} newName New variable name. + * @param {!VariableModel} conflictVar The variable that was already + * using newName. + * @param {!Array} blocks The list of all blocks in the + * workspace. + * @private + */ + private renameVariableWithConflict_; + /** + * Create a variable with a given name, optional type, and optional ID. + * @param {string} name The name of the variable. This must be unique across + * variables and procedures. + * @param {?string=} opt_type The type of the variable like 'int' or 'string'. + * Does not need to be unique. Field_variable can filter variables based + * on their type. This will default to '' which is a specific type. + * @param {?string=} opt_id The unique ID of the variable. This will default + * to a UUID. + * @return {!VariableModel} The newly created variable. + */ + createVariable(name: string, opt_type?: (string | null) | undefined, opt_id?: (string | null) | undefined): VariableModel; + /** + * Delete a variable. + * @param {!VariableModel} variable Variable to delete. + */ + deleteVariable(variable: VariableModel): void; + /** + * 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. + */ + deleteVariableById(id: string): void; + /** + * Deletes a variable and all of its uses from this workspace without asking + * the user for confirmation. + * @param {!VariableModel} variable Variable to delete. + * @param {!Array} uses An array of uses of the variable. + * @package + */ + deleteVariableInternal(variable: VariableModel, uses: Array): void; + /** + * 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 {?VariableModel} The variable with the given name, or null if + * it was not found. + */ + getVariable(name: string, opt_type?: (string | null) | undefined): VariableModel | null; + /** + * Find the variable by the given ID and return it. Return null if not found. + * @param {string} id The ID to check for. + * @return {?VariableModel} The variable with the given ID. + */ + getVariableById(id: string): VariableModel | null; + /** + * Get a list containing all of the variables of a specified type. If type is + * null, return list of variables with empty string type. + * @param {?string} type Type of the variables to find. + * @return {!Array} The sought after variables of the + * passed in type. An empty array if none are found. + */ + getVariablesOfType(type: string | null): Array; + /** + * Return all variable and potential variable types. This list always + * contains the empty string. + * @param {?Workspace} ws The workspace used to look for potential + * variables. This can be different than the workspace stored on this object + * if the passed in ws is a flyout workspace. + * @return {!Array} List of variable types. + * @package + */ + getVariableTypes(ws: Workspace | null): Array; + /** + * Return all variables of all types. + * @return {!Array} List of variable models. + */ + getAllVariables(): Array; + /** + * Returns all of the variable names of all types. + * @return {!Array} All of the variable names of all types. + */ + getAllVariableNames(): Array; + /** + * Find all the uses of a named variable. + * @param {string} id ID of the variable to find. + * @return {!Array} Array of block usages. + */ + getVariableUsesById(id: string): Array; + } + import { Workspace } from "core/workspace"; + import { VariableModel } from "core/variable_model"; + import { Block } from "core/block"; +} +declare module "core/names" { + /** + * * + */ + export type NameType = string; + export namespace NameType { + const DEVELOPER_VARIABLE: string; + const VARIABLE: string; + const PROCEDURE: string; + } + /** + * Class for a database of entity names (variables, procedures, etc). + * @alias Blockly.Names + */ + export const Names: { + new (reservedWords: string, opt_variablePrefix?: string | undefined): { + /** + * The prefix to attach to variable names in generated code. + * @type {string} + * @private + */ + variablePrefix_: string; + /** + * A dictionary of reserved words. + * @type {Object} + * @private + */ + reservedDict_: Object; + /** + * A map from type (e.g. name, procedure) to maps from names to generated + * names. + * @type {Object>} + * @private + */ + db_: { + [x: string]: { + [x: string]: string; + }; + }; + /** + * A map from used names to booleans to avoid collisions. + * @type {Object} + * @private + */ + dbReverse_: { + [x: string]: boolean; + }; + /** + * The variable map from the workspace, containing Blockly variable models. + * @type {?VariableMap} + * @private + */ + variableMap_: VariableMap | null; + /** + * Empty the database and start from scratch. The reserved words are kept. + */ + reset(): void; + /** + * Set the variable map that maps from variable name to variable object. + * @param {!VariableMap} map The map to track. + */ + setVariableMap(map: VariableMap): void; + /** + * Get the name for a user-defined variable, based on its ID. + * This should only be used for variables of NameType VARIABLE. + * @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 + */ + getNameForUserVariable_(id: string): string | null; + /** + * Generate names for user variables, but only ones that are being used. + * @param {!Workspace} workspace Workspace to generate variables from. + */ + populateVariables(workspace: Workspace): void; + /** + * Generate names for procedures. + * @param {!Workspace} workspace Workspace to generate procedures from. + */ + populateProcedures(workspace: Workspace): void; + /** + * Convert a Blockly entity name to a legal exportable entity name. + * @param {string} nameOrId The Blockly entity name (no constraints) or + * variable ID. + * @param {NameType|string} type The type of the name in Blockly + * ('VARIABLE', 'PROCEDURE', 'DEVELOPER_VARIABLE', etc...). + * @return {string} An entity name that is legal in the exported language. + */ + getName(nameOrId: string, type: NameType | string): string; + /** + * Return a list of all known user-created names of a specified name type. + * @param {NameType|string} type The type of entity in Blockly + * ('VARIABLE', 'PROCEDURE', 'DEVELOPER_VARIABLE', etc...). + * @return {!Array} A list of Blockly entity names (no constraints). + */ + getUserNames(type: NameType | string): Array; + /** + * Convert a Blockly entity name to a legal exportable entity name. + * Ensure that this is a new name not overlapping any previously defined name. + * Also check against list of reserved words for the current language and + * ensure name doesn't collide. + * @param {string} name The Blockly entity name (no constraints). + * @param {NameType|string} type The type of entity in Blockly + * ('VARIABLE', 'PROCEDURE', 'DEVELOPER_VARIABLE', etc...). + * @return {string} An entity name that is legal in the exported language. + */ + getDistinctName(name: string, type: NameType | string): string; + /** + * Given a proposed entity name, generate a name that conforms to the + * [_A-Za-z][_A-Za-z0-9]* format that most languages consider legal for + * variable and function names. + * @param {string} name Potentially illegal entity name. + * @return {string} Safe entity name. + * @private + */ + safeName_(name: string): string; + }; + /** + * Do the given two entity names refer to the same entity? + * Blockly names are case-insensitive. + * @param {string} name1 First name. + * @param {string} name2 Second name. + * @return {boolean} True if names are the same. + */ + equals(name1: string, name2: string): boolean; + /** + * 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. + */ + DEVELOPER_VARIABLE_TYPE: string; + }; + import { VariableMap } from "core/variable_map"; + import { Workspace } from "core/workspace"; +} +declare module "core/procedures" { + /** + * String for use in the "custom" attribute of a category in toolbox XML. + * This string indicates that the category should be dynamically populated with + * procedure blocks. + * See also Blockly.Variables.CATEGORY_NAME and + * Blockly.VariablesDynamic.CATEGORY_NAME. + * @const {string} + * @alias Blockly.Procedures.CATEGORY_NAME + */ + export const CATEGORY_NAME: "PROCEDURE"; + /** + * The default argument for a procedures_mutatorarg block. + * @type {string} + * @alias Blockly.Procedures.DEFAULT_ARG + */ + export const DEFAULT_ARG: string; + /** + * Procedure block type. + */ + export type ProcedureBlock = { + getProcedureCall: () => string; + renameProcedure: (arg0: string, arg1: string) => any; + getProcedureDef: () => any[]; + }; + /** + * Procedure block type. + * @typedef {{ + * getProcedureCall: function():string, + * renameProcedure: function(string,string), + * getProcedureDef: function():!Array + * }} + * @alias Blockly.Procedures.ProcedureBlock + */ + export let ProcedureBlock: any; + /** + * Find all user-created procedure definitions in a workspace. + * @param {!Workspace} root Root workspace. + * @return {!Array>} Pair of arrays, the + * first contains procedures without return variables, the second with. + * Each procedure is defined by a three-element list of name, parameter + * list, and return value boolean. + * @alias Blockly.Procedures.allProcedures + */ + export function allProcedures(root: Workspace): Array>; + /** + * Ensure two identically-named procedures don't exist. + * Take the proposed procedure name, and return a legal name i.e. one that + * is not empty and doesn't collide with other procedures. + * @param {string} name Proposed procedure name. + * @param {!Block} block Block to disambiguate. + * @return {string} Non-colliding name. + * @alias Blockly.Procedures.findLegalName + */ + export function findLegalName(name: string, block: Block): string; + /** + * Return if the given name is already a procedure name. + * @param {string} name The questionable name. + * @param {!Workspace} workspace The workspace to scan for collisions. + * @param {Block=} opt_exclude Optional block to exclude from + * comparisons (one doesn't want to collide with oneself). + * @return {boolean} True if the name is used, otherwise return false. + * @alias Blockly.Procedures.isNameUsed + */ + export function isNameUsed(name: string, workspace: Workspace, opt_exclude?: Block | undefined): boolean; + /** + * Rename a procedure. Called by the editable field. + * @param {string} name The proposed new name. + * @return {string} The accepted name. + * @this {Field} + * @alias Blockly.Procedures.rename + */ + export function rename(name: string): string; + /** + * Construct the blocks required by the flyout for the procedure category. + * @param {!WorkspaceSvg} workspace The workspace containing procedures. + * @return {!Array} Array of XML block elements. + * @alias Blockly.Procedures.flyoutCategory + */ + export function flyoutCategory(workspace: WorkspaceSvg): Array; + /** + * Listens for when a procedure mutator is opened. Then it triggers a flyout + * update and adds a mutator change listener to the mutator workspace. + * @param {!Abstract} e The event that triggered this listener. + * @alias Blockly.Procedures.mutatorOpenListener + * @package + */ + export function mutatorOpenListener(e: Abstract): void; + /** + * Find all the callers of a named procedure. + * @param {string} name Name of procedure. + * @param {!Workspace} workspace The workspace to find callers in. + * @return {!Array} Array of caller blocks. + * @alias Blockly.Procedures.getCallers + */ + export function getCallers(name: string, workspace: Workspace): Array; + /** + * When a procedure definition changes its parameters, find and edit all its + * callers. + * @param {!Block} defBlock Procedure definition block. + * @alias Blockly.Procedures.mutateCallers + */ + export function mutateCallers(defBlock: Block): void; + /** + * Find the definition block for the named procedure. + * @param {string} name Name of procedure. + * @param {!Workspace} workspace The workspace to search. + * @return {?Block} The procedure definition block, or null not found. + * @alias Blockly.Procedures.getDefinition + */ + export function getDefinition(name: string, workspace: Workspace): Block | null; + import { Workspace } from "core/workspace"; + import { Block } from "core/block"; + import { WorkspaceSvg } from "core/workspace_svg"; + import { Abstract } from "core/events/events_abstract"; +} +declare module "core/variables_dynamic" { + /** + * 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. + * See also Blockly.Variables.CATEGORY_NAME and + * Blockly.Procedures.CATEGORY_NAME. + * @const {string} + * @alias Blockly.VariablesDynamic.CATEGORY_NAME + */ + export const CATEGORY_NAME: "VARIABLE_DYNAMIC"; + function stringButtonClickHandler(button: any): void; + function numberButtonClickHandler(button: any): void; + function colourButtonClickHandler(button: any): void; + /** + * Construct the elements (blocks and button) required by the flyout for the + * variable category. + * @param {!WorkspaceSvg} workspace The workspace containing variables. + * @return {!Array} Array of XML elements. + * @alias Blockly.VariablesDynamic.flyoutCategory + */ + export function flyoutCategory(workspace: WorkspaceSvg): Array; + /** + * Construct the blocks required by the flyout for the variable category. + * @param {!Workspace} workspace The workspace containing variables. + * @return {!Array} Array of XML block elements. + * @alias Blockly.VariablesDynamic.flyoutCategoryBlocks + */ + export function flyoutCategoryBlocks(workspace: Workspace): Array; + import { WorkspaceSvg } from "core/workspace_svg"; + import { Workspace } from "core/workspace"; + export { stringButtonClickHandler as onCreateVariableButtonClick_String, numberButtonClickHandler as onCreateVariableButtonClick_Number, colourButtonClickHandler as onCreateVariableButtonClick_Colour }; +} +declare module "core/renderers/common/debug" { + /** + * Returns whether the debugger is turned on. + * @return {boolean} Whether the debugger is turned on. + * @alias Blockly.blockRendering.debug.isDebuggerEnabled + * @package + */ + export function isDebuggerEnabled(): boolean; + /** + * Turn on the blocks debugger. + * @package + * @alias Blockly.blockRendering.debug.startDebugger + * @deprecated March 2022. Use the rendering debugger in @blockly/dev-tools. + * See https://www.npmjs.com/package/@blockly/dev-tools for more information. + */ + export function startDebugger(): void; + /** + * Turn off the blocks debugger. + * @package + * @alias Blockly.blockRendering.debug.stopDebugger + * @deprecated March 2022. Use the rendering debugger in @blockly/dev-tools. + * See https://www.npmjs.com/package/@blockly/dev-tools for more information. + */ + export function stopDebugger(): void; +} +declare module "core/renderers/measurables/in_row_spacer" { + /** + * An object containing information about a spacer between two elements on a + * row. + * @extends {Measurable} + * @struct + * @alias Blockly.blockRendering.InRowSpacer + */ + export class InRowSpacer extends Measurable { + /** + * @param {!ConstantProvider} constants The rendering + * constants provider. + * @param {number} width The width of the spacer. + * @package + */ + constructor(constants: ConstantProvider, width: number); + } + import { Measurable } from "core/renderers/measurables/base"; + import { ConstantProvider } from "core/renderers/common/constants"; +} +declare module "core/renderers/measurables/input_connection" { /** * The base class to represent an input that takes up space on a block * during rendering - * @param {!ConstantProvider} constants The rendering - * constants provider. - * @param {!Input} input The input to measure and store information for. * @package - * @constructor * @extends {Connection} * @alias Blockly.blockRendering.InputConnection */ - export class InputConnection { - constructor(constants: any, input: any); - input: any; - align: any; - connectedBlock: any; - connectedBlockWidth: any; - connectedBlockHeight: any; + export class InputConnection extends Connection { + /** + * @param {!ConstantProvider} constants The rendering + * constants provider. + * @param {!Input} input The input to measure and store information for. + */ + constructor(constants: ConstantProvider, input: Input); + /** @type {!Input} */ + input: Input; + /** @type {number} */ + align: number; + /** @type {BlockSvg} */ + connectedBlock: BlockSvg; + connectedBlockWidth: number; + connectedBlockHeight: number; + /** @type {number} */ connectionOffsetX: number; + /** @type {number} */ connectionOffsetY: number; } + import { Connection } from "core/renderers/measurables/connection"; + import { Input } from "core/input"; + import { BlockSvg } from "core/block_svg"; + import { ConstantProvider } from "core/renderers/common/constants"; } -declare module "renderers/measurables/row" { +declare module "core/renderers/measurables/row" { /** * An object representing a single row on a rendered block and all of its * subcomponents. - * @param {!ConstantProvider} constants The rendering - * constants provider. - * @package - * @constructor * @alias Blockly.blockRendering.Row */ export class Row { - constructor(constants: any); + /** + * @param {!ConstantProvider} constants The rendering + * constants provider. + * @package + */ + constructor(constants: ConstantProvider); /** * The type of this rendering object. * @package @@ -6436,8 +10872,8 @@ declare module "renderers/measurables/row" { */ minHeight: number; /** - * The minimum width of the row, from the left edge of the block to the right. - * Does not include child blocks unless they are inline. + * The minimum width of the row, from the left edge of the block to the + * right. Does not include child blocks unless they are inline. * @package * @type {number} */ @@ -6450,13 +10886,15 @@ declare module "renderers/measurables/row" { */ widthWithConnectedBlocks: number; /** - * The Y position of the row relative to the origin of the block's svg group. + * The Y position of the row relative to the origin of the block's svg + * group. * @package * @type {number} */ yPos: number; /** - * The X position of the row relative to the origin of the block's svg group. + * The X position of the row relative to the origin of the block's svg + * group. * @package * @type {number} */ @@ -6473,6 +10911,13 @@ declare module "renderers/measurables/row" { * @type {boolean} */ hasStatement: boolean; + /** + * Where the left edge of all of the statement inputs on the block should + * be. This makes sure that statement inputs which are proceded by fields + * of varius widths are all aligned. + * @type {number} + */ + statementEdge: number; /** * Whether the row has any inline inputs. * @package @@ -6497,6 +10942,9 @@ declare module "renderers/measurables/row" { * @protected */ protected constants_: ConstantProvider; + /** + * @type {number} + */ notchOffset: number; /** * Alignment of the row. @@ -6543,12 +10991,12 @@ declare module "renderers/measurables/row" { */ getLastSpacer(): InRowSpacer; } - import { Measurable } from "renderers/measurables/base"; - import { ConstantProvider } from "renderers/common/constants"; - import { InputConnection } from "renderers/measurables/input_connection"; - import { InRowSpacer } from "renderers/measurables/in_row_spacer"; + import { Measurable } from "core/renderers/measurables/base"; + import { ConstantProvider } from "core/renderers/common/constants"; + import { InputConnection } from "core/renderers/measurables/input_connection"; + import { InRowSpacer } from "core/renderers/measurables/in_row_spacer"; } -declare module "renderers/measurables/types" { +declare module "core/renderers/measurables/types" { /** * * */ @@ -6748,26 +11196,32 @@ declare module "renderers/measurables/types" { */ function isInputRow(row: Row): number; } - import { Measurable } from "renderers/measurables/base"; - import { Row } from "renderers/measurables/row"; + import { Measurable } from "core/renderers/measurables/base"; + import { Row } from "core/renderers/measurables/row"; } -declare module "renderers/measurables/base" { +declare module "core/renderers/measurables/base" { /** * The base class to represent a part of a block that takes up space during * rendering. The constructor for each non-spacer Measurable records the size * of the block element (e.g. field, statement input). - * @param {!ConstantProvider} constants The rendering - * constants provider. - * @package - * @constructor * @alias Blockly.blockRendering.Measurable */ export class Measurable { - constructor(constants: any); + /** + * @param {!ConstantProvider} constants The rendering + * constants provider. + * @package + */ + constructor(constants: ConstantProvider); + /** @type {number} */ width: number; + /** @type {number} */ height: number; + /** @type {number} */ type: number; + /** @type {number} */ xPos: number; + /** @type {number} */ centerline: number; /** * The renderer's constant provider. @@ -6775,64 +11229,61 @@ declare module "renderers/measurables/base" { * @protected */ protected constants_: ConstantProvider; + /** @type {number} */ notchOffset: number; } - import { ConstantProvider } from "renderers/common/constants"; + import { ConstantProvider } from "core/renderers/common/constants"; } -declare module "renderers/measurables/connection" { +declare module "core/renderers/measurables/connection" { /** * The base class to represent a connection and the space that it takes up on * the block. - * @param {!ConstantProvider} constants The rendering - * constants provider. - * @param {!RenderedConnection} connectionModel The connection object on - * the block that this represents. - * @package - * @constructor * @extends {Measurable} * @alias Blockly.blockRendering.Connection */ - export class Connection { - constructor(constants: any, connectionModel: any); - connectionModel: any; - shape: any; + export class Connection extends Measurable { + /** + * @param {!ConstantProvider} constants The rendering + * constants provider. + * @param {!RenderedConnection} connectionModel The connection object on + * the block that this represents. + * @package + */ + constructor(constants: ConstantProvider, connectionModel: RenderedConnection); + /** @type {!RenderedConnection} */ + connectionModel: RenderedConnection; + /** @type {!Object} */ + shape: Object; + /** @type {boolean} */ isDynamicShape: boolean; } + import { Measurable } from "core/renderers/measurables/base"; + import { RenderedConnection } from "core/rendered_connection"; + import { ConstantProvider } from "core/renderers/common/constants"; } -declare module "renderers/measurables/next_connection" { +declare module "core/renderers/measurables/next_connection" { /** * An object containing information about the space a next connection takes * up during rendering. - * @param {!ConstantProvider} constants The rendering - * constants provider. - * @param {RenderedConnection} connectionModel The connection object on - * the block that this represents. - * @package - * @constructor * @extends {Connection} + * @struct * @alias Blockly.blockRendering.NextConnection */ - export class NextConnection { - constructor(constants: any, connectionModel: any); - height: any; - width: any; + export class NextConnection extends Connection { } + import { Connection } from "core/renderers/measurables/connection"; } -declare module "renderers/measurables/bottom_row" { +declare module "core/renderers/measurables/bottom_row" { /** * An object containing information about what elements are in the bottom row of * a block as well as spacing information for the bottom row. * Elements in a bottom row can consist of corners, spacers and next * connections. - * @param {!ConstantProvider} constants The rendering - * constants provider. - * @package - * @constructor * @extends {Row} + * @struct * @alias Blockly.blockRendering.BottomRow */ export class BottomRow extends Row { - constructor(constants: any); /** * Whether this row has a next connection. * @package @@ -6846,8 +11297,9 @@ declare module "renderers/measurables/bottom_row" { */ connection: NextConnection; /** - * The amount that the bottom of the block extends below the horizontal edge, - * e.g. because of a next connection. Must be non-negative (see #2820). + * The amount that the bottom of the block extends below the horizontal + * edge, e.g. because of a next connection. Must be non-negative (see + * #2820). * @package * @type {number} */ @@ -6870,1072 +11322,84 @@ declare module "renderers/measurables/bottom_row" { * @return {boolean} Whether or not the bottom row has a right square corner. */ hasRightSquareCorner(_block: BlockSvg): boolean; - /** - * @override - */ - override measure(): void; - width: number; - height: number; - widthWithConnectedBlocks: number; - /** - * @override - */ - override startsWithElemSpacer(): boolean; - /** - * @override - */ - override endsWithElemSpacer(): boolean; } - import { NextConnection } from "renderers/measurables/next_connection"; - import { BlockSvg } from "block_svg"; - import { Row } from "renderers/measurables/row"; + import { Row } from "core/renderers/measurables/row"; + import { NextConnection } from "core/renderers/measurables/next_connection"; + import { BlockSvg } from "core/block_svg"; } -declare module "renderers/measurables/external_value_input" { - /** - * An object containing information about the space an external value input - * takes up during rendering - * @param {!ConstantProvider} constants The rendering - * constants provider. - * @param {!Input} input The external value input to measure and store - * information for. - * @package - * @constructor - * @extends {InputConnection} - * @alias Blockly.blockRendering.ExternalValueInput - */ - export class ExternalValueInput { - constructor(constants: any, input: any); - height: any; - width: any; - connectionOffsetY: any; - connectionHeight: any; - connectionWidth: any; - } -} -declare module "renderers/measurables/field" { +declare module "core/renderers/measurables/field" { /** * An object containing information about the space a field takes up during * rendering - * @param {!ConstantProvider} constants The rendering - * constants provider. - * @param {!BlocklyField} field The field to measure and store information for. - * @param {!Input} parentInput The parent input for the field. - * @package - * @constructor + * @struct * @extends {Measurable} * @alias Blockly.blockRendering.Field */ - export class Field { - constructor(constants: any, field: any, parentInput: any); - field: any; - isEditable: any; - flipRtl: any; - height: any; - width: any; - parentInput: any; + export class Field extends Measurable { + /** + * @param {!ConstantProvider} constants The rendering + * constants provider. + * @param {!BlocklyField} field The field to measure and store information + * for. + * @param {!Input} parentInput The parent input for the field. + * @package + */ + constructor(constants: ConstantProvider, field: BlocklyField, parentInput: Input); + /** @type {!BlocklyField} */ + field: BlocklyField; + /** @type {boolean} */ + isEditable: boolean; + /** @type {boolean} */ + flipRtl: boolean; + /** @type {!Input} */ + parentInput: Input; } + import { Measurable } from "core/renderers/measurables/base"; + import { Field as BlocklyField } from "core/field"; + import { Input } from "core/input"; + import { ConstantProvider } from "core/renderers/common/constants"; } -declare module "renderers/measurables/hat" { +declare module "core/renderers/measurables/external_value_input" { + /** + * An object containing information about the space an external value input + * takes up during rendering + * @struct + * @extends {InputConnection} + * @alias Blockly.blockRendering.ExternalValueInput + */ + export class ExternalValueInput extends InputConnection { + /** @type {number} */ + connectionHeight: number; + /** @type {number} */ + connectionWidth: number; + } + import { InputConnection } from "core/renderers/measurables/input_connection"; +} +declare module "core/renderers/measurables/hat" { /** * An object containing information about the space a hat takes up during * rendering. - * @param {!ConstantProvider} constants The rendering - * constants provider. - * @package - * @constructor + * @struct * @extends {Measurable} * @alias Blockly.blockRendering.Hat */ - export class Hat { - constructor(constants: any); - height: any; - width: any; - ascenderHeight: any; + export class Hat extends Measurable { + /** @type {number} */ + ascenderHeight: number; } + import { Measurable } from "core/renderers/measurables/base"; } -declare module "block_drag_surface" { - /** - * Class for a drag surface for the currently dragged block. This is a separate - * SVG that contains only the currently moving block, or nothing. - * @param {!Element} container Containing element. - * @constructor - * @alias Blockly.BlockDragSurfaceSvg - */ - export class BlockDragSurfaceSvg { - constructor(container: any); - /** - * @type {!Element} - * @private - */ - private container_; - /** - * Create the drag surface and inject it into the container. - */ - createDom(): void; - SVG_: SVGElement | null; - dragGroup_: SVGElement | null; - /** - * Set the SVG blocks on the drag surface's group and show the surface. - * Only one block group should be on the drag surface at a time. - * @param {!SVGElement} blocks Block or group of blocks to place on the drag - * surface. - */ - setBlocksAndShow(blocks: SVGElement): void; - surfaceXY_: any; - /** - * Translate and scale the entire drag surface group to the given position, to - * keep in sync with the workspace. - * @param {number} x X translation in pixel coordinates. - * @param {number} y Y translation in pixel coordinates. - * @param {number} scale Scale of the group. - */ - translateAndScaleGroup(x: number, y: number, scale: number): void; - scale_: number; - /** - * Translate the drag surface's SVG based on its internal state. - * @private - */ - private translateSurfaceInternal_; - /** - * Translates the entire surface by a relative offset. - * @param {number} deltaX Horizontal offset in pixel units. - * @param {number} deltaY Vertical offset in pixel units. - */ - translateBy(deltaX: number, deltaY: number): void; - /** - * Translate the entire drag surface during a drag. - * We translate the drag surface instead of the blocks inside the surface - * so that the browser avoids repainting the SVG. - * Because of this, the drag coordinates must be adjusted by scale. - * @param {number} x X translation for the entire surface. - * @param {number} y Y translation for the entire surface. - */ - translateSurface(x: number, y: number): void; - /** - * Reports the surface translation in scaled workspace coordinates. - * Use this when finishing a drag to return blocks to the correct position. - * @return {!Coordinate} Current translation of the surface. - */ - getSurfaceTranslation(): Coordinate; - /** - * Provide a reference to the drag group (primarily for - * BlockSvg.getRelativeToSurfaceXY). - * @return {?SVGElement} Drag surface group element. - */ - getGroup(): SVGElement | null; - /** - * Returns the SVG drag surface. - * @returns {?SVGElement} The SVG drag surface. - */ - getSvgRoot(): SVGElement | null; - /** - * Get the current blocks on the drag surface, if any (primarily - * for BlockSvg.getRelativeToSurfaceXY). - * @return {?Element} Drag surface block DOM element, or null if no blocks - * exist. - */ - getCurrentBlock(): Element | null; - /** - * Gets the translation of the child block surface - * This surface is in charge of keeping track of how much the workspace has - * moved. - * @return {!Coordinate} The amount the workspace has been moved. - */ - getWsTranslation(): Coordinate; - /** - * Clear the group and hide the surface; move the blocks off onto the provided - * element. - * If the block is being deleted it doesn't need to go back to the original - * surface, since it would be removed immediately during dispose. - * @param {Element=} opt_newSurface Surface the dragging blocks should be moved - * to, or null if the blocks should be removed from this surface without - * being moved to a different surface. - */ - clearAndHide(opt_newSurface?: Element | undefined): void; - /** - * Cached value for the translation of the child drag surface in pixel units. - * Since the child drag surface tracks the translation of the workspace this is - * ultimately the translation of the workspace. - * @type {!Coordinate} - * @private - */ - private childSurfaceXY_; - } - import { Coordinate } from "utils/coordinate"; -} -declare module "interfaces/i_contextmenu" { - /** - * @license - * Copyright 2020 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - /** - * @fileoverview The interface for an object that supports a right-click. - */ - /** - * The interface for an object that supports a right-click. - * @namespace Blockly.IContextMenu - */ - /** - * @interface - * @alias Blockly.IContextMenu - */ - export class IContextMenu { - } -} -declare module "interfaces/i_draggable" { - /** - * The interface for an object that can be dragged. - * @extends {IDeletable} - * @interface - * @alias Blockly.IDraggable - */ - export class IDraggable { - } -} -declare module "interfaces/i_bubble" { - /** - * A bubble interface. - * @interface - * @extends {IDraggable} - * @extends {IContextMenu} - * @alias Blockly.IBubble - */ - export class IBubble { - } -} -declare module "interfaces/i_metrics_manager" { - /** - * Interface for a metrics manager. - * @interface - * @alias Blockly.IMetricsManager - */ - export class IMetricsManager { - } -} -declare module "metrics_manager" { - export class MetricsManager { - /** - * The manager for all workspace metrics calculations. - * @param {!WorkspaceSvg} workspace The workspace to calculate metrics - * for. - * @implements {IMetricsManager} - * @constructor - * @alias Blockly.MetricsManager - */ - constructor(workspace: WorkspaceSvg); - /** - * The workspace to calculate metrics for. - * @type {!WorkspaceSvg} - * @protected - */ - protected workspace_: WorkspaceSvg; - /** - * Gets the dimensions of the given workspace component, in pixel coordinates. - * @param {?IToolbox|?IFlyout} elem The element to get the - * dimensions of, or null. It should be a toolbox or flyout, and should - * implement getWidth() and getHeight(). - * @return {!Size} An object containing width and height - * attributes, which will both be zero if elem did not exist. - * @protected - */ - protected getDimensionsPx_(elem: (IToolbox | (IFlyout | null)) | null): Size; - /** - * Gets the width and the height of the flyout on the workspace in pixel - * coordinates. Returns 0 for the width and height if the workspace has a - * category toolbox instead of a simple toolbox. - * @param {boolean=} opt_own Whether to only return the workspace's own flyout. - * @return {!MetricsManager.ToolboxMetrics} The width and height of the - * flyout. - * @public - */ - public getFlyoutMetrics(opt_own?: boolean | undefined): MetricsManager.ToolboxMetrics; - /** - * Gets the width, height and position of the toolbox on the workspace in pixel - * coordinates. Returns 0 for the width and height if the workspace has a simple - * toolbox instead of a category toolbox. To get the width and height of a - * simple toolbox @see {@link getFlyoutMetrics}. - * @return {!MetricsManager.ToolboxMetrics} The object with the width, - * height and position of the toolbox. - * @public - */ - public getToolboxMetrics(): MetricsManager.ToolboxMetrics; - /** - * Gets the width and height of the workspace's parent SVG element in pixel - * coordinates. This area includes the toolbox and the visible workspace area. - * @return {!Size} The width and height of the workspace's parent - * SVG element. - * @public - */ - public getSvgMetrics(): Size; - /** - * Gets the absolute left and absolute top in pixel coordinates. - * This is where the visible workspace starts in relation to the SVG container. - * @return {!MetricsManager.AbsoluteMetrics} The absolute metrics for - * the workspace. - * @public - */ - public getAbsoluteMetrics(): MetricsManager.AbsoluteMetrics; - /** - * Gets the metrics for the visible workspace in either pixel or workspace - * coordinates. The visible workspace does not include the toolbox or flyout. - * @param {boolean=} opt_getWorkspaceCoordinates True to get the view metrics in - * workspace coordinates, false to get them in pixel coordinates. - * @return {!MetricsManager.ContainerRegion} The width, height, top and - * left of the viewport in either workspace coordinates or pixel - * coordinates. - * @public - */ - public getViewMetrics(opt_getWorkspaceCoordinates?: boolean | undefined): MetricsManager.ContainerRegion; - /** - * Gets content metrics in either pixel or workspace coordinates. - * The content area is a rectangle around all the top bounded elements on the - * workspace (workspace comments and blocks). - * @param {boolean=} opt_getWorkspaceCoordinates True to get the content metrics - * in workspace coordinates, false to get them in pixel coordinates. - * @return {!MetricsManager.ContainerRegion} The - * metrics for the content container. - * @public - */ - public getContentMetrics(opt_getWorkspaceCoordinates?: boolean | undefined): MetricsManager.ContainerRegion; - /** - * Returns whether the scroll area has fixed edges. - * @return {boolean} Whether the scroll area has fixed edges. - * @package - */ - hasFixedEdges(): boolean; - /** - * Computes the fixed edges of the scroll area. - * @param {!MetricsManager.ContainerRegion=} opt_viewMetrics The view - * metrics if they have been previously computed. Passing in null may cause - * the view metrics to be computed again, if it is needed. - * @return {!MetricsManager.FixedEdges} The fixed edges of the scroll - * area. - * @protected - */ - protected getComputedFixedEdges_(opt_viewMetrics?: MetricsManager.ContainerRegion | undefined): MetricsManager.FixedEdges; - /** - * Returns the content area with added padding. - * @param {!MetricsManager.ContainerRegion} viewMetrics The view - * metrics. - * @param {!MetricsManager.ContainerRegion} contentMetrics The content - * metrics. - * @return {{top: number, bottom: number, left: number, right: number}} The - * padded content area. - * @protected - */ - protected getPaddedContent_(viewMetrics: MetricsManager.ContainerRegion, contentMetrics: MetricsManager.ContainerRegion): { - top: number; - bottom: number; - left: number; - right: number; - }; - /** - * Returns the metrics for the scroll area of the workspace. - * @param {boolean=} opt_getWorkspaceCoordinates True to get the scroll metrics - * in workspace coordinates, false to get them in pixel coordinates. - * @param {!MetricsManager.ContainerRegion=} opt_viewMetrics The view - * metrics if they have been previously computed. Passing in null may cause - * the view metrics to be computed again, if it is needed. - * @param {!MetricsManager.ContainerRegion=} opt_contentMetrics The - * content metrics if they have been previously computed. Passing in null - * may cause the content metrics to be computed again, if it is needed. - * @return {!MetricsManager.ContainerRegion} The metrics for the scroll - * container. - */ - getScrollMetrics(opt_getWorkspaceCoordinates?: boolean | undefined, opt_viewMetrics?: MetricsManager.ContainerRegion | undefined, opt_contentMetrics?: MetricsManager.ContainerRegion | undefined): MetricsManager.ContainerRegion; - /** - * Returns common metrics used by UI elements. - * @return {!MetricsManager.UiMetrics} The UI metrics. - */ - getUiMetrics(): MetricsManager.UiMetrics; - /** - * Returns an object with all the metrics required to size scrollbars for a - * top level workspace. The following properties are computed: - * Coordinate system: pixel coordinates, -left, -up, +right, +down - * .viewHeight: Height of the visible portion of the workspace. - * .viewWidth: Width of the visible portion of the workspace. - * .contentHeight: Height of the content. - * .contentWidth: Width of the content. - * .scrollHeight: Height of the scroll area. - * .scrollWidth: Width of the scroll area. - * .svgHeight: Height of the Blockly div (the view + the toolbox, - * simple or otherwise), - * .svgWidth: Width of the Blockly div (the view + the toolbox, - * simple or otherwise), - * .viewTop: Top-edge of the visible portion of the workspace, relative to - * the workspace origin. - * .viewLeft: Left-edge of the visible portion of the workspace, relative to - * the workspace origin. - * .contentTop: Top-edge of the content, relative to the workspace origin. - * .contentLeft: Left-edge of the content relative to the workspace origin. - * .scrollTop: Top-edge of the scroll area, relative to the workspace origin. - * .scrollLeft: Left-edge of the scroll area relative to the workspace origin. - * .absoluteTop: Top-edge of the visible portion of the workspace, relative - * to the blocklyDiv. - * .absoluteLeft: Left-edge of the visible portion of the workspace, relative - * to the blocklyDiv. - * .toolboxWidth: Width of the toolbox, if it exists. Otherwise zero. - * .toolboxHeight: Height of the toolbox, if it exists. Otherwise zero. - * .flyoutWidth: Width of the flyout if it is always open. Otherwise zero. - * .flyoutHeight: Height of the flyout if it is always open. Otherwise zero. - * .toolboxPosition: Top, bottom, left or right. Use TOOLBOX_AT constants to - * compare. - * @return {!Metrics} Contains size and position metrics of a top - * level workspace. - * @public - */ - public getMetrics(): Metrics; - } - export namespace MetricsManager { - /** - * Describes the width, height and location of the toolbox on the main - * workspace. - */ - type ToolboxMetrics = { - width: number; - height: number; - position: toolboxUtils.Position; - }; - /** - * Describes where the viewport starts in relation to the workspace SVG. - */ - type AbsoluteMetrics = { - left: number; - top: number; - }; - /** - * All the measurements needed to describe the size and location of a container. - */ - type ContainerRegion = { - height: number; - width: number; - top: number; - left: number; - }; - /** - * Describes fixed edges of the workspace. - */ - type FixedEdges = { - top: (number | undefined); - bottom: (number | undefined); - left: (number | undefined); - right: (number | undefined); - }; - /** - * Common metrics used for UI elements. - */ - type UiMetrics = { - viewMetrics: MetricsManager.ContainerRegion; - absoluteMetrics: MetricsManager.AbsoluteMetrics; - toolboxMetrics: MetricsManager.ToolboxMetrics; - }; - } - import { WorkspaceSvg } from "workspace_svg"; - import { IToolbox } from "interfaces/i_toolbox"; - import { IFlyout } from "interfaces/i_flyout"; - import { Size } from "utils/size"; - import { Metrics } from "utils/metrics"; - import * as toolboxUtils from "utils/toolbox"; -} -declare module "scrollbar" { - /** - * A note on units: most of the numbers that are in CSS pixels are scaled if the - * scrollbar is in a mutator. - */ - export class Scrollbar { - /** - * @param {!Metrics} first An object containing computed - * measurements of a workspace. - * @param {!Metrics} second Another object containing computed - * measurements of a workspace. - * @return {boolean} Whether the two sets of metrics are equivalent. - * @private - */ - private static metricsAreEquivalent_; - /** - * Class for a pure SVG scrollbar. - * This technique offers a scrollbar that is guaranteed to work, but may not - * look or behave like the system's scrollbars. - * @param {!WorkspaceSvg} workspace Workspace to bind the scrollbar to. - * @param {boolean} horizontal True if horizontal, false if vertical. - * @param {boolean=} opt_pair True if scrollbar is part of a horiz/vert pair. - * @param {string=} opt_class A class to be applied to this scrollbar. - * @param {number=} opt_margin The margin to apply to this scrollbar. - * @constructor - * @alias Blockly.Scrollbar - */ - constructor(workspace: WorkspaceSvg, horizontal: boolean, opt_pair?: boolean | undefined, opt_class?: string | undefined, opt_margin?: number | undefined); - /** - * The workspace this scrollbar is bound to. - * @type {!WorkspaceSvg} - * @private - */ - private workspace_; - /** - * Whether this scrollbar is part of a pair. - * @type {boolean} - * @private - */ - private pair_; - /** - * Whether this is a horizontal scrollbar. - * @type {boolean} - * @private - */ - private horizontal_; - /** - * Margin around the scrollbar (between the scrollbar and the edge of the - * viewport in pixels). - * @type {number} - * @const - * @private - */ - private margin_; - /** - * Previously recorded metrics from the workspace. - * @type {?Metrics} - * @private - */ - private oldHostMetrics_; - /** - * The ratio of handle position offset to workspace content displacement. - * @type {?number} - * @package - */ - ratio: number | null; - /** - * The upper left corner of the scrollbar's SVG group in CSS pixels relative - * to the scrollbar's origin. This is usually relative to the injection div - * origin. - * @type {Coordinate} - * @package - */ - position: Coordinate; - lengthAttribute_: string; - positionAttribute_: string; - onMouseDownBarWrapper_: any[][]; - onMouseDownHandleWrapper_: any[][]; - /** - * Dispose of this scrollbar. - * Unlink from all DOM elements to prevent memory leaks. - * @suppress {checkTypes} - */ - dispose(): void; - outerSvg_: SVGSVGElement; - svgGroup_: SVGGElement; - svgBackground_: SVGRectElement; - svgHandle_: SVGRectElement; - /** - * Constrain the handle's length within the minimum (0) and maximum - * (scrollbar background) values allowed for the scrollbar. - * @param {number} value Value that is potentially out of bounds, in CSS pixels. - * @return {number} Constrained value, in CSS pixels. - * @private - */ - private constrainHandleLength_; - /** - * Set the length of the scrollbar's handle and change the SVG attribute - * accordingly. - * @param {number} newLength The new scrollbar handle length in CSS pixels. - * @private - */ - private setHandleLength_; - handleLength_: number; - /** - * Constrain the handle's position within the minimum (0) and maximum values - * allowed for the scrollbar. - * @param {number} value Value that is potentially out of bounds, in CSS pixels. - * @return {number} Constrained value, in CSS pixels. - * @private - */ - private constrainHandlePosition_; - /** - * Set the offset of the scrollbar's handle from the scrollbar's position, and - * change the SVG attribute accordingly. - * @param {number} newPosition The new scrollbar handle offset in CSS pixels. - */ - setHandlePosition(newPosition: number): void; - handlePosition_: number; - /** - * Set the size of the scrollbar's background and change the SVG attribute - * accordingly. - * @param {number} newSize The new scrollbar background length in CSS pixels. - * @private - */ - private setScrollbarLength_; - scrollbarLength_: number; - /** - * Set the position of the scrollbar's SVG group in CSS pixels relative to the - * scrollbar's origin. This sets the scrollbar's location within the workspace. - * @param {number} x The new x coordinate. - * @param {number} y The new y coordinate. - * @package - */ - setPosition(x: number, y: number): void; - /** - * Recalculate the scrollbar's location and its length. - * @param {Metrics=} opt_metrics A data structure of from the - * describing all the required dimensions. If not provided, it will be - * fetched from the host object. - */ - resize(opt_metrics?: Metrics | undefined): void; - /** - * Returns whether the a resizeView is necessary by comparing the passed - * hostMetrics with cached old host metrics. - * @param {!Metrics} hostMetrics A data structure describing all - * the required dimensions, possibly fetched from the host object. - * @return {boolean} Whether a resizeView is necessary. - * @private - */ - private requiresViewResize_; - /** - * Recalculate a horizontal scrollbar's location and length. - * @param {!Metrics} hostMetrics A data structure describing all - * the required dimensions, possibly fetched from the host object. - * @private - */ - private resizeHorizontal_; - /** - * Recalculate a horizontal scrollbar's location on the screen and path length. - * This should be called when the layout or size of the window has changed. - * @param {!Metrics} hostMetrics A data structure describing all - * the required dimensions, possibly fetched from the host object. - */ - resizeViewHorizontal(hostMetrics: Metrics): void; - /** - * Recalculate a horizontal scrollbar's location within its path and length. - * This should be called when the contents of the workspace have changed. - * @param {!Metrics} hostMetrics A data structure describing all - * the required dimensions, possibly fetched from the host object. - */ - resizeContentHorizontal(hostMetrics: Metrics): void; - /** - * Recalculate a vertical scrollbar's location and length. - * @param {!Metrics} hostMetrics A data structure describing all - * the required dimensions, possibly fetched from the host object. - * @private - */ - private resizeVertical_; - /** - * Recalculate a vertical scrollbar's location on the screen and path length. - * This should be called when the layout or size of the window has changed. - * @param {!Metrics} hostMetrics A data structure describing all - * the required dimensions, possibly fetched from the host object. - */ - resizeViewVertical(hostMetrics: Metrics): void; - /** - * Recalculate a vertical scrollbar's location within its path and length. - * This should be called when the contents of the workspace have changed. - * @param {!Metrics} hostMetrics A data structure describing all - * the required dimensions, possibly fetched from the host object. - */ - resizeContentVertical(hostMetrics: Metrics): void; - /** - * Create all the DOM elements required for a scrollbar. - * The resulting widget is not sized. - * @param {string=} opt_class A class to be applied to this scrollbar. - * @private - */ - private createDom_; - /** - * Is the scrollbar visible. Non-paired scrollbars disappear when they aren't - * needed. - * @return {boolean} True if visible. - */ - isVisible(): boolean; - /** - * Set whether the scrollbar's container is visible and update - * display accordingly if visibility has changed. - * @param {boolean} visible Whether the container is visible - */ - setContainerVisible(visible: boolean): void; - containerVisible_: boolean; - /** - * Set whether the scrollbar is visible. - * Only applies to non-paired scrollbars. - * @param {boolean} visible True if visible. - */ - setVisible(visible: boolean): void; - isVisible_: boolean; - /** - * Update visibility of scrollbar based on whether it thinks it should - * be visible and whether its containing workspace is visible. - * We cannot rely on the containing workspace being hidden to hide us - * because it is not necessarily our parent in the DOM. - */ - updateDisplay_(): void; - /** - * Scroll by one pageful. - * Called when scrollbar background is clicked. - * @param {!Event} e Mouse down event. - * @private - */ - private onMouseDownBar_; - /** - * Start a dragging operation. - * Called when scrollbar handle is clicked. - * @param {!Event} e Mouse down event. - * @private - */ - private onMouseDownHandle_; - startDragHandle: number; - startDragMouse_: number; - /** - * Drag the scrollbar's handle. - * @param {!Event} e Mouse up event. - * @private - */ - private onMouseMoveHandle_; - /** - * Release the scrollbar handle and reset state accordingly. - * @private - */ - private onMouseUpHandle_; - /** - * Hide chaff and stop binding to mouseup and mousemove events. Call this to - * wrap up loose ends associated with the scrollbar. - * @private - */ - private cleanUp_; - /** - * Helper to calculate the ratio of handle position to scrollbar view size. - * @return {number} Ratio. - * @package - */ - getRatio_(): number; - /** - * Updates workspace metrics based on new scroll ratio. Called when scrollbar is - * moved. - * @private - */ - private updateMetrics_; - /** - * Set the scrollbar handle's position. - * @param {number} value The content displacement, relative to the view in - * pixels. - * @param {boolean=} updateMetrics Whether to update metrics on this set call. - * Defaults to true. - */ - set(value: number, updateMetrics?: boolean | undefined): void; - /** - * Record the origin of the workspace that the scrollbar is in, in pixels - * relative to the injection div origin. This is for times when the scrollbar is - * used in an object whose origin isn't the same as the main workspace - * (e.g. in a flyout.) - * @param {number} x The x coordinate of the scrollbar's origin, in CSS pixels. - * @param {number} y The y coordinate of the scrollbar's origin, in CSS pixels. - */ - setOrigin(x: number, y: number): void; - origin_: Coordinate; - } - export namespace Scrollbar { - const scrollbarThickness: number; - const DEFAULT_SCROLLBAR_MARGIN: number; - } - import { Coordinate } from "utils/coordinate"; - import { Metrics } from "utils/metrics"; - import { WorkspaceSvg } from "workspace_svg"; -} -declare module "bubble" { - export class Bubble { - /** - * Stop binding to the global mouseup and mousemove events. - * @private - */ - private static unbindDragEvents_; - /** - * Handle a mouse-up event while dragging a bubble's border or resize handle. - * @param {!Event} _e Mouse up event. - * @private - */ - private static bubbleMouseUp_; - /** - * Create the text for a non editable bubble. - * @param {string} text The text to display. - * @return {!SVGTextElement} The top-level node of the text. - * @package - */ - static textToDom(text: string): SVGTextElement; - /** - * Creates a bubble that can not be edited. - * @param {!SVGTextElement} paragraphElement The text element for the non - * editable bubble. - * @param {!BlockSvg} block The block that the bubble is attached to. - * @param {!Coordinate} iconXY The coordinate of the icon. - * @return {!Bubble} The non editable bubble. - * @package - */ - static createNonEditableBubble(paragraphElement: SVGTextElement, block: BlockSvg, iconXY: Coordinate): Bubble; - /** - * Class for UI bubble. - * @param {!WorkspaceSvg} workspace The workspace on which to draw the - * bubble. - * @param {!Element} content SVG content for the bubble. - * @param {!Element} shape SVG element to avoid eclipsing. - * @param {!Coordinate} anchorXY Absolute position of bubble's - * anchor point. - * @param {?number} bubbleWidth Width of bubble, or null if not resizable. - * @param {?number} bubbleHeight Height of bubble, or null if not resizable. - * @implements {IBubble} - * @constructor - * @alias Blockly.Bubble - */ - constructor(workspace: WorkspaceSvg, content: Element, shape: Element, anchorXY: Coordinate, bubbleWidth: number | null, bubbleHeight: number | null); - workspace_: WorkspaceSvg; - content_: Element; - shape_: Element; - /** - * Method to call on resize of bubble. - * @type {?function()} - * @private - */ - private resizeCallback_; - /** - * Method to call on move of bubble. - * @type {?function()} - * @private - */ - private moveCallback_; - /** - * Mouse down on bubbleBack_ event data. - * @type {?browserEvents.Data} - * @private - */ - private onMouseDownBubbleWrapper_; - /** - * Mouse down on resizeGroup_ event data. - * @type {?browserEvents.Data} - * @private - */ - private onMouseDownResizeWrapper_; - /** - * Describes whether this bubble has been disposed of (nodes and event - * listeners removed from the page) or not. - * @type {boolean} - * @package - */ - disposed: boolean; - arrow_radians_: number; - rendered_: boolean; - /** - * Create the bubble's DOM. - * @param {!Element} content SVG content for the bubble. - * @param {boolean} hasResize Add diagonal resize gripper if true. - * @return {!SVGElement} The bubble's SVG group. - * @private - */ - private createDom_; - bubbleGroup_: SVGGElement; - bubbleArrow_: SVGPathElement; - bubbleBack_: SVGRectElement; - resizeGroup_: SVGGElement; - /** - * Return the root node of the bubble's SVG group. - * @return {!SVGElement} The root SVG node of the bubble's group. - */ - getSvgRoot(): SVGElement; - /** - * Expose the block's ID on the bubble's top-level SVG group. - * @param {string} id ID of block. - */ - setSvgId(id: string): void; - /** - * Handle a mouse-down on bubble's border. - * @param {!Event} e Mouse down event. - * @private - */ - private bubbleMouseDown_; - /** - * Show the context menu for this bubble. - * @param {!Event} _e Mouse event. - * @package - */ - showContextMenu(_e: Event): void; - /** - * Get whether this bubble is deletable or not. - * @return {boolean} True if deletable. - * @package - */ - isDeletable(): boolean; - /** - * Update the style of this bubble when it is dragged over a delete area. - * @param {boolean} _enable True if the bubble is about to be deleted, false - * otherwise. - */ - setDeleteStyle(_enable: boolean): void; - /** - * Handle a mouse-down on bubble's resize corner. - * @param {!Event} e Mouse down event. - * @private - */ - private resizeMouseDown_; - /** - * Resize this bubble to follow the mouse. - * @param {!Event} e Mouse move event. - * @private - */ - private resizeMouseMove_; - autoLayout_: boolean; - /** - * Register a function as a callback event for when the bubble is resized. - * @param {!Function} callback The function to call on resize. - */ - registerResizeEvent(callback: Function): void; - /** - * Register a function as a callback event for when the bubble is moved. - * @param {!Function} callback The function to call on move. - */ - registerMoveEvent(callback: Function): void; - /** - * Move this bubble to the top of the stack. - * @return {boolean} Whether or not the bubble has been moved. - * @package - */ - promote(): boolean; - /** - * Notification that the anchor has moved. - * Update the arrow and bubble accordingly. - * @param {!Coordinate} xy Absolute location. - */ - setAnchorLocation(xy: Coordinate): void; - anchorXY_: Coordinate; - /** - * Position the bubble so that it does not fall off-screen. - * @private - */ - private layoutBubble_; - relativeLeft_: any; - relativeTop_: any; - /** - * Calculate the what percentage of the bubble overlaps with the visible - * workspace (what percentage of the bubble is visible). - * @param {!{x: number, y: number}} relativeMin The position of the top-left - * corner of the bubble relative to the anchor point. - * @param {!MetricsManager.ContainerRegion} viewMetrics The view metrics - * of the workspace the bubble will appear in. - * @return {number} The percentage of the bubble that is visible. - * @private - */ - private getOverlap_; - /** - * Calculate what the optimal horizontal position of the top-left corner of the - * bubble is (relative to the anchor point) so that the most area of the - * bubble is shown. - * @param {!MetricsManager.ContainerRegion} viewMetrics The view metrics - * of the workspace the bubble will appear in. - * @return {number} The optimal horizontal position of the top-left corner - * of the bubble. - * @private - */ - private getOptimalRelativeLeft_; - /** - * Calculate what the optimal vertical position of the top-left corner of - * the bubble is (relative to the anchor point) so that the most area of the - * bubble is shown. - * @param {!MetricsManager.ContainerRegion} viewMetrics The view metrics - * of the workspace the bubble will appear in. - * @return {number} The optimal vertical position of the top-left corner - * of the bubble. - * @private - */ - private getOptimalRelativeTop_; - /** - * Move the bubble to a location relative to the anchor's centre. - * @private - */ - private positionBubble_; - /** - * 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 - */ - moveTo(x: number, y: number): void; - /** - * Triggers a move callback if one exists at the end of a drag. - * @param {boolean} adding True if adding, false if removing. - * @package - */ - setDragging(adding: boolean): void; - /** - * Get the dimensions of this bubble. - * @return {!Size} The height and width of the bubble. - */ - getBubbleSize(): Size; - /** - * Size this bubble. - * @param {number} width Width of the bubble. - * @param {number} height Height of the bubble. - */ - setBubbleSize(width: number, height: number): void; - width_: number; - height_: number; - /** - * Draw the arrow between the bubble and the origin. - * @private - */ - private renderArrow_; - /** - * Change the colour of a bubble. - * @param {string} hexColour Hex code of colour. - */ - setColour(hexColour: string): void; - /** - * Dispose of this bubble. - */ - dispose(): void; - /** - * Move this bubble during a drag, taking into account whether or not there is - * a drag surface. - * @param {BlockDragSurfaceSvg} dragSurface The surface that carries - * rendered items during a drag, or null if no drag surface is in use. - * @param {!Coordinate} newLoc The location to translate to, in - * workspace coordinates. - * @package - */ - moveDuringDrag(dragSurface: BlockDragSurfaceSvg, newLoc: Coordinate): void; - /** - * 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 {!Coordinate} Object with .x and .y properties. - */ - getRelativeToSurfaceXY(): Coordinate; - /** - * 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 - */ - setAutoLayout(enable: boolean): void; - } - export namespace Bubble { - const BORDER_WIDTH: number; - const ARROW_THICKNESS: number; - const ARROW_ANGLE: number; - const ARROW_BEND: number; - const ANCHOR_RADIUS: number; - const onMouseUpWrapper_: any[][] | null; - const onMouseMoveWrapper_: any[][] | null; - } - import { WorkspaceSvg } from "workspace_svg"; - import { Coordinate } from "utils/coordinate"; - import { Size } from "utils/size"; - import { BlockDragSurfaceSvg } from "block_drag_surface"; - import { BlockSvg } from "block_svg"; -} -declare module "icon" { +declare module "core/icon" { /** * Class for an icon. - * @param {BlockSvg} block The block associated with this icon. - * @constructor * @abstract * @alias Blockly.Icon */ export class Icon { - constructor(block: any); + /** + * @param {BlockSvg} block The block associated with this icon. + */ + constructor(block: BlockSvg); /** * The block this icon is attached to. * @type {BlockSvg} @@ -7947,6 +11411,156 @@ declare module "icon" { * @type {?SVGGElement} */ iconGroup_: SVGGElement | null; + /** + * Whether this icon gets hidden when the block is collapsed. + * @type {boolean} + */ + collapseHidden: boolean; + /** + * Height and width of icons. + * @const + */ + SIZE: number; + /** + * Bubble UI (if visible). + * @type {?Bubble} + * @protected + */ + protected bubble_: { + workspace_: import("core/workspace_svg").WorkspaceSvg; + content_: Element; + shape_: Element; + rendered_: boolean; + bubbleGroup_: SVGGElement; + bubbleArrow_: SVGPathElement; + bubbleBack_: SVGRectElement; + resizeGroup_: SVGGElement; + anchorXY_: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + relativeLeft_: number; + relativeTop_: number; + width_: number; + height_: number; + autoLayout_: boolean; + resizeCallback_: (() => any) | null; + moveCallback_: (() => any) | null; + onMouseDownBubbleWrapper_: any[][] | null; + onMouseDownResizeWrapper_: any[][] | null; + disposed: boolean; + arrow_radians_: number; + createDom_(content: Element, hasResize: boolean): SVGElement; + getSvgRoot(): SVGElement; + setSvgId(id: string): void; + bubbleMouseDown_(e: Event): void; + showContextMenu(_e: Event): void; + isDeletable(): boolean; + setDeleteStyle(_enable: boolean): void; + resizeMouseDown_(e: Event): void; + resizeMouseMove_(e: Event): void; + registerResizeEvent(callback: Function): void; + registerMoveEvent(callback: Function): void; + promote(): boolean; + setAnchorLocation(xy: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): void; + layoutBubble_(): void; + getOverlap_(relativeMin: { + x: number; + y: number; + }, viewMetrics: import("core/metrics_manager").MetricsManager.ContainerRegion): number; + getOptimalRelativeLeft_(viewMetrics: import("core/metrics_manager").MetricsManager.ContainerRegion): number; + getOptimalRelativeTop_(viewMetrics: import("core/metrics_manager").MetricsManager.ContainerRegion): number; + positionBubble_(): void; + moveTo(x: number, y: number): void; + setDragging(adding: boolean): void; + getBubbleSize(): { + width: number; + height: number; + }; + setBubbleSize(width: number, height: number): void; + renderArrow_(): void; + setColour(hexColour: string): void; + dispose(): void; + moveDuringDrag(dragSurface: { + SVG_: SVGElement | null; + dragGroup_: SVGElement | null; + container_: Element; + scale_: number; + surfaceXY_: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + } | null; + childSurfaceXY_: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + createDom(): void; + setBlocksAndShow(blocks: SVGElement): void; + translateAndScaleGroup(x: number, y: number, scale: number): void; + translateSurfaceInternal_(): void; + translateBy(deltaX: number, deltaY: number): void; + translateSurface(x: number, y: number): void; + getSurfaceTranslation(): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + getGroup(): SVGElement | null; + getSvgRoot(): SVGElement | null; + getCurrentBlock(): Element | null; + getWsTranslation(): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + clearAndHide(opt_newSurface?: Element | undefined): void; + }, newLoc: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): void; + getRelativeToSurfaceXY(): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + setAutoLayout(enable: boolean): void; + } | null; + /** + * Absolute coordinate of icon's center. + * @type {?Coordinate} + * @protected + */ + protected iconXY_: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + } | null; /** * Create the icon on the block. */ @@ -7978,8 +11592,13 @@ declare module "icon" { * Notification that the icon has moved. Update the arrow accordingly. * @param {!Coordinate} xy Absolute location in workspace coordinates. */ - setIconLocation(xy: Coordinate): void; - iconXY_: Coordinate | null; + setIconLocation(xy: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): void; /** * Notification that the icon has moved, but we don't really know where. * Recompute the icon's location from scratch. @@ -7990,185 +11609,173 @@ declare module "icon" { * @return {?Coordinate} Object with x and y properties in * workspace coordinates. */ - getIconLocation(): Coordinate | null; + getIconLocation(): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + } | null; /** * Get the size of the icon as used for rendering. * This differs from the actual size of the icon, because it bulges slightly * out of its row rather than increasing the height of its row. * @return {!Size} Height and width. */ - getCorrectedSize(): Size; + getCorrectedSize(): { + width: number; + height: number; + }; /** - * Does this icon get hidden when the block is collapsed. - */ - collapseHidden: boolean; - /** - * Height and width of icons. - * @const - */ - SIZE: number; - /** - * Bubble UI (if visible). - * @type {?Bubble} + * Draw the icon. + * @param {!Element} _group The icon group. * @protected */ - protected bubble_: Bubble | null; + protected drawIcon_(_group: Element): void; + /** + * Show or hide the icon. + * @param {boolean} _visible True if the icon should be visible. + */ + setVisible(_visible: boolean): void; } - import { BlockSvg } from "block_svg"; - import { Coordinate } from "utils/coordinate"; - import { Size } from "utils/size"; - import { Bubble } from "bubble"; + import { BlockSvg } from "core/block_svg"; } -declare module "renderers/measurables/icon" { +declare module "core/renderers/measurables/icon" { /** * An object containing information about the space an icon takes up during * rendering - * @param {!ConstantProvider} constants The rendering - * constants provider. - * @param {!BlocklyIcon} icon The icon to measure and store information for. - * @package - * @constructor * @extends {Measurable} + * @struct * @alias Blockly.blockRendering.Icon */ - export class Icon { - constructor(constants: any, icon: any); - icon: any; - isVisible: any; - height: any; - width: any; + export class Icon extends Measurable { + /** + * An object containing information about the space an icon takes up during + * rendering + * @param {!ConstantProvider} constants The rendering + * constants provider. + * @param {!BlocklyIcon} icon The icon to measure and store information for. + * @package + */ + constructor(constants: ConstantProvider, icon: BlocklyIcon); + /** @type {!BlocklyIcon} */ + icon: BlocklyIcon; + /** @type {boolean} */ + isVisible: boolean; } + import { Measurable } from "core/renderers/measurables/base"; + import { Icon as BlocklyIcon } from "core/icon"; + import { ConstantProvider } from "core/renderers/common/constants"; } -declare module "renderers/measurables/inline_input" { +declare module "core/renderers/measurables/inline_input" { /** * An object containing information about the space an inline input takes up * during rendering - * @param {!ConstantProvider} constants The rendering - * constants provider. - * @param {!Input} input The inline input to measure and store - * information for. - * @package - * @constructor * @extends {InputConnection} + * @struct * @alias Blockly.blockRendering.InlineInput */ - export class InlineInput { - constructor(constants: any, input: any); - height: any; - width: any; - connectionHeight: any; - connectionWidth: any; - connectionOffsetY: any; - connectionOffsetX: any; + export class InlineInput extends InputConnection { + /** @type {number} */ + connectionHeight: number; + /** @type {number} */ + connectionWidth: number; } + import { InputConnection } from "core/renderers/measurables/input_connection"; } -declare module "renderers/measurables/input_row" { +declare module "core/renderers/measurables/statement_input" { + /** + * An object containing information about the space a statement input takes up + * during rendering + * @struct + * @extends {InputConnection} + * @alias Blockly.blockRendering.StatementInput + */ + export class StatementInput extends InputConnection { + } + import { InputConnection } from "core/renderers/measurables/input_connection"; +} +declare module "core/renderers/measurables/input_row" { + /** + * An object containing information about a row that holds one or more inputs. + * @extends {Row} + * @struct + * @alias Blockly.blockRendering.InputRow + */ export class InputRow extends Row { - /** - * An object containing information about a row that holds one or more inputs. - * @param {!ConstantProvider} constants The rendering - * constants provider. - * @package - * @constructor - * @extends {Row} - * @alias Blockly.blockRendering.InputRow - */ - constructor(constants: ConstantProvider); /** * The total width of all blocks connected to this row. * @type {number} * @package */ connectedBlockWidths: number; - /** - * Inspect all subcomponents and populate all size properties on the row. - * @package - */ - measure(): void; - width: any; - height: any; - widthWithConnectedBlocks: any; - /** - * @override - */ - override endsWithElemSpacer(): boolean; } - import { ConstantProvider } from "renderers/common/constants"; - import { Row } from "renderers/measurables/row"; + import { Row } from "core/renderers/measurables/row"; } -declare module "renderers/measurables/jagged_edge" { +declare module "core/renderers/measurables/jagged_edge" { /** * An object containing information about the jagged edge of a collapsed block * takes up during rendering - * @param {!ConstantProvider} constants The rendering - * constants provider. - * @package - * @constructor * @extends {Measurable} + * @struct * @alias Blockly.blockRendering.JaggedEdge */ - export class JaggedEdge { - constructor(constants: any); - height: any; - width: any; + export class JaggedEdge extends Measurable { } + import { Measurable } from "core/renderers/measurables/base"; } -declare module "renderers/measurables/output_connection" { +declare module "core/renderers/measurables/output_connection" { /** * An object containing information about the space an output connection takes * up during rendering. - * @param {!ConstantProvider} constants The rendering - * constants provider. - * @param {RenderedConnection} connectionModel The connection object on - * the block that this represents. - * @package - * @constructor * @extends {Connection} + * @struct * @alias Blockly.blockRendering.OutputConnection */ - export class OutputConnection { - constructor(constants: any, connectionModel: any); - height: any; - width: any; - startX: any; - connectionOffsetY: any; + export class OutputConnection extends Connection { + /** @type {number} */ + startX: number; + /** @type {number} */ + connectionOffsetY: number; + /** @type {number} */ connectionOffsetX: number; } + import { Connection } from "core/renderers/measurables/connection"; } -declare module "renderers/measurables/previous_connection" { +declare module "core/renderers/measurables/previous_connection" { /** * An object containing information about the space a previous connection takes * up during rendering. - * @param {!ConstantProvider} constants The rendering - * constants provider. - * @param {RenderedConnection} connectionModel The connection object on - * the block that this represents. - * @package - * @constructor * @extends {Connection} + * @struct * @alias Blockly.blockRendering.PreviousConnection */ - export class PreviousConnection { - constructor(constants: any, connectionModel: any); - height: any; - width: any; + export class PreviousConnection extends Connection { } + import { Connection } from "core/renderers/measurables/connection"; } -declare module "renderers/common/drawer" { +declare module "core/renderers/common/drawer" { + /** + * An object that draws a block based on the given rendering information. + * @alias Blockly.blockRendering.Drawer + */ export class Drawer { /** - * An object that draws a block based on the given rendering information. * @param {!BlockSvg} block The block to render. * @param {!RenderInfo} info An object containing all * information needed to render this block. * @package - * @constructor - * @alias Blockly.blockRendering.Drawer */ constructor(block: BlockSvg, info: RenderInfo); block_: BlockSvg; info_: RenderInfo; - topLeft_: import("utils/coordinate").Coordinate; + topLeft_: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; outlinePath_: string; inlinePath_: string; /** @@ -8191,7 +11798,8 @@ declare module "renderers/common/drawer" { /** * Save sizing information back to the block * Most of the rendering information can be thrown away at the end of the - * render. Anything that needs to be kept around should be set in this function. + * render. Anything that needs to be kept around should be set in this + * function. * @protected */ protected recordSizeOnBlock_(): void; @@ -8250,8 +11858,8 @@ declare module "renderers/common/drawer" { */ protected drawLeft_(): void; /** - * Draw the internals of the block: inline inputs, fields, and icons. These do - * not depend on the outer path for placement. + * Draw the internals of the block: inline inputs, fields, and icons. These + * do not depend on the outer path for placement. * @protected */ protected drawInternals_(): void; @@ -8271,8 +11879,8 @@ declare module "renderers/common/drawer" { protected drawInlineInput_(input: InlineInput): void; /** * Position the connection on an inline value input, taking into account - * RTL and the small gap between the parent block and child block which lets the - * parent block's dark path show through. + * RTL and the small gap between the parent block and child block which lets + * the parent block's dark path show through. * @param {InlineInput} input The information about * the input that the connection is on. * @protected @@ -8280,16 +11888,16 @@ declare module "renderers/common/drawer" { protected positionInlineInputConnection_(input: InlineInput): void; /** * Position the connection on a statement input, taking into account - * RTL and the small gap between the parent block and child block which lets the - * parent block's dark path show through. + * RTL and the small gap between the parent block and child block which lets + * the parent block's dark path show through. * @param {!Row} row The row that the connection is on. * @protected */ protected positionStatementInputConnection_(row: Row): void; /** * Position the connection on an external value input, taking into account - * RTL and the small gap between the parent block and child block which lets the - * parent block's dark path show through. + * RTL and the small gap between the parent block and child block which lets + * the parent block's dark path show through. * @param {!Row} row The row that the connection is on. * @protected */ @@ -8310,23 +11918,15 @@ declare module "renderers/common/drawer" { */ protected positionOutputConnection_(): void; } - import { BlockSvg } from "block_svg"; - import { RenderInfo } from "renderers/common/info"; - import { ConstantProvider } from "renderers/common/constants"; - import { Row } from "renderers/measurables/row"; - import { Icon } from "renderers/measurables/icon"; - import { Field } from "renderers/measurables/field"; - import { InlineInput } from "renderers/measurables/inline_input"; + import { BlockSvg } from "core/block_svg"; + import { RenderInfo } from "core/renderers/common/info"; + import { ConstantProvider } from "core/renderers/common/constants"; + import { Row } from "core/renderers/measurables/row"; + import { Icon } from "core/renderers/measurables/icon"; + import { Field } from "core/renderers/measurables/field"; + import { InlineInput } from "core/renderers/measurables/inline_input"; } -declare module "renderers/common/i_path_object" { - /** - * An interface for a block's path object. - * @param {!SVGElement} _root The root SVG element. - * @param {!ConstantProvider} _constants The renderer's - * constants. - * @interface - * @alias Blockly.blockRendering.IPathObject - */ +declare module "core/renderers/common/i_path_object" { export class IPathObject { /** * The primary path of the block. @@ -8356,927 +11956,24 @@ declare module "renderers/common/i_path_object" { */ markerSvg: SVGElement; } - import { ConstantProvider } from "renderers/common/constants"; - import { Theme } from "theme"; + import { ConstantProvider } from "core/renderers/common/constants"; + import { Theme } from "core/theme"; } -declare module "interfaces/i_component" { +declare module "core/renderers/common/path_object" { /** - * @license - * Copyright 2021 Google LLC - * SPDX-License-Identifier: Apache-2.0 + * An object that handles creating and setting each of the SVG elements + * used by the renderer. + * @implements {IPathObject} + * @alias Blockly.blockRendering.PathObject */ - /** - * @fileoverview Interface for a workspace component that can be registered with - * the ComponentManager. - */ - /** - * Interface for a workspace component that can be registered with - * the ComponentManager. - * @namespace Blockly.IComponent - */ - /** - * The interface for a workspace component that can be registered with the - * ComponentManager. - * @interface - * @alias Blockly.IComponent - */ - export class IComponent { - } - export namespace IComponent { - const id: string; - } -} -declare module "interfaces/i_autohideable" { - export interface IAutoHideable extends IComponent { - + export class PathObject implements IPathObject { /** - * Hides the component. Called in Blockly.hideChaff. - * @param {boolean} onlyClosePopups Whether only popups should be closed. - * Flyouts should not be closed if this is true. - */ - autoHide(onlyClosePopups: boolean): void; - } - import { IComponent } from "interfaces/i_component"; -} -declare module "interfaces/i_drag_target" { - /** - * Interface for a component with custom behaviour when a block or bubble is - * dragged over or dropped on top of it. - * @extends {IComponent} - * @interface - * @alias Blockly.IDragTarget - */ - export class IDragTarget { - } -} -declare module "interfaces/i_delete_area" { - /** - * Interface for a component that can delete a block or bubble that is dropped - * on top of it. - * @extends {IDragTarget} - * @interface - * @alias Blockly.IDeleteArea - */ - export class IDeleteArea { - } -} -declare module "interfaces/i_positionable" { - /** - * Interface for a component that is positioned on top of the workspace. - * @extends {IComponent} - * @interface - * @alias Blockly.IPositionable - */ - export class IPositionable { - } -} -declare module "component_manager" { - export class ComponentManager { - /** - * A map of the components registered with the workspace, mapped to id. - * @type {!Object} - * @private - */ - private componentData_; - /** - * A map of capabilities to component IDs. - * @type {!Object>} - * @private - */ - private capabilityToComponentIds_; - /** - * Adds a component. - * @param {!ComponentManager.ComponentDatum} componentInfo The data for - * the component to register. - * @param {boolean=} opt_allowOverrides True to prevent an error when overriding - * an already registered item. - */ - addComponent(componentInfo: ComponentManager.ComponentDatum, opt_allowOverrides?: boolean | undefined): void; - /** - * Removes a component. - * @param {string} id The ID of the component to remove. - */ - removeComponent(id: string): void; - /** - * Adds a capability to a existing registered component. - * @param {string} id The ID of the component to add the capability to. - * @param {string|!ComponentManager.Capability} capability The - * capability to add. - * @template T - */ - addCapability(id: string, capability: any): void; - /** - * Removes a capability from an existing registered component. - * @param {string} id The ID of the component to remove the capability from. - * @param {string|!ComponentManager.Capability} capability The - * capability to remove. - * @template T - */ - removeCapability(id: string, capability: any): void; - /** - * Returns whether the component with this id has the specified capability. - * @param {string} id The ID of the component to check. - * @param {string|!ComponentManager.Capability} capability The - * capability to check for. - * @return {boolean} Whether the component has the capability. - * @template T - */ - hasCapability(id: string, capability: any): boolean; - /** - * Gets the component with the given ID. - * @param {string} id The ID of the component to get. - * @return {!IComponent|undefined} The component with the given name - * or undefined if not found. - */ - getComponent(id: string): IComponent | undefined; - /** - * Gets all the components with the specified capability. - * @param {string|!ComponentManager.Capability - * } capability The capability of the component. - * @param {boolean} sorted Whether to return list ordered by weights. - * @return {!Array} The components that match the specified capability. - * @template T - */ - getComponents(capability: any, sorted: boolean): T_6[]; - } - export namespace ComponentManager { - /** - * A name with the capability of the element stored in the generic. - * @param {string} name The name of the component capability. - * @constructor - * @template T - */ - class Capability { - /** - * Returns the name of the capability. - * @return {string} The name. - * @override - */ - toString(): string; - } - namespace Capability { - const POSITIONABLE: any; - const DRAG_TARGET: any; - const DELETE_AREA: any; - const AUTOHIDEABLE: any; - } - const name_: string; - /** - * An object storing component information. - */ - type ComponentDatum = { - component: IComponent; - capabilities: (Array>); - weight: number; - }; - } - import { IComponent } from "interfaces/i_component"; -} -declare module "insertion_marker_manager" { - export class InsertionMarkerManager { - /** - * Class that controls updates to connections during drags. It is primarily - * responsible for finding the closest eligible connection and highlighting or - * unhighlighting it as needed during a drag. - * @param {!BlockSvg} block The top block in the stack being dragged. - * @constructor - * @alias Blockly.InsertionMarkerManager - */ - constructor(block: BlockSvg); - /** - * The top block in the stack being dragged. - * Does not change during a drag. - * @type {!BlockSvg} - * @private - */ - private topBlock_; - /** - * The workspace on which these connections are being dragged. - * Does not change during a drag. - * @type {!WorkspaceSvg} - * @private - */ - private workspace_; - /** - * The last connection on the stack, if it's not the last connection on the - * first block. - * Set in initAvailableConnections, if at all. - * @type {RenderedConnection} - * @private - */ - private lastOnStack_; - /** - * The insertion marker corresponding to the last block in the stack, if - * that's not the same as the first block in the stack. - * Set in initAvailableConnections, if at all - * @type {BlockSvg} - * @private - */ - private lastMarker_; - /** - * The insertion marker that shows up between blocks to show where a block - * would go if dropped immediately. - * @type {BlockSvg} - * @private - */ - private firstMarker_; - /** - * The connection that this block would connect to if released immediately. - * Updated on every mouse move. - * This is not on any of the blocks that are being dragged. - * @type {RenderedConnection} - * @private - */ - private closestConnection_; - /** - * The connection that would connect to this.closestConnection_ if this block - * were released immediately. - * Updated on every mouse move. - * This is on the top block that is being dragged or the last block in the - * dragging stack. - * @type {RenderedConnection} - * @private - */ - private localConnection_; - /** - * Whether the block would be deleted if it were dropped immediately. - * Updated on every mouse move. - * @type {boolean} - * @private - */ - private wouldDeleteBlock_; - /** - * Connection on the insertion marker block that corresponds to - * this.localConnection_ on the currently dragged block. - * @type {RenderedConnection} - * @private - */ - private markerConnection_; - /** - * The block that currently has an input being highlighted, or null. - * @type {BlockSvg} - * @private - */ - private highlightedBlock_; - /** - * The block being faded to indicate replacement, or null. - * @type {BlockSvg} - * @private - */ - private fadedBlock_; - /** - * The connections on the dragging blocks that are available to connect to - * other blocks. This includes all open connections on the top block, as well - * as the last connection on the block stack. - * Does not change during a drag. - * @type {!Array} - * @private - */ - private availableConnections_; - /** - * Sever all links from this object. - * @package - */ - dispose(): void; - /** - * Update the available connections for the top block. These connections can - * change if a block is unplugged and the stack is healed. - * @package - */ - updateAvailableConnections(): void; - /** - * Return whether the block would be deleted if dropped immediately, based on - * information from the most recent move event. - * @return {boolean} True if the block would be deleted if dropped immediately. - * @package - */ - wouldDeleteBlock(): boolean; - /** - * Return whether the block would be connected if dropped immediately, based on - * information from the most recent move event. - * @return {boolean} True if the block would be connected if dropped - * immediately. - * @package - */ - wouldConnectBlock(): boolean; - /** - * Connect to the closest connection and render the results. - * This should be called at the end of a drag. - * @package - */ - applyConnections(): void; - /** - * Update connections based on the most recent move location. - * @param {!Coordinate} dxy Position relative to drag start, - * in workspace units. - * @param {?IDragTarget} dragTarget The drag target that the block is - * currently over. - * @package - */ - update(dxy: Coordinate, dragTarget: IDragTarget | null): void; - /** - * Create an insertion marker that represents the given block. - * @param {!BlockSvg} sourceBlock The block that the insertion marker - * will represent. - * @return {!BlockSvg} The insertion marker that represents the given - * block. - * @private - */ - private createMarkerBlock_; - /** - * Populate the list of available connections on this block stack. This should - * only be called once, at the beginning of a drag. - * If the stack has more than one block, this function will populate - * lastOnStack_ and create the corresponding insertion marker. - * @return {!Array} A list of available - * connections. - * @private - */ - private initAvailableConnections_; - /** - * Whether the previews (insertion marker and replacement marker) should be - * updated based on the closest candidate and the current drag distance. - * @param {!Object} candidate An object containing a local connection, a closest - * connection, and a radius. Returned by getCandidate_. - * @param {!Coordinate} dxy Position relative to drag start, - * in workspace units. - * @return {boolean} Whether the preview should be updated. - * @private - */ - private shouldUpdatePreviews_; - /** - * Find the nearest valid connection, which may be the same as the current - * closest connection. - * @param {!Coordinate} dxy Position relative to drag start, - * in workspace units. - * @return {!Object} An object containing a local connection, a closest - * connection, and a radius. - * @private - */ - private getCandidate_; - /** - * Decide the radius at which to start searching for the closest connection. - * @return {number} The radius at which to start the search for the closest - * connection. - * @private - */ - private getStartRadius_; - /** - * Whether ending the drag would delete the block. - * @param {!Object} candidate An object containing a local connection, a closest - * connection, and a radius. - * @param {?IDragTarget} dragTarget The drag target that the block is - * currently over. - * @return {boolean} Whether dropping the block immediately would delete the - * block. - * @private - */ - private shouldDelete_; - /** - * Show an insertion marker or replacement highlighting during a drag, if - * needed. - * At the beginning of this function, this.localConnection_ and - * this.closestConnection_ should both be null. - * @param {!Object} candidate An object containing a local connection, a closest - * connection, and a radius. - * @private - */ - private maybeShowPreview_; - /** - * A preview should be shown. This function figures out if it should be a block - * highlight or an insertion marker, and shows the appropriate one. - * @private - */ - private showPreview_; - /** - * Show an insertion marker or replacement highlighting during a drag, if - * needed. - * At the end of this function, this.localConnection_ and - * this.closestConnection_ should both be null. - * @param {!Object} candidate An object containing a local connection, a closest - * connection, and a radius. - * @private - */ - private maybeHidePreview_; - /** - * A preview should be hidden. This function figures out if it is a block - * highlight or an insertion marker, and hides the appropriate one. - * @private - */ - private hidePreview_; - /** - * Shows an insertion marker connected to the appropriate blocks (based on - * manager state). - * @private - */ - private showInsertionMarker_; - /** - * Disconnects and hides the current insertion marker. Should return the blocks - * to their original state. - * @private - */ - private hideInsertionMarker_; - /** - * Shows an outline around the input the closest connection belongs to. - * @private - */ - private showInsertionInputOutline_; - /** - * Hides any visible input outlines. - * @private - */ - private hideInsertionInputOutline_; - /** - * Shows a replacement fade affect on the closest connection's target block - * (the block that is currently connected to it). - * @private - */ - private showReplacementFade_; - /** - * Hides/Removes any visible fade affects. - * @private - */ - private hideReplacementFade_; - /** - * Get a list of the insertion markers that currently exist. Drags have 0, 1, - * or 2 insertion markers. - * @return {!Array} A possibly empty list of insertion - * marker blocks. - * @package - */ - getInsertionMarkers(): Array; - } - export namespace InsertionMarkerManager { - namespace PREVIEW_TYPE { - const INSERTION_MARKER: number; - const INPUT_OUTLINE: number; - const REPLACEMENT_FADE: number; - } - /** - * An enum describing different kinds of previews the InsertionMarkerManager - * could display. - */ - type PREVIEW_TYPE = number; - const DUPLICATE_BLOCK_ERROR: string; - } - import { Coordinate } from "utils/coordinate"; - import { IDragTarget } from "interfaces/i_drag_target"; - import { BlockSvg } from "block_svg"; -} -declare module "interfaces/i_ast_node_location_svg" { - /** - * An AST node location SVG interface. - * @interface - * @extends {IASTNodeLocation} - * @alias Blockly.IASTNodeLocationSvg - */ - export class IASTNodeLocationSvg { - } -} -declare module "keyboard_nav/marker" { - /** - * Class for a marker. - * This is used in keyboard navigation to save a location in the Blockly AST. - * @constructor - * @alias Blockly.Marker - */ - export class Marker { - /** - * The colour of the marker. - * @type {?string} - */ - colour: string | null; - /** - * The current location of the marker. - * @type {ASTNode} - * @private - */ - private curNode_; - /** - * The object in charge of drawing the visual representation of the current - * node. - * @type {MarkerSvg} - * @private - */ - private drawer_; - /** - * The type of the marker. - * @type {string} - */ - type: string; - /** - * Sets the object in charge of drawing the marker. - * @param {MarkerSvg} drawer The object in charge of - * drawing the marker. - */ - setDrawer(drawer: MarkerSvg): void; - /** - * Get the current drawer for the marker. - * @return {MarkerSvg} The object in charge of drawing - * the marker. - */ - getDrawer(): MarkerSvg; - /** - * Gets the current location of the marker. - * @return {ASTNode} The current field, connection, or block the marker - * is on. - */ - getCurNode(): ASTNode; - /** - * Set the location of the marker and call the update method. - * Setting isStack to true will only work if the newLocation is the top most - * output or previous connection on a stack. - * @param {ASTNode} newNode The new location of the marker. - */ - setCurNode(newNode: ASTNode): void; - /** - * Redraw the current marker. - * @package - */ - draw(): void; - /** - * Hide the marker SVG. - */ - hide(): void; - /** - * Dispose of this marker. - */ - dispose(): void; - } - import { MarkerSvg } from "renderers/common/marker_svg"; - import { ASTNode } from "keyboard_nav/ast_node"; -} -declare module "events/events_ui_base" { - /** - * Base class for a UI event. - * UI events are events that don't need to be sent over the wire for multi-user - * editing to work (e.g. scrolling the workspace, zooming, opening toolbox - * categories). - * UI events do not undo or redo. - * @param {string=} opt_workspaceId The workspace identifier for this event. - * Undefined for a blank event. - * @extends {Abstract} - * @constructor - * @alias Blockly.Events.UiBase - */ - export class UiBase { - constructor(opt_workspaceId: any); - /** - * Whether or not the event is blank (to be populated by fromJson). - * @type {boolean} - */ - isBlank: boolean; - /** - * The workspace identifier for this event. - * @type {string} - */ - workspaceId: string; - recordUndo: boolean; - /** - * Whether or not the event is a UI event. - * @type {boolean} - */ - isUiEvent: boolean; - } -} -declare module "events/events_marker_move" { - /** - * Class for a marker move event. - * @param {?Block=} opt_block The affected block. Null if current node - * is of type workspace. Undefined for a blank event. - * @param {boolean=} isCursor Whether this is a cursor event. Undefined for a - * blank event. - * @param {?ASTNode=} opt_oldNode The old node the marker used to be on. - * Undefined for a blank event. - * @param {!ASTNode=} opt_newNode The new node the marker is now on. - * Undefined for a blank event. - * @extends {UiBase} - * @constructor - * @alias Blockly.Events.MarkerMove - */ - export class MarkerMove { - constructor(opt_block: any, isCursor: any, opt_oldNode: any, opt_newNode: any); - /** - * The workspace identifier for this event. - * @type {?string} - */ - blockId: string | null; - /** - * The old node the marker used to be on. - * @type {?ASTNode|undefined} - */ - oldNode: (ASTNode | undefined) | null; - /** - * The new node the marker is now on. - * @type {ASTNode|undefined} - */ - newNode: ASTNode | undefined; - /** - * Whether this is a cursor event. - * @type {boolean|undefined} - */ - isCursor: boolean | undefined; - /** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ - toJson(): any; - /** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ - fromJson(json: any): void; - /** - * Type of this event. - * @type {string} - */ - type: string; - } - import { ASTNode } from "keyboard_nav/ast_node"; -} -declare module "renderers/common/marker_svg" { - export class MarkerSvg { - /** - * Class for a marker. - * @param {!WorkspaceSvg} workspace The workspace the marker belongs to. - * @param {!ConstantProvider} constants The constants for - * the renderer. - * @param {!Marker} marker The marker to draw. - * @constructor - * @alias Blockly.blockRendering.MarkerSvg - */ - constructor(workspace: WorkspaceSvg, constants: ConstantProvider, marker: Marker); - /** - * The workspace the marker belongs to. - * @type {!WorkspaceSvg} - * @private - */ - private workspace_; - /** - * The marker to draw. - * @type {!Marker} - * @private - */ - private marker_; - /** - * The workspace, field, or block that the marker SVG element should be - * attached to. - * @type {IASTNodeLocationSvg} - * @private - */ - private parent_; - /** - * The constants necessary to draw the marker. - * @type {ConstantProvider} - * @protected - */ - protected constants_: ConstantProvider; - /** - * The current SVG element for the marker. - * @type {Element} - */ - currentMarkerSvg: Element; - /** - * The colour of the marker. - * @type {string} - */ - colour_: string; - /** - * Return the root node of the SVG or null if none exists. - * @return {SVGElement} The root SVG node. - */ - getSvgRoot(): SVGElement; - /** - * Get the marker. - * @return {!Marker} The marker to draw for. - */ - getMarker(): Marker; - /** - * True if the marker should be drawn as a cursor, false otherwise. - * A cursor is drawn as a flashing line. A marker is drawn as a solid line. - * @return {boolean} True if the marker is a cursor, false otherwise. - */ - isCursor(): boolean; - /** - * Create the DOM element for the marker. - * @return {!SVGElement} The marker controls SVG group. - * @package - */ - createDom(): SVGElement; - svgGroup_: SVGGElement; - /** - * Attaches the SVG root of the marker to the SVG group of the parent. - * @param {!IASTNodeLocationSvg} newParent The workspace, field, or - * block that the marker SVG element should be attached to. - * @protected - */ - protected setParent_(newParent: IASTNodeLocationSvg): void; - /** - * Update the marker. - * @param {ASTNode} oldNode The previous node the marker was on or null. - * @param {ASTNode} curNode The node that we want to draw the marker for. - */ - draw(oldNode: ASTNode, curNode: ASTNode): void; - /** - * Update the marker's visible state based on the type of curNode.. - * @param {!ASTNode} curNode The node that we want to draw the marker for. - * @protected - */ - protected showAtLocation_(curNode: ASTNode): void; - /************************** - * Display - **************************/ - /** - * Show the marker as a combination of the previous connection and block, - * the output connection and block, or just the block. - * @param {!ASTNode} curNode The node to draw the marker for. - * @private - */ - private showWithBlockPrevOutput_; - /** - * Position and display the marker for a block. - * @param {!ASTNode} curNode The node to draw the marker for. - * @protected - */ - protected showWithBlock_(curNode: ASTNode): void; - /** - * Position and display the marker for a previous connection. - * @param {!ASTNode} curNode The node to draw the marker for. - * @protected - */ - protected showWithPrevious_(curNode: ASTNode): void; - /** - * Position and display the marker for an output connection. - * @param {!ASTNode} curNode The node to draw the marker for. - * @protected - */ - protected showWithOutput_(curNode: ASTNode): void; - /** - * Position and display the marker for a workspace coordinate. - * This is a horizontal line. - * @param {!ASTNode} curNode The node to draw the marker for. - * @protected - */ - protected showWithCoordinates_(curNode: ASTNode): void; - /** - * Position and display the marker for a field. - * This is a box around the field. - * @param {!ASTNode} curNode The node to draw the marker for. - * @protected - */ - protected showWithField_(curNode: ASTNode): void; - /** - * Position and display the marker for an input. - * This is a puzzle piece. - * @param {!ASTNode} curNode The node to draw the marker for. - * @protected - */ - protected showWithInput_(curNode: ASTNode): void; - /** - * Position and display the marker for a next connection. - * This is a horizontal line. - * @param {!ASTNode} curNode The node to draw the marker for. - * @protected - */ - protected showWithNext_(curNode: ASTNode): void; - /** - * Position and display the marker for a stack. - * This is a box with extra padding around the entire stack of blocks. - * @param {!ASTNode} curNode The node to draw the marker for. - * @protected - */ - protected showWithStack_(curNode: ASTNode): void; - /** - * Show the current marker. - * @protected - */ - protected showCurrent_(): void; - /************************** - * Position - **************************/ - /** - * Position the marker for a block. - * Displays an outline of the top half of a rectangle around a block. - * @param {number} width The width of the block. - * @param {number} markerOffset The extra padding for around the block. - * @param {number} markerHeight The height of the marker. - * @protected - */ - protected positionBlock_(width: number, markerOffset: number, markerHeight: number): void; - /** - * Position the marker for an input connection. - * Displays a filled in puzzle piece. - * @param {!RenderedConnection} connection The connection to position - * marker around. - * @protected - */ - protected positionInput_(connection: RenderedConnection): void; - /** - * Move and show the marker at the specified coordinate in workspace units. - * Displays a horizontal line. - * @param {number} x The new x, in workspace units. - * @param {number} y The new y, in workspace units. - * @param {number} width The new width, in workspace units. - * @protected - */ - protected positionLine_(x: number, y: number, width: number): void; - /** - * Position the marker for an output connection. - * Displays a puzzle outline and the top and bottom path. - * @param {number} width The width of the block. - * @param {number} height The height of the block. - * @param {!Object} connectionShape The shape object for the connection. - * @protected - */ - protected positionOutput_(width: number, height: number, connectionShape: any): void; - /** - * Position the marker for a previous connection. - * Displays a half rectangle with a notch in the top to represent the previous - * connection. - * @param {number} width The width of the block. - * @param {number} markerOffset The offset of the marker from around the block. - * @param {number} markerHeight The height of the marker. - * @param {!Object} connectionShape The shape object for the connection. - * @protected - */ - protected positionPrevious_(width: number, markerOffset: number, markerHeight: number, connectionShape: any): void; - /** - * Move and show the marker at the specified coordinate in workspace units. - * Displays a filled in rectangle. - * @param {number} x The new x, in workspace units. - * @param {number} y The new y, in workspace units. - * @param {number} width The new width, in workspace units. - * @param {number} height The new height, in workspace units. - * @protected - */ - protected positionRect_(x: number, y: number, width: number, height: number): void; - /** - * Flip the SVG paths in RTL. - * @param {!SVGElement} markerSvg The marker that we want to flip. - * @private - */ - private flipRtl_; - /** - * Hide the marker. - */ - hide(): void; - /** - * Fire event for the marker or marker. - * @param {ASTNode} oldNode The old node the marker used to be on. - * @param {!ASTNode} curNode The new node the marker is currently on. - * @private - */ - private fireMarkerEvent_; - /** - * Get the properties to make a marker blink. - * @return {!Object} The object holding attributes to make the marker blink. - * @protected - */ - protected getBlinkProperties_(): any; - /** - * Create the marker SVG. - * @return {Element} The SVG node created. - * @protected - */ - protected createDomInternal_(): Element; - markerSvg_: SVGGElement; - markerSvgLine_: SVGRectElement; - markerSvgRect_: SVGRectElement; - markerInput_: SVGPathElement; - markerBlock_: SVGPathElement; - /** - * Apply the marker's colour. - * @param {!ASTNode} _curNode The node that we want to draw the marker - * for. - * @protected - */ - protected applyColour_(_curNode: ASTNode): void; - /** - * Dispose of this marker. - */ - dispose(): void; - } - import { ConstantProvider } from "renderers/common/constants"; - import { Marker } from "keyboard_nav/marker"; - import { IASTNodeLocationSvg } from "interfaces/i_ast_node_location_svg"; - import { ASTNode } from "keyboard_nav/ast_node"; - import { RenderedConnection } from "rendered_connection"; - import { WorkspaceSvg } from "workspace_svg"; -} -declare module "renderers/common/path_object" { - export class PathObject { - /** - * An object that handles creating and setting each of the SVG elements - * used by the renderer. * @param {!SVGElement} root The root SVG element. * @param {!Theme.BlockStyle} style The style object to use for * colouring. * @param {!ConstantProvider} constants The renderer's * constants. - * @constructor - * @implements {IPathObject} * @package - * @alias Blockly.blockRendering.PathObject */ constructor(root: SVGElement, style: Theme.BlockStyle, constants: ConstantProvider); /** @@ -9340,10 +12037,10 @@ declare module "renderers/common/path_object" { /** * Apply the stored colours to the block's path, taking into account whether * the paths belong to a shadow block. - * @param {!Block} block The source block. + * @param {!BlockSvg} block The source block. * @package */ - applyColour(block: Block): void; + applyColour(block: BlockSvg): void; /** * Set the style. * @param {!Theme.BlockStyle} blockStyle The block style to use. @@ -9404,38 +12101,40 @@ declare module "renderers/common/path_object" { */ updateMovable(enable: boolean): void; /** - * Add or remove styling that shows that if the dragging block is dropped, this - * block will be replaced. If a shadow block, it will disappear. Otherwise it - * will bump. + * Add or remove styling that shows that if the dragging block is dropped, + * this block will be replaced. If a shadow block, it will disappear. + * Otherwise it will bump. * @param {boolean} enable True if styling should be added. * @package */ updateReplacementFade(enable: boolean): void; /** - * Add or remove styling that shows that if the dragging block is dropped, this - * block will be connected to the input. + * Add or remove styling that shows that if the dragging block is dropped, + * this block will be connected to the input. * @param {Connection} _conn The connection on the input to highlight. * @param {boolean} _enable True if styling should be added. * @package */ updateShapeForInputHighlight(_conn: Connection, _enable: boolean): void; } - import { ConstantProvider } from "renderers/common/constants"; - import { Theme } from "theme"; - import { Block } from "block"; - import { Connection } from "connection"; + import { IPathObject } from "core/renderers/common/i_path_object"; + import { ConstantProvider } from "core/renderers/common/constants"; + import { Theme } from "core/theme"; + import { BlockSvg } from "core/block_svg"; + import { Connection } from "core/connection"; } -declare module "renderers/common/renderer" { +declare module "core/renderers/common/renderer" { /** * The base class for a block renderer. - * @param {string} name The renderer name. - * @package - * @constructor * @implements {IRegistrable} * @alias Blockly.blockRendering.Renderer */ export class Renderer implements IRegistrable { - constructor(name: any); + /** + * @param {string} name The renderer name. + * @package + */ + constructor(name: string); /** * The renderer name. * @type {string} @@ -9453,7 +12152,7 @@ declare module "renderers/common/renderer" { * @type {?Object} * @package */ - overrides: any | null; + overrides: Object | null; /** * Gets the class name that identifies this renderer. * @return {string} The CSS class name. @@ -9466,7 +12165,7 @@ declare module "renderers/common/renderer" { * @param {Object=} opt_rendererOverrides Rendering constant overrides. * @package */ - init(theme: Theme, opt_rendererOverrides?: any | undefined): void; + init(theme: Theme, opt_rendererOverrides?: Object | undefined): void; /** * Create any DOM elements that this renderer needs. * @param {!SVGElement} svg The root of the workspace's SVG. @@ -9512,7 +12211,8 @@ declare module "renderers/common/renderer" { /** * Create a new instance of the renderer's debugger. * @return {!Debug} The renderer debugger. - * @suppress {strictModuleDepCheck} Debug renderer only included in playground. + * @suppress {strictModuleDepCheck} Debug renderer only included in + * playground. * @protected */ protected makeDebugger_(): Debug; @@ -9582,106 +12282,86 @@ declare module "renderers/common/renderer" { */ render(block: BlockSvg): void; } - import { IRegistrable } from "interfaces/i_registrable"; - import { Theme } from "theme"; - import { ConstantProvider } from "renderers/common/constants"; - import { BlockSvg } from "block_svg"; - import { RenderInfo } from "renderers/common/info"; - import { Drawer } from "renderers/common/drawer"; - import { Debug } from "renderers/common/debugger"; - import { WorkspaceSvg } from "workspace_svg"; - import { Marker } from "keyboard_nav/marker"; - import { MarkerSvg } from "renderers/common/marker_svg"; - import { IPathObject } from "renderers/common/i_path_object"; - import { Connection } from "connection"; - import { RenderedConnection } from "rendered_connection"; - import { InsertionMarkerManager } from "insertion_marker_manager"; + import { IRegistrable } from "core/interfaces/i_registrable"; + import { Theme } from "core/theme"; + import { ConstantProvider } from "core/renderers/common/constants"; + import { BlockSvg } from "core/block_svg"; + import { RenderInfo } from "core/renderers/common/info"; + import { Drawer } from "core/renderers/common/drawer"; + import { Debug } from "core/renderers/common/debugger"; + import { WorkspaceSvg } from "core/workspace_svg"; + import { Marker } from "core/keyboard_nav/marker"; + import { MarkerSvg } from "core/renderers/common/marker_svg"; + import { IPathObject } from "core/renderers/common/i_path_object"; + import { Connection } from "core/connection"; + import { RenderedConnection } from "core/rendered_connection"; + import { InsertionMarkerManager } from "core/insertion_marker_manager"; } -declare module "renderers/measurables/round_corner" { +declare module "core/renderers/measurables/round_corner" { /** * An object containing information about the space a rounded corner takes up * during rendering. - * @param {!ConstantProvider} constants The rendering - * constants provider. - * @param {string=} opt_position The position of this corner. - * @package - * @constructor * @extends {Measurable} + * @struct * @alias Blockly.blockRendering.RoundCorner */ - export class RoundCorner { - constructor(constants: any, opt_position: any); - type: number; - width: any; - height: number; + export class RoundCorner extends Measurable { + /** + * @param {!ConstantProvider} constants The rendering + * constants provider. + * @param {string=} opt_position The position of this corner. + * @package + */ + constructor(constants: ConstantProvider, opt_position?: string | undefined); } + import { Measurable } from "core/renderers/measurables/base"; + import { ConstantProvider } from "core/renderers/common/constants"; } -declare module "renderers/measurables/spacer_row" { +declare module "core/renderers/measurables/spacer_row" { /** * An object containing information about a spacer between two rows. - * @param {!ConstantProvider} constants The rendering - * constants provider. - * @param {number} height The height of the spacer. - * @param {number} width The width of the spacer. - * @package - * @constructor * @extends {Row} + * @struct * @alias Blockly.blockRendering.SpacerRow */ export class SpacerRow extends Row { - constructor(constants: any, height: any, width: any); - width: any; - height: any; - followsStatement: boolean; - widthWithConnectedBlocks: number; - elements: InRowSpacer[]; /** - * @override + * @param {!ConstantProvider} constants The rendering + * constants provider. + * @param {number} height The height of the spacer. + * @param {number} width The width of the spacer. + * @package */ - override measure(): void; + constructor(constants: ConstantProvider, height: number, width: number); + /** @type {boolean} */ + followsStatement: boolean; + /** @type {boolean} */ + precedesStatement: boolean; } - import { InRowSpacer } from "renderers/measurables/in_row_spacer"; - import { Row } from "renderers/measurables/row"; + import { Row } from "core/renderers/measurables/row"; + import { ConstantProvider } from "core/renderers/common/constants"; } -declare module "renderers/measurables/square_corner" { +declare module "core/renderers/measurables/square_corner" { /** * An object containing information about the space a square corner takes up * during rendering. - * @param {!ConstantProvider} constants The rendering - * constants provider. - * @param {string=} opt_position The position of this corner. - * @package - * @constructor * @extends {Measurable} + * @struct * @alias Blockly.blockRendering.SquareCorner */ - export class SquareCorner { - constructor(constants: any, opt_position: any); - type: number; - height: any; - width: any; + export class SquareCorner extends Measurable { + /** + * @param {!ConstantProvider} constants The rendering + * constants provider. + * @param {string=} opt_position The position of this corner. + * @package + */ + constructor(constants: ConstantProvider, opt_position?: string | undefined); } + import { Measurable } from "core/renderers/measurables/base"; + import { ConstantProvider } from "core/renderers/common/constants"; } -declare module "renderers/measurables/statement_input" { - /** - * An object containing information about the space a statement input takes up - * during rendering - * @param {!ConstantProvider} constants The rendering - * constants provider. - * @param {!Input} input The statement input to measure and store - * information for. - * @package - * @constructor - * @extends {InputConnection} - * @alias Blockly.blockRendering.StatementInput - */ - export class StatementInput { - constructor(constants: any, input: any); - height: any; - width: any; - } -} -declare module "renderers/measurables/top_row" { +declare module "core/renderers/measurables/top_row" { /** * An object containing information about what elements are in the top row of a * block as well as sizing information for the top row. @@ -9689,15 +12369,11 @@ declare module "renderers/measurables/top_row" { * connections. * After this constructor is called, the row will contain all non-spacer * elements it needs. - * @param {!ConstantProvider} constants The rendering - * constants provider. - * @package - * @constructor * @extends {Row} + * @struct * @alias Blockly.blockRendering.TopRow */ export class TopRow extends Row { - constructor(constants: any); /** * The starting point for drawing the row, in the y direction. * This allows us to draw hats and similar shapes that don't start at the @@ -9734,40 +12410,25 @@ declare module "renderers/measurables/top_row" { * @return {boolean} Whether or not the top row has a right square corner. */ hasRightSquareCorner(_block: BlockSvg): boolean; - /** - * @override - */ - override measure(): void; - width: number; - height: number; - widthWithConnectedBlocks: number; - /** - * @override - */ - override startsWithElemSpacer(): boolean; - /** - * @override - */ - override endsWithElemSpacer(): boolean; } - import { PreviousConnection } from "renderers/measurables/previous_connection"; - import { BlockSvg } from "block_svg"; - import { Row } from "renderers/measurables/row"; + import { Row } from "core/renderers/measurables/row"; + import { PreviousConnection } from "core/renderers/measurables/previous_connection"; + import { BlockSvg } from "core/block_svg"; } -declare module "renderers/common/info" { +declare module "core/renderers/common/info" { + /** + * An object containing all sizing information needed to draw this block. + * + * This measure pass does not propagate changes to the block (although fields + * may choose to rerender when getSize() is called). However, calling it + * repeatedly may be expensive. + * @alias Blockly.blockRendering.RenderInfo + */ export class RenderInfo { /** - * An object containing all sizing information needed to draw this block. - * - * This measure pass does not propagate changes to the block (although fields - * may choose to rerender when getSize() is called). However, calling it - * repeatedly may be expensive. - * * @param {!Renderer} renderer The renderer in use. * @param {!BlockSvg} block The block to measure. - * @constructor * @package - * @alias Blockly.blockRendering.RenderInfo */ constructor(renderer: Renderer, block: BlockSvg); block_: BlockSvg; @@ -9790,8 +12451,8 @@ declare module "renderers/common/info" { */ outputConnection: OutputConnection; /** - * Whether the block should be rendered as a single line, either because it's - * inline or because it has been collapsed. + * Whether the block should be rendered as a single line, either because + * it's inline or because it has been collapsed. * @type {boolean} */ isInline: boolean; @@ -9822,8 +12483,8 @@ declare module "renderers/common/info" { */ widthWithChildren: number; /** - * The width of the rendered block, excluding child blocks. This is the right - * edge of the block when rendered LTR. + * The width of the rendered block, excluding child blocks. This is the + * right edge of the block when rendered LTR. * @type {number} */ width: number; @@ -9893,8 +12554,8 @@ declare module "renderers/common/info" { */ populateBottomRow_(): void; /** - * Add an input element to the active row, if needed, and record the type of the - * input on the row. + * Add an input element to the active row, if needed, and record the type of + * the input on the row. * @param {!Input} input The input to record information about. * @param {!Row} activeRow The row that is currently being * populated. @@ -9916,8 +12577,8 @@ declare module "renderers/common/info" { protected addElemSpacing_(): void; /** * Calculate the width of a spacer element in a row based on the previous and - * next elements in that row. For instance, extra padding is added between two - * editable fields. + * next elements in that row. For instance, extra padding is added between + * two editable fields. * @param {Measurable} prev The element before the * spacer. * @param {Measurable} next The element after the spacer. @@ -9949,7 +12610,7 @@ declare module "renderers/common/info" { * Modify the given row to add the given amount of padding around its fields. * The exact location of the padding is based on the alignment property of the * last input in the field. - * @param {Row} row The row to add padding to. + * @param {!Row} row The row to add padding to. * @param {number} missingSpace How much padding to add. * @protected */ @@ -9978,7 +12639,8 @@ declare module "renderers/common/info" { * Calculate the width of a spacer row. * @param {!Row} _prev The row before the spacer. * @param {!Row} _next The row after the spacer. - * @return {number} The desired width of the spacer row between these two rows. + * @return {number} The desired width of the spacer row between these two + * rows. * @protected */ protected getSpacerRowWidth_(_prev: Row, _next: Row): number; @@ -9986,7 +12648,8 @@ declare module "renderers/common/info" { * Calculate the height of a spacer row. * @param {!Row} _prev The row before the spacer. * @param {!Row} _next The row after the spacer. - * @return {number} The desired height of the spacer row between these two rows. + * @return {number} The desired height of the spacer row between these two + * rows. * @protected */ protected getSpacerRowHeight_(_prev: Row, _next: Row): number; @@ -10016,28 +12679,725 @@ declare module "renderers/common/info" { */ protected finalize_(): void; } - import { BlockSvg } from "block_svg"; - import { Renderer } from "renderers/common/renderer"; - import { ConstantProvider } from "renderers/common/constants"; - import { OutputConnection } from "renderers/measurables/output_connection"; - import { Row } from "renderers/measurables/row"; - import { InputRow } from "renderers/measurables/input_row"; - import { Icon } from "renderers/measurables/icon"; - import { TopRow } from "renderers/measurables/top_row"; - import { BottomRow } from "renderers/measurables/bottom_row"; - import { Input } from "input"; - import { Measurable } from "renderers/measurables/base"; - import { SpacerRow } from "renderers/measurables/spacer_row"; + import { BlockSvg } from "core/block_svg"; + import { Renderer } from "core/renderers/common/renderer"; + import { ConstantProvider } from "core/renderers/common/constants"; + import { OutputConnection } from "core/renderers/measurables/output_connection"; + import { Row } from "core/renderers/measurables/row"; + import { InputRow } from "core/renderers/measurables/input_row"; + import { Icon } from "core/renderers/measurables/icon"; + import { TopRow } from "core/renderers/measurables/top_row"; + import { BottomRow } from "core/renderers/measurables/bottom_row"; + import { Input } from "core/input"; + import { Measurable } from "core/renderers/measurables/base"; + import { SpacerRow } from "core/renderers/measurables/spacer_row"; } -declare module "renderers/common/debugger" { +declare module "core/renderers/zelos/measurables/bottom_row" { + /** + * An object containing information about what elements are in the bottom row of + * a block as well as spacing information for the top row. + * Elements in a bottom row can consist of corners, spacers and next + * connections. + * @extends {BaseBottomRow} + * @alias Blockly.zelos.BottomRow + */ + export class BottomRow extends BaseBottomRow { + } + import { BottomRow as BaseBottomRow } from "core/renderers/measurables/bottom_row"; +} +declare module "core/renderers/zelos/constants" { + /** + * An object that provides constants for rendering blocks in Zelos mode. + * @extends {BaseConstantProvider} + * @alias Blockly.zelos.ConstantProvider + */ + export class ConstantProvider extends BaseConstantProvider { + GRID_UNIT: number; + /** + * Minimum statement input spacer width. + * @type {number} + */ + STATEMENT_INPUT_SPACER_MIN_WIDTH: number; + /** + * Radius of the cursor for input and output connections. + * @type {number} + * @package + */ + CURSOR_RADIUS: number; + /** + * Map of output/input shapes and the amount they should cause a block to be + * padded. Outer key is the outer shape, inner key is the inner shape. + * When a block with the outer shape contains an input block with the inner + * shape on its left or right edge, the block elements are aligned such that + * the padding specified is reached. + * @package + */ + SHAPE_IN_SHAPE_PADDING: { + 1: { + 0: number; + 1: number; + 2: number; + 3: number; + }; + 2: { + 0: number; + 1: number; + 2: number; + 3: number; + }; + 3: { + 0: number; + 1: number; + 2: number; + 3: number; + }; + }; + /** + * The maximum width of a dynamic connection shape. + * @type {number} + */ + MAX_DYNAMIC_CONNECTION_SHAPE_WIDTH: number; + /** + * The selected glow colour. + * @type {string} + */ + SELECTED_GLOW_COLOUR: string; + /** + * The size of the selected glow. + * @type {number} + */ + SELECTED_GLOW_SIZE: number; + /** + * The replacement glow colour. + * @type {string} + */ + REPLACEMENT_GLOW_COLOUR: string; + /** + * The size of the selected glow. + * @type {number} + */ + REPLACEMENT_GLOW_SIZE: number; + /** + * The ID of the selected glow filter, or the empty string if no filter is + * set. + * @type {string} + * @package + */ + selectedGlowFilterId: string; + /** + * The element to use for a selected glow, or null if not set. + * @type {SVGElement} + * @private + */ + private selectedGlowFilter_; + /** + * The ID of the replacement glow filter, or the empty string if no filter + * is set. + * @type {string} + * @package + */ + replacementGlowFilterId: string; + /** + * The element to use for a replacement glow, or null if not set. + * @type {SVGElement} + * @private + */ + private replacementGlowFilter_; + /** + * The object containing information about the hexagon used for a boolean + * reporter block. Null before init is called. + * @type {Object} + */ + HEXAGONAL: Object; + /** + * The object containing information about the hexagon used for a number or + * string reporter block. Null before init is called. + * @type {Object} + */ + ROUNDED: Object; + /** + * The object containing information about the hexagon used for a + * rectangular reporter block. Null before init is called. + * @type {Object} + */ + SQUARED: Object; + /** + * Create sizing and path information about a hexagonal shape. + * @return {!Object} An object containing sizing and path information about + * a hexagonal shape for connections. + * @package + */ + makeHexagonal(): Object; + /** + * Create sizing and path information about a rounded shape. + * @return {!Object} An object containing sizing and path information about + * a rounded shape for connections. + * @package + */ + makeRounded(): Object; + /** + * Create sizing and path information about a squared shape. + * @return {!Object} An object containing sizing and path information about + * a squared shape for connections. + * @package + */ + makeSquared(): Object; + } + import { ConstantProvider as BaseConstantProvider } from "core/renderers/common/constants"; +} +declare module "core/field_image" { + /** + * Class for an image on a block. + * @extends {Field} + * @alias Blockly.FieldImage + */ + export class FieldImage extends 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, + * alt, and flipRtl). + * @return {!FieldImage} The new field instance. + * @package + * @nocollapse + */ + static fromJson(options: Object): FieldImage; + /** + * @param {string|!Sentinel} src The URL of the image. + * Also accepts Field.SKIP_SETUP if you wish to skip setup (only used by + * subclasses that want to handle configuration and setting the field + * value after their own constructors have run). + * @param {!(string|number)} width Width of the image. + * @param {!(string|number)} height Height of the image. + * @param {string=} opt_alt Optional alt text for when block is collapsed. + * @param {function(!FieldImage)=} opt_onClick Optional function to be + * called when the image is clicked. If opt_onClick is defined, opt_alt + * must also be defined. + * @param {boolean=} opt_flipRtl Whether to flip the icon in RTL. + * @param {Object=} opt_config A map of options used to configure the field. + * See the [field creation documentation]{@link + * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/image#creation} + * for a list of properties this parameter supports. + */ + constructor(src: string | Sentinel, width: (string | number), height: (string | number), opt_alt?: string | undefined, opt_onClick?: ((arg0: FieldImage) => any) | undefined, opt_flipRtl?: boolean | undefined, opt_config?: Object | undefined); + /** + * Store the image height, since it is different from the field height. + * @type {number} + * @private + */ + private imageHeight_; + /** + * The function to be called when this field is clicked. + * @type {?function(!FieldImage)} + * @private + */ + private clickHandler_; + /** + * The rendered field's image element. + * @type {SVGImageElement} + * @private + */ + private imageElement_; + /** + * Whether to flip this image in RTL. + * @type {boolean} + * @private + */ + private flipRtl_; + /** + * Alt text of this image. + * @type {string} + * @private + */ + private altText_; + /** + * Set the alt text of this image. + * @param {?string} alt New alt text. + * @public + */ + public setAlt(alt: string | null): void; + /** + * Set the function that is called when this image is clicked. + * @param {?function(!FieldImage)} func The function that is called + * when the image is clicked, or null to remove. + */ + setOnClickHandler(func: ((arg0: FieldImage) => any) | null): void; + } + export namespace FieldImage { + const Y_PADDING: number; + } + import { Field } from "core/field"; + import { Sentinel } from "core/utils/sentinel"; +} +declare module "core/field_textinput" { + /** + * Class for an editable text field. + * @alias Blockly.FieldTextInput + */ + export class FieldTextInput extends Field { + /** + * Construct a FieldTextInput from a JSON arg object, + * dereferencing any string table references. + * @param {!Object} options A JSON object with options (text, and spellcheck). + * @return {!FieldTextInput} The new field instance. + * @package + * @nocollapse + */ + static fromJson(options: Object): FieldTextInput; + /** + * @param {(string|!Sentinel)=} opt_value The initial value of the + * field. Should cast to a string. Defaults to an empty string if null or + * undefined. + * Also accepts Field.SKIP_SETUP if you wish to skip setup (only used by + * subclasses that want to handle configuration and setting the field + * value after their own constructors have run). + * @param {?Function=} opt_validator A function that is called to validate + * changes to the field's value. Takes in a string & returns a validated + * string, or null to abort the change. + * @param {Object=} opt_config A map of options used to configure the field. + * See the [field creation documentation]{@link + * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/text-input#creation} + * for a list of properties this parameter supports. + */ + constructor(opt_value?: (string | Sentinel) | undefined, opt_validator?: (Function | null) | undefined, opt_config?: Object | undefined); + /** + * Allow browser to spellcheck this field. + * @type {boolean} + * @protected + */ + protected spellcheck_: boolean; + /** + * The HTML input element. + * @type {HTMLElement} + * @protected + */ + protected htmlInput_: HTMLElement; + /** + * True if the field's value is currently being edited via the UI. + * @type {boolean} + * @private + */ + private isBeingEdited_; + /** + * True if the value currently displayed in the field's editory UI is valid. + * @type {boolean} + * @private + */ + private isTextValid_; + /** + * Key down event data. + * @type {?browserEvents.Data} + * @private + */ + private onKeyDownWrapper_; + /** + * Key input event data. + * @type {?browserEvents.Data} + * @private + */ + private onKeyInputWrapper_; + /** + * Whether the field should consider the whole parent block to be its click + * target. + * @type {?boolean} + */ + fullBlockClickTarget_: boolean | null; + /** + * The workspace that this field belongs to. + * @type {?WorkspaceSvg} + * @protected + */ + protected workspace_: WorkspaceSvg | null; + /** + * Set whether this field is spellchecked by the browser. + * @param {boolean} check True if checked. + */ + setSpellcheck(check: boolean): void; + /** + * Create and show a text input editor that is a prompt (usually a popup). + * Mobile browsers have issues with in-line textareas (focus and keyboards). + * @private + */ + private showPromptEditor_; + /** + * Create and show a text input editor that sits directly over the text input. + * @param {boolean} quietInput True if editor should be created without + * focus. + * @private + */ + private showInlineEditor_; + /** + * Create the text input editor widget. + * @return {!HTMLElement} The newly created text input editor. + * @protected + */ + protected widgetCreate_(): HTMLElement; + /** + * Closes the editor, saves the results, and disposes of any events or + * DOM-references belonging to the editor. + * @protected + */ + protected widgetDispose_(): void; + /** + * A callback triggered when the user is done editing the field via the UI. + * @param {*} _value The new value of the field. + */ + onFinishEditing_(_value: any): void; + /** + * Bind handlers for user input on the text input field's editor. + * @param {!HTMLElement} htmlInput The htmlInput to which event + * handlers will be bound. + * @protected + */ + protected bindInputEvents_(htmlInput: HTMLElement): void; + /** + * Unbind handlers for user input and workspace size changes. + * @protected + */ + protected unbindInputEvents_(): void; + /** + * Handle key down to the editor. + * @param {!Event} e Keyboard event. + * @protected + */ + protected onHtmlInputKeyDown_(e: Event): void; + /** + * Handle a change to the editor. + * @param {!Event} _e Keyboard event. + * @private + */ + private onHtmlInputChange_; + /** + * Set the HTML input value and the field's internal value. The difference + * between this and ``setValue`` is that this also updates the HTML input + * value whilst editing. + * @param {*} newValue New value. + * @protected + */ + protected setEditorValue_(newValue: any): void; + /** + * Resize the editor to fit the text. + * @protected + */ + protected resizeEditor_(): void; + /** + * Transform the provided value into a text to show in the HTML input. + * Override this method if the field's HTML input representation is different + * than the field's value. This should be coupled with an override of + * `getValueFromEditorText_`. + * @param {*} value The value stored in this field. + * @return {string} The text to show on the HTML input. + * @protected + */ + protected getEditorText_(value: any): string; + /** + * Transform the text received from the HTML input into a value to store + * in this field. + * Override this method if the field's HTML input representation is different + * than the field's value. This should be coupled with an override of + * `getEditorText_`. + * @param {string} text Text received from the HTML input. + * @return {*} The value to store. + * @protected + */ + protected getValueFromEditorText_(text: string): any; + } + export namespace FieldTextInput { + const BORDERRADIUS: number; + } + import { Field } from "core/field"; + import { WorkspaceSvg } from "core/workspace_svg"; + import { Sentinel } from "core/utils/sentinel"; +} +declare module "core/renderers/zelos/path_object" { + /** + * An object that handles creating and setting each of the SVG elements + * used by the renderer. + * @alias Blockly.zelos.PathObject + */ + export class PathObject extends BasePathObject { + /** + * @param {!SVGElement} root The root SVG element. + * @param {!Theme.BlockStyle} style The style object to use for + * colouring. + * @param {!ConstantProvider} constants The renderer's constants. + * @package + */ + constructor(root: SVGElement, style: Theme.BlockStyle, constants: ConstantProvider); + /** + * The selected path of the block. + * @type {?SVGElement} + * @private + */ + private svgPathSelected_; + /** + * The outline paths on the block. + * @type {!Object} + * @private + */ + private outlines_; + /** + * A set used to determine which outlines were used during a draw pass. The + * set is initialized with a reference to all the outlines in + * `this.outlines_`. Every time we use an outline during the draw pass, the + * reference is removed from this set. + * @type {Object} + * @private + */ + private remainingOutlines_; + /** + * The type of block's output connection shape. This is set when a block + * with an output connection is drawn. + * @package + */ + outputShapeType: any; + /** + * Method that's called when the drawer is about to draw the block. + * @package + */ + beginDrawing(): void; + /** + * Method that's called when the drawer is done drawing. + * @package + */ + endDrawing(): void; + /** + * Set the path generated by the renderer for an outline path on the + * respective outline path SVG element. + * @param {string} name The input name. + * @param {string} pathString The path. + * @package + */ + setOutlinePath(name: string, pathString: string): void; + /** + * Create's an outline path for the specified input. + * @param {string} name The input name. + * @return {!SVGElement} The SVG outline path. + * @private + */ + private getOutlinePath_; + /** + * Remove an outline path that is associated with the specified input. + * @param {string} name The input name. + * @private + */ + private removeOutlinePath_; + } + import { PathObject as BasePathObject } from "core/renderers/common/path_object"; + import { Theme } from "core/theme"; + import { ConstantProvider } from "core/renderers/zelos/constants"; +} +declare module "core/renderers/zelos/measurables/inputs" { + /** + * An object containing information about the space a statement input takes up + * during rendering. + * @extends {BaseStatementInput} + * @alias Blockly.zelos.StatementInput + */ + export class StatementInput extends BaseStatementInput { + connectedBottomNextConnection: boolean | undefined; + } + import { StatementInput as BaseStatementInput } from "core/renderers/measurables/statement_input"; +} +declare module "core/renderers/zelos/drawer" { + /** + * An object that draws a block based on the given rendering information. + * @extends {BaseDrawer} + * @alias Blockly.zelos.Drawer + */ + export class Drawer extends BaseDrawer { + /** + * @param {!BlockSvg} block The block to render. + * @param {!RenderInfo} info An object containing all + * information needed to render this block. + * @package + */ + constructor(block: BlockSvg, info: RenderInfo); + /** + * Add steps to draw the right side of an output with a dynamic connection. + * @protected + */ + protected drawRightDynamicConnection_(): void; + /** + * Add steps to draw the left side of an output with a dynamic connection. + * @protected + */ + protected drawLeftDynamicConnection_(): void; + /** + * Add steps to draw a flat top row. + * @protected + */ + protected drawFlatTop_(): void; + /** + * Add steps to draw a flat bottom row. + * @protected + */ + protected drawFlatBottom_(): void; + } + import { Drawer as BaseDrawer } from "core/renderers/common/drawer"; + import { BlockSvg } from "core/block_svg"; + import { RenderInfo } from "core/renderers/zelos/info"; +} +declare module "core/renderers/zelos/marker_svg" { + /** + * Class to draw a marker. + * @extends {BaseMarkerSvg} + * @alias Blockly.zelos.MarkerSvg + */ + export class MarkerSvg extends BaseMarkerSvg { + /** + * @type {SVGCircleElement} + * @private + */ + private markerCircle_; + /** + * Position and display the marker for an input or an output connection. + * @param {!ASTNode} curNode The node to draw the marker for. + * @private + */ + private showWithInputOutput_; + /** + * Position the circle we use for input and output connections. + * @param {number} x The x position of the circle. + * @param {number} y The y position of the circle. + * @private + */ + private positionCircle_; + } + import { MarkerSvg as BaseMarkerSvg } from "core/renderers/common/marker_svg"; +} +declare module "core/renderers/zelos/renderer" { + /** + * The zelos renderer. + * @extends {BaseRenderer} + * @alias Blockly.zelos.Renderer + */ + export class Renderer extends blockRendering.Renderer { + } + import * as blockRendering from "core/renderers/common/block_rendering"; +} +declare module "core/renderers/zelos/measurables/row_elements" { + /** + * An object containing information about the space a right connection shape + * takes up during rendering. + * @extends {Measurable} + * @alias Blockly.zelos.RightConnectionShape + */ + export class RightConnectionShape extends Measurable { + } + import { Measurable } from "core/renderers/measurables/base"; +} +declare module "core/renderers/zelos/measurables/top_row" { + /** + * An object containing information about what elements are in the top row of a + * block as well as sizing information for the top row. + * Elements in a top row can consist of corners, hats, spacers, and previous + * connections. + * After this constructor is called, the row will contain all non-spacer + * elements it needs. + * @extends {BaseTopRow} + * @alias Blockly.zelos.TopRow + */ + export class TopRow extends BaseTopRow { + } + import { TopRow as BaseTopRow } from "core/renderers/measurables/top_row"; +} +declare module "core/renderers/zelos/info" { + /** + * An object containing all sizing information needed to draw this block. + * + * This measure pass does not propagate changes to the block (although fields + * may choose to rerender when getSize() is called). However, calling it + * repeatedly may be expensive. + * @extends {BaseRenderInfo} + * @alias Blockly.zelos.RenderInfo + */ + export class RenderInfo extends BaseRenderInfo { + /** + * @param {!Renderer} renderer The renderer in use. + * @param {!BlockSvg} block The block to measure. + * @package + */ + constructor(renderer: Renderer, block: BlockSvg); + /** + * Whether the block should be rendered as a multi-line block, either + * because it's not inline or because it has been collapsed. + * @type {boolean} + */ + isMultiRow: boolean; + /** + * Whether or not the block has a statement input in one of its rows. + * @type {boolean} + */ + hasStatementInput: boolean; + /** + * An object with rendering information about the right connection shape. + * @type {RightConnectionShape} + */ + rightSide: RightConnectionShape; + /** + * A map of rows to right aligned dummy inputs within those rows. Used to + * add padding between left and right aligned inputs. + * @type {!WeakMap} + * @private + */ + private rightAlignedDummyInputs_; + /** + * Adjust the x position of fields to bump all non-label fields in the first + * row past the notch position. This must be called before ``computeBounds`` + * is called. + * @protected + */ + protected adjustXPosition_(): void; + /** + * Finalize the output connection info. In particular, set the height of the + * output connection to match that of the block. For the right side, add a + * right connection shape element and have it match the dimensions of the + * output connection. + * @protected + */ + protected finalizeOutputConnection_(): void; + /** + * Finalize horizontal alignment of elements on the block. In particular, + * reduce the implicit spacing created by the left and right output connection + * shapes by adding setting negative spacing onto the leftmost and rightmost + * spacers. + * @protected + */ + protected finalizeHorizontalAlignment_(): void; + /** + * Calculate the spacing to reduce the left and right edges by based on the + * outer and inner connection shape. + * @param {Measurable} elem The first or last element on + * a block. + * @return {number} The amount of spacing to reduce the first or last spacer. + * @protected + */ + protected getNegativeSpacing_(elem: Measurable): number; + /** + * Finalize vertical alignment of rows on a block. In particular, reduce the + * implicit spacing when a non-shadow block is connected to any of an input + * row's inline inputs. + * @protected + */ + protected finalizeVerticalAlignment_(): void; + } + import { RenderInfo as BaseRenderInfo } from "core/renderers/common/info"; + import { RightConnectionShape } from "core/renderers/zelos/measurables/row_elements"; + import { Measurable } from "core/renderers/measurables/base"; + import { Renderer } from "core/renderers/zelos/renderer"; + import { BlockSvg } from "core/block_svg"; +} +declare module "core/renderers/common/debugger" { + /** + * An object that renders rectangles and dots for debugging rendering code. + * @alias Blockly.blockRendering.Debug + */ export class Debug { /** - * An object that renders rectangles and dots for debugging rendering code. * @param {!ConstantProvider} constants The renderer's * constants. * @package - * @constructor - * @alias Blockly.blockRendering.Debug */ constructor(constants: ConstantProvider); /** @@ -10059,6 +13419,11 @@ declare module "renderers/common/debugger" { * @private */ private constants_; + /** + * @type {string} + * @private + */ + private randomColour_; /** * Remove all elements the this object created on the last pass. * @package @@ -10092,8 +13457,8 @@ declare module "renderers/common/debugger" { * share the same colours, as do previous and next. When positioned correctly * a connected pair will look like a bullseye. * @param {RenderedConnection} conn The connection to circle. - * @suppress {visibility} Suppress visibility of conn.offsetInBlock_ since this - * is a debug module. + * @suppress {visibility} Suppress visibility of conn.offsetInBlock_ since + * this is a debug module. * @package */ drawConnection(conn: RenderedConnection): void; @@ -10128,7 +13493,6 @@ declare module "renderers/common/debugger" { * @package */ drawDebug(block: BlockSvg, info: RenderInfo): void; - randomColour_: string; /** * Show a debug filter to highlight that a block has been rendered. * @param {!SVGElement} svgPath The block's SVG path. @@ -10141,20 +13505,21 @@ declare module "renderers/common/debugger" { [x: string]: boolean; }; } - import { Row } from "renderers/measurables/row"; - import { InRowSpacer } from "renderers/measurables/in_row_spacer"; - import { Measurable } from "renderers/measurables/base"; - import { RenderedConnection } from "rendered_connection"; - import { RenderInfo } from "renderers/common/info"; - import { BlockSvg } from "block_svg"; - import { ConstantProvider } from "renderers/common/constants"; + import { Row } from "core/renderers/measurables/row"; + import { InRowSpacer } from "core/renderers/measurables/in_row_spacer"; + import { Measurable } from "core/renderers/measurables/base"; + import { RenderedConnection } from "core/rendered_connection"; + import { RenderInfo } from "core/renderers/common/info"; + import { BlockSvg } from "core/block_svg"; + import { ConstantProvider } from "core/renderers/common/constants"; } -declare module "renderers/common/block_rendering" { +declare module "core/renderers/common/block_rendering" { /** * Returns whether the debugger is turned on. * @return {boolean} Whether the debugger is turned on. * @alias Blockly.blockRendering.isDebuggerEnabled * @package + * @deprecated */ export function isDebuggerEnabled(): boolean; /** @@ -10175,12 +13540,14 @@ declare module "renderers/common/block_rendering" { * Turn on the blocks debugger. * @package * @alias Blockly.blockRendering.startDebugger + * @deprecated */ export function startDebugger(): void; /** * Turn off the blocks debugger. * @package * @alias Blockly.blockRendering.stopDebugger + * @deprecated */ export function stopDebugger(): void; /** @@ -10193,465 +13560,64 @@ declare module "renderers/common/block_rendering" { * @package * @alias Blockly.blockRendering.init */ - export function init(name: string, theme: Theme, opt_rendererOverrides?: any | undefined): Renderer; - import { BottomRow } from "renderers/measurables/bottom_row"; - import { Connection } from "renderers/measurables/connection"; - import { ConstantProvider } from "renderers/common/constants"; - import { Debug } from "renderers/common/debugger"; - import { Drawer } from "renderers/common/drawer"; - import { ExternalValueInput } from "renderers/measurables/external_value_input"; - import { Field } from "renderers/measurables/field"; - import { Hat } from "renderers/measurables/hat"; - import { Icon } from "renderers/measurables/icon"; - import { InRowSpacer } from "renderers/measurables/in_row_spacer"; - import { InlineInput } from "renderers/measurables/inline_input"; - import { InputConnection } from "renderers/measurables/input_connection"; - import { InputRow } from "renderers/measurables/input_row"; - import { IPathObject } from "renderers/common/i_path_object"; - import { JaggedEdge } from "renderers/measurables/jagged_edge"; - import { MarkerSvg } from "renderers/common/marker_svg"; - import { Measurable } from "renderers/measurables/base"; - import { NextConnection } from "renderers/measurables/next_connection"; - import { OutputConnection } from "renderers/measurables/output_connection"; - import { PathObject } from "renderers/common/path_object"; - import { PreviousConnection } from "renderers/measurables/previous_connection"; - import { Renderer } from "renderers/common/renderer"; - import { RenderInfo } from "renderers/common/info"; - import { RoundCorner } from "renderers/measurables/round_corner"; - import { Row } from "renderers/measurables/row"; - import { SpacerRow } from "renderers/measurables/spacer_row"; - import { SquareCorner } from "renderers/measurables/square_corner"; - import { StatementInput } from "renderers/measurables/statement_input"; - import { TopRow } from "renderers/measurables/top_row"; - import { Types } from "renderers/measurables/types"; - import * as debug from "renderers/common/debug"; - import { Theme } from "theme"; + export function init(name: string, theme: Theme, opt_rendererOverrides?: Object | undefined): Renderer; + import { BottomRow } from "core/renderers/measurables/bottom_row"; + import { Connection } from "core/renderers/measurables/connection"; + import { ConstantProvider } from "core/renderers/common/constants"; + import { Debug } from "core/renderers/common/debugger"; + import { Drawer } from "core/renderers/common/drawer"; + import { ExternalValueInput } from "core/renderers/measurables/external_value_input"; + import { Field } from "core/renderers/measurables/field"; + import { Hat } from "core/renderers/measurables/hat"; + import { Icon } from "core/renderers/measurables/icon"; + import { InRowSpacer } from "core/renderers/measurables/in_row_spacer"; + import { InlineInput } from "core/renderers/measurables/inline_input"; + import { InputConnection } from "core/renderers/measurables/input_connection"; + import { InputRow } from "core/renderers/measurables/input_row"; + import { IPathObject } from "core/renderers/common/i_path_object"; + import { JaggedEdge } from "core/renderers/measurables/jagged_edge"; + import { MarkerSvg } from "core/renderers/common/marker_svg"; + import { Measurable } from "core/renderers/measurables/base"; + import { NextConnection } from "core/renderers/measurables/next_connection"; + import { OutputConnection } from "core/renderers/measurables/output_connection"; + import { PathObject } from "core/renderers/common/path_object"; + import { PreviousConnection } from "core/renderers/measurables/previous_connection"; + import { Renderer } from "core/renderers/common/renderer"; + import { RenderInfo } from "core/renderers/common/info"; + import { RoundCorner } from "core/renderers/measurables/round_corner"; + import { Row } from "core/renderers/measurables/row"; + import { SpacerRow } from "core/renderers/measurables/spacer_row"; + import { SquareCorner } from "core/renderers/measurables/square_corner"; + import { StatementInput } from "core/renderers/measurables/statement_input"; + import { TopRow } from "core/renderers/measurables/top_row"; + import { Types } from "core/renderers/measurables/types"; + import * as debug from "core/renderers/common/debug"; + import { Theme } from "core/theme"; export { BottomRow, Connection, ConstantProvider, Debug, Drawer, ExternalValueInput, Field, Hat, Icon, InRowSpacer, InlineInput, InputConnection, InputRow, IPathObject, JaggedEdge, MarkerSvg, Measurable, NextConnection, OutputConnection, PathObject, PreviousConnection, Renderer, RenderInfo, RoundCorner, Row, SpacerRow, SquareCorner, StatementInput, TopRow, Types, debug }; } -declare module "events/events_bubble_open" { - /** - * Class for a bubble open event. - * @param {BlockSvg} opt_block The associated block. Undefined for a - * blank event. - * @param {boolean=} opt_isOpen Whether the bubble is opening (false if - * closing). Undefined for a blank event. - * @param {string=} opt_bubbleType The type of bubble. One of 'mutator', - * 'comment' - * or 'warning'. Undefined for a blank event. - * @extends {UiBase} - * @constructor - * @alias Blockly.Events.BubbleOpen - */ - export class BubbleOpen { - constructor(opt_block: any, opt_isOpen: any, opt_bubbleType: any); - blockId: any; - /** - * Whether the bubble is opening (false if closing). - * @type {boolean|undefined} - */ - isOpen: boolean | undefined; - /** - * The type of bubble. One of 'mutator', 'comment', or 'warning'. - * @type {string|undefined} - */ - bubbleType: string | undefined; - /** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ - toJson(): any; - /** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ - fromJson(json: any): void; - /** - * Type of this event. - * @type {string} - */ - type: string; - } -} -declare module "mutator" { - export class Mutator extends Icon { - /** - * Reconnect an block to a mutated input. - * @param {Connection} connectionChild Connection on child block. - * @param {!Block} block Parent block. - * @param {string} inputName Name of input on parent block. - * @return {boolean} True iff a reconnection was made, false otherwise. - */ - static reconnect(connectionChild: Connection, block: Block, inputName: string): boolean; - /** - * Get the parent workspace of a workspace that is inside a mutator, taking into - * account whether it is a flyout. - * @param {Workspace} workspace The workspace that is inside a mutator. - * @return {?Workspace} The mutator's parent workspace or null. - * @public - */ - public static findParentWs(workspace: Workspace): Workspace | null; - /** - * Class for a mutator dialog. - * @param {!Array} quarkNames List of names of sub-blocks for flyout. - * @extends {Icon} - * @constructor - * @alias Blockly.Mutator - */ - constructor(quarkNames: Array); - quarkNames_: string[]; - /** - * Set the block this mutator is associated with. - * @param {!BlockSvg} block The block associated with this mutator. - * @package - */ - setBlock(block: BlockSvg): void; - block_: BlockSvg; - /** - * Returns the workspace inside this mutator icon's bubble. - * @return {?WorkspaceSvg} The workspace inside this mutator icon's - * bubble or null if the mutator isn't open. - * @package - */ - getWorkspace(): WorkspaceSvg | null; - /** - * Draw the mutator icon. - * @param {!Element} group The icon group. - * @protected - */ - protected drawIcon_(group: Element): void; - /** - * Clicking on the icon toggles if the mutator bubble is visible. - * Disable if block is uneditable. - * @param {!Event} e Mouse click event. - * @protected - * @override - */ - protected override iconClick_(e: Event): void; - /** - * Create the editor for the mutator's bubble. - * @return {!SVGElement} The top-level node of the editor. - * @private - */ - private createEditor_; - svgDialog_: SVGSVGElement; - workspace_: WorkspaceSvg | null; - /** - * Add or remove the UI indicating if this icon may be clicked or not. - */ - updateEditable(): void; - /** - * Resize the bubble to match the size of the workspace. - * @private - */ - private resizeBubble_; - workspaceWidth_: number; - workspaceHeight_: number; - /** - * A method handler for when the bubble is moved. - * @private - */ - private onBubbleMove_; - /** - * Show or hide the mutator bubble. - * @param {boolean} visible True if the bubble should be visible. - */ - setVisible(visible: boolean): void; - bubble_: Bubble; - rootBlock_: BlockSvg; - sourceListener_: () => void; - /** - * Fired whenever a change is made to the mutator's workspace. - * @param {!Abstract} e Custom data for event. - * @private - */ - private workspaceChanged_; - /** - * Updates the source block when the mutator's blocks are changed. - * Bump down any block that's too high. - * @private - */ - private updateWorkspace_; - /** - * Dispose of this mutator. - */ - dispose(): void; - /** - * Update the styles on all blocks in the mutator. - * @public - */ - public updateBlockStyle(): void; - } - import { BlockSvg } from "block_svg"; - import { WorkspaceSvg } from "workspace_svg"; - import { Bubble } from "bubble"; - import { Connection } from "connection"; - import { Block } from "block"; - import { Workspace } from "workspace"; - import { Icon } from "icon"; -} -declare module "extensions" { - export namespace TEST_ONLY { - export { allExtensions }; - } - /** - * The set of all registered extensions, keyed by extension name/id. - * @private - */ - const allExtensions: any; - /** - * Registers a new extension function. Extensions are functions that help - * initialize blocks, usually adding dynamic behavior such as onchange - * handlers and mutators. These are applied using Block.applyExtension(), or - * the JSON "extensions" array attribute. - * @param {string} name The name of this extension. - * @param {Function} initFn The function to initialize an extended block. - * @throws {Error} if the extension name is empty, the extension is already - * registered, or extensionFn is not a function. - * @alias Blockly.Extensions.register - */ - export function register(name: string, initFn: Function): void; - /** - * Registers a new extension function that adds all key/value of mixinObj. - * @param {string} name The name of this extension. - * @param {!Object} mixinObj The values to mix in. - * @throws {Error} if the extension name is empty or the extension is already - * registered. - * @alias Blockly.Extensions.registerMixin - */ - export function registerMixin(name: string, mixinObj: any): void; - /** - * Registers a new extension function that adds a mutator to the block. - * At register time this performs some basic sanity checks on the mutator. - * The wrapper may also add a mutator dialog to the block, if both compose and - * decompose are defined on the mixin. - * @param {string} name The name of this mutator extension. - * @param {!Object} mixinObj The values to mix in. - * @param {(function())=} opt_helperFn An optional function to apply after - * mixing in the object. - * @param {!Array=} opt_blockList A list of blocks to appear in the - * flyout of the mutator dialog. - * @throws {Error} if the mutation is invalid or can't be applied to the block. - * @alias Blockly.Extensions.registerMutator - */ - export function registerMutator(name: string, mixinObj: any, opt_helperFn?: (() => any) | undefined, opt_blockList?: Array | undefined): void; - /** - * Unregisters the extension registered with the given name. - * @param {string} name The name of the extension to unregister. - * @alias Blockly.Extensions.unregister - */ - export function unregister(name: string): void; - /** - * Returns whether an extension is registered with the given name. - * @param {string} name The name of the extension to check for. - * @return {boolean} True if the extension is registered. False if it is - * not registered. - * @alias Blockly.Extensions.isRegistered - */ - export function isRegistered(name: string): boolean; - /** - * Applies an extension method to a block. This should only be called during - * block construction. - * @param {string} name The name of the extension. - * @param {!Block} block The block to apply the named extension to. - * @param {boolean} isMutator True if this extension defines a mutator. - * @throws {Error} if the extension is not found. - * @alias Blockly.Extensions.apply - */ - export function apply(name: string, block: Block, isMutator: boolean): void; - /** - * Calls a function after the page has loaded, possibly immediately. - * @param {function()} fn Function to run. - * @throws Error Will throw if no global document can be found (e.g., Node.js). - * @package - */ - export function runAfterPageLoad(fn: () => any): void; - /** - * Builds an extension function that will map a dropdown value to a tooltip - * string. - * - * This method includes multiple checks to ensure tooltips, dropdown options, - * and message references are aligned. This aims to catch errors as early as - * possible, without requiring developers to manually test tooltips under each - * option. After the page is loaded, each tooltip text string will be checked - * for matching message keys in the internationalized string table. Deferring - * this until the page is loaded decouples loading dependencies. Later, upon - * loading the first block of any given type, the extension will validate every - * dropdown option has a matching tooltip in the lookupTable. Errors are - * reported as warnings in the console, and are never fatal. - * @param {string} dropdownName The name of the field whose value is the key - * to the lookup table. - * @param {!Object} lookupTable The table of field values to - * tooltip text. - * @return {!Function} The extension function. - * @alias Blockly.Extensions.buildTooltipForDropdown - */ - export function buildTooltipForDropdown(dropdownName: string, lookupTable: { - [x: string]: string; - }): Function; - /** - * Builds an extension function that will install a dynamic tooltip. The - * tooltip message should include the string '%1' and that string will be - * replaced with the text of the named field. - * @param {string} msgTemplate The template form to of the message text, with - * %1 placeholder. - * @param {string} fieldName The field with the replacement text. - * @return {!Function} The extension function. - * @alias Blockly.Extensions.buildTooltipWithFieldText - */ - export function buildTooltipWithFieldText(msgTemplate: string, fieldName: string): Function; - import { Block } from "block"; - export {}; -} -declare module "utils/keycodes" { - /** - * * - */ - export type KeyCodes = number; - export namespace KeyCodes { - const WIN_KEY_FF_LINUX: number; - const MAC_ENTER: number; - const BACKSPACE: number; - const TAB: number; - const NUM_CENTER: number; - const ENTER: number; - const SHIFT: number; - const CTRL: number; - const ALT: number; - const PAUSE: number; - const CAPS_LOCK: number; - const ESC: number; - const SPACE: number; - const PAGE_UP: number; - const PAGE_DOWN: number; - const END: number; - const HOME: number; - const LEFT: number; - const UP: number; - const RIGHT: number; - const DOWN: number; - const PLUS_SIGN: number; - const PRINT_SCREEN: number; - const INSERT: number; - const DELETE: number; - const ZERO: number; - const ONE: number; - const TWO: number; - const THREE: number; - const FOUR: number; - const FIVE: number; - const SIX: number; - const SEVEN: number; - const EIGHT: number; - const NINE: number; - const FF_SEMICOLON: number; - const FF_EQUALS: number; - const FF_DASH: number; - const FF_HASH: number; - const QUESTION_MARK: number; - const AT_SIGN: number; - const A: number; - const B: number; - const C: number; - const D: number; - const E: number; - const F: number; - const G: number; - const H: number; - const I: number; - const J: number; - const K: number; - const L: number; - const M: number; - const N: number; - const O: number; - const P: number; - const Q: number; - const R: number; - const S: number; - const T: number; - const U: number; - const V: number; - const W: number; - const X: number; - const Y: number; - const Z: number; - const META: number; - const WIN_KEY_RIGHT: number; - const CONTEXT_MENU: number; - const NUM_ZERO: number; - const NUM_ONE: number; - const NUM_TWO: number; - const NUM_THREE: number; - const NUM_FOUR: number; - const NUM_FIVE: number; - const NUM_SIX: number; - const NUM_SEVEN: number; - const NUM_EIGHT: number; - const NUM_NINE: number; - const NUM_MULTIPLY: number; - const NUM_PLUS: number; - const NUM_MINUS: number; - const NUM_PERIOD: number; - const NUM_DIVISION: number; - const F1: number; - const F2: number; - const F3: number; - const F4: number; - const F5: number; - const F6: number; - const F7: number; - const F8: number; - const F9: number; - const F10: number; - const F11: number; - const F12: number; - const NUMLOCK: number; - const SCROLL_LOCK: number; - const FIRST_MEDIA_KEY: number; - const LAST_MEDIA_KEY: number; - const SEMICOLON: number; - const DASH: number; - const EQUALS: number; - const COMMA: number; - const PERIOD: number; - const SLASH: number; - const APOSTROPHE: number; - const TILDE: number; - const SINGLE_QUOTE: number; - const OPEN_SQUARE_BRACKET: number; - const BACKSLASH: number; - const CLOSE_SQUARE_BRACKET: number; - const WIN_KEY: number; - const MAC_FF_META: number; - const MAC_WK_CMD_LEFT: number; - const MAC_WK_CMD_RIGHT: number; - const WIN_IME: number; - const VK_NONAME: number; - const PHANTOM: number; - } -} -declare module "utils" { - import * as aria from "utils/aria"; - import * as colourUtils from "utils/colour"; - import { Coordinate } from "utils/coordinate"; - import * as deprecation from "utils/deprecation"; - import * as dom from "utils/dom"; - import * as idGenerator from "utils/idgenerator"; - import { KeyCodes } from "utils/keycodes"; - import * as math from "utils/math"; - import { Metrics } from "utils/metrics"; - import * as object from "utils/object"; - import * as parsing from "utils/parsing"; - import { Rect } from "utils/rect"; - import { Size } from "utils/size"; - import * as stringUtils from "utils/string"; - import * as style from "utils/style"; - import { Svg } from "utils/svg"; - import * as svgPaths from "utils/svg_paths"; - import * as svgMath from "utils/svg_math"; - import * as toolbox from "utils/toolbox"; - import * as userAgent from "utils/useragent"; - import * as xmlUtils from "utils/xml"; +declare module "core/utils" { + import * as aria from "core/utils/aria"; + import * as colourUtils from "core/utils/colour"; + import { Coordinate } from "core/utils/coordinate"; + import * as deprecation from "core/utils/deprecation"; + import * as dom from "core/utils/dom"; + const globalThis: any; + import * as idGenerator from "core/utils/idgenerator"; + import { KeyCodes } from "core/utils/keycodes"; + import * as math from "core/utils/math"; + import { Metrics } from "core/utils/metrics"; + import * as object from "core/utils/object"; + import * as parsing from "core/utils/parsing"; + import { Rect } from "core/utils/rect"; + import { Size } from "core/utils/size"; + import * as stringUtils from "core/utils/string"; + import * as style from "core/utils/style"; + import { Svg } from "core/utils/svg"; + import * as svgPaths from "core/utils/svg_paths"; + import * as svgMath from "core/utils/svg_math"; + import * as toolbox from "core/utils/toolbox"; + import * as userAgent from "core/utils/useragent"; + import * as xmlUtils from "core/utils/xml"; /** * Halts the propagation of the event without doing anything else. * @param {!Event} e An event. @@ -10675,7 +13641,13 @@ declare module "utils" { * @deprecated * @alias Blockly.utils.getRelativeXY */ - export function getRelativeXY(element: Element): Coordinate; + export function getRelativeXY(element: Element): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; /** * Return the coordinates of the top-left corner of this element relative to * the div Blockly was injected into. @@ -10686,7 +13658,13 @@ declare module "utils" { * @deprecated * @alias Blockly.utils.getInjectionDivXY_ */ - function getInjectionDivXY(element: Element): Coordinate; + function getInjectionDivXY(element: Element): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; /** * Returns true this event is a right-click. * @param {!Event} e Mouse event. @@ -10776,7 +13754,14 @@ declare module "utils" { * @deprecated * @package */ - export function getViewportBBox(): Rect; + export function getViewportBBox(): { + top: number; + bottom: number; + left: number; + right: number; + contains(x: number, y: number): boolean; + intersects(other: any): boolean; + }; /** * Removes the first occurrence of a particular value from an array. * @param {!Array} arr Array from which to remove value. @@ -10794,7 +13779,13 @@ declare module "utils" { * @deprecated * @alias Blockly.utils.getDocumentScroll */ - export function getDocumentScroll(): Coordinate; + export function getDocumentScroll(): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; /** * Get a map of all the block's descendants mapping their type to the number of * children with that type. @@ -10806,7 +13797,7 @@ declare module "utils" { * @deprecated * @alias Blockly.utils.getBlockTypeCounts */ - export function getBlockTypeCounts(block: Block, opt_stripFollowing?: boolean | undefined): any; + export function getBlockTypeCounts(block: Block, opt_stripFollowing?: boolean | undefined): Object; /** * Converts screen coordinates to workspace coordinates. * @param {!WorkspaceSvg} ws The workspace to find the coordinates on. @@ -10815,7 +13806,19 @@ declare module "utils" { * @deprecated * @return {!Coordinate} The workspace coordinates. */ - export function screenToWsCoordinates(ws: WorkspaceSvg, screenCoordinates: Coordinate): Coordinate; + export function screenToWsCoordinates(ws: WorkspaceSvg, screenCoordinates: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; /** * Parse a block colour from a number or string, as provided in a block * definition. @@ -10839,20 +13842,42 @@ declare module "utils" { * @alias Blockly.utils.runAfterPageLoad */ export function runAfterPageLoad(fn: () => any): void; - import { Block } from "block"; - import { WorkspaceSvg } from "workspace_svg"; - export { aria, colourUtils as colour, Coordinate, deprecation, dom, idGenerator, KeyCodes, math, Metrics, object, parsing, Rect, Size, stringUtils as string, style, Svg, svgPaths, svgMath, toolbox, userAgent, xmlUtils as xml, getInjectionDivXY as getInjectionDivXY_ }; + import { Block } from "core/block"; + import { WorkspaceSvg } from "core/workspace_svg"; + export { aria, colourUtils as colour, Coordinate, deprecation, dom, globalThis as global, idGenerator, KeyCodes, math, Metrics, object, parsing, Rect, Size, stringUtils as string, style, Svg, svgPaths, svgMath, toolbox, userAgent, xmlUtils as xml, getInjectionDivXY as getInjectionDivXY_ }; } -declare module "contextmenu_registry" { +declare module "core/blockly_options" { + /** + * Blockly options. + * This interface is further described in + * `typings/parts/blockly-interfaces.d.ts`. + * @interface + * @alias Blockly.BlocklyOptions + */ + export class BlocklyOptions {} +} +declare module "core/theme/classic" { + /** + * Classic theme. + * Contains multi-coloured border to create shadow effect. + * @type {Theme} + * @alias Blockly.Themes.Classic + */ + export const Classic: Theme; + import { Theme } from "core/theme"; +} +declare module "core/contextmenu_registry" { /** * Class for the registry of context menu items. This is intended to be a * singleton. You should not create a new instance, and only access this class * from ContextMenuRegistry.registry. - * @constructor - * @private * @alias Blockly.ContextMenuRegistry */ export class ContextMenuRegistry { + /** + * Clear and recreate the registry. + */ + reset(): void; /** * Registry of all registered RegistryItems, keyed by ID. * @type {!Object} @@ -10874,13 +13899,14 @@ declare module "contextmenu_registry" { unregister(id: string): void; /** * @param {string} id The ID of the RegistryItem to get. - * @return {?ContextMenuRegistry.RegistryItem} RegistryItem or null if not found + * @return {?ContextMenuRegistry.RegistryItem} RegistryItem or null if not + * found */ getItem(id: string): ContextMenuRegistry.RegistryItem | null; /** * Gets the valid context menu options for the given scope type (e.g. block or - * workspace) and scope. Blocks are only shown if the preconditionFn shows they - * should not be hidden. + * workspace) and scope. Blocks are only shown if the preconditionFn shows + * they should not be hidden. * @param {!ContextMenuRegistry.ScopeType} scopeType Type of scope where menu * should be shown (e.g. on a block or on a workspace) * @param {!ContextMenuRegistry.Scope} scope Current scope of context menu @@ -10901,7 +13927,7 @@ declare module "contextmenu_registry" { * registered for each scope. */ type ScopeType = string; - const registry: ContextMenuRegistry | null; + const registry: ContextMenuRegistry; /** * The actual workspace/block where the menu is being rendered. This is passed * to callback and displayText functions that depend on this information. @@ -10932,218 +13958,23 @@ declare module "contextmenu_registry" { weight: number; }; } - import { BlockSvg } from "block_svg"; - import { WorkspaceSvg } from "workspace_svg"; + import { BlockSvg } from "core/block_svg"; + import { WorkspaceSvg } from "core/workspace_svg"; } -declare module "dropdowndiv" { +declare module "core/flyout_button" { /** - * Class for drop-down div. - * @constructor - * @package - * @alias Blockly.DropDownDiv + * Class for a button or label in the flyout. + * @alias Blockly.FlyoutButton */ - export class DropDownDiv { - /** - * Create and insert the DOM element for this div. - * @package - */ - static createDom(): void; - /** - * Set an element to maintain bounds within. Drop-downs will appear - * within the box of this element if possible. - * @param {?Element} boundsElement Element to bind drop-down to. - */ - static setBoundsElement(boundsElement: Element | null): void; - /** - * Provide the div for inserting content into the drop-down. - * @return {!Element} Div to populate with content. - */ - static getContentDiv(): Element; - /** - * Clear the content of the drop-down. - */ - static clearContent(): void; - /** - * Set the colour for the drop-down. - * @param {string} backgroundColour Any CSS colour for the background. - * @param {string} borderColour Any CSS colour for the border. - */ - static setColour(backgroundColour: string, borderColour: string): void; - /** - * Shortcut to show and place the drop-down with positioning determined - * by a particular block. The primary position will be below the block, - * and the secondary position above the block. Drop-down will be - * constrained to the block's workspace. - * @param {!Field} field The field showing the drop-down. - * @param {!BlockSvg} block Block to position the drop-down around. - * @param {Function=} opt_onHide Optional callback for when the drop-down is - * hidden. - * @param {number=} opt_secondaryYOffset Optional Y offset for above-block - * positioning. - * @return {boolean} True if the menu rendered below block; false if above. - */ - static showPositionedByBlock(field: Field, block: BlockSvg, opt_onHide?: Function | undefined, opt_secondaryYOffset?: number | undefined): boolean; - /** - * Shortcut to show and place the drop-down with positioning determined - * by a particular field. The primary position will be below the field, - * and the secondary position above the field. Drop-down will be - * constrained to the block's workspace. - * @param {!Field} field The field to position the dropdown against. - * @param {Function=} opt_onHide Optional callback for when the drop-down is - * hidden. - * @param {number=} opt_secondaryYOffset Optional Y offset for above-block - * positioning. - * @return {boolean} True if the menu rendered below block; false if above. - */ - static showPositionedByField(field: Field, opt_onHide?: Function | undefined, opt_secondaryYOffset?: number | undefined): boolean; - /** - * Show and place the drop-down. - * The drop-down is placed with an absolute "origin point" (x, y) - i.e., - * the arrow will point at this origin and box will positioned below or above - * it. If we can maintain the container bounds at the primary point, the arrow - * will point there, and the container will be positioned below it. - * If we can't maintain the container bounds at the primary point, fall-back to - * the secondary point and position above. - * @param {?Object} owner The object showing the drop-down - * @param {boolean} rtl Right-to-left (true) or left-to-right (false). - * @param {number} primaryX Desired origin point x, in absolute px. - * @param {number} primaryY Desired origin point y, in absolute px. - * @param {number} secondaryX Secondary/alternative origin point x, in absolute - * px. - * @param {number} secondaryY Secondary/alternative origin point y, in absolute - * px. - * @param {Function=} opt_onHide Optional callback for when the drop-down is - * hidden. - * @return {boolean} True if the menu rendered at the primary origin point. - * @package - */ - static show(owner: any | null, rtl: boolean, primaryX: number, primaryY: number, secondaryX: number, secondaryY: number, opt_onHide?: Function | undefined): boolean; - /** - * Get the x positions for the left side of the DropDownDiv and the arrow, - * accounting for the bounds of the workspace. - * @param {number} sourceX Desired origin point x, in absolute px. - * @param {number} boundsLeft The left edge of the bounding element, in - * absolute px. - * @param {number} boundsRight The right edge of the bounding element, in - * absolute px. - * @param {number} divWidth The width of the div in px. - * @return {{divX: number, arrowX: number}} An object containing metrics for - * the x positions of the left side of the DropDownDiv and the arrow. - * @package - */ - static getPositionX(sourceX: number, boundsLeft: number, boundsRight: number, divWidth: number): { - divX: number; - arrowX: number; - }; - /** - * Is the container visible? - * @return {boolean} True if visible. - */ - static isVisible(): boolean; - /** - * Hide the menu only if it is owned by the provided object. - * @param {?Object} owner Object which must be owning the drop-down to hide. - * @param {boolean=} opt_withoutAnimation True if we should hide the dropdown - * without animating. - * @return {boolean} True if hidden. - */ - static hideIfOwner(owner: any | null, opt_withoutAnimation?: boolean | undefined): boolean; - /** - * Hide the menu, triggering animation. - */ - static hide(): void; - /** - * Hide the menu, without animation. - */ - static hideWithoutAnimation(): void; - /** - * Repositions the dropdownDiv on window resize. If it doesn't know how to - * calculate the new position, it will just hide it instead. - * @package - */ - static repositionForWindowResize(): void; - } - export namespace DropDownDiv { - export const ARROW_SIZE: number; - export const BORDER_SIZE: number; - export const ARROW_HORIZONTAL_PADDING: number; - export const PADDING_Y: number; - export const ANIMATION_TIME: number; - export const animateOutTimer_: number | null; - export const onHide_: Function | null; - export const rendererClassName_: string; - export const themeClassName_: string; - export const DIV_: Element; - export const content_: Element; - export const arrow_: Element; - export const boundsElement_: Element | null; - export const owner_: any | null; - export const positionToField_: boolean | null; - export { internal as TEST_ONLY }; - /** - * Dropdown bounds info object used to encapsulate sizing information about a - * bounding element (bounding box and width/height). - */ - export type BoundsInfo = { - top: number; - left: number; - bottom: number; - right: number; - width: number; - height: number; - }; - /** - * Dropdown position metrics. - */ - export type PositionMetrics = { - initialX: number; - initialY: number; - finalX: number; - finalY: number; - arrowX: number | null; - arrowY: number | null; - arrowAtTop: boolean | null; - arrowVisible: boolean; - }; - } - import { Field } from "field"; - import { BlockSvg } from "block_svg"; - namespace internal { - /** - * Get sizing info about the bounding element. - * @return {!DropDownDiv.BoundsInfo} An object containing size - * information about the bounding element (bounding box and width/height). - */ - function getBoundsInfo(): DropDownDiv.BoundsInfo; - /** - * Helper to position the drop-down and the arrow, maintaining bounds. - * See explanation of origin points in DropDownDiv.show. - * @param {number} primaryX Desired origin point x, in absolute px. - * @param {number} primaryY Desired origin point y, in absolute px. - * @param {number} secondaryX Secondary/alternative origin point x, - * in absolute px. - * @param {number} secondaryY Secondary/alternative origin point y, - * in absolute px. - * @return {!DropDownDiv.PositionMetrics} Various final metrics, - * including rendered positions for drop-down and arrow. - */ - function getPositionMetrics(primaryX: number, primaryY: number, secondaryX: number, secondaryY: number): DropDownDiv.PositionMetrics; - } - export {}; -} -declare module "flyout_button" { export class FlyoutButton { /** - * Class for a button in the flyout. * @param {!WorkspaceSvg} workspace The workspace in which to place this * button. * @param {!WorkspaceSvg} targetWorkspace The flyout's target workspace. * @param {!toolbox.ButtonOrLabelInfo} json * The JSON specifying the label/button. * @param {boolean} isLabel Whether this button should be styled as a label. - * @constructor * @package - * @alias Blockly.FlyoutButton */ constructor(workspace: WorkspaceSvg, targetWorkspace: WorkspaceSvg, json: toolbox.ButtonOrLabelInfo, isLabel: boolean); /** @@ -11168,6 +13999,7 @@ declare module "flyout_button" { private position_; /** * Whether this button should be styled as a label. + * Labels behave the same as buttons, but are styled differently. * @type {boolean} * @private */ @@ -11195,15 +14027,33 @@ declare module "flyout_button" { * @type {!toolbox.ButtonOrLabelInfo} */ info: toolbox.ButtonOrLabelInfo; + /** + * The width of the button's rect. + * @type {number} + */ + width: number; + /** + * The height of the button's rect. + * @type {number} + */ + height: number; + /** + * The root SVG group for the button or label. + * @type {?SVGGElement} + * @private + */ + private svgGroup_; + /** + * The SVG element with the text of the label or button. + * @type {?SVGTextElement} + * @private + */ + private svgText_; /** * Create the button elements. * @return {!SVGElement} The button's SVG group. */ createDom(): SVGElement; - svgGroup_: SVGGElement; - svgText_: SVGTextElement; - width: number; - height: number; /** * Correctly position the flyout button and make it visible. */ @@ -11228,7 +14078,13 @@ declare module "flyout_button" { * @return {!Coordinate} x, y coordinates. * @package */ - getPosition(): Coordinate; + getPosition(): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; /** * @return {string} Text of the button. */ @@ -11251,22 +14107,15 @@ declare module "flyout_button" { private onMouseUp_; } export namespace FlyoutButton { - const MARGIN_X: number; - const MARGIN_Y: number; + const TEXT_MARGIN_X: number; + const TEXT_MARGIN_Y: number; } - import * as toolbox from "utils/toolbox"; - import { Coordinate } from "utils/coordinate"; - import { WorkspaceSvg } from "workspace_svg"; + import * as toolbox from "core/utils/toolbox"; + import { WorkspaceSvg } from "core/workspace_svg"; } -declare module "grid" { +declare module "core/grid" { /** * Class for a workspace's grid. - * @param {!SVGElement} pattern The grid's SVG pattern, created during - * injection. - * @param {!Object} options A dictionary of normalized options for the grid. - * See grid documentation: - * https://developers.google.com/blockly/guides/configure/web/grid - * @constructor * @alias Blockly.Grid */ export class Grid { @@ -11278,8 +14127,22 @@ declare module "grid" { * @return {!SVGElement} The SVG element for the grid pattern. * @package */ - static createDom(rnd: string, gridOptions: any, defs: SVGElement): SVGElement; - constructor(pattern: any, options: any); + static createDom(rnd: string, gridOptions: Object, defs: SVGElement): SVGElement; + /** + * @param {!SVGElement} pattern The grid's SVG pattern, created during + * injection. + * @param {!Object} options A dictionary of normalized options for the grid. + * See grid documentation: + * https://developers.google.com/blockly/guides/configure/web/grid + */ + constructor(pattern: SVGElement, options: Object); + /** + * The scale of the grid, used to set stroke width on grid lines. + * This should always be the same as the workspace scale. + * @type {number} + * @private + */ + private scale_; /** * The grid's SVG pattern, created during injection. * @type {!SVGElement} @@ -11347,7 +14210,6 @@ declare module "grid" { * @package */ update(scale: number): void; - scale_: number; /** * Set the attributes on one of the lines in the grid. Use this to update the * length and stroke width of the grid lines. @@ -11370,234 +14232,303 @@ declare module "grid" { moveTo(x: number, y: number): void; } } -declare module "interfaces/i_bounded_element" { +declare module "core/options" { /** - * A bounded element interface. - * @interface - * @alias Blockly.IBoundedElement + * Parse the user-specified options, using reasonable defaults where behaviour + * is unspecified. + * @alias Blockly.Options */ - export class IBoundedElement { - } -} -declare module "marker_manager" { - export class MarkerManager { + export class Options { /** - * Class to manage the multiple markers and the cursor on a workspace. - * @param {!WorkspaceSvg} workspace The workspace for the marker manager. - * @constructor - * @alias Blockly.MarkerManager - * @package - */ - constructor(workspace: WorkspaceSvg); - /** - * The cursor. - * @type {?Cursor} + * Parse the user-specified move options, using reasonable defaults where + * behaviour is unspecified. + * @param {!Object} options Dictionary of options. + * @param {boolean} hasCategories Whether the workspace has categories or not. + * @return {!Options.MoveOptions} Normalized move options. * @private */ - private cursor_; + private static parseMoveOptions_; /** - * The cursor's SVG element. + * Parse the user-specified zoom options, using reasonable defaults where + * behaviour is unspecified. See zoom documentation: + * https://developers.google.com/blockly/guides/configure/web/zoom + * @param {!Object} options Dictionary of options. + * @return {!Options.ZoomOptions} Normalized zoom options. + * @private + */ + private static parseZoomOptions_; + /** + * Parse the user-specified grid options, using reasonable defaults where + * behaviour is unspecified. See grid documentation: + * https://developers.google.com/blockly/guides/configure/web/grid + * @param {!Object} options Dictionary of options. + * @return {!Options.GridOptions} Normalized grid options. + * @private + */ + private static parseGridOptions_; + /** + * Parse the user-specified theme options, using the classic theme as a + * default. https://developers.google.com/blockly/guides/configure/web/themes + * @param {!Object} options Dictionary of options. + * @return {!Theme} A Blockly Theme. + * @private + */ + private static parseThemeOptions_; + /** + * @param {!BlocklyOptions} options Dictionary of options. + * Specification: + * https://developers.google.com/blockly/guides/get-started/web#configuration + */ + constructor(options: () => void); + /** @type {boolean} */ + RTL: boolean; + /** @type {boolean} */ + oneBasedIndex: boolean; + /** @type {boolean} */ + collapse: boolean; + /** @type {boolean} */ + comments: boolean; + /** @type {boolean} */ + disable: boolean; + /** @type {boolean} */ + readOnly: boolean; + /** @type {number} */ + maxBlocks: number; + /** @type {?Object} */ + maxInstances: { + [x: string]: number; + } | null; + /** @type {string} */ + pathToMedia: string; + /** @type {boolean} */ + hasCategories: boolean; + /** @type {!Options.MoveOptions} */ + moveOptions: Options.MoveOptions; + /** @deprecated January 2019 */ + hasScrollbars: boolean; + /** @type {boolean} */ + hasTrashcan: boolean; + /** @type {number} */ + maxTrashcanContents: number; + /** @type {boolean} */ + hasSounds: boolean; + /** @type {boolean} */ + hasCss: boolean; + /** @type {boolean} */ + horizontalLayout: boolean; + /** @type {?toolbox.ToolboxInfo} */ + languageTree: toolbox.ToolboxInfo | null; + /** @type {!Options.GridOptions} */ + gridOptions: Options.GridOptions; + /** @type {!Options.ZoomOptions} */ + zoomOptions: Options.ZoomOptions; + /** @type {!toolbox.Position} */ + toolboxPosition: toolbox.Position; + /** @type {!Theme} */ + theme: Theme; + /** @type {string} */ + renderer: string; + /** @type {?Object} */ + rendererOverrides: Object | null; + /** + * The SVG element for the grid pattern. + * Created during injection. * @type {?SVGElement} - * @private */ - private cursorSvg_; + gridPattern: SVGElement | null; /** - * The map of markers for the workspace. - * @type {!Object} - * @private + * The parent of the current workspace, or null if there is no parent + * workspace. We can assert that this is of type WorkspaceSvg as opposed to + * Workspace as this is only used in a rendered workspace. + * @type {?WorkspaceSvg} */ - private markers_; + parentWorkspace: WorkspaceSvg | null; /** - * The workspace this marker manager is associated with. - * @type {!WorkspaceSvg} - * @private + * Map of plugin type to name of registered plugin or plugin class. + * @type {!Object} */ - private workspace_; + plugins: { + [x: string]: ((new (...args: unknown[]) => unknown) | string); + }; /** - * Register the marker by adding it to the map of markers. - * @param {string} id A unique identifier for the marker. - * @param {!Marker} marker The marker to register. + * If set, sets the translation of the workspace to match the scrollbars. + * @type {undefined|function(!{x:number,y:number}):void} A function that + * sets the translation of the workspace to match the scrollbars. The + * argument Contains an x and/or y property which is a float between 0 + * and 1 specifying the degree of scrolling. */ - registerMarker(id: string, marker: Marker): void; + setMetrics: ((arg0: { + x: number; + y: number; + }) => void) | undefined; /** - * Unregister the marker by removing it from the map of markers. - * @param {string} id The ID of the marker to unregister. + * @type {undefined|function():!Metrics} A function that returns a metrics + * object that describes the current workspace. */ - unregisterMarker(id: string): void; - /** - * Get the cursor for the workspace. - * @return {?Cursor} The cursor for this workspace. - */ - getCursor(): Cursor | null; - /** - * Get a single marker that corresponds to the given ID. - * @param {string} id A unique identifier for the marker. - * @return {?Marker} The marker that corresponds to the given ID, - * or null if none exists. - */ - getMarker(id: string): Marker | null; - /** - * Sets the cursor and initializes the drawer for use with keyboard navigation. - * @param {Cursor} cursor The cursor used to move around this workspace. - */ - setCursor(cursor: Cursor): void; - /** - * Add the cursor SVG to this workspace SVG group. - * @param {?SVGElement} cursorSvg The SVG root of the cursor to be added to the - * workspace SVG group. - * @package - */ - setCursorSvg(cursorSvg: SVGElement | null): void; - /** - * Add the marker SVG to this workspaces SVG group. - * @param {?SVGElement} markerSvg The SVG root of the marker to be added to the - * workspace SVG group. - * @package - */ - setMarkerSvg(markerSvg: SVGElement | null): void; - markerSvg_: any; - /** - * Redraw the attached cursor SVG if needed. - * @package - */ - updateMarkers(): void; - /** - * Dispose of the marker manager. - * Go through and delete all markers associated with this marker manager. - * @suppress {checkTypes} - * @package - */ - dispose(): void; + getMetrics: (() => Metrics) | undefined; } - export namespace MarkerManager { - const LOCAL_MARKER: string; + export namespace Options { + /** + * Grid Options. + */ + type GridOptions = { + colour: string; + length: number; + snap: boolean; + spacing: number; + }; + /** + * Move Options. + */ + type MoveOptions = { + drag: boolean; + scrollbars: (boolean | Options.ScrollbarOptions); + wheel: boolean; + }; + /** + * Scrollbar Options. + */ + type ScrollbarOptions = { + horizontal: boolean; + vertical: boolean; + }; + /** + * Zoom Options. + */ + type ZoomOptions = { + controls: boolean; + maxScale: number; + minScale: number; + pinch: boolean; + scaleSpeed: number; + startScale: number; + wheel: boolean; + }; } - import { Marker } from "keyboard_nav/marker"; - import { Cursor } from "keyboard_nav/cursor"; - import { WorkspaceSvg } from "workspace_svg"; + import * as toolbox from "core/utils/toolbox"; + import { Theme } from "core/theme"; + import { WorkspaceSvg } from "core/workspace_svg"; + import { Metrics } from "core/utils/metrics"; } -declare module "scrollbar_pair" { - export class ScrollbarPair { - /** - * Class for a pair of scrollbars. Horizontal and vertical. - * @param {!WorkspaceSvg} workspace Workspace to bind the scrollbars to. - * @param {boolean=} addHorizontal Whether to add a horizontal scrollbar. - * Defaults to true. - * @param {boolean=} addVertical Whether to add a vertical scrollbar. Defaults - * to true. - * @param {string=} opt_class A class to be applied to these scrollbars. - * @param {number=} opt_margin The margin to apply to these scrollbars. - * @constructor - * @alias Blockly.ScrollbarPair - */ - constructor(workspace: WorkspaceSvg, addHorizontal?: boolean | undefined, addVertical?: boolean | undefined, opt_class?: string | undefined, opt_margin?: number | undefined); - /** - * The workspace this scrollbar pair is bound to. - * @type {!WorkspaceSvg} - * @private - */ - private workspace_; - hScroll: Scrollbar; - vScroll: Scrollbar; - corner_: SVGRectElement; - /** - * Previously recorded metrics from the workspace. - * @type {?Metrics} - * @private - */ - private oldHostMetrics_; - /** - * Dispose of this pair of scrollbars. - * Unlink from all DOM elements to prevent memory leaks. - * @suppress {checkTypes} - */ - dispose(): void; - /** - * Recalculate both of the scrollbars' locations and lengths. - * Also reposition the corner rectangle. - */ - resize(): void; - /** - * Returns whether scrolling horizontally is enabled. - * @return {boolean} True if horizontal scroll is enabled. - */ - canScrollHorizontally(): boolean; - /** - * Returns whether scrolling vertically is enabled. - * @return {boolean} True if vertical scroll is enabled. - */ - canScrollVertically(): boolean; - /** - * Record the origin of the workspace that the scrollbar is in, in pixels - * relative to the injection div origin. This is for times when the scrollbar is - * used in an object whose origin isn't the same as the main workspace - * (e.g. in a flyout.) - * @param {number} x The x coordinate of the scrollbar's origin, in CSS pixels. - * @param {number} y The y coordinate of the scrollbar's origin, in CSS pixels. - * @package - */ - setOrigin(x: number, y: number): void; - /** - * Set the handles of both scrollbars. - * @param {number} x The horizontal content displacement, relative to the view - * in pixels. - * @param {number} y The vertical content displacement, relative to the view in - * pixels. - * @param {boolean} updateMetrics Whether to update metrics on this set call. - * Defaults to true. - */ - set(x: number, y: number, updateMetrics: boolean): void; - /** - * Set the handle of the horizontal scrollbar to be at a certain position in - * CSS pixels relative to its parents. - * @param {number} x Horizontal scroll value. - */ - setX(x: number): void; - /** - * Set the handle of the vertical scrollbar to be at a certain position in - * CSS pixels relative to its parents. - * @param {number} y Vertical scroll value. - */ - setY(y: number): void; - /** - * Set whether this scrollbar's container is visible. - * @param {boolean} visible Whether the container is visible. - */ - setContainerVisible(visible: boolean): void; - /** - * If any of the scrollbars are visible. Non-paired scrollbars may disappear - * when they aren't needed. - * @return {boolean} True if visible. - */ - isVisible(): boolean; - /** - * Recalculates the scrollbars' locations within their path and length. - * This should be called when the contents of the workspace have changed. - * @param {!Metrics} hostMetrics A data structure describing all - * the required dimensions, possibly fetched from the host object. - */ - resizeContent(hostMetrics: Metrics): void; - /** - * Recalculates the scrollbars' locations on the screen and path length. - * This should be called when the layout or size of the window has changed. - * @param {!Metrics} hostMetrics A data structure describing all - * the required dimensions, possibly fetched from the host object. - */ - resizeView(hostMetrics: Metrics): void; - } - import { Scrollbar } from "scrollbar"; - import { Metrics } from "utils/metrics"; - import { WorkspaceSvg } from "workspace_svg"; +declare module "core/scrollbar_pair" { + /** + * Class for a pair of scrollbars. Horizontal and vertical. + * @alias Blockly.ScrollbarPair + */ + export const ScrollbarPair: { + new (workspace: WorkspaceSvg, addHorizontal?: boolean | undefined, addVertical?: boolean | undefined, opt_class?: string | undefined, opt_margin?: number | undefined): { + /** + * The workspace this scrollbar pair is bound to. + * @type {!WorkspaceSvg} + * @private + */ + workspace_: WorkspaceSvg; + hScroll: Scrollbar | undefined; + vScroll: Scrollbar | undefined; + corner_: SVGRectElement | undefined; + /** + * Previously recorded metrics from the workspace. + * @type {?Metrics} + * @private + */ + oldHostMetrics_: Metrics | null; + /** + * Dispose of this pair of scrollbars. + * Unlink from all DOM elements to prevent memory leaks. + * @suppress {checkTypes} + */ + dispose(): void; + /** + * Recalculate both of the scrollbars' locations and lengths. + * Also reposition the corner rectangle. + */ + resize(): void; + /** + * Returns whether scrolling horizontally is enabled. + * @return {boolean} True if horizontal scroll is enabled. + */ + canScrollHorizontally(): boolean; + /** + * Returns whether scrolling vertically is enabled. + * @return {boolean} True if vertical scroll is enabled. + */ + canScrollVertically(): boolean; + /** + * Record the origin of the workspace that the scrollbar is in, in pixels + * relative to the injection div origin. This is for times when the scrollbar + * is used in an object whose origin isn't the same as the main workspace + * (e.g. in a flyout.) + * @param {number} x The x coordinate of the scrollbar's origin, in CSS + * pixels. + * @param {number} y The y coordinate of the scrollbar's origin, in CSS + * pixels. + * @package + */ + setOrigin(x: number, y: number): void; + /** + * Set the handles of both scrollbars. + * @param {number} x The horizontal content displacement, relative to the view + * in pixels. + * @param {number} y The vertical content displacement, relative to the view + * in + * pixels. + * @param {boolean} updateMetrics Whether to update metrics on this set call. + * Defaults to true. + */ + set(x: number, y: number, updateMetrics: boolean): void; + /** + * Set the handle of the horizontal scrollbar to be at a certain position in + * CSS pixels relative to its parents. + * @param {number} x Horizontal scroll value. + */ + setX(x: number): void; + /** + * Set the handle of the vertical scrollbar to be at a certain position in + * CSS pixels relative to its parents. + * @param {number} y Vertical scroll value. + */ + setY(y: number): void; + /** + * Set whether this scrollbar's container is visible. + * @param {boolean} visible Whether the container is visible. + */ + setContainerVisible(visible: boolean): void; + /** + * If any of the scrollbars are visible. Non-paired scrollbars may disappear + * when they aren't needed. + * @return {boolean} True if visible. + */ + isVisible(): boolean; + /** + * Recalculates the scrollbars' locations within their path and length. + * This should be called when the contents of the workspace have changed. + * @param {!Metrics} hostMetrics A data structure describing all + * the required dimensions, possibly fetched from the host object. + */ + resizeContent(hostMetrics: Metrics): void; + /** + * Recalculates the scrollbars' locations on the screen and path length. + * This should be called when the layout or size of the window has changed. + * @param {!Metrics} hostMetrics A data structure describing all + * the required dimensions, possibly fetched from the host object. + */ + resizeView(hostMetrics: Metrics): void; + }; + }; + import { WorkspaceSvg } from "core/workspace_svg"; + import { Scrollbar } from "core/scrollbar"; + import { Metrics } from "core/utils/metrics"; } -declare module "theme_manager" { +declare module "core/theme_manager" { + /** + * Class for storing and updating a workspace's theme and UI components. + * @alias Blockly.ThemeManager + */ export class ThemeManager { /** - * Class for storing and updating a workspace's theme and UI components. * @param {!WorkspaceSvg} workspace The main workspace. * @param {!Theme} theme The workspace theme. - * @constructor * @package - * @alias Blockly.ThemeManager */ constructor(workspace: WorkspaceSvg, theme: Theme); /** @@ -11682,22 +14613,17 @@ declare module "theme_manager" { propertyName: string; }; } - import { Theme } from "theme"; - import { Workspace } from "workspace"; - import { WorkspaceSvg } from "workspace_svg"; + import { Theme } from "core/theme"; + import { Workspace } from "core/workspace"; + import { WorkspaceSvg } from "core/workspace_svg"; } -declare module "touch_gesture" { - export class TouchGesture { - /** - * Class for one gesture. - * @param {!Event} e The event that kicked off this gesture. - * @param {!WorkspaceSvg} creatorWorkspace The workspace that created - * this gesture and has a reference to it. - * @extends {Gesture} - * @constructor - * @alias Blockly.TouchGesture - */ - constructor(e: Event, creatorWorkspace: WorkspaceSvg); +declare module "core/touch_gesture" { + /** + * Class for one gesture. + * @extends {Gesture} + * @alias Blockly.TouchGesture + */ + export class TouchGesture extends Gesture { /** * Boolean for whether or not this gesture is a multi-touch gesture. * @type {boolean} @@ -11739,54 +14665,18 @@ declare module "touch_gesture" { * @private */ private isPinchZoomEnabled_; - /** - * 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 - */ - doStart(e: Event): void; - /** - * 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 - */ - bindMouseEvents(e: Event): void; - onMoveWrapper_: any[][]; - onUpWrapper_: any[][]; /** * Handle a mouse down, touch start, or pointer down event. * @param {!Event} e A mouse down, touch start, or pointer down event. * @package */ handleStart(e: Event): void; - /** - * Handle a mouse move, touch move, or pointer move event. - * @param {!Event} e A mouse move, touch move, or pointer move event. - * @package - */ - handleMove(e: Event): void; - /** - * Handle a mouse up, touch end, or pointer up event. - * @param {!Event} e A mouse up, touch end, or pointer up event. - * @package - */ - handleUp(e: Event): void; /** * Whether this gesture is part of a multi-touch gesture. * @return {boolean} Whether this gesture is part of a multi-touch gesture. * @package */ isMultiTouch(): boolean; - /** - * Sever all links from this object. - * @package - */ - dispose(): void; /** * Handle a touch start or pointer down event and keep track of current * pointers. @@ -11819,16 +14709,17 @@ declare module "touch_gesture" { * @return {?Coordinate} The current touch point coordinate * @package */ - getTouchPoint(e: Event): Coordinate | null; + getTouchPoint(e: Event): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + } | null; } - export namespace TouchGesture { - const ZOOM_IN_MULTIPLIER: number; - const ZOOM_OUT_MULTIPLIER: number; - } - import { Coordinate } from "utils/coordinate"; - import { WorkspaceSvg } from "workspace_svg"; + import { Gesture } from "core/gesture"; } -declare module "positionable_helpers" { +declare module "core/positionable_helpers" { /** * * */ @@ -11887,7 +14778,17 @@ declare module "positionable_helpers" { * @alias Blockly.uiPosition.getStartPositionRect * @package */ - export function getStartPositionRect(position: Position, size: Size, horizontalPadding: number, verticalPadding: number, metrics: MetricsManager.UiMetrics, workspace: WorkspaceSvg): Rect; + export function getStartPositionRect(position: Position, size: { + width: number; + height: number; + }, horizontalPadding: number, verticalPadding: number, metrics: MetricsManager.UiMetrics, workspace: WorkspaceSvg): { + top: number; + bottom: number; + left: number; + right: number; + contains(x: number, y: number): boolean; + intersects(other: any): boolean; + }; /** * Returns a corner position that is on the opposite side of the workspace from * the toolbox. @@ -11914,18 +14815,63 @@ declare module "positionable_helpers" { * @alias Blockly.uiPosition.bumpPositionRect * @package */ - export function bumpPositionRect(startRect: Rect, margin: number, bumpDir: bumpDirection, savedPositions: Array): Rect; - import { Size } from "utils/size"; - import { MetricsManager } from "metrics_manager"; - import { WorkspaceSvg } from "workspace_svg"; - import { Rect } from "utils/rect"; + export function bumpPositionRect(startRect: { + top: number; + bottom: number; + left: number; + right: number; + contains(x: number, y: number): boolean; + intersects(other: any): boolean; + }, margin: number, bumpDir: bumpDirection, savedPositions: Array<{ + top: number; + bottom: number; + left: number; + right: number; + contains(x: number, y: number): boolean; + intersects(other: any): boolean; + }>): { + top: number; + bottom: number; + left: number; + right: number; + contains(x: number, y: number): boolean; + intersects(other: any): boolean; + }; + import { MetricsManager } from "core/metrics_manager"; + import { WorkspaceSvg } from "core/workspace_svg"; } -declare module "drag_target" { +declare module "core/events/events_block_delete" { + /** + * Class for a block deletion event. + * @extends {BlockBase} + * @alias Blockly.Events.BlockDelete + */ + export class BlockDelete extends BlockBase { + oldXml: Element | DocumentFragment | undefined; + ids: string[] | undefined; + /** + * Was the block that was just deleted a shadow? + * @type {boolean} + */ + wasShadow: boolean; + /** + * Play UI effects (sound and animation)? + * @type {boolean} + */ + ui: boolean; + /** + * JSON representation of the block that was just deleted. + * @type {!blocks.State} + */ + oldJson: any; + } + import { BlockBase } from "core/events/events_block_base"; +} +declare module "core/drag_target" { /** * Abstract class for a component with custom behaviour when a block or bubble * is dragged over or dropped on top of it. * @implements {IDragTarget} - * @constructor * @alias Blockly.DragTarget */ export class DragTarget implements IDragTarget { @@ -11934,48 +14880,60 @@ declare module "drag_target" { * @param {!IDraggable} _dragElement The block or bubble currently being * dragged. */ - onDragEnter(_dragElement: IDraggable): void; + onDragEnter(_dragElement: () => void): void; /** * Handles when a cursor with a block or bubble is dragged over this drag * target. * @param {!IDraggable} _dragElement The block or bubble currently being * dragged. */ - onDragOver(_dragElement: IDraggable): void; + onDragOver(_dragElement: () => void): void; /** * Handles when a cursor with a block or bubble exits this drag target. * @param {!IDraggable} _dragElement The block or bubble currently being * dragged. */ - onDragExit(_dragElement: IDraggable): void; + onDragExit(_dragElement: () => void): void; /** * Handles when a block or bubble is dropped on this component. * Should not handle delete here. * @param {!IDraggable} _dragElement The block or bubble currently being * dragged. */ - onDrop(_dragElement: IDraggable): void; + onDrop(_dragElement: () => void): void; /** - * Returns whether the provided block or bubble should not be moved after being - * dropped on this component. If true, the element will return to where it was - * when the drag started. + * Returns the bounding rectangle of the drag target area in pixel units + * relative to the Blockly injection div. + * @return {?Rect} The component's bounding box. Null if drag + * target area should be ignored. + */ + getClientRect(): { + top: number; + bottom: number; + left: number; + right: number; + contains(x: number, y: number): boolean; + intersects(other: any): boolean; + } | null; + /** + * Returns whether the provided block or bubble should not be moved after + * being dropped on this component. If true, the element will return to where + * it was when the drag started. * @param {!IDraggable} _dragElement The block or bubble currently being * dragged. - * @return {boolean} Whether the block or bubble provided should be returned to - * drag start. + * @return {boolean} Whether the block or bubble provided should be returned + * to drag start. */ - shouldPreventMove(_dragElement: IDraggable): boolean; + shouldPreventMove(_dragElement: () => void): boolean; } - import { IDragTarget } from "interfaces/i_drag_target"; - import { IDraggable } from "interfaces/i_draggable"; + import { IDragTarget } from "core/interfaces/i_drag_target"; } -declare module "delete_area" { +declare module "core/delete_area" { /** * Abstract class for a component that can delete a block or bubble that is * dropped on top of it. * @extends {DragTarget} * @implements {IDeleteArea} - * @constructor * @alias Blockly.DeleteArea */ export class DeleteArea extends DragTarget implements IDeleteArea { @@ -11996,10 +14954,10 @@ declare module "delete_area" { * dragged. * @param {boolean} couldConnect Whether the element could could connect to * another. - * @return {boolean} Whether the element provided would be deleted if dropped on - * this area. + * @return {boolean} Whether the element provided would be deleted if dropped + * on this area. */ - wouldDelete(element: IDraggable, couldConnect: boolean): boolean; + wouldDelete(element: () => void, couldConnect: boolean): boolean; /** * Updates the internal wouldDelete_ state. * @param {boolean} wouldDelete The new value for the wouldDelete state. @@ -12007,55 +14965,49 @@ declare module "delete_area" { */ protected updateWouldDelete_(wouldDelete: boolean): void; } - import { IDeleteArea } from "interfaces/i_delete_area"; - import { IDraggable } from "interfaces/i_draggable"; - import { DragTarget } from "drag_target"; + import { IDeleteArea } from "core/interfaces/i_delete_area"; + import { DragTarget } from "core/drag_target"; } -declare module "events/events_trashcan_open" { +declare module "core/sprites" { + export namespace SPRITE { + const width: number; + const height: number; + const url: string; + } +} +declare module "core/events/events_trashcan_open" { /** * Class for a trashcan open event. - * @param {boolean=} opt_isOpen Whether the trashcan flyout is opening (false if - * opening). Undefined for a blank event. - * @param {string=} opt_workspaceId The workspace identifier for this event. - * Undefined for a blank event. * @extends {UiBase} - * @constructor * @alias Blockly.Events.TrashcanOpen */ - export class TrashcanOpen { - constructor(opt_isOpen: any, opt_workspaceId: any); + export class TrashcanOpen extends UiBase { + /** + * @param {boolean=} opt_isOpen Whether the trashcan flyout is opening (false + * if opening). Undefined for a blank event. + * @param {string=} opt_workspaceId The workspace identifier for this event. + * Undefined for a blank event. + */ + constructor(opt_isOpen?: boolean | undefined, opt_workspaceId?: string | undefined); /** * Whether the trashcan flyout is opening (false if closing). * @type {boolean|undefined} */ isOpen: boolean | undefined; - /** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ - toJson(): any; - /** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ - fromJson(json: any): void; - /** - * Type of this event. - * @type {string} - */ - type: string; } + import { UiBase } from "core/events/events_ui_base"; } -declare module "trashcan" { +declare module "core/trashcan" { + /** + * Class for a trash can. + * @implements {IAutoHideable} + * @implements {IPositionable} + * @extends {DeleteArea} + * @alias Blockly.Trashcan + */ export class Trashcan extends DeleteArea implements IAutoHideable, IPositionable { /** - * Class for a trash can. * @param {!WorkspaceSvg} workspace The workspace to sit in. - * @constructor - * @implements {IAutoHideable} - * @implements {IPositionable} - * @extends {DeleteArea} - * @alias Blockly.Trashcan */ constructor(workspace: WorkspaceSvg); /** @@ -12082,18 +15034,69 @@ declare module "trashcan" { * @package */ flyout: IFlyout; + /** + * Current open/close state of the lid. + * @type {boolean} + */ + isLidOpen: boolean; + /** + * The minimum openness of the lid. Used to indicate if the trashcan + * contains blocks. + * @type {number} + * @private + */ + private minOpenness_; + /** + * The SVG group containing the trash can. + * @type {SVGElement} + * @private + */ + private svgGroup_; + /** + * The SVG image element of the trash can lid. + * @type {SVGElement} + * @private + */ + private svgLid_; + /** + * Task ID of opening/closing animation. + * @type {number} + * @private + */ + private lidTask_; + /** + * Current state of lid opening (0.0 = closed, 1.0 = open). + * @type {number} + * @private + */ + private lidOpen_; + /** + * Left coordinate of the trash can. + * @type {number} + * @private + */ + private left_; + /** + * Top coordinate of the trash can. + * @type {number} + * @private + */ + private top_; + /** + * Whether this trash can has been initialized. + * @type {boolean} + * @private + */ + private initialized_; /** * Create the trash can elements. * @return {!SVGElement} The trash can's SVG group. */ createDom(): SVGElement; - svgGroup_: SVGElement; - svgLid_: SVGElement; /** * Initializes the trash can. */ init(): void; - initialized_: boolean; /** * Dispose of this trash can. * Unlink from all DOM elements to prevent memory leaks. @@ -12138,60 +15141,39 @@ declare module "trashcan" { * @param {!Array} savedPositions List of rectangles that * are already on the workspace. */ - position(metrics: MetricsManager.UiMetrics, savedPositions: Array): void; - top_: number; - left_: number; + position(metrics: MetricsManager.UiMetrics, savedPositions: Array<{ + top: number; + bottom: number; + left: number; + right: number; + contains(x: number, y: number): boolean; + intersects(other: any): boolean; + }>): void; /** * Returns the bounding rectangle of the UI element in pixel units relative to * the Blockly injection div. - * @return {?Rect} The UI elements’s bounding box. Null if + * @return {?Rect} The UI elements's bounding box. Null if * bounding box should be ignored by other UI elements. */ - getBoundingRectangle(): Rect | null; - /** - * Returns the bounding rectangle of the drag target area in pixel units - * relative to viewport. - * @return {?Rect} The component's bounding box. Null if drag - * target area should be ignored. - */ - getClientRect(): Rect | null; - /** - * Handles when a cursor with a block or bubble is dragged over this drag - * target. - * @param {!IDraggable} _dragElement The block or bubble currently being - * dragged. - * @override - */ - override onDragOver(_dragElement: IDraggable): void; - /** - * Handles when a cursor with a block or bubble exits this drag target. - * @param {!IDraggable} _dragElement The block or bubble currently being - * dragged. - * @override - */ - override onDragExit(_dragElement: IDraggable): void; - /** - * Handles when a block or bubble is dropped on this component. - * Should not handle delete here. - * @param {!IDraggable} _dragElement The block or bubble currently being - * dragged. - * @override - */ - override onDrop(_dragElement: IDraggable): void; + getBoundingRectangle(): { + top: number; + bottom: number; + left: number; + right: number; + contains(x: number, y: number): boolean; + intersects(other: any): boolean; + } | null; /** * Flip the lid open or shut. * @param {boolean} state True if open. * @package */ setLidOpen(state: boolean): void; - isLidOpen: any; /** * Rotate the lid open or closed by one step. Then wait and recurse. * @private */ private animateLid_; - lidOpen_: number; - lidTask_: number; /** * Set the angle of the trashcan's lid. * @param {number} lidAngle The angle at which to set the lid. @@ -12201,12 +15183,11 @@ declare module "trashcan" { /** * Sets the minimum openness of the trashcan lid. If the lid is currently * closed, this will update lid's position. - * @param {number} newMin The new minimum openness of the lid. Should be between - * 0 and 1. + * @param {number} newMin The new minimum openness of the lid. Should be + * between 0 and 1. * @private */ private setMinOpenness_; - minOpenness_: number; /** * Flip the lid shut. * Called externally after a drag. @@ -12240,7 +15221,8 @@ declare module "trashcan" { */ private mouseOut_; /** - * Handle a BLOCK_DELETE event. Adds deleted blocks oldXml to the content array. + * Handle a BLOCK_DELETE event. Adds deleted blocks oldXml to the content + * array. * @param {!Abstract} event Workspace event. * @private */ @@ -12256,23 +15238,22 @@ declare module "trashcan" { */ private cleanBlockJson_; } - import { IFlyout } from "interfaces/i_flyout"; - import { MetricsManager } from "metrics_manager"; - import { Rect } from "utils/rect"; - import { IDraggable } from "interfaces/i_draggable"; - import { WorkspaceSvg } from "workspace_svg"; - import { DeleteArea } from "delete_area"; - import { IAutoHideable } from "interfaces/i_autohideable"; - import { IPositionable } from "interfaces/i_positionable"; + import { IAutoHideable } from "core/interfaces/i_autohideable"; + import { IPositionable } from "core/interfaces/i_positionable"; + import { DeleteArea } from "core/delete_area"; + import { IFlyout } from "core/interfaces/i_flyout"; + import { MetricsManager } from "core/metrics_manager"; + import { WorkspaceSvg } from "core/workspace_svg"; } -declare module "workspace_audio" { +declare module "core/workspace_audio" { + /** + * Class for loading, storing, and playing audio for a workspace. + * @alias Blockly.WorkspaceAudio + */ export class WorkspaceAudio { /** - * Class for loading, storing, and playing audio for a workspace. * @param {WorkspaceSvg} parentWorkspace The parent of the workspace * this audio object belongs to, or null. - * @constructor - * @alias Blockly.WorkspaceAudio */ constructor(parentWorkspace: WorkspaceSvg); /** @@ -12287,6 +15268,12 @@ declare module "workspace_audio" { * @private */ private SOUNDS_; + /** + * Time that the last sound was played. + * @type {Date} + * @private + */ + private lastSound_; /** * Dispose of this audio manager. * @package @@ -12312,455 +15299,44 @@ declare module "workspace_audio" { * @param {number=} opt_volume Volume of sound (0-1). */ play(name: string, opt_volume?: number | undefined): void; - lastSound_: Date; } - import { WorkspaceSvg } from "workspace_svg"; + import { WorkspaceSvg } from "core/workspace_svg"; } -declare module "events/events_selected" { +declare module "core/workspace_drag_surface_svg" { /** - * Class for a selected event. - * @param {?string=} opt_oldElementId The ID of the previously selected - * element. Null if no element last selected. Undefined for a blank event. - * @param {?string=} opt_newElementId The ID of the selected element. Null if no - * element currently selected (deselect). Undefined for a blank event. - * @param {string=} opt_workspaceId The workspace identifier for this event. - * Null if no element previously selected. Undefined for a blank event. - * @extends {UiBase} - * @constructor - * @alias Blockly.Events.Selected + * Blocks are moved into this SVG during a drag, improving performance. + * The entire SVG is translated using CSS transforms instead of SVG so the + * blocks are never repainted during drag improving performance. + * @alias Blockly.WorkspaceDragSurfaceSvg */ - export class Selected { - constructor(opt_oldElementId: any, opt_newElementId: any, opt_workspaceId: any); - /** - * The id of the last selected element. - * @type {?string|undefined} - */ - oldElementId: (string | undefined) | null; - /** - * The id of the selected element. - * @type {?string|undefined} - */ - newElementId: (string | undefined) | null; - /** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ - toJson(): any; - /** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ - fromJson(json: any): void; - /** - * Type of this event. - * @type {string} - */ - type: string; - } -} -declare module "workspace_comment_svg" { - export class WorkspaceCommentSvg { - /** - * Decode an XML comment tag and create a rendered comment on the workspace. - * @param {!Element} xmlComment XML comment element. - * @param {!WorkspaceSvg} workspace The workspace. - * @param {number=} opt_wsWidth The width of the workspace, which is used to - * position comments correctly in RTL. - * @return {!WorkspaceCommentSvg} The created workspace comment. - * @package - */ - static fromXml(xmlComment: Element, workspace: WorkspaceSvg, opt_wsWidth?: number | undefined): WorkspaceCommentSvg; - /** - * Class for a workspace comment's SVG representation. - * @param {!WorkspaceSvg} workspace The block's workspace. - * @param {string} content The content of this workspace comment. - * @param {number} height Height of the comment. - * @param {number} width Width of the comment. - * @param {string=} opt_id Optional ID. Use this ID if provided, otherwise - * create a new ID. - * @extends {WorkspaceComment} - * @implements {IBoundedElement} - * @implements {IBubble} - * @implements {ICopyable} - * @constructor - * @alias Blockly.WorkspaceCommentSvg - */ - constructor(workspace: WorkspaceSvg, content: string, height: number, width: number, opt_id?: string | undefined); - /** - * @type {!WorkspaceSvg} - */ - workspace: WorkspaceSvg; - /** - * Mouse up event data. - * @type {?browserEvents.Data} - * @private - */ - private onMouseUpWrapper_; - /** - * Mouse move event data. - * @type {?browserEvents.Data} - * @private - */ - private onMouseMoveWrapper_; - /** - * @type {!SVGElement} - * @private - */ - private svgGroup_; - svgRect_: SVGRectElement; - /** - * Whether the comment is rendered onscreen and is a part of the DOM. - * @type {boolean} - * @private - */ - private rendered_; - /** - * Whether to move the comment to the drag surface when it is dragged. - * True if it should move, false if it should be translated directly. - * @type {boolean} - * @private - */ - private useDragSurface_; - /** - * Dispose of this comment. - * @package - */ - dispose(): void; - /** - * Create and initialize the SVG representation of a workspace comment. - * May be called more than once. - * - * @param {boolean=} opt_noSelect Text inside text area will be selected if - * false - * - * @package - */ - initSvg(opt_noSelect?: boolean | undefined): void; - eventsInit_: boolean; - /** - * Handle a mouse-down on an SVG comment. - * @param {!Event} e Mouse down event or touch start event. - * @private - */ - private pathMouseDown_; - /** - * Show the context menu for this workspace comment. - * @param {!Event} e Mouse event. - * @package - */ - showContextMenu(e: Event): void; - /** - * Select this comment. Highlight it visually. - * @package - */ - select(): void; - /** - * Unselect this comment. Remove its highlighting. - * @package - */ - unselect(): void; - /** - * Select this comment. Highlight it visually. - * @package - */ - addSelect(): void; - /** - * Unselect this comment. Remove its highlighting. - * @package - */ - removeSelect(): void; - /** - * Focus this comment. Highlight it visually. - * @package - */ - addFocus(): void; - /** - * Unfocus this comment. Remove its highlighting. - * @package - */ - removeFocus(): void; - /** - * Return the coordinates of the top-left corner of this comment relative to - * the drawing surface's origin (0,0), in workspace units. - * If the comment is on the workspace, (0, 0) is the origin of the workspace - * coordinate system. - * This does not change with workspace scale. - * @return {!Coordinate} Object with .x and .y properties in - * workspace coordinates. - * @package - */ - getRelativeToSurfaceXY(): Coordinate; - xy_: Coordinate; - /** - * Move a comment by a relative offset. - * @param {number} dx Horizontal offset, in workspace units. - * @param {number} dy Vertical offset, in workspace units. - * @package - */ - moveBy(dx: number, dy: number): void; - /** - * Transforms a comment by setting the translation on the transform attribute - * of the block's SVG. - * @param {number} x The x coordinate of the translation in workspace units. - * @param {number} y The y coordinate of the translation in workspace units. - * @package - */ - translate(x: number, y: number): void; - /** - * Move this comment to its workspace's drag surface, accounting for - * positioning. Generally should be called at the same time as - * setDragging(true). Does nothing if useDragSurface_ is false. - * @package - */ - moveToDragSurface(): void; - /** - * Move this comment during a drag, taking into account whether we are using a - * drag surface to translate blocks. - * @param {BlockDragSurfaceSvg} dragSurface The surface that carries - * rendered items during a drag, or null if no drag surface is in use. - * @param {!Coordinate} newLoc The location to translate to, in - * workspace coordinates. - * @package - */ - moveDuringDrag(dragSurface: BlockDragSurfaceSvg, newLoc: Coordinate): void; - /** - * 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 - */ - moveTo(x: number, y: number): void; - /** - * Clear the comment of transform="..." attributes. - * Used when the comment is switching from 3d to 2d transform or vice versa. - * @private - */ - private clearTransformAttributes_; - /** - * Returns the coordinates of a bounding box describing the dimensions of this - * comment. - * Coordinate system: workspace coordinates. - * @return {!Rect} Object with coordinates of the bounding box. - * @package - */ - getBoundingRectangle(): Rect; - /** - * Add or remove the UI indicating if this comment is movable or not. - * @package - */ - updateMovable(): void; - /** - * Set whether this comment is movable or not. - * @param {boolean} movable True if movable. - * @package - */ - setMovable(movable: boolean): void; - /** - * Set whether this comment is editable or not. - * @param {boolean} editable True if editable. - */ - setEditable(editable: boolean): void; - /** - * Recursively adds or removes the dragging class to this node and its children. - * @param {boolean} adding True if adding, false if removing. - * @package - */ - setDragging(adding: boolean): void; - /** - * Return the root node of the SVG or null if none exists. - * @return {!SVGElement} The root SVG node (probably a group). - * @package - */ - getSvgRoot(): SVGElement; - /** - * Returns this comment's text. - * @return {string} Comment text. - * @package - */ - getContent(): string; - /** - * Set this comment's content. - * @param {string} content Comment content. - * @package - */ - setContent(content: string): void; - /** - * Update the cursor over this comment by adding or removing a class. - * @param {boolean} enable True if the delete cursor should be shown, false - * otherwise. - * @package - */ - setDeleteStyle(enable: boolean): void; - /** - * 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 - */ - setAutoLayout(_enable: boolean): void; - /** - * Encode a comment subtree as XML with XY coordinates. - * @param {boolean=} opt_noId True if the encoder should skip the comment ID. - * @return {!Element} Tree of XML elements. - * @package - */ - toXmlWithXY(opt_noId?: boolean | undefined): Element; - /** - * Encode a comment for copying. - * @return {!ICopyable.CopyData} Copy metadata. - * @package - */ - toCopyData(): ICopyable.CopyData; - /** - * Returns a bounding box describing the dimensions of this comment. - * @return {!{height: number, width: number}} Object with height and width - * properties in workspace units. - * @package - */ - getHeightWidth(): { - height: number; - width: number; - }; - /** - * Renders the workspace comment. - * @package - */ - render(): void; - svgHandleTarget_: SVGRectElement; - svgRectTarget_: SVGRectElement; - /** - * Create the text area for the comment. - * @return {!Element} The top-level node of the editor. - * @private - */ - private createEditor_; - foreignObject_: SVGForeignObjectElement; - textarea_: HTMLElement; - /** - * Add the resize icon to the DOM - * @private - */ - private addResizeDom_; - resizeGroup_: SVGGElement; - /** - * Add the delete icon to the DOM - * @private - */ - private addDeleteDom_; - deleteGroup_: SVGGElement; - deleteIconBorder_: SVGCircleElement; - /** - * Handle a mouse-down on comment's resize corner. - * @param {!Event} e Mouse down event. - * @private - */ - private resizeMouseDown_; - /** - * Handle a mouse-down on comment's delete icon. - * @param {!Event} e Mouse down event. - * @private - */ - private deleteMouseDown_; - /** - * Handle a mouse-out on comment's delete icon. - * @param {!Event} _e Mouse out event. - * @private - */ - private deleteMouseOut_; - /** - * Handle a mouse-up on comment's delete icon. - * @param {!Event} e Mouse up event. - * @private - */ - private deleteMouseUp_; - /** - * Stop binding to the global mouseup and mousemove events. - * @private - */ - private unbindDragEvents_; - /** - * Handle a mouse-up event while dragging a comment's border or resize handle. - * @param {!Event} _e Mouse up event. - * @private - */ - private resizeMouseUp_; - /** - * Resize this comment to follow the mouse. - * @param {!Event} e Mouse move event. - * @private - */ - private resizeMouseMove_; - autoLayout_: boolean; - /** - * Callback function triggered when the comment has resized. - * Resize the text area accordingly. - * @private - */ - private resizeComment_; - /** - * Set size - * @param {number} width width of the container - * @param {number} height height of the container - * @private - */ - private setSize_; - width_: number; - height_: number; - /** - * Dispose of any rendered comment components. - * @private - */ - private disposeInternal_; - disposed_: boolean; - /** - * Set the focus on the text area. - * @package - */ - setFocus(): void; - focused_: boolean; - /** - * Remove focus from the text area. - * @package - */ - blurFocus(): void; - } - export namespace WorkspaceCommentSvg { - const DEFAULT_SIZE: number; - const TOP_OFFSET: number; - } - import { WorkspaceSvg } from "workspace_svg"; - import { Coordinate } from "utils/coordinate"; - import { BlockDragSurfaceSvg } from "block_drag_surface"; - import { Rect } from "utils/rect"; - import { ICopyable } from "interfaces/i_copyable"; -} -declare module "workspace_drag_surface_svg" { export class WorkspaceDragSurfaceSvg { /** - * Blocks are moved into this SVG during a drag, improving performance. - * The entire SVG is translated using CSS transforms instead of SVG so the - * blocks are never repainted during drag improving performance. * @param {!Element} container Containing element. - * @constructor - * @alias Blockly.WorkspaceDragSurfaceSvg */ constructor(container: Element); - container_: Element; + /** + * The SVG drag surface. Set once by WorkspaceDragSurfaceSvg.createDom. + * @type {SVGElement} + * @private + */ + private SVG_; + /** + * Containing HTML element; parent of the workspace and the drag surface. + * @type {Element} + * @private + */ + private container_; + /** + * The element to insert the block canvas and bubble canvas after when it + * goes back in the DOM at the end of a drag. + * @type {Element} + * @private + */ + private previousSibling_; /** * Create the drag surface and inject it into the container. */ createDom(): void; - /** - * Dom structure when the workspace is being dragged. If there is no drag in - * progress, the SVG is empty and display: none. - * - * - * /g> - * - */ - SVG_: SVGElement; /** * Translate the entire drag surface during a drag. * We translate the drag surface instead of the blocks inside the surface @@ -12777,7 +15353,13 @@ declare module "workspace_drag_surface_svg" { * @return {!Coordinate} Current translation of the surface * @package */ - getSurfaceTranslation(): Coordinate; + getSurfaceTranslation(): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; /** * Move the blockCanvas and bubbleCanvas out of the surface SVG and on to * newSurface. @@ -12786,13 +15368,13 @@ declare module "workspace_drag_surface_svg" { * @package */ clearAndHide(newSurface: SVGElement): void; - previousSibling_: Element; /** * Set the SVG to have the block canvas and bubble canvas in it and then * show the surface. * @param {!SVGElement} blockCanvas The block canvas element from the * workspace. - * @param {!SVGElement} bubbleCanvas The element that contains the bubbles. + * @param {!SVGElement} bubbleCanvas The element that contains the + bubbles. * @param {Element} previousSibling The element to insert the block canvas and bubble canvas after when it goes back in the DOM at the end of a drag. * @param {number} width The width of the workspace SVG element. @@ -12802,55 +15384,16 @@ declare module "workspace_drag_surface_svg" { */ setContentsAndShow(blockCanvas: SVGElement, bubbleCanvas: SVGElement, previousSibling: Element, width: number, height: number, scale: number): void; } - import { Coordinate } from "utils/coordinate"; } -declare module "events/events_click" { +declare module "core/zoom_controls" { /** - * Class for a click event. - * @param {?Block=} opt_block The affected block. Null for click events - * that do not have an associated block (i.e. workspace click). Undefined - * for a blank event. - * @param {?string=} opt_workspaceId The workspace identifier for this event. - * Not used if block is passed. Undefined for a blank event. - * @param {string=} opt_targetType The type of element targeted by this click - * event. Undefined for a blank event. - * @extends {UiBase} - * @constructor - * @alias Blockly.Events.Click + * Class for a zoom controls. + * @implements {IPositionable} + * @alias Blockly.ZoomControls */ - export class Click { - constructor(opt_block: any, opt_workspaceId: any, opt_targetType: any); - blockId: any; + export class ZoomControls implements IPositionable { /** - * The type of element targeted by this click event. - * @type {string|undefined} - */ - targetType: string | undefined; - /** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ - toJson(): any; - /** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ - fromJson(json: any): void; - /** - * Type of this event. - * @type {string} - */ - type: string; - } -} -declare module "zoom_controls" { - export class ZoomControls { - /** - * Class for a zoom controls. * @param {!WorkspaceSvg} workspace The workspace to sit in. - * @constructor - * @implements {IPositionable} - * @alias Blockly.ZoomControls */ constructor(workspace: WorkspaceSvg); /** @@ -12872,15 +15415,15 @@ declare module "zoom_controls" { */ private onZoomResetWrapper_; /** - * A handle to use to unbind the mouse down event handler for zoom in button. - * Opaque data returned from browserEvents.conditionalBind. + * A handle to use to unbind the mouse down event handler for zoom in + * button. Opaque data returned from browserEvents.conditionalBind. * @type {?browserEvents.Data} * @private */ private onZoomInWrapper_; /** - * A handle to use to unbind the mouse down event handler for zoom out button. - * Opaque data returned from browserEvents.conditionalBind. + * A handle to use to unbind the mouse down event handler for zoom out + * button. Opaque data returned from browserEvents.conditionalBind. * @type {?browserEvents.Data} * @private */ @@ -12903,17 +15446,80 @@ declare module "zoom_controls" { * @private */ private zoomResetGroup_; + /** + * Width of the zoom controls. + * @type {number} + * @const + * @private + */ + private WIDTH_; + /** + * Height of each zoom control. + * @type {number} + * @const + * @private + */ + private HEIGHT_; + /** + * Small spacing used between the zoom in and out control, in pixels. + * @type {number} + * @const + * @private + */ + private SMALL_SPACING_; + /** + * Large spacing used between the zoom in and reset control, in pixels. + * @type {number} + * @const + * @private + */ + private LARGE_SPACING_; + /** + * Distance between zoom controls and bottom or top edge of workspace. + * @type {number} + * @const + * @private + */ + private MARGIN_VERTICAL_; + /** + * Distance between zoom controls and right or left edge of workspace. + * @type {number} + * @private + */ + private MARGIN_HORIZONTAL_; + /** + * The SVG group containing the zoom controls. + * @type {SVGElement} + * @private + */ + private svgGroup_; + /** + * Left coordinate of the zoom controls. + * @type {number} + * @private + */ + private left_; + /** + * Top coordinate of the zoom controls. + * @type {number} + * @private + */ + private top_; + /** + * Whether this has been initialized. + * @type {boolean} + * @private + */ + private initialized_; /** * Create the zoom controls. * @return {!SVGElement} The zoom controls SVG group. */ createDom(): SVGElement; - svgGroup_: SVGElement; /** * Initializes the zoom controls. */ init(): void; - initialized_: boolean; /** * Disposes of this zoom controls. * Unlink from all DOM elements to prevent memory leaks. @@ -12922,10 +15528,17 @@ declare module "zoom_controls" { /** * Returns the bounding rectangle of the UI element in pixel units relative to * the Blockly injection div. - * @return {?Rect} The UI elements’s bounding box. Null if + * @return {?Rect} The UI elements's bounding box. Null if * bounding box should be ignored by other UI elements. */ - getBoundingRectangle(): Rect | null; + getBoundingRectangle(): { + top: number; + bottom: number; + left: number; + right: number; + contains(x: number, y: number): boolean; + intersects(other: any): boolean; + } | null; /** * Positions the zoom controls. * It is positioned in the opposite corner to the corner the @@ -12934,9 +15547,14 @@ declare module "zoom_controls" { * @param {!Array} savedPositions List of rectangles that * are already on the workspace. */ - position(metrics: MetricsManager.UiMetrics, savedPositions: Array): void; - top_: number; - left_: number; + position(metrics: MetricsManager.UiMetrics, savedPositions: Array<{ + top: number; + bottom: number; + left: number; + right: number; + contains(x: number, y: number): boolean; + intersects(other: any): boolean; + }>): void; /** * Create the zoom in icon and its event handler. * @param {string} rnd The random string to use as a suffix in the clip path's @@ -12981,187 +15599,55 @@ declare module "zoom_controls" { * @private */ private fireZoomEvent_; - /** - * Width of the zoom controls. - * @type {number} - * @const - * @private - */ - private WIDTH_; - /** - * Height of each zoom control. - * @type {number} - * @const - * @private - */ - private HEIGHT_; - /** - * Small spacing used between the zoom in and out control, in pixels. - * @type {number} - * @const - * @private - */ - private SMALL_SPACING_; - /** - * Large spacing used between the zoom in and reset control, in pixels. - * @type {number} - * @const - * @private - */ - private LARGE_SPACING_; - /** - * Distance between zoom controls and bottom or top edge of workspace. - * @type {number} - * @const - * @private - */ - private MARGIN_VERTICAL_; - /** - * Distance between zoom controls and right or left edge of workspace. - * @type {number} - * @private - */ - private MARGIN_HORIZONTAL_; } - import { Rect } from "utils/rect"; - import { MetricsManager } from "metrics_manager"; - import { WorkspaceSvg } from "workspace_svg"; + import { IPositionable } from "core/interfaces/i_positionable"; + import { MetricsManager } from "core/metrics_manager"; + import { WorkspaceSvg } from "core/workspace_svg"; } -declare module "events/events_block_create" { - export class BlockCreate { +declare module "core/events/events_block_create" { + /** + * Class for a block creation event. + * @extends {BlockBase} + * @alias Blockly.Events.BlockCreate + */ + export class BlockCreate extends BlockBase { + xml: Element | DocumentFragment | undefined; + ids: string[] | undefined; /** - * Class for a block creation event. - * @param {!Block=} opt_block The created block. Undefined for a blank - * event. - * @extends {BlockBase} - * @constructor - * @alias Blockly.Events.BlockCreate + * Play UI effects (sound and animation)? + * @type {boolean} */ - constructor(opt_block?: Block | undefined); - recordUndo: boolean; - xml: Element | DocumentFragment; - ids: string[]; + ui: boolean; /** * JSON representation of the block that was just created. * @type {!blocks.State} */ json: any; - /** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ - toJson(): any; - /** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ - fromJson(json: any): void; - /** - * Run a creation event. - * @param {boolean} forward True if run forward, false if run backward (undo). - */ - run(forward: boolean): void; - /** - * Type of this event. - * @type {string} - */ - type: string; } - import { Block } from "block"; + import { BlockBase } from "core/events/events_block_base"; } -declare module "events/events_theme_change" { +declare module "core/events/events_theme_change" { /** * Class for a theme change event. - * @param {string=} opt_themeName The theme name. Undefined for a blank event. - * @param {string=} opt_workspaceId The workspace identifier for this event. - * event. Undefined for a blank event. * @extends {UiBase} - * @constructor * @alias Blockly.Events.ThemeChange */ - export class ThemeChange { - constructor(opt_themeName: any, opt_workspaceId: any); + export class ThemeChange extends UiBase { + /** + * @param {string=} opt_themeName The theme name. Undefined for a blank event. + * @param {string=} opt_workspaceId The workspace identifier for this event. + * event. Undefined for a blank event. + */ + constructor(opt_themeName?: string | undefined, opt_workspaceId?: string | undefined); /** * The theme name. * @type {string|undefined} */ themeName: string | undefined; - /** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ - toJson(): any; - /** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ - fromJson(json: any): void; - /** - * Type of this event. - * @type {string} - */ - type: string; } + import { UiBase } from "core/events/events_ui_base"; } -declare module "events/events_viewport" { - /** - * Class for a viewport change event. - * @param {number=} opt_top Top-edge of the visible portion of the workspace, - * relative to the workspace origin. Undefined for a blank event. - * @param {number=} opt_left Left-edge of the visible portion of the workspace, - * relative to the workspace origin. Undefined for a blank event. - * @param {number=} opt_scale The scale of the workspace. Undefined for a blank - * event. - * @param {string=} opt_workspaceId The workspace identifier for this event. - * Undefined for a blank event. - * @param {number=} opt_oldScale The old scale of the workspace. Undefined for a - * blank event. - * @extends {UiBase} - * @constructor - * @alias Blockly.Events.ViewportChange - */ - export class ViewportChange { - constructor(opt_top: any, opt_left: any, opt_scale: any, opt_workspaceId: any, opt_oldScale: any); - /** - * Top-edge of the visible portion of the workspace, relative to the workspace - * origin. - * @type {number|undefined} - */ - viewTop: number | undefined; - /** - * Left-edge of the visible portion of the workspace, relative to the - * workspace origin. - * @type {number|undefined} - */ - viewLeft: number | undefined; - /** - * The scale of the workspace. - * @type {number|undefined} - */ - scale: number | undefined; - /** - * The old scale of the workspace. - * @type {number|undefined} - */ - oldScale: number | undefined; - /** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ - toJson(): any; - /** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ - fromJson(json: any): void; - /** - * Type of this event. - * @type {string} - */ - type: string; - } -} -declare module "workspace_svg" { +declare module "core/workspace_svg" { /** * Size the workspace when the contents change. This also updates * scrollbars accordingly. @@ -13169,7 +15655,14 @@ declare module "workspace_svg" { * @alias Blockly.WorkspaceSvg.resizeSvgContents */ export function resizeSvgContents(workspace: WorkspaceSvg): void; - export class WorkspaceSvg extends Workspace { + /** + * Class for a workspace. This is an onscreen area with optional trashcan, + * scrollbars, bubbles, and dragging. + * @extends {Workspace} + * @implements {IASTNodeLocationSvg} + * @alias Blockly.WorkspaceSvg + */ + export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { /** * Sets the X/Y translations of a top level workspace. * @param {!Object} xyRatio Contains an x and/or y property which is a float @@ -13179,19 +15672,296 @@ declare module "workspace_svg" { */ private static setTopLevelWorkspaceMetrics_; /** - * Class for a workspace. This is an onscreen area with optional trashcan, - * scrollbars, bubbles, and dragging. * @param {!Options} options Dictionary of options. * @param {BlockDragSurfaceSvg=} opt_blockDragSurface Drag surface for * blocks. * @param {WorkspaceDragSurfaceSvg=} opt_wsDragSurface Drag surface for * the workspace. - * @extends {Workspace} - * @implements {IASTNodeLocationSvg} - * @constructor - * @alias Blockly.WorkspaceSvg */ - constructor(options: Options, opt_blockDragSurface?: BlockDragSurfaceSvg | undefined, opt_wsDragSurface?: WorkspaceDragSurfaceSvg | undefined); + constructor(options: Options, opt_blockDragSurface?: { + SVG_: SVGElement | null; + dragGroup_: SVGElement | null; + container_: Element; + scale_: number; + surfaceXY_: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + } | null; + childSurfaceXY_: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + createDom(): void; + setBlocksAndShow(blocks: SVGElement): void; + translateAndScaleGroup(x: number, y: number, scale: number): void; + translateSurfaceInternal_(): void; + translateBy(deltaX: number, deltaY: number): void; + translateSurface(x: number, y: number): void; + getSurfaceTranslation(): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + getGroup(): SVGElement | null; + getSvgRoot(): SVGElement | null; + getCurrentBlock(): Element | null; + getWsTranslation(): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + clearAndHide(opt_newSurface?: Element | undefined): void; + } | undefined, opt_wsDragSurface?: WorkspaceDragSurfaceSvg | undefined); + /** + * A wrapper function called when a resize event occurs. + * You can pass the result to `eventHandling.unbind`. + * @type {?browserEvents.Data} + * @private + */ + private resizeHandlerWrapper_; + /** + * Whether the workspace is visible. False if the workspace has been hidden + * by calling `setVisible(false)`. + * @type {boolean} + * @private + */ + private isVisible_; + /** + * Whether this workspace has resizes enabled. + * Disable during batch operations for a performance improvement. + * @type {boolean} + * @private + */ + private resizesEnabled_; + /** + * Current horizontal scrolling offset in pixel units, relative to the + * workspace origin. + * + * It is useful to think about a view, and a canvas moving beneath that + * view. As the canvas moves right, this value becomes more positive, and + * the view is now "seeing" the left side of the canvas. As the canvas moves + * left, this value becomes more negative, and the view is now "seeing" the + * right side of the canvas. + * + * The confusing thing about this value is that it does not, and must not + * include the absoluteLeft offset. This is because it is used to calculate + * the viewLeft value. + * + * The viewLeft is relative to the workspace origin (although in pixel + * units). The workspace origin is the top-left corner of the workspace (at + * least when it is enabled). It is shifted from the top-left of the + * blocklyDiv so as not to be beneath the toolbox. + * + * When the workspace is enabled the viewLeft and workspace origin are at + * the same X location. As the canvas slides towards the right beneath the + * view this value (scrollX) becomes more positive, and the viewLeft becomes + * more negative relative to the workspace origin (imagine the workspace + * origin as a dot on the canvas sliding to the right as the canvas moves). + * + * So if the scrollX were to include the absoluteLeft this would in a way + * "unshift" the workspace origin. This means that the viewLeft would be + * representing the left edge of the blocklyDiv, rather than the left edge + * of the workspace. + * + * @type {number} + */ + scrollX: number; + /** + * Current vertical scrolling offset in pixel units, relative to the + * workspace origin. + * + * It is useful to think about a view, and a canvas moving beneath that + * view. As the canvas moves down, this value becomes more positive, and the + * view is now "seeing" the upper part of the canvas. As the canvas moves + * up, this value becomes more negative, and the view is "seeing" the lower + * part of the canvas. + * + * This confusing thing about this value is that it does not, and must not + * include the absoluteTop offset. This is because it is used to calculate + * the viewTop value. + * + * The viewTop is relative to the workspace origin (although in pixel + * units). The workspace origin is the top-left corner of the workspace (at + * least when it is enabled). It is shifted from the top-left of the + * blocklyDiv so as not to be beneath the toolbox. + * + * When the workspace is enabled the viewTop and workspace origin are at the + * same Y location. As the canvas slides towards the bottom this value + * (scrollY) becomes more positive, and the viewTop becomes more negative + * relative to the workspace origin (image in the workspace origin as a dot + * on the canvas sliding downwards as the canvas moves). + * + * So if the scrollY were to include the absoluteTop this would in a way + * "unshift" the workspace origin. This means that the viewTop would be + * representing the top edge of the blocklyDiv, rather than the top edge of + * the workspace. + * + * @type {number} + */ + scrollY: number; + /** + * Horizontal scroll value when scrolling started in pixel units. + * @type {number} + */ + startScrollX: number; + /** + * Vertical scroll value when scrolling started in pixel units. + * @type {number} + */ + startScrollY: number; + /** + * Distance from mouse to object being dragged. + * @type {Coordinate} + * @private + */ + private dragDeltaXY_; + /** + * Current scale. + * @type {number} + */ + scale: number; + /** + * Cached scale value. Used to detect changes in viewport. + * @type {number} + * @private + */ + private oldScale_; + /** + * Cached viewport top value. Used to detect changes in viewport. + * @type {number} + * @private + */ + private oldTop_; + /** + * Cached viewport left value. Used to detect changes in viewport. + * @type {number} + * @private + */ + private oldLeft_; + /** + * The workspace's trashcan (if any). + * @type {Trashcan} + */ + trashcan: Trashcan; + /** + * This workspace's scrollbars, if they exist. + * @type {ScrollbarPair} + */ + scrollbar: { + workspace_: WorkspaceSvg; + hScroll: import("core/scrollbar").Scrollbar | undefined; + vScroll: import("core/scrollbar").Scrollbar | undefined; + corner_: SVGRectElement | undefined; + oldHostMetrics_: utils.Metrics | null; + dispose(): void; + resize(): void; + canScrollHorizontally(): boolean; /** + * The render status of an SVG workspace. + * Returns `false` for headless workspaces and true for instances of + * `WorkspaceSvg`. + * @type {boolean} + */ + canScrollVertically(): boolean; + setOrigin(x: number, y: number): void; + set(x: number, y: number, updateMetrics: boolean): void; + setX(x: number): void; + setY(y: number): void; + setContainerVisible(visible: boolean): void; + isVisible(): boolean; + resizeContent(hostMetrics: utils.Metrics): void; + resizeView(hostMetrics: utils.Metrics): void; + }; + /** + * Fixed flyout providing blocks which may be dragged into this workspace. + * @type {IFlyout} + * @private + */ + private flyout_; + /** + * Category-based toolbox providing blocks which may be dragged into this + * workspace. + * @type {IToolbox} + * @private + */ + private toolbox_; + /** + * The current gesture in progress on this workspace, if any. + * @type {TouchGesture} + * @package + */ + currentGesture_: TouchGesture; + /** + * This workspace's surface for dragging blocks, if it exists. + * @type {BlockDragSurfaceSvg} + * @private + */ + private blockDragSurface_; + /** + * This workspace's drag surface, if it exists. + * @type {WorkspaceDragSurfaceSvg} + * @private + */ + private workspaceDragSurface_; + /** + * Whether to move workspace to the drag surface when it is dragged. + * True if it should move, false if it should be translated directly. + * @type {boolean} + * @private + */ + private useWorkspaceDragSurface_; + /** + * Whether the drag surface is actively in use. When true, calls to + * translate will translate the drag surface instead of the translating the + * workspace directly. + * This is set to true in setupDragSurface and to false in resetDragSurface. + * @type {boolean} + * @private + */ + private isDragSurfaceActive_; + /** + * The first parent div with 'injectionDiv' in the name, or null if not set. + * Access this with getInjectionDiv. + * @type {Element} + * @private + */ + private injectionDiv_; + /** + * Last known position of the page scroll. + * This is used to determine whether we have recalculated screen coordinate + * stuff since the page scrolled. + * @type {Coordinate} + * @private + */ + private lastRecordedPageScroll_; + /** + * In a flyout, the target workspace where blocks should be placed after a + * drag. Otherwise null. + * @type {WorkspaceSvg} + * @package + */ + targetWorkspace: WorkspaceSvg; + /** + * Inverted screen CTM, for use in mouseToSvg. + * @type {?SVGMatrix} + * @private + */ + private inverseScreenCTM_; + /** + * Inverted screen CTM is dirty, recalculate it. + * @type {boolean} + * @private + */ + private inverseScreenCTMDirty_; /** * Object in charge of calculating metrics for the workspace. * @type {!IMetricsManager} @@ -13200,7 +15970,7 @@ declare module "workspace_svg" { private metricsManager_; /** * Method to get all the metrics that have to do with a workspace. - * @type {function():!Metrics} + * @type {function(): Metrics} * @package */ getMetrics: () => Metrics; @@ -13218,13 +15988,9 @@ declare module "workspace_svg" { * @private */ private componentManager_; - connectionDBList: ConnectionDB[]; - blockDragSurface_: BlockDragSurfaceSvg; - workspaceDragSurface_: WorkspaceDragSurfaceSvg; - useWorkspaceDragSurface_: boolean; /** - * List of currently highlighted blocks. Block highlighting is often used to - * visually mark blocks currently being executed. + * List of currently highlighted blocks. Block highlighting is often used + * to visually mark blocks currently being executed. * @type {!Array} * @private */ @@ -13248,16 +16014,16 @@ declare module "workspace_svg" { */ private markerManager_; /** - * Map from function names to callbacks, for deciding what to do when a custom - * toolbox category is opened. - * @type {!Object} * @private */ private toolboxCategoryCallbacks_; /** - * Map from function names to callbacks, for deciding what to do when a button - * is clicked. + * Map from function names to callbacks, for deciding what to do when a + * button is clicked. * @type {!Object} * @private */ @@ -13318,13 +16084,13 @@ declare module "workspace_svg" { * @return {!IMetricsManager} The metrics manager. * @public */ - public getMetricsManager(): IMetricsManager; + public getMetricsManager(): () => void; /** * Sets the metrics manager for the workspace. * @param {!IMetricsManager} metricsManager The metrics manager. * @package */ - setMetricsManager(metricsManager: IMetricsManager): void; + setMetricsManager(metricsManager: () => void): void; /** * Gets the component manager for this workspace. * @return {!ComponentManager} The component manager. @@ -13398,8 +16164,6 @@ declare module "workspace_svg" { * @return {?SVGMatrix} The matrix to use in mouseToSvg */ getInverseScreenCTM(): SVGMatrix | null; - inverseScreenCTM_: SVGMatrix | null; - inverseScreenCTMDirty_: boolean; /** * Mark the inverse screen CTM as dirty. */ @@ -13418,14 +16182,23 @@ declare module "workspace_svg" { * @return {!Coordinate} Object with .x and .y properties. * @package */ - getSvgXY(element: SVGElement): Coordinate; + getSvgXY(element: SVGElement): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; /** * Gets the size of the workspace's parent SVG element. * @return {!Size} The cached width and height of the workspace's * parent SVG element. * @package */ - getCachedParentSvgSize(): Size; + getCachedParentSvgSize(): { + width: number; + height: number; + }; /** * Return the position of the workspace origin relative to the injection div * origin in pixels. @@ -13434,7 +16207,13 @@ declare module "workspace_svg" { * @return {!Coordinate} Offset in pixels. * @package */ - getOriginOffsetInPixels(): Coordinate; + getOriginOffsetInPixels(): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; /** * Return the injection div that is a parent of this workspace. * Walks the DOM the first time it's called, then returns a cached value. @@ -13444,7 +16223,6 @@ declare module "workspace_svg" { * @package */ getInjectionDiv(): Element; - injectionDiv_: Element; /** * Get the SVG block canvas for the workspace. * @return {?SVGElement} The SVG group for the workspace. @@ -13457,7 +16235,6 @@ declare module "workspace_svg" { * eventHandling.unbind. */ setResizeHandlerWrapper(handler: any[][]): void; - resizeHandlerWrapper_: any[][] | null; /** * Create the workspace DOM elements. * @param {string=} opt_backgroundClass Either 'blocklyMainBackground' or @@ -13474,39 +16251,14 @@ declare module "workspace_svg" { * * @type {SVGElement} */ - svgGroup_: SVGElement; + svgGroup_: SVGElement | undefined; /** @type {SVGElement} */ - svgBackground_: SVGElement; + svgBackground_: SVGElement | undefined; /** @type {SVGElement} */ - svgBlockCanvas_: SVGElement; + svgBlockCanvas_: SVGElement | undefined; /** @type {SVGElement} */ - svgBubbleCanvas_: SVGElement; - toolbox_: IToolbox; - /** - * Dispose of this workspace. - * Unlink from all DOM elements to prevent memory leaks. - * @suppress {checkTypes} - */ - dispose(): void; - rendered: boolean; - flyout_: IFlyout; - trashcan: Trashcan; - scrollbar: ScrollbarPair; - zoomControls_: ZoomControls; - /** - * Obtain a newly created block. - * - * This block's SVG must still be initialized - * ([initSvg]{@link BlockSvg#initSvg}) and it must be rendered - * ([render]{@link BlockSvg#render}) before the block will be visible. - * @param {!string} prototypeName Name of the language object containing - * type-specific functions for this block. - * @param {string=} opt_id Optional ID. Use this ID if provided, otherwise - * create a new ID. - * @return {!BlockSvg} The created block. - * @override - */ - override newBlock(prototypeName: string, opt_id?: string | undefined): BlockSvg; + svgBubbleCanvas_: SVGElement | undefined; + zoomControls_: ZoomControls | undefined; /** * Add a trashcan. * @package @@ -13531,7 +16283,8 @@ declare module "workspace_svg" { * Getter for the flyout associated with this workspace. This flyout may be * owned by either the toolbox or the workspace, depending on toolbox * configuration. It will be null if there is no flyout. - * @param {boolean=} opt_own Whether to only return the workspace's own flyout. + * @param {boolean=} opt_own Whether to only return the workspace's own + * flyout. * @return {?IFlyout} The flyout on this workspace. * @package */ @@ -13541,7 +16294,7 @@ declare module "workspace_svg" { * @return {?IToolbox} The toolbox on this workspace. * @package */ - getToolbox(): IToolbox | null; + getToolbox(): (() => void) | null; /** * Update items that use screen coordinate calculations * because something has changed (e.g. scroll position, window size). @@ -13549,9 +16302,9 @@ declare module "workspace_svg" { */ private updateScreenCalculations_; /** - * If enabled, resize the parts of the workspace that change when the workspace - * contents (e.g. block positions) change. This will also scroll the - * workspace contents if needed. + * If enabled, resize the parts of the workspace that change when the + * workspace contents (e.g. block positions) change. This will also scroll + * the workspace contents if needed. * @package */ resizeContents(): void; @@ -13569,7 +16322,6 @@ declare module "workspace_svg" { * @package */ updateScreenCalculationsIfScrolled(): void; - lastRecordedPageScroll_: Coordinate; /** * Get the SVG element that forms the drawing surface. * @return {!SVGGElement} SVG group element. @@ -13601,9 +16353,6 @@ declare module "workspace_svg" { * @package */ maybeFireViewportChangeEvent(): void; - oldScale_: any; - oldTop_: number; - oldLeft_: number; /** * Translate this workspace to new coordinates. * @param {number} x Horizontal translation, in pixel units relative to the @@ -13619,7 +16368,6 @@ declare module "workspace_svg" { * @package */ resetDragSurface(): void; - isDragSurfaceActive_: boolean; /** * Called at the beginning of a workspace drag to move contents of * the workspace to the drag surface. @@ -13633,20 +16381,56 @@ declare module "workspace_svg" { * if one is in use. * @package */ - getBlockDragSurface(): BlockDragSurfaceSvg | null; - /** - * Returns the horizontal offset of the workspace. - * Intended for LTR/RTL compatibility in XML. - * @return {number} Width. - */ - getWidth(): number; + getBlockDragSurface(): { + SVG_: SVGElement | null; + dragGroup_: SVGElement | null; + container_: Element; + scale_: number; + surfaceXY_: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + } | null; + childSurfaceXY_: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + createDom(): void; + setBlocksAndShow(blocks: SVGElement): void; + translateAndScaleGroup(x: number, y: number, scale: number): void; + translateSurfaceInternal_(): void; + translateBy(deltaX: number, deltaY: number): void; + translateSurface(x: number, y: number): void; + getSurfaceTranslation(): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + getGroup(): SVGElement | null; + getSvgRoot(): SVGElement | null; + getCurrentBlock(): Element | null; + getWsTranslation(): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; + clearAndHide(opt_newSurface?: Element | undefined): void; + } | null; /** * Toggles the visibility of the workspace. * Currently only intended for main workspace. * @param {boolean} isVisible True if workspace should be visible. */ setVisible(isVisible: boolean): void; - isVisible_: boolean; /** * Render all blocks in workspace. */ @@ -13667,19 +16451,26 @@ declare module "workspace_svg" { * should be done before calling this method. * @param {!Object|!Element|!DocumentFragment} state The representation of the * thing to paste. + * @return {!ICopyable|null} The pasted thing, or null if + * the paste was not successful. */ - paste(state: any | Element | DocumentFragment): void; + paste(state: Object | Element | DocumentFragment): { + (): void; + CopyData: ICopyable.CopyData; + } | null; /** * Paste the provided block onto the workspace. * @param {?Element} xmlBlock XML block element. * @param {?blocks.State} jsonBlock JSON block * representation. + * @return {!BlockSvg} The pasted block. * @private */ private pasteBlock_; /** * Paste the provided comment onto the workspace. * @param {!Element} xmlComment XML workspace comment element. + * @return {!WorkspaceCommentSvg} The pasted workspace comment. * @private * @suppress {checkTypes} Suppress checks while workspace comments are not * bundled in. @@ -13690,31 +16481,6 @@ declare module "workspace_svg" { * @package */ refreshToolboxSelection(): void; - /** - * Rename a variable by updating its name in the variable map. Update the - * flyout to show the renamed variable immediately. - * @param {string} id ID of the variable to rename. - * @param {string} newName New variable name. - */ - renameVariableById(id: string, newName: string): void; - /** - * Delete a variable by the passed in ID. Update the flyout to show - * immediately that the variable is deleted. - * @param {string} id ID of variable to delete. - */ - deleteVariableById(id: string): void; - /** - * Create a new variable with the given name. Update the flyout to show the - * new variable immediately. - * @param {string} name The new variable's name. - * @param {?string=} opt_type The type of the variable like 'int' or 'string'. - * Does not need to be unique. Field_variable can filter variables based on - * their type. This will default to '' which is a specific type. - * @param {?string=} opt_id The unique ID of the variable. This will default to - * a UUID. - * @return {!VariableModel} The newly created variable. - */ - createVariable(name: string, opt_type?: (string | null) | undefined, opt_id?: (string | null) | undefined): VariableModel; /** * Make a list of all the delete areas for this workspace. * @deprecated Use workspace.recordDragTargets. (2021 June) @@ -13730,7 +16496,7 @@ declare module "workspace_svg" { * @return {?IDragTarget} Null if not over a drag target, or the drag * target the event is over. */ - getDragTarget(e: Event): IDragTarget | null; + getDragTarget(e: Event): (() => void) | null; /** * Handle a mouse-down on SVG drawing surface. * @param {!Event} e Mouse down event. @@ -13742,14 +16508,25 @@ declare module "workspace_svg" { * @param {!Event} e Mouse down event. * @param {!Coordinate} xy Starting location of object. */ - startDrag(e: Event, xy: Coordinate): void; - dragDeltaXY_: Coordinate; + startDrag(e: Event, xy: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): void; /** * Track a drag of an object on this workspace. * @param {!Event} e Mouse move event. * @return {!Coordinate} New location of object. */ - moveDrag(e: Event): Coordinate; + moveDrag(e: Event): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; /** * Is the user currently dragging a block or scrolling the flyout/workspace? * @return {boolean} True if currently dragging or scrolling. @@ -13765,9 +16542,9 @@ declare module "workspace_svg" { * * This means the user can reposition the X Y coordinates of the workspace * through input. This can be through scrollbars, scroll wheel, dragging, or - * through zooming with the scroll wheel or pinch (since the zoom is centered on - * the mouse position). This does not include zooming with the zoom controls - * since the X Y coordinates are decided programmatically. + * through zooming with the scroll wheel or pinch (since the zoom is centered + * on the mouse position). This does not include zooming with the zoom + * controls since the X Y coordinates are decided programmatically. * @return {boolean} True if the workspace is movable, false otherwise. */ isMovable(): boolean; @@ -13796,7 +16573,14 @@ declare module "workspace_svg" { * @return {!Rect} Contains the position and size of the * bounding box containing the blocks on the workspace. */ - getBlocksBoundingBox(): Rect; + getBlocksBoundingBox(): { + top: number; + bottom: number; + left: number; + right: number; + contains(x: number, y: number): boolean; + intersects(other: any): boolean; + }; /** * Clean up the workspace by ordering all the blocks in a column. */ @@ -13836,8 +16620,6 @@ declare module "workspace_svg" { * amount values zoom in. */ zoom(x: number, y: number, amount: number): void; - scrollX: number; - scrollY: number; /** * Zooming the blocks centered in the center of view with zooming in or out. * @param {number} type Type of zooming (-1 zooming out and 1 zooming in). @@ -13874,11 +16656,11 @@ declare module "workspace_svg" { * @param {number} newScale Zoom factor. Units: (pixels / workspaceUnit). */ setScale(newScale: number): void; - scale: number; /** - * Get the workspace's zoom factor. If the workspace has a parent, we call into - * the parent to get the workspace scale. - * @return {number} The workspace zoom factor. Units: (pixels / workspaceUnit). + * Get the workspace's zoom factor. If the workspace has a parent, we call + * into the parent to get the workspace scale. + * @return {number} The workspace zoom factor. Units: (pixels / + * workspaceUnit). */ getScale(): number; /** @@ -13890,56 +16672,21 @@ declare module "workspace_svg" { * @package */ scroll(x: number, y: number): void; - /** - * Find the block on this workspace with the specified ID. - * @param {string} id ID of block to find. - * @return {?BlockSvg} The sought after block, or null if not found. - * @override - */ - override getBlockById(id: string): BlockSvg | null; - /** - * Finds the top-level blocks and returns them. Blocks are optionally sorted - * by position; top to bottom (with slight LTR or RTL bias). - * @param {boolean} ordered Sort the list if true. - * @return {!Array} The top-level block objects. - * @override - */ - override getTopBlocks(ordered: boolean): Array; - /** - * Adds a block to the list of top blocks. - * @param {!Block} block Block to add. - */ - addTopBlock(block: Block): void; - /** - * Removes a block from the list of top blocks. - * @param {!Block} block Block to remove. - */ - removeTopBlock(block: Block): void; - /** - * Adds a comment to the list of top comments. - * @param {!WorkspaceComment} comment comment to add. - */ - addTopComment(comment: WorkspaceComment): void; - /** - * Removes a comment from the list of top comments. - * @param {!WorkspaceComment} comment comment to remove. - */ - removeTopComment(comment: WorkspaceComment): void; /** * Adds a bounded element to the list of top bounded elements. * @param {!IBoundedElement} element Bounded element to add. */ - addTopBoundedElement(element: IBoundedElement): void; + addTopBoundedElement(element: () => void): void; /** * Removes a bounded element from the list of top bounded elements. * @param {!IBoundedElement} element Bounded element to remove. */ - removeTopBoundedElement(element: IBoundedElement): void; + removeTopBoundedElement(element: () => void): void; /** * Finds the top-level bounded elements and returns them. * @return {!Array} The top-level bounded elements. */ - getTopBoundedElements(): Array; + getTopBoundedElements(): Array<() => void>; /** * Update whether this workspace has resizes enabled. * If enabled, workspace will resize when appropriate. @@ -13948,11 +16695,6 @@ declare module "workspace_svg" { * @param {boolean} enabled Whether resizes should be enabled. */ setResizesEnabled(enabled: boolean): void; - resizesEnabled_: boolean; - /** - * Dispose of all blocks in workspace, with an optimization to prevent resizes. - */ - clear(): void; /** * Register a callback function associated with a given key, for clicks on * buttons and labels in the flyout. @@ -13966,8 +16708,8 @@ declare module "workspace_svg" { */ registerButtonCallback(key: string, func: (arg0: FlyoutButton) => any): void; /** - * Get the callback function associated with a given key, for clicks on buttons - * and labels in the flyout. + * Get the callback function associated with a given key, for clicks on + * buttons and labels in the flyout. * @param {string} key The name to use to look up the function. * @return {?function(!FlyoutButton)} The function corresponding to the * given key for this workspace; null if no callback is registered. @@ -13980,22 +16722,22 @@ declare module "workspace_svg" { removeButtonCallback(key: string): void; /** * Register a callback function associated with a given key, for populating - * custom toolbox categories in this workspace. See the variable and procedure - * categories as an example. + * custom toolbox categories in this workspace. See the variable and + * procedure categories as an example. * @param {string} key The name to use to look up this function. - * @param {function(!Workspace): !toolbox.FlyoutDefinition} func The function to - * call when the given toolbox category is opened. + * @param {function(!WorkspaceSvg): !toolbox.FlyoutDefinition} func The + * function to call when the given toolbox category is opened. */ - registerToolboxCategoryCallback(key: string, func: (arg0: Workspace) => toolbox.FlyoutDefinition): void; + registerToolboxCategoryCallback(key: string, func: (arg0: WorkspaceSvg) => toolbox.FlyoutDefinition): void; /** * Get the callback function associated with a given key, for populating * custom toolbox categories in this workspace. * @param {string} key The name to use to look up the function. - * @return {?function(!Workspace): !toolbox.FlyoutDefinition} The function - * corresponding to the given key for this workspace, or null if no function - * is registered. + * @return {?function(!WorkspaceSvg): !toolbox.FlyoutDefinition} The function + * corresponding to the given key for this workspace, or null if no + * function is registered. */ - getToolboxCategoryCallback(key: string): ((arg0: Workspace) => toolbox.FlyoutDefinition) | null; + getToolboxCategoryCallback(key: string): ((arg0: WorkspaceSvg) => toolbox.FlyoutDefinition) | null; /** * Remove a callback for a click on a custom category's name in the toolbox. * @param {string} key The name associated with the callback function. @@ -14010,7 +16752,6 @@ declare module "workspace_svg" { * @package */ getGesture(e: Event): TouchGesture | null; - currentGesture_: TouchGesture; /** * Clear the reference to the current gesture. * @package @@ -14037,186 +16778,45 @@ declare module "workspace_svg" { * @param {boolean=} opt_onlyClosePopups Whether only popups should be closed. */ hideChaff(opt_onlyClosePopups?: boolean | undefined): void; - /** - * Is this workspace the surface for a flyout? - * @type {boolean} - */ - isFlyout: boolean; - /** - * Is this workspace the surface for a mutator? - * @type {boolean} - * @package - */ - isMutator: boolean; - /** - * Horizontal scroll value when scrolling started in pixel units. - * @type {number} - */ - startScrollX: number; - /** - * Vertical scroll value when scrolling started in pixel units. - * @type {number} - */ - startScrollY: number; - /** - * In a flyout, the target workspace where blocks should be placed after a drag. - * Otherwise null. - * @type {WorkspaceSvg} - * @package - */ - targetWorkspace: WorkspaceSvg; } - import { Metrics } from "utils/metrics"; - import { ConnectionDB } from "connection_db"; - import { BlockDragSurfaceSvg } from "block_drag_surface"; - import { WorkspaceDragSurfaceSvg } from "workspace_drag_surface_svg"; - import { ThemeManager } from "theme_manager"; - import { MarkerManager } from "marker_manager"; - import { IMetricsManager } from "interfaces/i_metrics_manager"; - import { ComponentManager } from "component_manager"; - import { Marker } from "keyboard_nav/marker"; - import { Cursor } from "keyboard_nav/cursor"; - import { Renderer } from "renderers/common/renderer"; - import { Theme } from "theme"; - import { Coordinate } from "utils/coordinate"; - import { Size } from "utils/size"; - import { IToolbox } from "interfaces/i_toolbox"; - import { IFlyout } from "interfaces/i_flyout"; - import { Trashcan } from "trashcan"; - import { ScrollbarPair } from "scrollbar_pair"; - import { ZoomControls } from "zoom_controls"; - import { BlockSvg } from "block_svg"; - import { Svg } from "utils/svg"; - import { VariableModel } from "variable_model"; - import { IDragTarget } from "interfaces/i_drag_target"; - import { Rect } from "utils/rect"; - import * as toolbox from "utils/toolbox"; - import { Block } from "block"; - import { WorkspaceComment } from "workspace_comment"; - import { IBoundedElement } from "interfaces/i_bounded_element"; - import { FlyoutButton } from "flyout_button"; - import { Workspace } from "workspace"; - import { TouchGesture } from "touch_gesture"; - import { WorkspaceAudio } from "workspace_audio"; - import { Grid } from "grid"; - import { Options } from "options"; + import { IASTNodeLocationSvg } from "core/interfaces/i_ast_node_location_svg"; + import { Workspace } from "core/workspace"; + import { Trashcan } from "core/trashcan"; + import * as utils from "core/utils"; + import { TouchGesture } from "core/touch_gesture"; + import { ThemeManager } from "core/theme_manager"; + import { MarkerManager } from "core/marker_manager"; + import { ComponentManager } from "core/component_manager"; + import { Marker } from "core/keyboard_nav/marker"; + import { Metrics } from "core/utils/metrics"; + import { Cursor } from "core/keyboard_nav/cursor"; + import { Renderer } from "core/renderers/common/renderer"; + import { Theme } from "core/theme"; + import { ZoomControls } from "core/zoom_controls"; + import { Svg } from "core/utils/svg"; + import { IFlyout } from "core/interfaces/i_flyout"; + import { ICopyable } from "core/interfaces/i_copyable"; + import * as toolbox from "core/utils/toolbox"; + import { FlyoutButton } from "core/flyout_button"; + import { WorkspaceAudio } from "core/workspace_audio"; + import { Grid } from "core/grid"; + import { Options } from "core/options"; + import { WorkspaceDragSurfaceSvg } from "core/workspace_drag_surface_svg"; } -declare module "interfaces/i_copyable" { - /** - * @extends {ISelectable} - * @interface - * @alias Blockly.ICopyable - */ - export class ICopyable { - } - export namespace ICopyable { - /** - * Copy Metadata. - */ - type CopyData = { - saveInfo: (any | Element); - source: WorkspaceSvg; - typeCounts: any | null; - }; - } - import { WorkspaceSvg } from "workspace_svg"; -} -declare module "common" { - /** - * All of the connections on blocks that are currently being dragged. - * @type {!Array} - */ - export const draggingConnections: Array; - /** - * Returns the last used top level workspace (based on focus). Try not to use - * this function, particularly if there are multiple Blockly instances on a - * page. - * @return {!Workspace} The main workspace. - * @alias Blockly.common.getMainWorkspace - */ - export function getMainWorkspace(): Workspace; - /** - * Sets last used main workspace. - * @param {!Workspace} workspace The most recently used top level workspace. - * @alias Blockly.common.setMainWorkspace - */ - export function setMainWorkspace(workspace: Workspace): void; - /** - * Returns the currently selected block. - * @return {?ICopyable} The currently selected block. - * @alias Blockly.common.getSelected - */ - export function getSelected(): ICopyable | null; - /** - * Sets the currently selected block. - * @param {?ICopyable} newSelection The newly selected block. - * @alias Blockly.common.setSelected - */ - export function setSelected(newSelection: ICopyable | null): void; - /** - * Get the container element in which to render the WidgetDiv, DropDownDiv and\ - * Tooltip. - * @return {?Element} The parent container. - * @alias Blockly.common.getParentContainer - */ - export function getParentContainer(): Element | null; - /** - * Set the parent container. This is the container element that the WidgetDiv, - * DropDownDiv, and Tooltip are rendered into the first time `Blockly.inject` - * is called. - * This method is a NOP if called after the first ``Blockly.inject``. - * @param {!Element} newParent The container element. - * @alias Blockly.common.setParentContainer - */ - export function setParentContainer(newParent: Element): void; - /** - * Size the SVG image to completely fill its container. Call this when the view - * actually changes sizes (e.g. on a window resize/device orientation change). - * See Blockly.resizeSvgContents to resize the workspace when the contents - * change (e.g. when a block is added or removed). - * Record the height/width of the SVG image. - * @param {!WorkspaceSvg} workspace Any workspace in the SVG. - * @alias Blockly.common.svgResize - */ - export function svgResize(workspace: WorkspaceSvg): void; - import { Connection } from "connection"; - /** - * Get a map of all the block's descendants mapping their type to the number of - * children with that type. - * @param {!Block} block The block to map. - * @param {boolean=} opt_stripFollowing Optionally ignore all following - * statements (blocks that are not inside a value or statement input - * of the block). - * @return {!Object} Map of types to type counts for descendants of the bock. - * @alias Blockly.common.getBlockTypeCounts - */ - export function getBlockTypeCounts(block: Block, opt_stripFollowing?: boolean | undefined): any; - /** - * Define blocks from an array of JSON block definitions, as might be generated - * by the Blockly Developer Tools. - * @param {!Array} jsonArray An array of JSON block definitions. - * @alias Blockly.common.defineBlocksWithJsonArray - */ - export function defineBlocksWithJsonArray(jsonArray: Array): void; - import { Workspace } from "workspace"; - import { ICopyable } from "interfaces/i_copyable"; - import { WorkspaceSvg } from "workspace_svg"; - import { Block } from "block"; -} -declare module "widgetdiv" { +declare module "core/widgetdiv" { /** * Returns the HTML container for editor widgets. - * @return {?Element} The editor widget container. + * @return {?HTMLDivElement} The editor widget container. * @alias Blockly.WidgetDiv.getDiv */ - export function getDiv(): Element | null; + export function getDiv(): HTMLDivElement | null; /** * Allows unit tests to reset the div. - * @param {?Element} newDiv The new value for the DIV field. + * @param {?HTMLDivElement} newDiv The new value for the DIV field. * @alias Blockly.WidgetDiv.testOnly_setDiv * @ignore */ - export function testOnly_setDiv(newDiv: Element | null): void; + export function testOnly_setDiv(newDiv: HTMLDivElement | null): void; /** * Create the widget div and inject it onto the page. * @alias Blockly.WidgetDiv.createDom @@ -14230,7 +16830,7 @@ declare module "widgetdiv" { * widget is closed. * @alias Blockly.WidgetDiv.show */ - export function show(newOwner: any, rtl: boolean, newDispose: Function): void; + export function show(newOwner: Object, rtl: boolean, newDispose: Function): void; /** * Destroy the widget and hide the div. * @alias Blockly.WidgetDiv.hide @@ -14248,7 +16848,7 @@ declare module "widgetdiv" { * @param {!Object} oldOwner The object that was using this container. * @alias Blockly.WidgetDiv.hideIfOwner */ - export function hideIfOwner(oldOwner: any): void; + export function hideIfOwner(oldOwner: Object): void; /** * Position the widget div based on an anchor rectangle. * The widget should be placed adjacent to but not overlapping the anchor @@ -14265,385 +16865,590 @@ declare module "widgetdiv" { * @alias Blockly.WidgetDiv.positionWithAnchor * @package */ - export function positionWithAnchor(viewportBBox: Rect, anchorBBox: Rect, widgetSize: Size, rtl: boolean): void; - import { Rect } from "utils/rect"; - import { Size } from "utils/size"; + export function positionWithAnchor(viewportBBox: { + top: number; + bottom: number; + left: number; + right: number; + contains(x: number, y: number): boolean; + intersects(other: any): boolean; + }, anchorBBox: { + top: number; + bottom: number; + left: number; + right: number; + contains(x: number, y: number): boolean; + intersects(other: any): boolean; + }, widgetSize: { + width: number; + height: number; + }, rtl: boolean): void; } -declare module "clipboard" { +declare module "core/clipboard" { /** * Copy a block or workspace comment onto the local clipboard. * @param {!ICopyable} toCopy Block or Workspace Comment to be copied. * @alias Blockly.clipboard.copy * @package */ - export function copy(toCopy: ICopyable): void; + export function copy(toCopy: { + (): void; + CopyData: ICopyable.CopyData; + }): void; /** * Paste a block or workspace comment on to the main workspace. - * @return {boolean} True if the paste was successful, false otherwise. + * @return {!ICopyable|null} The pasted thing if the paste + * was successful, null otherwise. * @alias Blockly.clipboard.paste * @package */ - export function paste(): boolean; + export function paste(): { + (): void; + CopyData: ICopyable.CopyData; + } | null; /** * Duplicate this block and its children, or a workspace comment. * @param {!ICopyable} toDuplicate Block or Workspace Comment to be * duplicated. + * @return {!ICopyable|null} The block or workspace comment that was duplicated, + * or null if the duplication failed. * @alias Blockly.clipboard.duplicate * @package */ - export function duplicate(toDuplicate: ICopyable): void; - import { ICopyable } from "interfaces/i_copyable"; + export function duplicate(toDuplicate: { + (): void; + CopyData: ICopyable.CopyData; + }): { + (): void; + CopyData: ICopyable.CopyData; + } | null; + import { ICopyable } from "core/interfaces/i_copyable"; } -declare module "menuitem" { +declare module "core/menuitem" { /** * Class representing an item in a menu. - * - * @param {string|!HTMLElement} content Text caption to display as the content - * of the item, or a HTML element to display. - * @param {string=} opt_value Data/model associated with the menu item. - * @constructor * @alias Blockly.MenuItem */ - export class MenuItem { - constructor(content: any, opt_value: any); - /** - * Human-readable text of this menu item, or the HTML element to display. - * @type {string|!HTMLElement} - * @private - */ - private content_; - /** - * Machine-readable value of this menu item. - * @type {string|undefined} - * @private - */ - private value_; - /** - * Is the menu item clickable, as opposed to greyed-out. - * @type {boolean} - * @private - */ - private enabled_; - /** - * The DOM element for the menu item. - * @type {?Element} - * @private - */ - private element_; - /** - * Whether the menu item is rendered right-to-left. - * @type {boolean} - * @private - */ - private rightToLeft_; - /** - * ARIA name for this menu. - * @type {?aria.Role} - * @private - */ - private roleName_; - /** - * Is this menu item checkable. - * @type {boolean} - * @private - */ - private checkable_; - /** - * Is this menu item currently checked. - * @type {boolean} - * @private - */ - private checked_; - /** - * Is this menu item currently highlighted. - * @type {boolean} - * @private - */ - private highlight_; - /** - * Bound function to call when this menu item is clicked. - * @type {?Function} - * @private - */ - private actionHandler_; - /** - * Creates the menuitem's DOM. - * @return {!Element} Completed DOM. - */ - createDom(): Element; - /** - * Dispose of this menu item. - */ - dispose(): void; - /** - * Gets the menu item's element. - * @return {?Element} The DOM element. - * @package - */ - getElement(): Element | null; - /** - * Gets the unique ID for this menu item. - * @return {string} Unique component ID. - * @package - */ - getId(): string; - /** - * Gets the value associated with the menu item. - * @return {*} value Value associated with the menu item. - * @package - */ - getValue(): any; - /** - * Set menu item's rendering direction. - * @param {boolean} rtl True if RTL, false if LTR. - * @package - */ - setRightToLeft(rtl: boolean): void; - /** - * Set the menu item's accessibility role. - * @param {!aria.Role} roleName Role name. - * @package - */ - setRole(roleName: aria.Role): void; - /** - * Sets the menu item to be checkable or not. Set to true for menu items - * that represent checkable options. - * @param {boolean} checkable Whether the menu item is checkable. - * @package - */ - setCheckable(checkable: boolean): void; - /** - * Checks or unchecks the component. - * @param {boolean} checked Whether to check or uncheck the component. - * @package - */ - setChecked(checked: boolean): void; - /** - * Highlights or unhighlights the component. - * @param {boolean} highlight Whether to highlight or unhighlight the component. - * @package - */ - setHighlighted(highlight: boolean): void; - /** - * Returns true if the menu item is enabled, false otherwise. - * @return {boolean} Whether the menu item is enabled. - * @package - */ - isEnabled(): boolean; - /** - * Enables or disables the menu item. - * @param {boolean} enabled Whether to enable or disable the menu item. - * @package - */ - setEnabled(enabled: boolean): void; - /** - * Performs the appropriate action when the menu item is activated - * by the user. - * @package - */ - performAction(): void; - /** - * Set the handler that's called when the menu item is activated by the user. - * `obj` will be used as the 'this' object in the function when called. - * @param {function(!MenuItem)} fn The handler. - * @param {!Object} obj Used as the 'this' object in fn when called. - * @package - */ - onAction(fn: (arg0: MenuItem) => any, obj: any): void; - } - import * as aria from "utils/aria"; + export const MenuItem: { + new (content: string | HTMLElement, opt_value?: string | undefined): { + /** + * Human-readable text of this menu item, or the HTML element to display. + * @type {string|!HTMLElement} + * @private + */ + content_: string | HTMLElement; + /** + * Machine-readable value of this menu item. + * @type {string|undefined} + * @private + */ + value_: string | undefined; + /** + * Is the menu item clickable, as opposed to greyed-out. + * @type {boolean} + * @private + */ + enabled_: boolean; + /** + * The DOM element for the menu item. + * @type {?HTMLDivElement} + * @private + */ + element_: HTMLDivElement | null; + /** + * Whether the menu item is rendered right-to-left. + * @type {boolean} + * @private + */ + rightToLeft_: boolean; + /** + * ARIA name for this menu. + * @type {?aria.Role} + * @private + */ + roleName_: aria.Role | null; + /** + * Is this menu item checkable. + * @type {boolean} + * @private + */ + checkable_: boolean; + /** + * Is this menu item currently checked. + * @type {boolean} + * @private + */ + checked_: boolean; + /** + * Is this menu item currently highlighted. + * @type {boolean} + * @private + */ + highlight_: boolean; + /** + * Bound function to call when this menu item is clicked. + * @type {?Function} + * @private + */ + actionHandler_: Function | null; + /** + * Creates the menuitem's DOM. + * @return {!Element} Completed DOM. + */ + createDom(): Element; + /** + * Dispose of this menu item. + */ + dispose(): void; + /** + * Gets the menu item's element. + * @return {?Element} The DOM element. + * @package + */ + getElement(): Element | null; + /** + * Gets the unique ID for this menu item. + * @return {string} Unique component ID. + * @package + */ + getId(): string; + /** + * Gets the value associated with the menu item. + * @return {*} value Value associated with the menu item. + * @package + */ + getValue(): any; + /** + * Set menu item's rendering direction. + * @param {boolean} rtl True if RTL, false if LTR. + * @package + */ + setRightToLeft(rtl: boolean): void; + /** + * Set the menu item's accessibility role. + * @param {!aria.Role} roleName Role name. + * @package + */ + setRole(roleName: aria.Role): void; + /** + * Sets the menu item to be checkable or not. Set to true for menu items + * that represent checkable options. + * @param {boolean} checkable Whether the menu item is checkable. + * @package + */ + setCheckable(checkable: boolean): void; + /** + * Checks or unchecks the component. + * @param {boolean} checked Whether to check or uncheck the component. + * @package + */ + setChecked(checked: boolean): void; + /** + * Highlights or unhighlights the component. + * @param {boolean} highlight Whether to highlight or unhighlight the + * component. + * @package + */ + setHighlighted(highlight: boolean): void; + /** + * Returns true if the menu item is enabled, false otherwise. + * @return {boolean} Whether the menu item is enabled. + * @package + */ + isEnabled(): boolean; + /** + * Enables or disables the menu item. + * @param {boolean} enabled Whether to enable or disable the menu item. + * @package + */ + setEnabled(enabled: boolean): void; + /** + * Performs the appropriate action when the menu item is activated + * by the user. + * @package + */ + performAction(): void; + /** + * Set the handler that's called when the menu item is activated by the user. + * `obj` will be used as the 'this' object in the function when called. + * @param {function(!MenuItem)} fn The handler. + * @param {!Object} obj Used as the 'this' object in fn when called. + * @package + */ + onAction(fn: (arg0: any) => any, obj: Object): void; + }; + }; + import * as aria from "core/utils/aria"; } -declare module "menu" { - export class Menu { - /** - * Array of menu items. - * (Nulls are never in the array, but typing the array as nullable prevents - * the compiler from objecting to .indexOf(null)) - * @type {!Array} - * @private - */ - private menuItems_; - /** - * Coordinates of the mousedown event that caused this menu to open. Used to - * prevent the consequent mouseup event due to a simple click from activating - * a menu item immediately. - * @type {?Coordinate} - * @package - */ - openingCoords: Coordinate | null; - /** - * This is the element that we will listen to the real focus events on. - * A value of null means no menu item is highlighted. - * @type {?MenuItem} - * @private - */ - private highlightedItem_; - /** - * Mouse over event data. - * @type {?browserEvents.Data} - * @private - */ - private mouseOverHandler_; - /** - * Click event data. - * @type {?browserEvents.Data} - * @private - */ - private clickHandler_; - /** - * Mouse enter event data. - * @type {?browserEvents.Data} - * @private - */ - private mouseEnterHandler_; - /** - * Mouse leave event data. - * @type {?browserEvents.Data} - * @private - */ - private mouseLeaveHandler_; - /** - * Key down event data. - * @type {?browserEvents.Data} - * @private - */ - private onKeyDownHandler_; - /** - * The menu's root DOM element. - * @type {?Element} - * @private - */ - private element_; - /** - * ARIA name for this menu. - * @type {?aria.Role} - * @private - */ - private roleName_; - /** - * Add a new menu item to the bottom of this menu. - * @param {!MenuItem} menuItem Menu item to append. - */ - addChild(menuItem: MenuItem): void; - /** - * Creates the menu DOM. - * @param {!Element} container Element upon which to append this menu. - */ - render(container: Element): void; - /** - * Gets the menu's element. - * @return {?Element} The DOM element. - * @package - */ - getElement(): Element | null; - /** - * Focus the menu element. - * @package - */ - focus(): void; - /** - * Blur the menu element. - * @private - */ - private blur_; - /** - * Set the menu accessibility role. - * @param {!aria.Role} roleName role name. - * @package - */ - setRole(roleName: aria.Role): void; - /** - * Dispose of this menu. - */ - dispose(): void; - /** - * Returns the child menu item that owns the given DOM element, - * or null if no such menu item is found. - * @param {Element} elem DOM element whose owner is to be returned. - * @return {?MenuItem} Menu item for which the DOM element belongs to. - * @private - */ - private getMenuItem_; - /** - * Highlights the given menu item, or clears highlighting if null. - * @param {?MenuItem} item Item to highlight, or null. - * @package - */ - setHighlighted(item: MenuItem | null): void; - /** - * Highlights the next highlightable item (or the first if nothing is currently - * highlighted). - * @package - */ - highlightNext(): void; - /** - * Highlights the previous highlightable item (or the last if nothing is - * currently highlighted). - * @package - */ - highlightPrevious(): void; - /** - * Highlights the first highlightable item. - * @private - */ - private highlightFirst_; - /** - * Highlights the last highlightable item. - * @private - */ - private highlightLast_; - /** - * Helper function that manages the details of moving the highlight among - * child menuitems in response to keyboard events. - * @param {number} startIndex Start index. - * @param {number} delta Step direction: 1 to go down, -1 to go up. - * @private - */ - private highlightHelper_; - /** - * Handles mouseover events. Highlight menuitems as the user hovers over them. - * @param {!Event} e Mouse event to handle. - * @private - */ - private handleMouseOver_; - /** - * Handles click events. Pass the event onto the child menuitem to handle. - * @param {!Event} e Click event to handle. - * @private - */ - private handleClick_; - /** - * Handles mouse enter events. Focus the element. - * @param {!Event} _e Mouse event to handle. - * @private - */ - private handleMouseEnter_; - /** - * Handles mouse leave events. Blur and clear highlight. - * @param {!Event} _e Mouse event to handle. - * @private - */ - private handleMouseLeave_; - /** - * Attempts to handle a keyboard event, if the menu item is enabled, by calling - * {@link handleKeyEventInternal_}. - * @param {!Event} e Key event to handle. - * @private - */ - private handleKeyEvent_; - /** - * Get the size of a rendered menu. - * @return {!Size} Object with width and height properties. - * @package - */ - getSize(): Size; - } - import { Coordinate } from "utils/coordinate"; - import { MenuItem } from "menuitem"; - import * as aria from "utils/aria"; - import { Size } from "utils/size"; +declare module "core/menu" { + /** + * A basic menu class. + * @alias Blockly.Menu + */ + export const Menu: { + new (): { + /** + * Array of menu items. + * (Nulls are never in the array, but typing the array as nullable prevents + * the compiler from objecting to .indexOf(null)) + * @type {!Array} + * @private + */ + menuItems_: Array<{ + content_: string | HTMLElement; + value_: string | undefined; + enabled_: boolean; + element_: HTMLDivElement | null; + rightToLeft_: boolean; + roleName_: string | null; + checkable_: boolean; /** + * Click event data. + * @type {?browserEvents.Data} + * @private + */ + checked_: boolean; + highlight_: boolean; /** + * Mouse leave event data. + * @type {?browserEvents.Data} + * @private + */ + actionHandler_: Function | null; + createDom(): Element; + dispose(): void; + getElement(): Element | null; + getId(): string; + getValue(): any; + setRightToLeft(rtl: boolean): void; + setRole(roleName: string): void; + setCheckable(checkable: boolean): void; + setChecked(checked: boolean): void; + setHighlighted(highlight: boolean): void; + isEnabled(): boolean; + setEnabled(enabled: boolean): void; + performAction(): void; + onAction(fn: (arg0: any) => any, obj: Object): void; + }>; + /** + * Coordinates of the mousedown event that caused this menu to open. Used to + * prevent the consequent mouseup event due to a simple click from + * activating a menu item immediately. + * @type {?Coordinate} + * @package + */ + openingCoords: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + } | null; + /** + * This is the element that we will listen to the real focus events on. + * A value of null means no menu item is highlighted. + * @type {?MenuItem} + * @private + */ + highlightedItem_: { + content_: string | HTMLElement; + value_: string | undefined; + enabled_: boolean; + element_: HTMLDivElement | null; + rightToLeft_: boolean; + roleName_: string | null; + checkable_: boolean; /** + * Click event data. + * @type {?browserEvents.Data} + * @private + */ + checked_: boolean; + highlight_: boolean; /** + * Mouse leave event data. + * @type {?browserEvents.Data} + * @private + */ + actionHandler_: Function | null; + createDom(): Element; + dispose(): void; + getElement(): Element | null; + getId(): string; + getValue(): any; + setRightToLeft(rtl: boolean): void; + setRole(roleName: string): void; + setCheckable(checkable: boolean): void; + setChecked(checked: boolean): void; + setHighlighted(highlight: boolean): void; + isEnabled(): boolean; + setEnabled(enabled: boolean): void; + performAction(): void; + onAction(fn: (arg0: any) => any, obj: Object): void; + } | null; + /** + * Mouse over event data. + * @type {?browserEvents.Data} + * @private + */ + mouseOverHandler_: any[][] | null; + /** + * Click event data. + * @type {?browserEvents.Data} + * @private + */ + clickHandler_: any[][] | null; + /** + * Mouse enter event data. + * @type {?browserEvents.Data} + * @private + */ + mouseEnterHandler_: any[][] | null; + /** + * Mouse leave event data. + * @type {?browserEvents.Data} + * @private + */ + mouseLeaveHandler_: any[][] | null; + /** + * Key down event data. + * @type {?browserEvents.Data} + * @private + */ + onKeyDownHandler_: any[][] | null; + /** + * The menu's root DOM element. + * @type {?HTMLDivElement} + * @private + */ + element_: HTMLDivElement | null; + /** + * ARIA name for this menu. + * @type {?aria.Role} + * @private + */ + roleName_: aria.Role | null; + /** + * Add a new menu item to the bottom of this menu. + * @param {!MenuItem} menuItem Menu item to append. + */ + addChild(menuItem: { + content_: string | HTMLElement; + value_: string | undefined; + enabled_: boolean; + element_: HTMLDivElement | null; + rightToLeft_: boolean; + roleName_: string | null; + checkable_: boolean; /** + * Click event data. + * @type {?browserEvents.Data} + * @private + */ + checked_: boolean; + highlight_: boolean; /** + * Mouse leave event data. + * @type {?browserEvents.Data} + * @private + */ + actionHandler_: Function | null; + createDom(): Element; + dispose(): void; + getElement(): Element | null; + getId(): string; + getValue(): any; + setRightToLeft(rtl: boolean): void; + setRole(roleName: string): void; + setCheckable(checkable: boolean): void; + setChecked(checked: boolean): void; + setHighlighted(highlight: boolean): void; + isEnabled(): boolean; + setEnabled(enabled: boolean): void; + performAction(): void; + onAction(fn: (arg0: any) => any, obj: Object): void; + }): void; + /** + * Creates the menu DOM. + * @param {!Element} container Element upon which to append this menu. + */ + render(container: Element): void; + /** + * Gets the menu's element. + * @return {?Element} The DOM element. + * @package + */ + getElement(): Element | null; + /** + * Focus the menu element. + * @package + */ + focus(): void; + /** + * Blur the menu element. + * @private + */ + blur_(): void; + /** + * Set the menu accessibility role. + * @param {!aria.Role} roleName role name. + * @package + */ + setRole(roleName: aria.Role): void; + /** + * Dispose of this menu. + */ + dispose(): void; + /** + * Returns the child menu item that owns the given DOM element, + * or null if no such menu item is found. + * @param {Element} elem DOM element whose owner is to be returned. + * @return {?MenuItem} Menu item for which the DOM element belongs to. + * @private + */ + getMenuItem_(elem: Element): { + content_: string | HTMLElement; + value_: string | undefined; + enabled_: boolean; + element_: HTMLDivElement | null; + rightToLeft_: boolean; + roleName_: string | null; + checkable_: boolean; /** + * Click event data. + * @type {?browserEvents.Data} + * @private + */ + checked_: boolean; + highlight_: boolean; /** + * Mouse leave event data. + * @type {?browserEvents.Data} + * @private + */ + actionHandler_: Function | null; + createDom(): Element; + dispose(): void; + getElement(): Element | null; + getId(): string; + getValue(): any; + setRightToLeft(rtl: boolean): void; + setRole(roleName: string): void; + setCheckable(checkable: boolean): void; + setChecked(checked: boolean): void; + setHighlighted(highlight: boolean): void; + isEnabled(): boolean; + setEnabled(enabled: boolean): void; + performAction(): void; + onAction(fn: (arg0: any) => any, obj: Object): void; + } | null; + /** + * Highlights the given menu item, or clears highlighting if null. + * @param {?MenuItem} item Item to highlight, or null. + * @package + */ + setHighlighted(item: { + content_: string | HTMLElement; + value_: string | undefined; + enabled_: boolean; + element_: HTMLDivElement | null; + rightToLeft_: boolean; + roleName_: string | null; + checkable_: boolean; /** + * Click event data. + * @type {?browserEvents.Data} + * @private + */ + checked_: boolean; + highlight_: boolean; /** + * Mouse leave event data. + * @type {?browserEvents.Data} + * @private + */ + actionHandler_: Function | null; + createDom(): Element; + dispose(): void; + getElement(): Element | null; + getId(): string; + getValue(): any; + setRightToLeft(rtl: boolean): void; + setRole(roleName: string): void; + setCheckable(checkable: boolean): void; + setChecked(checked: boolean): void; + setHighlighted(highlight: boolean): void; + isEnabled(): boolean; + setEnabled(enabled: boolean): void; + performAction(): void; + onAction(fn: (arg0: any) => any, obj: Object): void; + } | null): void; + /** + * Highlights the next highlightable item (or the first if nothing is + * currently highlighted). + * @package + */ + highlightNext(): void; + /** + * Highlights the previous highlightable item (or the last if nothing is + * currently highlighted). + * @package + */ + highlightPrevious(): void; + /** + * Highlights the first highlightable item. + * @private + */ + highlightFirst_(): void; + /** + * Highlights the last highlightable item. + * @private + */ + highlightLast_(): void; + /** + * Helper function that manages the details of moving the highlight among + * child menuitems in response to keyboard events. + * @param {number} startIndex Start index. + * @param {number} delta Step direction: 1 to go down, -1 to go up. + * @private + */ + highlightHelper_(startIndex: number, delta: number): void; + /** + * Handles mouseover events. Highlight menuitems as the user hovers over them. + * @param {!Event} e Mouse event to handle. + * @private + */ + handleMouseOver_(e: Event): void; + /** + * Handles click events. Pass the event onto the child menuitem to handle. + * @param {!Event} e Click event to handle. + * @private + */ + handleClick_(e: Event): void; + /** + * Handles mouse enter events. Focus the element. + * @param {!Event} _e Mouse event to handle. + * @private + */ + handleMouseEnter_(_e: Event): void; + /** + * Handles mouse leave events. Blur and clear highlight. + * @param {!Event} _e Mouse event to handle. + * @private + */ + handleMouseLeave_(_e: Event): void; + /** + * Attempts to handle a keyboard event, if the menu item is enabled, by + * calling + * {@link handleKeyEventInternal_}. + * @param {!Event} e Key event to handle. + * @private + */ + handleKeyEvent_(e: Event): void; + /** + * Get the size of a rendered menu. + * @return {!Size} Object with width and height properties. + * @package + */ + getSize(): { + width: number; + height: number; + }; + }; + }; + import * as aria from "core/utils/aria"; } -declare module "contextmenu" { +declare module "core/contextmenu" { /** * Gets the block the context menu is currently attached to. * @return {?Block} The block the context menu is attached to. @@ -14663,7 +17468,7 @@ declare module "contextmenu" { * @param {boolean} rtl True if RTL, false if LTR. * @alias Blockly.ContextMenu.show */ - export function show(e: Event, options: Array, rtl: boolean): void; + export function show(e: Event, options: Array, rtl: boolean): void; /** * Hide the context menu. * @alias Blockly.ContextMenu.hide @@ -14691,7 +17496,7 @@ declare module "contextmenu" { * @alias Blockly.ContextMenu.commentDeleteOption * @package */ - export function commentDeleteOption(comment: WorkspaceCommentSvg): any; + export function commentDeleteOption(comment: WorkspaceCommentSvg): Object; /** * Make a context menu option for duplicating the current workspace comment. * @param {!WorkspaceCommentSvg} comment The workspace comment where the @@ -14700,7 +17505,7 @@ declare module "contextmenu" { * @alias Blockly.ContextMenu.commentDuplicateOption * @package */ - export function commentDuplicateOption(comment: WorkspaceCommentSvg): any; + export function commentDuplicateOption(comment: WorkspaceCommentSvg): Object; /** * Make a context menu option for adding a comment on the workspace. * @param {!WorkspaceSvg} ws The workspace where the right-click @@ -14712,40 +17517,30 @@ declare module "contextmenu" { * comments are not bundled in. * @alias Blockly.ContextMenu.workspaceCommentOption */ - export function workspaceCommentOption(ws: WorkspaceSvg, e: Event): any; - import { Block } from "block"; - import { WorkspaceCommentSvg } from "workspace_comment_svg"; - import { WorkspaceSvg } from "workspace_svg"; + export function workspaceCommentOption(ws: WorkspaceSvg, e: Event): Object; + import { Block } from "core/block"; + import { WorkspaceCommentSvg } from "core/workspace_comment_svg"; + import { WorkspaceSvg } from "core/workspace_svg"; } -declare module "warning" { - export class Warning { - /** - * Class for a warning. - * @param {!Block} block The block associated with this warning. - * @extends {Icon} - * @constructor - * @alias Blockly.Warning - */ - constructor(block: Block); +declare module "core/warning" { + /** + * Class for a warning. + * @extends {Icon} + * @alias Blockly.Warning + */ + export class Warning extends Icon { text_: any; /** - * Draw the warning icon. - * @param {!Element} group The icon group. - * @protected + * The top-level node of the warning text, or null if not created. + * @type {?SVGTextElement} + * @private */ - protected drawIcon_(group: Element): void; - /** - * Show or hide the warning bubble. - * @param {boolean} visible True if the bubble should be visible. - */ - setVisible(visible: boolean): void; + private paragraphElement_; /** * Show the bubble. * @private */ private createBubble_; - paragraphElement_: SVGTextElement; - bubble_: Bubble; /** * Dispose of the bubble and references to it. * @private @@ -14764,28 +17559,16 @@ declare module "warning" { * @return {string} All texts concatenated into one string. */ getText(): string; - /** - * Dispose of this warning. - */ - dispose(): void; - /** - * Does this icon get hidden when the block is collapsed. - */ - collapseHidden: boolean; } - import { Bubble } from "bubble"; - import { Block } from "block"; + import { Icon } from "core/icon"; } -declare module "comment" { +declare module "core/comment" { + /** + * Class for a comment. + * @extends {Icon} + * @alias Blockly.Comment + */ export class Comment extends Icon { - /** - * Class for a comment. - * @param {!Block} block The block associated with this comment. - * @extends {Icon} - * @constructor - * @alias Blockly.Comment - */ - constructor(block: Block); /** * The model for this comment. * @type {!Block.CommentModel} @@ -14824,24 +17607,29 @@ declare module "comment" { */ private onInputWrapper_; /** - * Draw the comment icon. - * @param {!Element} group The icon group. - * @protected + * The SVG element that contains the text edit area, or null if not created. + * @type {?SVGForeignObjectElement} + * @private */ - protected drawIcon_(group: Element): void; + private foreignObject_; + /** + * The editable text area, or null if not created. + * @type {?Element} + * @private + */ + private textarea_; + /** + * The top-level node of the comment text, or null if not created. + * @type {?SVGTextElement} + * @private + */ + private paragraphElement_; /** * Create the editor for the comment's bubble. * @return {!SVGElement} The top-level node of the editor. * @private */ private createEditor_; - foreignObject_: SVGForeignObjectElement; - textarea_: HTMLElement; - /** - * Add or remove editability of the comment. - * @override - */ - override updateEditable(): void; /** * Callback function triggered when the bubble has resized. * Resize the text area accordingly. @@ -14854,11 +17642,6 @@ declare module "comment" { * @private */ private resizeTextarea_; - /** - * Show or hide the comment bubble. - * @param {boolean} visible True if the bubble should be visible. - */ - setVisible(visible: boolean): void; /** * Show the bubble. Handles deciding if it should be editable or not. * @private @@ -14869,14 +17652,12 @@ declare module "comment" { * @private */ private createEditableBubble_; - bubble_: Bubble; /** * Show a non-editable bubble. * @private * @suppress {checkTypes} Suppress `this` type mismatch. */ private createNonEditableBubble_; - paragraphElement_: SVGTextElement; /** * Dispose of the bubble. * @private @@ -14896,7 +17677,10 @@ declare module "comment" { * Get the dimensions of this comment's bubble. * @return {Size} Object with width and height properties. */ - getBubbleSize(): Size; + getBubbleSize(): { + width: number; + height: number; + }; /** * Size this comment's bubble. * @param {number} width Width of the bubble. @@ -14908,53 +17692,138 @@ declare module "comment" { * @package */ updateText(): void; - /** - * Dispose of this comment. - * - * If you want to receive a comment "delete" event (newValue: null), then this - * should not be called directly. Instead call block.setCommentText(null); - */ - dispose(): void; } - import { Bubble } from "bubble"; - import { Size } from "utils/size"; - import { Block } from "block"; - import { Icon } from "icon"; + import { Icon } from "core/icon"; } -declare module "keyboard_nav/basic_cursor" { +declare module "core/mutator" { + /** + * Class for a mutator dialog. + * @extends {Icon} + * @alias Blockly.Mutator + */ + export class Mutator extends Icon { + /** + * Reconnect an block to a mutated input. + * @param {Connection} connectionChild Connection on child block. + * @param {!Block} block Parent block. + * @param {string} inputName Name of input on parent block. + * @return {boolean} True iff a reconnection was made, false otherwise. + */ + static reconnect(connectionChild: Connection, block: Block, inputName: string): boolean; + /** + * Get the parent workspace of a workspace that is inside a mutator, taking + * into account whether it is a flyout. + * @param {WorkspaceSvg} workspace The workspace that is inside a mutator. + * @return {?WorkspaceSvg} The mutator's parent workspace or null. + * @public + */ + public static findParentWs(workspace: WorkspaceSvg): WorkspaceSvg | null; + /** + * @param {!Array} quarkNames List of names of sub-blocks for flyout. + */ + constructor(quarkNames: Array); + quarkNames_: string[]; + /** + * Workspace in the mutator's bubble. + * @type {?WorkspaceSvg} + * @private + */ + private workspace_; + /** + * Width of workspace. + * @type {number} + * @private + */ + private workspaceWidth_; + /** + * Height of workspace. + * @type {number} + * @private + */ + private workspaceHeight_; + /** + * The SVG element that is the parent of the mutator workspace, or null if + * not created. + * @type {?SVGSVGElement} + * @private + */ + private svgDialog_; + /** + * The root block of the mutator workspace, created by decomposing the + * source block. + * @type {?BlockSvg} + * @private + */ + private rootBlock_; + /** + * Function registered on the main workspace to update the mutator contents + * when the main workspace changes. + * @type {?Function} + * @private + */ + private sourceListener_; + /** + * Set the block this mutator is associated with. + * @param {!BlockSvg} block The block associated with this mutator. + * @package + */ + setBlock(block: BlockSvg): void; + /** + * Returns the workspace inside this mutator icon's bubble. + * @return {?WorkspaceSvg} The workspace inside this mutator icon's + * bubble or null if the mutator isn't open. + * @package + */ + getWorkspace(): WorkspaceSvg | null; + /** + * Create the editor for the mutator's bubble. + * @return {!SVGElement} The top-level node of the editor. + * @private + */ + private createEditor_; + /** + * Resize the bubble to match the size of the workspace. + * @private + */ + private resizeBubble_; + /** + * A method handler for when the bubble is moved. + * @private + */ + private onBubbleMove_; + /** + * Fired whenever a change is made to the mutator's workspace. + * @param {!Abstract} e Custom data for event. + * @private + */ + private workspaceChanged_; + /** + * Updates the source block when the mutator's blocks are changed. + * Bump down any block that's too high. + * @private + */ + private updateWorkspace_; + /** + * Update the styles on all blocks in the mutator. + * @public + */ + public updateBlockStyle(): void; + } + import { Icon } from "core/icon"; + import { BlockSvg } from "core/block_svg"; + import { WorkspaceSvg } from "core/workspace_svg"; + import { Connection } from "core/connection"; + import { Block } from "core/block"; +} +declare module "core/keyboard_nav/basic_cursor" { + /** + * Class for a basic cursor. + * This will allow the user to get to all nodes in the AST by hitting next or + * previous. + * @extends {Cursor} + * @alias Blockly.BasicCursor + */ export class BasicCursor extends Cursor { - /** - * Find the next node in the pre order traversal. - * @return {?ASTNode} The next node, or null if the current node is - * not set or there is no next value. - * @override - */ - override next(): ASTNode | null; - /** - * For a basic cursor we only have the ability to go next and previous, so - * in will also allow the user to get to the next node in the pre order - * traversal. - * @return {?ASTNode} The next node, or null if the current node is - * not set or there is no next value. - * @override - */ - override in(): ASTNode | null; - /** - * Find the previous node in the pre order traversal. - * @return {?ASTNode} The previous node, or null if the current node - * is not set or there is no previous value. - * @override - */ - override prev(): ASTNode | null; - /** - * For a basic cursor we only have the ability to go next and previous, so - * out will allow the user to get to the previous node in the pre order - * traversal. - * @return {?ASTNode} The previous node, or null if the current node is - * not set or there is no previous value. - * @override - */ - override out(): ASTNode | null; /** * Uses pre order traversal to navigate the Blockly AST. This will allow * a user to easily navigate the entire Blockly AST without having to go in @@ -14968,8 +17837,8 @@ declare module "keyboard_nav/basic_cursor" { protected getNextNode_(node: ASTNode | null, isValid: (arg0: ASTNode) => boolean): ASTNode | null; /** * Reverses the pre order traversal in order to find the previous node. This - * will allow a user to easily navigate the entire Blockly AST without having to - * go in and out levels on the tree. + * will allow a user to easily navigate the entire Blockly AST without having + * to go in and out levels on the tree. * @param {?ASTNode} node The current position in the AST. * @param {!function(ASTNode) : boolean} isValid A function true/false * depending on whether the given node should be traversed. @@ -15006,104 +17875,110 @@ declare module "keyboard_nav/basic_cursor" { export namespace BasicCursor { const registrationName: string; } - import { ASTNode } from "keyboard_nav/ast_node"; - import { Cursor } from "keyboard_nav/cursor"; + import { Cursor } from "core/keyboard_nav/cursor"; + import { ASTNode } from "core/keyboard_nav/ast_node"; } -declare module "keyboard_nav/tab_navigate_cursor" { +declare module "core/keyboard_nav/tab_navigate_cursor" { /** * A cursor for navigating between tab navigable fields. - * @constructor * @extends {BasicCursor} * @alias Blockly.TabNavigateCursor */ export class TabNavigateCursor extends BasicCursor { - /** - * Skip all nodes except for tab navigable fields. - * @param {?ASTNode} node The AST node to check whether it is valid. - * @return {boolean} True if the node should be visited, false otherwise. - * @override - */ - override validNode_(node: ASTNode | null): boolean; } - import { ASTNode } from "keyboard_nav/ast_node"; - import { BasicCursor } from "keyboard_nav/basic_cursor"; + import { BasicCursor } from "core/keyboard_nav/basic_cursor"; } -declare module "events/events_block_move" { - export class BlockMove { +declare module "core/block_svg" { + /** + * Class for a block's SVG representation. + * Not normally called directly, workspace.newBlock() is preferred. + * @extends {Block} + * @implements {IASTNodeLocationSvg} + * @implements {IBoundedElement} + * @implements {ICopyable} + * @implements {IDraggable} + * @alias Blockly.BlockSvg + */ + export class BlockSvg extends Block implements IASTNodeLocationSvg, IBoundedElement, ICopyable, IDraggable { /** - * Class for a block move event. Created before the move. - * @param {!Block=} opt_block The moved block. Undefined for a blank - * event. - * @extends {BlockBase} - * @constructor - * @alias Blockly.Events.BlockMove - */ - constructor(opt_block?: Block | undefined); - recordUndo: boolean; - oldParentId: any; - oldInputName: any; - oldCoordinate: any; - /** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ - toJson(): any; - /** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ - fromJson(json: any): void; - newParentId: any; - newInputName: any; - newCoordinate: any; - /** - * Record the block's new location. Called after the move. - */ - recordNew(): void; - /** - * Returns the parentId and input if the block is connected, - * or the XY location if disconnected. - * @return {!Object} Collection of location info. - * @private - */ - private currentLocation_; - /** - * Does this event record any change of state? - * @return {boolean} False if something changed. - */ - isNull(): boolean; - /** - * Run a move event. - * @param {boolean} forward True if run forward, false if run backward (undo). - */ - run(forward: boolean): void; - /** - * Type of this event. - * @type {string} - */ - type: string; - } - import { Block } from "block"; -} -declare module "block_svg" { - export class BlockSvg extends Block { - /** - * Class for a block's SVG representation. - * Not normally called directly, workspace.newBlock() is preferred. * @param {!WorkspaceSvg} workspace The block's workspace. - * @param {?string} prototypeName Name of the language object containing + * @param {string} prototypeName Name of the language object containing * type-specific functions for this block. * @param {string=} opt_id Optional ID. Use this ID if provided, otherwise * create a new ID. - * @extends {Block} - * @implements {IASTNodeLocationSvg} - * @implements {IBoundedElement} - * @implements {ICopyable} - * @implements {IDraggable} - * @constructor - * @alias Blockly.BlockSvg */ - constructor(workspace: WorkspaceSvg, prototypeName: string | null, opt_id?: string | undefined); + constructor(workspace: WorkspaceSvg, prototypeName: string, opt_id?: string | undefined); + /** + * An optional method called when a mutator dialog is first opened. + * This function must create and initialize a top-level block for the + * mutator dialog, and return it. This function should also populate this + * top-level block with any sub-blocks which are appropriate. This method + * must also be coupled with defining a `compose` method for the default + * mutation dialog button and UI to appear. + * @type {undefined|?function(WorkspaceSvg):!BlockSvg} + */ + decompose: ((arg0: WorkspaceSvg) => BlockSvg) | null | undefined; + /** + * An optional method called when a mutator dialog saves its content. + * This function is called to modify the original block according to new + * settings. This method must also be coupled with defining a `decompose` + * method for the default mutation dialog button and UI to appear. + * @type {undefined|?function(!BlockSvg)} + */ + compose: ((arg0: BlockSvg) => any) | null | undefined; + /** + * An optional method called by the default mutator UI which gives the block + * a chance to save information about what child blocks are connected to + * what mutated connections. + * @type {undefined|?function(!BlockSvg)} + */ + saveConnections: ((arg0: BlockSvg) => any) | null | undefined; + /** + * An optional method for defining custom block context menu items. + * @type {undefined|?function(!Array)} + */ + customContextMenu: ((arg0: Array) => any) | null | undefined; + /** + * An property used internally to reference the block's rendering debugger. + * @type {?BlockRenderingDebug} + * @package + */ + renderingDebugger: BlockRenderingDebug | null; + /** + * Height of this block, not including any statement blocks above or below. + * Height is in workspace units. + * @type {number} + */ + height: number; + /** + * Width of this block, including any connected value blocks. + * Width is in workspace units. + * @type {number} + */ + width: number; + /** + * Map from IDs for warnings text to PIDs of functions to apply them. + * Used to be able to maintain multiple warnings. + * @type {Object} + * @private + */ + private warningTextDb_; + /** + * Block's mutator icon (if any). + * @type {?Mutator} + */ + mutator: Mutator | null; + /** + * Block's comment icon (if any). + * @type {?Comment} + * @private + */ + private commentIcon_; + /** + * Block's warning icon (if any). + * @type {?Warning} + */ + warning: Warning | null; /** * @type {!SVGGElement} * @private @@ -15120,8 +17995,6 @@ declare module "block_svg" { * @package */ pathObject: IPathObject; - /** @type {boolean} */ - rendered: boolean; /** * Is this block currently rendering? Used to stop recursive render calls * from actually triggering a re-render. @@ -15129,14 +18002,12 @@ declare module "block_svg" { * @private */ private renderIsInProgress_; - /** @type {!WorkspaceSvg} */ - workspace: WorkspaceSvg; - /** @type {RenderedConnection} */ - outputConnection: RenderedConnection; - /** @type {RenderedConnection} */ - nextConnection: RenderedConnection; - /** @type {RenderedConnection} */ - previousConnection: RenderedConnection; + /** + * Whether mousedown events have been bound yet. + * @type {boolean} + * @private + */ + private eventsInit_; /** * Whether to move the block to the drag surface when it is dragged. * True if it should move, false if it should be translated directly. @@ -15149,7 +18020,6 @@ declare module "block_svg" { * May be called more than once. */ initSvg(): void; - eventsInit_: boolean; /** * Get the secondary colour of a block. * @return {?string} #RRGGBB string. @@ -15166,8 +18036,8 @@ declare module "block_svg" { */ select(): void; /** - * Unselects this block. Unhighlights the block and fires a select (false) event - * if the block is currently selected. + * Unselects this block. Unhighlights the block and fires a select (false) + * event if the block is currently selected. */ unselect(): void; /** @@ -15175,29 +18045,6 @@ declare module "block_svg" { * @return {!Array} List of icons. */ getIcons(): Array; - /** - * Sets the parent of this block to be a new block or null. - * @param {?Block} newParent New parent block. - * @package - * @override - */ - override setParent(newParent: Block | null): void; - /** - * Return the coordinates of the top-left corner of this block relative to the - * drawing surface's origin (0,0), in workspace units. - * If the block is on the workspace, (0, 0) is the origin of the workspace - * coordinate system. - * This does not change with workspace scale. - * @return {!Coordinate} Object with .x and .y properties in - * workspace coordinates. - */ - getRelativeToSurfaceXY(): Coordinate; - /** - * Move a block by a relative offset. - * @param {number} dx Horizontal offset in workspace units. - * @param {number} dy Vertical offset in workspace units. - */ - moveBy(dx: number, dy: number): void; /** * Transforms a block by setting the translation on the transform attribute * of the block's SVG. @@ -15206,9 +18053,9 @@ declare module "block_svg" { */ translate(x: number, y: number): void; /** - * Move this block to its workspace's drag surface, accounting for positioning. - * Generally should be called at the same time as setDragging_(true). - * Does nothing if useDragSurface_ is false. + * Move this block to its workspace's drag surface, accounting for + * positioning. Generally should be called at the same time as + * setDragging_(true). Does nothing if useDragSurface_ is false. * @package */ moveToDragSurface(): void; @@ -15216,7 +18063,13 @@ declare module "block_svg" { * Move a block to a position. * @param {Coordinate} xy The position to move to in workspace units. */ - moveTo(xy: Coordinate): void; + moveTo(xy: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): void; /** * Move this block back to the workspace block canvas. * Generally should be called at the same time as setDragging_(false). @@ -15225,7 +18078,13 @@ declare module "block_svg" { * on the workspace canvas, in workspace coordinates. * @package */ - moveOffDragSurface(newXY: Coordinate): void; + moveOffDragSurface(newXY: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): void; /** * Move this block during a drag, taking into account whether we are using a * drag surface to translate blocks. @@ -15234,7 +18093,13 @@ declare module "block_svg" { * workspace coordinates. * @package */ - moveDuringDrag(newLoc: Coordinate): void; + moveDuringDrag(newLoc: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): void; /** * Clear the block of transform="..." attributes. * Used when the block is switching from 3d to 2d transform or vice versa. @@ -15251,17 +18116,19 @@ declare module "block_svg" { * Coordinate system: workspace coordinates. * @return {!Rect} Object with coordinates of the bounding box. */ - getBoundingRectangle(): Rect; + getBoundingRectangle(): { + top: number; + bottom: number; + left: number; + right: number; + contains(x: number, y: number): boolean; + intersects(other: any): boolean; + }; /** * Notify every input on this block to mark its fields as dirty. * A dirty field is a field that needs to be re-rendered. */ markDirty(): void; - /** - * Set whether the block is collapsed or not. - * @param {boolean} collapsed True if collapsed. - */ - setCollapsed(collapsed: boolean): void; /** * Makes sure that when the block is collapsed, it is rendered correctly * for that state. @@ -15290,7 +18157,7 @@ declare module "block_svg" { * @return {?Array} Context menu options or null if no menu. * @protected */ - protected generateContextMenu(): Array | null; + protected generateContextMenu(): Array | null; /** * Show the context menu for this block. * @param {!Event} e Mouse event. @@ -15308,59 +18175,23 @@ declare module "block_svg" { */ moveConnections(dx: number, dy: number): void; /** - * Recursively adds or removes the dragging class to this node and its children. + * Recursively adds or removes the dragging class to this node and its + * children. * @param {boolean} adding True if adding, false if removing. * @package */ setDragging(adding: boolean): void; - /** - * Set whether this block is movable or not. - * @param {boolean} movable True if movable. - */ - setMovable(movable: boolean): void; - /** - * Set whether this block is editable or not. - * @param {boolean} editable True if editable. - */ - setEditable(editable: boolean): void; - /** - * Sets whether this block is a shadow block or not. - * @param {boolean} shadow True if a shadow. - * @package - */ - setShadow(shadow: boolean): void; - /** - * Set whether this block is an insertion marker block or not. - * Once set this cannot be unset. - * @param {boolean} insertionMarker True if an insertion marker. - * @package - */ - setInsertionMarker(insertionMarker: boolean): void; - isInsertionMarker_: any; /** * Return the root node of the SVG or null if none exists. * @return {!SVGGElement} The root SVG node (probably a group). */ getSvgRoot(): SVGGElement; /** - * Dispose of this block. - * @param {boolean=} healStack If true, then try to heal any gap by connecting - * the next statement with the previous statement. Otherwise, dispose of - * all children of this block. - * @param {boolean=} animate If true, show a disposal animation and sound. - * @suppress {checkTypes} - */ - dispose(healStack?: boolean | undefined, animate?: boolean | undefined): void; - warningTextDb_: { - [x: string]: number; - }; - /** - * Delete a block and hide chaff when doing so. The block will not be deleted if - * it's in a flyout. This is called from the context menu and keyboard shortcuts - * as the full delete action. If you are disposing of a block from the workspace - * and don't need to perform flyout checks, handle event grouping, or hide - * chaff, then use `block.dispose()` directly. - * @package + * Delete a block and hide chaff when doing so. The block will not be deleted + * if it's in a flyout. This is called from the context menu and keyboard + * shortcuts as the full delete action. If you are disposing of a block from + * the workspace and don't need to perform flyout checks, handle event + * grouping, or hide chaff, then use `block.dispose()` directly. */ checkAndDelete(): void; /** @@ -15387,32 +18218,6 @@ declare module "block_svg" { * @return {?Comment} The comment icon attached to this block, or null. */ getCommentIcon(): Comment | null; - /** - * Set this block's comment text. - * @param {?string} text The text, or null to delete. - */ - setCommentText(text: string | null): void; - commentIcon_: Comment | null; - comment: Comment | null; - /** - * Set this block's warning text. - * @param {?string} text The text, or null to delete. - * @param {string=} opt_id An optional ID for the warning text to be able to - * maintain multiple warnings. - */ - setWarningText(text: string | null, opt_id?: string | undefined): void; - warning: Warning | null; - /** - * Give this block a mutator dialog. - * @param {?Mutator} mutator A mutator dialog instance or null to remove. - */ - setMutator(mutator: Mutator | null): void; - mutator: any; - /** - * Set whether the block is enabled or not. - * @param {boolean} enabled True if enabled. - */ - setEnabled(enabled: boolean): void; /** * Set whether the block is highlighted or not. Block highlighting is * often used to visually mark blocks currently being executed. @@ -15438,25 +18243,6 @@ declare module "block_svg" { * @package */ setDeleteStyle(enable: boolean): void; - /** - * Get the colour of a block. - * @return {string} #RRGGBB string. - */ - getColour(): string; - /** - * Change the colour of a block. - * @param {number|string} colour HSV hue value, or #RRGGBB string. - */ - setColour(colour: number | string): void; - styleName_: string; - /** - * Set the style and colour values of a block. - * @param {string} blockStyleName Name of the block style. - * @throws {Error} if the block style does not exist. - */ - setStyle(blockStyleName: string): void; - hat: string; - colour_: string; /** * Move this block to the front of the visible workspace. * tags do not respect z-index so SVG renders them in the @@ -15465,58 +18251,6 @@ declare module "block_svg" { * @package */ bringToFront(): void; - /** - * Set whether this block can chain onto the bottom of another block. - * @param {boolean} newBoolean True if there can be a previous statement. - * @param {(string|Array|null)=} opt_check Statement type or - * list of statement types. Null/undefined if any type could be connected. - */ - setPreviousStatement(newBoolean: boolean, opt_check?: (string | Array | null) | undefined): void; - /** - * Set whether another block can chain onto the bottom of this block. - * @param {boolean} newBoolean True if there can be a next statement. - * @param {(string|Array|null)=} opt_check Statement type or - * list of statement types. Null/undefined if any type could be connected. - */ - setNextStatement(newBoolean: boolean, opt_check?: (string | Array | null) | undefined): void; - /** - * Set whether this block returns a value. - * @param {boolean} newBoolean True if there is an output. - * @param {(string|Array|null)=} opt_check Returned type or list - * of returned types. Null or undefined if any type could be returned - * (e.g. variable get). - */ - setOutput(newBoolean: boolean, opt_check?: (string | Array | null) | undefined): void; - /** - * Set whether value inputs are arranged horizontally or vertically. - * @param {boolean} newBoolean True if inputs are horizontal. - */ - setInputsInline(newBoolean: boolean): void; - /** - * Remove an input from this block. - * @param {string} name The name of the input. - * @param {boolean=} opt_quiet True to prevent error if input is not present. - * @return {boolean} True if operation succeeds, false if input is not present - * and opt_quiet is true - * @throws {Error} if the input is not present and opt_quiet is not true. - */ - removeInput(name: string, opt_quiet?: boolean | undefined): boolean; - /** - * Move a numbered input to a different location on this block. - * @param {number} inputIndex Index of the input to move. - * @param {number} refIndex Index of input that should be after the moved input. - */ - moveNumberedInputBefore(inputIndex: number, refIndex: number): void; - /** - * Add a value input, statement input or local variable to this block. - * @param {number} type One of Blockly.inputTypes. - * @param {string} name Language-neutral identifier which may used to find this - * input again. Should be unique to this block. - * @return {!Input} The input object created. - * @protected - * @override - */ - protected override appendInput_(type: number, name: string): Input; /** * Sets whether this block's connections are tracked in the database or not. * @@ -15527,50 +18261,6 @@ declare module "block_svg" { * @package */ setConnectionTracking(track: boolean): void; - /** - * Returns connections originating from this block. - * @param {boolean} all If true, return all connections even hidden ones. - * Otherwise, for a non-rendered block return an empty list, and for a - * collapsed block don't return inputs connections. - * @return {!Array} Array of connections. - * @package - */ - getConnections_(all: boolean): Array; - /** - * Walks down a stack of blocks and finds the last next connection on the stack. - * @param {boolean} ignoreShadows If true,the last connection on a non-shadow - * block will be returned. If false, this will follow shadows to find the - * last connection. - * @return {?RenderedConnection} The last next connection on the stack, - * or null. - * @package - * @override - */ - override lastConnectionInStack(ignoreShadows: boolean): RenderedConnection | null; - /** - * Find the connection on this block that corresponds to the given connection - * on the other block. - * Used to match connections between a block and its insertion marker. - * @param {!Block} otherBlock The other block to match against. - * @param {!Connection} conn The other connection to match. - * @return {?RenderedConnection} The matching connection on this block, - * or null. - * @package - * @override - */ - override getMatchingConnection(otherBlock: Block, conn: Connection): RenderedConnection | null; - /** - * Create a connection of the specified type. - * @param {number} type The type of the connection to create. - * @return {!RenderedConnection} A new connection of the specified type. - * @protected - */ - protected makeConnection_(type: number): RenderedConnection; - /** - * Bump unconnected blocks out of alignment. Two blocks which aren't actually - * connected should not coincidentally line up on screen. - */ - bumpNeighbours(): void; /** * Schedule snapping to grid and bumping neighbours to occur after a brief * delay. @@ -15579,8 +18269,8 @@ declare module "block_svg" { scheduleSnapAndBump(): void; /** * Position a block so that it doesn't move the target block when connected. - * The block to position is usually either the first block in a dragged stack or - * an insertion marker. + * The block to position is usually either the first block in a dragged stack + * or an insertion marker. * @param {!RenderedConnection} sourceConnection The connection on the * moving block's stack. * @param {!RenderedConnection} targetConnection The connection that @@ -15588,19 +18278,6 @@ declare module "block_svg" { * @package */ positionNearConnection(sourceConnection: RenderedConnection, targetConnection: RenderedConnection): void; - /** - * Return the parent block or null if this block is at the top level. - * @return {?BlockSvg} The block (if any) that holds the current block. - * @override - */ - override getParent(): BlockSvg | null; - /** - * Return the top-most block in this block's tree. - * This will return itself if this block is at the top level. - * @return {!BlockSvg} The root block. - * @override - */ - override getRootBlock(): BlockSvg; /** * Lays out and reflows a block based on its contents and settings. * @param {boolean=} opt_bubble If false, just render this block. @@ -15613,9 +18290,9 @@ declare module "block_svg" { */ protected updateMarkers_(): void; /** - * Update all of the connections on this block with the new locations calculated - * during rendering. Also move all of the connected blocks based on the new - * connection locations. + * Update all of the connections on this block with the new locations + * calculated during rendering. Also move all of the connected blocks based + * on the new connection locations. * @private */ private updateConnectionLocations_; @@ -15645,8 +18322,9 @@ declare module "block_svg" { width: number; }; /** - * Visual effect to show that if the dragging block is dropped, this block will - * be replaced. If a shadow block, it will disappear. Otherwise it will bump. + * Visual effect to show that if the dragging block is dropped, this block + * will be replaced. If a shadow block, it will disappear. Otherwise it will + * bump. * @param {boolean} add True if highlighting should be added. * @package */ @@ -15659,2785 +18337,1536 @@ declare module "block_svg" { * @package */ highlightShapeForInput(conn: Connection, add: boolean): void; - /** - * Height of this block, not including any statement blocks above or below. - * Height is in workspace units. - */ - height: number; - /** - * Width of this block, including any connected value blocks. - * Width is in workspace units. - */ - width: number; - /** - * An optional method called when a mutator dialog is first opened. - * This function must create and initialize a top-level block for the mutator - * dialog, and return it. This function should also populate this top-level - * block with any sub-blocks which are appropriate. This method must also be - * coupled with defining a `compose` method for the default mutation dialog - * button and UI to appear. - * @type {?function(WorkspaceSvg):!BlockSvg} - */ - decompose: ((arg0: WorkspaceSvg) => BlockSvg) | null; - /** - * An optional method called when a mutator dialog saves its content. - * This function is called to modify the original block according to new - * settings. This method must also be coupled with defining a `decompose` - * method for the default mutation dialog button and UI to appear. - * @type {?function(!BlockSvg)} - */ - compose: ((arg0: BlockSvg) => any) | null; - /** - * An optional method for defining custom block context menu items. - * @type {?function(!Array)} - */ - customContextMenu: ((arg0: Array) => any) | null; - /** - * An property used internally to reference the block's rendering debugger. - * @type {?BlockRenderingDebug} - * @package - */ - renderingDebugger: BlockRenderingDebug | null; } export namespace BlockSvg { const INLINE: number; const COLLAPSED_WARNING_ID: string; } - import { Theme } from "theme"; - import { IPathObject } from "renderers/common/i_path_object"; - import { WorkspaceSvg } from "workspace_svg"; - import { RenderedConnection } from "rendered_connection"; - import { Icon } from "icon"; - import { Block } from "block"; - import { Coordinate } from "utils/coordinate"; - import { Rect } from "utils/rect"; - import { Field } from "field"; - import { ICopyable } from "interfaces/i_copyable"; - import { Comment } from "comment"; - import { Warning } from "warning"; - import { Mutator } from "mutator"; - import { Input } from "input"; - import { Connection } from "connection"; - import { Debug as BlockRenderingDebug } from "renderers/common/debugger"; + import { IASTNodeLocationSvg } from "core/interfaces/i_ast_node_location_svg"; + import { IBoundedElement } from "core/interfaces/i_bounded_element"; + import { ICopyable } from "core/interfaces/i_copyable"; + import { IDraggable } from "core/interfaces/i_draggable"; + import { Block } from "core/block"; + import { WorkspaceSvg } from "core/workspace_svg"; + import { Debug as BlockRenderingDebug } from "core/renderers/common/debugger"; + import { Mutator } from "core/mutator"; + import { Warning } from "core/warning"; + import { Theme } from "core/theme"; + import { IPathObject } from "core/renderers/common/i_path_object"; + import { Icon } from "core/icon"; + import { Field } from "core/field"; + import { Comment } from "core/comment"; + import { RenderedConnection } from "core/rendered_connection"; + import { Connection } from "core/connection"; } -declare module "block_animations" { +declare module "core/serialization/blocks" { /** - * Play some UI effects (sound, animation) when disposing of a block. - * @param {!BlockSvg} block The block being disposed of. - * @alias Blockly.blockAnimations.disposeUiEffect - * @package + * Represents the state of a connection. */ - export function disposeUiEffect(block: BlockSvg): void; - /** - * Play some UI effects (sound, ripple) after a connection has been established. - * @param {!BlockSvg} block The block being connected. - * @alias Blockly.blockAnimations.connectionUiEffect - * @package - */ - export function connectionUiEffect(block: BlockSvg): void; - /** - * Play some UI effects (sound, animation) when disconnecting a block. - * @param {!BlockSvg} block The block being disconnected. - * @alias Blockly.blockAnimations.disconnectUiEffect - * @package - */ - export function disconnectUiEffect(block: BlockSvg): void; - /** - * Stop the disconnect UI animation immediately. - * @alias Blockly.blockAnimations.disconnectUiStop - * @package - */ - export function disconnectUiStop(): void; - import { BlockSvg } from "block_svg"; -} -declare module "bubble_dragger" { - export class BubbleDragger { - /** - * Class for a bubble dragger. It moves things on the bubble canvas around the - * workspace when they are being dragged by a mouse or touch. These can be - * block comments, mutators, warnings, or workspace comments. - * @param {!IBubble} bubble The item on the bubble canvas to drag. - * @param {!WorkspaceSvg} workspace The workspace to drag on. - * @constructor - * @alias Blockly.BubbleDragger - */ - constructor(bubble: IBubble, workspace: WorkspaceSvg); - /** - * The item on the bubble canvas that is being dragged. - * @type {!IBubble} - * @private - */ - private draggingBubble_; - /** - * The workspace on which the bubble is being dragged. - * @type {!WorkspaceSvg} - * @private - */ - private workspace_; - /** - * Which drag target the mouse pointer is over, if any. - * @type {?IDragTarget} - * @private - */ - private dragTarget_; - /** - * Whether the bubble would be deleted if dropped immediately. - * @type {boolean} - * @private - */ - private wouldDeleteBubble_; - /** - * The location of the top left corner of the dragging bubble's body at the - * beginning of the drag, in workspace coordinates. - * @type {!Coordinate} - * @private - */ - private startXY_; - /** - * 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 {BlockDragSurfaceSvg} - * @private - */ - private dragSurface_; - /** - * Sever all links from this object. - * @package - * @suppress {checkTypes} - */ - dispose(): void; - /** - * Start dragging a bubble. This includes moving it to the drag surface. - * @package - */ - startBubbleDrag(): void; - /** - * Execute a step of bubble dragging, based on the given event. Update the - * display accordingly. - * @param {!Event} e The most recent move event. - * @param {!Coordinate} currentDragDeltaXY How far the pointer has - * moved from the position at the start of the drag, in pixel units. - * @package - */ - dragBubble(e: Event, currentDragDeltaXY: Coordinate): void; - /** - * Whether ending the drag would delete the bubble. - * @param {?IDragTarget} dragTarget The drag target that the bubblee is - * currently over. - * @return {boolean} Whether dropping the bubble immediately would delete the - * block. - * @private - */ - private shouldDelete_; - /** - * Update the cursor (and possibly the trash can lid) to reflect whether the - * dragging bubble would be deleted if released immediately. - * @private - */ - private updateCursorDuringBubbleDrag_; - /** - * Finish a bubble drag and put the bubble back on the workspace. - * @param {!Event} e The mouseup/touchend event. - * @param {!Coordinate} currentDragDeltaXY How far the pointer has - * moved from the position at the start of the drag, in pixel units. - * @package - */ - endBubbleDrag(e: Event, currentDragDeltaXY: Coordinate): void; - /** - * Fire a move event at the end of a bubble drag. - * @private - */ - private fireMoveEvent_; - /** - * 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 {!Coordinate} pixelCoord A coordinate with x and y - * values in CSS pixel units. - * @return {!Coordinate} The input coordinate divided by the - * workspace scale. - * @private - */ - private pixelsToWorkspaceUnits_; - /** - * 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 - */ - private moveToDragSurface_; - } - import { Coordinate } from "utils/coordinate"; - import { IBubble } from "interfaces/i_bubble"; - import { WorkspaceSvg } from "workspace_svg"; -} -declare module "interfaces/i_block_dragger" { - /** - * A block dragger interface. - * @interface - * @alias Blockly.IBlockDragger - */ - export class IBlockDragger { - } -} -declare module "workspace_dragger" { - /** - * Class for a workspace dragger. It moves the workspace around when it is - * being dragged by a mouse or touch. - * Note that the workspace itself manages whether or not it has a drag surface - * and how to do translations based on that. This simply passes the right - * commands based on events. - * @param {!WorkspaceSvg} workspace The workspace to drag. - * @constructor - * @alias Blockly.WorkspaceDragger - */ - export class WorkspaceDragger { - constructor(workspace: any); - /** - * @type {!WorkspaceSvg} - * @private - */ - private workspace_; - /** - * Whether horizontal scroll is enabled. - * @type {boolean} - * @private - */ - private horizontalScrollEnabled_; - /** - * Whether vertical scroll is enabled. - * @type {boolean} - * @private - */ - private verticalScrollEnabled_; - /** - * The scroll position of the workspace at the beginning of the drag. - * Coordinate system: pixel coordinates. - * @type {!Coordinate} - * @protected - */ - protected startScrollXY_: Coordinate; - /** - * Sever all links from this object. - * @package - * @suppress {checkTypes} - */ - dispose(): void; - /** - * Start dragging the workspace. - * @package - */ - startDrag(): void; - /** - * Finish dragging the workspace and put everything back where it belongs. - * @param {!Coordinate} currentDragDeltaXY How far the pointer has - * moved from the position at the start of the drag, in pixel coordinates. - * @package - */ - endDrag(currentDragDeltaXY: Coordinate): void; - /** - * Move the workspace based on the most recent mouse movements. - * @param {!Coordinate} currentDragDeltaXY How far the pointer has - * moved from the position at the start of the drag, in pixel coordinates. - * @package - */ - drag(currentDragDeltaXY: Coordinate): void; - } - import { Coordinate } from "utils/coordinate"; -} -declare module "bump_objects" { - /** - * Bumps the given object that has passed out of bounds. - * @param {!WorkspaceSvg} workspace The workspace containing the object. - * @param {!MetricsManager.ContainerRegion} scrollMetrics Scroll metrics - * in workspace coordinates. - * @param {!IBoundedElement} object The object to bump. - * @return {boolean} True if block was bumped. - * @alias Blockly.bumpObjects.bumpIntoBounds - */ - function bumpObjectIntoBounds(workspace: WorkspaceSvg, scrollMetrics: MetricsManager.ContainerRegion, object: IBoundedElement): boolean; - /** - * Creates a handler for bumping objects when they cross fixed bounds. - * @param {!WorkspaceSvg} workspace The workspace to handle. - * @return {function(Abstract)} The event handler. - * @alias Blockly.bumpObjects.bumpIntoBoundsHandler - */ - export function bumpIntoBoundsHandler(workspace: WorkspaceSvg): (arg0: typeof Abstract) => any; - /** - * Bumps the top objects in the given workspace into bounds. - * @param {!WorkspaceSvg} workspace The workspace. - * @alias Blockly.bumpObjects.bumpTopObjectsIntoBounds - */ - export function bumpTopObjectsIntoBounds(workspace: WorkspaceSvg): void; - import { WorkspaceSvg } from "workspace_svg"; - import { MetricsManager } from "metrics_manager"; - import { IBoundedElement } from "interfaces/i_bounded_element"; - import * as Abstract from "events/events_abstract"; - export { bumpObjectIntoBounds as bumpIntoBounds }; -} -declare module "events/events_block_drag" { - /** - * Class for a block drag event. - * @param {!Block=} opt_block The top block in the stack that is being - * dragged. Undefined for a blank event. - * @param {boolean=} opt_isStart Whether this is the start of a block drag. - * Undefined for a blank event. - * @param {!Array=} opt_blocks The blocks affected by this - * drag. Undefined for a blank event. - * @extends {UiBase} - * @constructor - * @alias Blockly.Events.BlockDrag - */ - export class BlockDrag { - constructor(opt_block: any, opt_isStart: any, opt_blocks: any); - blockId: any; - /** - * Whether this is the start of a block drag. - * @type {boolean|undefined} - */ - isStart: boolean | undefined; - /** - * The blocks affected by this drag event. - * @type {!Array|undefined} - */ - blocks: Array | undefined; - /** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ - toJson(): any; - /** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ - fromJson(json: any): void; - /** - * Type of this event. - * @type {string} - */ + export type ConnectionState = { + shadow: (any | undefined); + block: (any | undefined); + }; + export type State = { type: string; - } - import { Block } from "block"; + id: (string | undefined); + x: (number | undefined); + y: (number | undefined); + collapsed: (boolean | undefined); + enabled: (boolean | undefined); + inline: (boolean | undefined); + data: (string | undefined); + 'extra-state': (any | undefined); + icons: ({ + [x: string]: any; + } | undefined); + fields: ({ + [x: string]: any; + } | undefined); + inputs: ({ + [x: string]: ConnectionState; + } | undefined); + next: (ConnectionState | undefined); + }; + /** + * Represents the state of a given block. + * @typedef {{ + * type: string, + * id: (string|undefined), + * x: (number|undefined), + * y: (number|undefined), + * collapsed: (boolean|undefined), + * enabled: (boolean|undefined), + * inline: (boolean|undefined), + * data: (string|undefined), + * 'extra-state': (*|undefined), + * icons: (!Object|undefined), + * fields: (!Object|undefined), + * inputs: (!Object|undefined), + * next: (!ConnectionState|undefined) + * }} + * @alias Blockly.serialization.blocks.State + */ + export let State: any; + /** + * Returns the state of the given block as a plain JavaScript object. + * @param {!Block} block The block to serialize. + * @param {{addCoordinates: (boolean|undefined), addInputBlocks: + * (boolean|undefined), addNextBlocks: (boolean|undefined), + * doFullSerialization: (boolean|undefined)}=} param1 + * addCoordinates: If true, the coordinates of the block are added to the + * serialized state. False by default. + * addinputBlocks: If true, children of the block which are connected to + * inputs will be serialized. True by default. + * addNextBlocks: If true, children of the block which are connected to the + * block's next connection (if it exists) will be serialized. + * True by default. + * doFullSerialization: If true, fields that normally just save a reference + * to some external state (eg variables) will instead serialize all of the + * info about that state. This supports deserializing the block into a + * workspace where that state doesn't yet exist. True by default. + * @return {?State} The serialized state of the block, or null if the block + * could not be serialied (eg it was an insertion marker). + * @alias Blockly.serialization.blocks.save + */ + export function save(block: Block, { addCoordinates, addInputBlocks, addNextBlocks, doFullSerialization, }?: { + addCoordinates: (boolean | undefined); + addInputBlocks: (boolean | undefined); + addNextBlocks: (boolean | undefined); + doFullSerialization: (boolean | undefined); + } | undefined): State | null; + /** + * Loads the block represented by the given state into the given workspace. + * @param {!State} state The state of a block to deserialize into the workspace. + * @param {!Workspace} workspace The workspace to add the block to. + * @param {{recordUndo: (boolean|undefined)}=} param1 + * recordUndo: If true, events triggered by this function will be undo-able + * by the user. False by default. + * @return {!Block} The block that was just loaded. + * @alias Blockly.serialization.blocks.append + */ + export function append(state: State, workspace: Workspace, { recordUndo }?: { + recordUndo: (boolean | undefined); + } | undefined): Block; + /** + * Loads the block represented by the given state into the given workspace. + * This is defined internally so that the extra parameters don't clutter our + * external API. + * But it is exported so that other places within Blockly can call it directly + * with the extra parameters. + * @param {!State} state The state of a block to deserialize into the workspace. + * @param {!Workspace} workspace The workspace to add the block to. + * @param {{parentConnection: (!Connection|undefined), isShadow: + * (boolean|undefined), recordUndo: (boolean|undefined)}=} param1 + * parentConnection: If provided, the system will attempt to connect the + * block to this connection after it is created. Undefined by default. + * isShadow: If true, the block will be set to a shadow block after it is + * created. False by default. + * recordUndo: If true, events triggered by this function will be undo-able + * by the user. False by default. + * @return {!Block} The block that was just appended. + * @alias Blockly.serialization.blocks.appendInternal + * @package + */ + export function appendInternal(state: State, workspace: Workspace, { parentConnection, isShadow, recordUndo, }?: { + parentConnection: (Connection | undefined); + isShadow: (boolean | undefined); + recordUndo: (boolean | undefined); + } | undefined): Block; + import { Block } from "core/block"; + import { Workspace } from "core/workspace"; + import { Connection } from "core/connection"; } -declare module "block_dragger" { - export class BlockDragger { +declare module "core/interfaces/i_selectable_toolbox_item" { + /** + * Interface for an item in the toolbox that can be selected. + * @extends {IToolboxItem} + * @interface + * @alias Blockly.ISelectableToolboxItem + */ + export class ISelectableToolboxItem {} +} +declare module "core/interfaces/i_collapsible_toolbox_item" { + /** + * Interface for an item in the toolbox that can be collapsed. + * @extends {ISelectableToolboxItem} + * @interface + * @alias Blockly.ICollapsibleToolboxItem + */ + export class ICollapsibleToolboxItem {} +} +declare module "core/toolbox/toolbox_item" { + /** + * Class for an item in the toolbox. + * @implements {IToolboxItem} + * @alias Blockly.ToolboxItem + */ + export class ToolboxItem implements IToolboxItem { /** - * Class for a block dragger. It moves blocks around the workspace when they - * are being dragged by a mouse or touch. - * @param {!BlockSvg} block The block to drag. - * @param {!WorkspaceSvg} workspace The workspace to drag on. - * @constructor - * @implements {IBlockDragger} - * @alias Blockly.BlockDragger + * @param {!ToolboxItemInfo} toolboxItemDef The JSON defining + * the toolbox item. + * @param {!IToolbox} toolbox The toolbox that holds the toolbox item. + * @param {ICollapsibleToolboxItem=} opt_parent The parent toolbox item + * or null if the category does not have a parent. */ - constructor(block: BlockSvg, workspace: WorkspaceSvg); + constructor(toolboxItemDef: ToolboxItemInfo, toolbox: () => void, opt_parent?: (() => void) | undefined); /** - * The top block in the stack that is being dragged. - * @type {!BlockSvg} + * The id for the category. + * @type {string} * @protected */ - protected draggingBlock_: BlockSvg; + protected id_: string; /** - * The workspace on which the block is being dragged. + * The parent of the category. + * @type {?ICollapsibleToolboxItem} + * @protected + */ + protected parent_: (() => void) | null; + /** + * The level that the category is nested at. + * @type {number} + * @protected + */ + protected level_: number; + /** + * The JSON definition of the toolbox item. + * @type {?ToolboxItemInfo} + * @protected + */ + protected toolboxItemDef_: ToolboxItemInfo | null; + /** + * The toolbox this category belongs to. + * @type {!IToolbox} + * @protected + */ + protected parentToolbox_: () => void; + /** + * The workspace of the parent toolbox. * @type {!WorkspaceSvg} * @protected */ protected workspace_: WorkspaceSvg; /** - * Object that keeps track of connections on dragged blocks. - * @type {!InsertionMarkerManager} + * Initializes the toolbox item. + * This includes creating the DOM and updating the state of any items based + * on the info object. + * @public + */ + public init(): void; + /** + * Gets the div for the toolbox item. + * @return {?Element} The div for the toolbox item. + * @public + */ + public getDiv(): Element | null; + /** + * Gets the HTML element that is clickable. + * The parent toolbox element receives clicks. The parent toolbox will add an + * ID to this element so it can pass the onClick event to the correct + * toolboxItem. + * @return {?Element} The HTML element that receives clicks, or null if this + * item should not receive clicks. + * @public + */ + public getClickTarget(): Element | null; + /** + * Gets a unique identifier for this toolbox item. + * @return {string} The ID for the toolbox item. + * @public + */ + public getId(): string; + /** + * Gets the parent if the toolbox item is nested. + * @return {?IToolboxItem} The parent toolbox item, or null if + * this toolbox item is not nested. + * @public + */ + public getParent(): (() => void) | null; + /** + * Gets the nested level of the category. + * @return {number} The nested level of the category. + * @package + */ + getLevel(): number; + /** + * Whether the toolbox item is selectable. + * @return {boolean} True if the toolbox item can be selected. + * @public + */ + public isSelectable(): boolean; + /** + * Whether the toolbox item is collapsible. + * @return {boolean} True if the toolbox item is collapsible. + * @public + */ + public isCollapsible(): boolean; + /** + * Dispose of this toolbox item. No-op by default. + * @public + */ + public dispose(): void; + } + import { IToolboxItem } from "core/interfaces/i_toolbox_item"; + import { ToolboxItemInfo } from "core/utils/toolbox"; + import { WorkspaceSvg } from "core/workspace_svg"; +} +declare module "core/toolbox/category" { + /** + * Class for a category in a toolbox. + * @implements {ISelectableToolboxItem} + * @alias Blockly.ToolboxCategory + */ + export class ToolboxCategory extends ToolboxItem implements ISelectableToolboxItem { + /** + * @param {!CategoryInfo} categoryDef The information needed + * to create a category in the toolbox. + * @param {!IToolbox} toolbox The parent toolbox for the category. + * @param {ICollapsibleToolboxItem=} opt_parent The parent category or null if + * the category does not have a parent. + */ + constructor(categoryDef: CategoryInfo, toolbox: () => void, opt_parent?: (() => void) | undefined); + /** + * The name that will be displayed on the category. + * @type {string} * @protected */ - protected draggedConnectionManager_: InsertionMarkerManager; + protected name_: string; /** - * Which drag area the mouse pointer is over, if any. - * @type {?IDragTarget} - * @private + * The colour of the category. + * @type {string} + * @protected */ - private dragTarget_; + protected colour_: string; /** - * Whether the block would be deleted if dropped immediately. + * The html container for the category. + * @type {?HTMLDivElement} + * @protected + */ + protected htmlDiv_: HTMLDivElement | null; + /** + * The html element for the category row. + * @type {?HTMLDivElement} + * @protected + */ + protected rowDiv_: HTMLDivElement | null; + /** + * The html element that holds children elements of the category row. + * @type {?HTMLDivElement} + * @protected + */ + protected rowContents_: HTMLDivElement | null; + /** + * The html element for the toolbox icon. + * @type {?Element} + * @protected + */ + protected iconDom_: Element | null; + /** + * The html element for the toolbox label. + * @type {?Element} + * @protected + */ + protected labelDom_: Element | null; + /** + * All the css class names that are used to create a category. + * @type {!ToolboxCategory.CssConfig} + * @protected + */ + protected cssConfig_: ToolboxCategory.CssConfig; + /** + * True if the category is meant to be hidden, false otherwise. * @type {boolean} * @protected */ - protected wouldDeleteBlock_: boolean; + protected isHidden_: boolean; /** - * The location of the top left corner of the dragging block at the beginning - * of the drag in workspace coordinates. - * @type {!Coordinate} + * True if this category is disabled, false otherwise. + * @type {boolean} * @protected */ - protected startXY_: Coordinate; + protected isDisabled_: boolean; /** - * A list of all of the icons (comment, warning, and mutator) that are - * on this block and its descendants. Moving an icon moves the bubble that - * extends from it if that bubble is open. - * @type {Array} + * The flyout items for this category. + * @type {string|!FlyoutItemInfoArray} * @protected */ - protected dragIconData_: Array; + protected flyoutItems_: string | any; /** - * Sever all links from this object. - * @package + * Creates an object holding the default classes for a category. + * @return {!ToolboxCategory.CssConfig} The configuration object holding + * all the CSS classes for a category. + * @protected */ - dispose(): void; + protected makeDefaultCssConfig_(): ToolboxCategory.CssConfig; /** - * Start dragging a block. This includes moving it to the drag surface. - * @param {!Coordinate} currentDragDeltaXY How far the pointer has - * moved from the position at mouse down, in pixel units. - * @param {boolean} healStack Whether or not to heal the stack after - * disconnecting. + * Parses the contents array depending on if the category is a dynamic + * category, or if its contents are meant to be shown in the flyout. + * @param {!CategoryInfo} categoryDef The information needed + * to create a category. + * @protected + */ + protected parseContents_(categoryDef: CategoryInfo): void; + /** + * Parses the non-contents parts of the category def. + * @param {!CategoryInfo} categoryDef The information needed to create + * a category. + * @protected + */ + protected parseCategoryDef_(categoryDef: CategoryInfo): void; + /** + * Creates the DOM for the category. + * @return {!HTMLDivElement} The parent element for the category. + * @protected + */ + protected createDom_(): HTMLDivElement; + /** + * Creates the container that holds the row and any subcategories. + * @return {!HTMLDivElement} The div that holds the icon and the label. + * @protected + */ + protected createContainer_(): HTMLDivElement; + /** + * Creates the parent of the contents container. All clicks will happen on + * this div. + * @return {!HTMLDivElement} The div that holds the contents container. + * @protected + */ + protected createRowContainer_(): HTMLDivElement; + /** + * Creates the container for the label and icon. + * This is necessary so we can set all subcategory pointer events to none. + * @return {!HTMLDivElement} The div that holds the icon and the label. + * @protected + */ + protected createRowContentsContainer_(): HTMLDivElement; + /** + * Creates the span that holds the category icon. + * @return {!Element} The span that holds the category icon. + * @protected + */ + protected createIconDom_(): Element; + /** + * Creates the span that holds the category label. + * This should have an ID for accessibility purposes. + * @param {string} name The name of the category. + * @return {!Element} The span that holds the category label. + * @protected + */ + protected createLabelDom_(name: string): Element; + /** + * Updates the colour for this category. * @public */ - public startDrag(currentDragDeltaXY: Coordinate, healStack: boolean): void; + public refreshTheme(): void; /** - * Whether or not we should disconnect the block when a drag is started. - * @param {boolean} healStack Whether or not to heal the stack after - * disconnecting. - * @return {boolean} True to disconnect the block, false otherwise. + * Add the strip of colour to the toolbox category. + * @param {string} colour The category colour. * @protected */ - protected shouldDisconnect_(healStack: boolean): boolean; + protected addColourBorder_(colour: string): void; /** - * Disconnects the block and moves it to a new location. - * @param {boolean} healStack Whether or not to heal the stack after - * disconnecting. - * @param {!Coordinate} currentDragDeltaXY How far the pointer has - * moved from the position at mouse down, in pixel units. + * Gets either the colour or the style for a category. + * @param {!CategoryInfo} categoryDef The object holding + * information on the category. + * @return {string} The hex colour for the category. * @protected */ - protected disconnectBlock_(healStack: boolean, currentDragDeltaXY: Coordinate): void; + protected getColour_(categoryDef: CategoryInfo): string; /** - * Fire a UI event at the start of a block drag. + * Sets the colour for the category using the style name and returns the new + * colour as a hex string. + * @param {string} styleName Name of the style. + * @return {string} The hex colour for the category. + * @private + */ + private getColourfromStyle_; + /** + * Parses the colour on the category. + * @param {number|string} colourValue HSV hue value (0 to 360), #RRGGBB + * string, or a message reference string pointing to one of those two + * values. + * @return {string} The hex colour for the category. + * @private + */ + private parseColour_; + /** + * Adds appropriate classes to display an open icon. + * @param {?Element} iconDiv The div that holds the icon. * @protected */ - protected fireDragStartEvent_(): void; + protected openIcon_(iconDiv: Element | null): void; /** - * Execute a step of block dragging, based on the given event. Update the - * display accordingly. - * @param {!Event} e The most recent move event. - * @param {!Coordinate} currentDragDeltaXY How far the pointer has - * moved from the position at the start of the drag, in pixel units. + * Adds appropriate classes to display a closed icon. + * @param {?Element} iconDiv The div that holds the icon. + * @protected + */ + protected closeIcon_(iconDiv: Element | null): void; + /** + * Sets whether the category is visible or not. + * For a category to be visible its parent category must also be expanded. + * @param {boolean} isVisible True if category should be visible. + * @protected + */ + protected setVisible_(isVisible: boolean): void; + /** + * Hide the category. + */ + hide(): void; + /** + * Show the category. Category will only appear if its parent category is also + * expanded. + */ + show(): void; + /** + * Whether the category is visible. + * A category is only visible if all of its ancestors are expanded and + * isHidden_ is false. + * @return {boolean} True if the category is visible, false otherwise. * @public */ - public drag(e: Event, currentDragDeltaXY: Coordinate): void; + public isVisible(): boolean; /** - * Finish a block drag and put the block back on the workspace. - * @param {!Event} e The mouseup/touchend event. - * @param {!Coordinate} currentDragDeltaXY How far the pointer has - * moved from the position at the start of the drag, in pixel units. - * @public - */ - public endDrag(e: Event, currentDragDeltaXY: Coordinate): void; - /** - * Calculates the drag delta and new location values after a block is dragged. - * @param {!Coordinate} currentDragDeltaXY How far the pointer has - * moved from the start of the drag, in pixel units. - * @return {{delta: !Coordinate, newLocation: - * !Coordinate}} New location after drag. delta is in - * workspace units. newLocation is the new coordinate where the block should - * end up. + * Whether all ancestors of a category (parent and parent's parent, etc.) are + * expanded. + * @return {boolean} True only if every ancestor is expanded * @protected */ - protected getNewLocationAfterDrag_(currentDragDeltaXY: Coordinate): { - delta: Coordinate; - newLocation: Coordinate; + protected allAncestorsExpanded_(): boolean; + /** + * Handles when the toolbox item is clicked. + * @param {!Event} _e Click event to handle. + * @public + */ + public onClick(_e: Event): void; + /** + * Sets the current category as selected. + * @param {boolean} isSelected True if this category is selected, false + * otherwise. + * @public + */ + public setSelected(isSelected: boolean): void; + /** + * Sets whether the category is disabled. + * @param {boolean} isDisabled True to disable the category, false otherwise. + */ + setDisabled(isDisabled: boolean): void; + /** + * Gets the name of the category. Used for emitting events. + * @return {string} The name of the toolbox item. + * @public + */ + public getName(): string; + /** + * Gets the contents of the category. These are items that are meant to be + * displayed in the flyout. + * @return {!FlyoutItemInfoArray|string} The definition + * of items to be displayed in the flyout. + * @public + */ + public getContents(): any | string; + /** + * Updates the contents to be displayed in the flyout. + * If the flyout is open when the contents are updated, refreshSelection on + * the toolbox must also be called. + * @param {!FlyoutDefinition|string} contents The contents + * to be displayed in the flyout. A string can be supplied to create a + * dynamic category. + * @public + */ + public updateFlyoutContents(contents: FlyoutDefinition | string): void; + } + export namespace ToolboxCategory { + const registrationName: string; + const nestedPadding: number; + const borderWidth: number; + const defaultBackgroundColour: string; + /** + * All the CSS class names that are used to create a category. + */ + type CssConfig = { + container: (string | undefined); + row: (string | undefined); + rowcontentcontainer: (string | undefined); + icon: (string | undefined); + label: (string | undefined); + selected: (string | undefined); + openicon: (string | undefined); + closedicon: (string | undefined); }; - /** - * May delete the dragging block, if allowed. If `this.wouldDeleteBlock_` is not - * true, the block will not be deleted. This should be called at the end of a - * block drag. - * @return {boolean} True if the block was deleted. - * @protected - */ - protected maybeDeleteBlock_(): boolean; - /** - * Updates the necessary information to place a block at a certain location. - * @param {!Coordinate} delta The change in location from where - * the block started the drag to where it ended the drag. - * @protected - */ - protected updateBlockAfterMove_(delta: Coordinate): void; - /** - * Fire a UI event at the end of a block drag. - * @protected - */ - protected fireDragEndEvent_(): void; - /** - * Adds or removes the style of the cursor for the toolbox. - * This is what changes the cursor to display an x when a deletable block is - * held over the toolbox. - * @param {boolean} isEnd True if we are at the end of a drag, false otherwise. - * @protected - */ - protected updateToolboxStyle_(isEnd: boolean): void; - /** - * Fire a move event at the end of a block drag. - * @protected - */ - protected fireMoveEvent_(): void; - /** - * Update the cursor (and possibly the trash can lid) to reflect whether the - * dragging block would be deleted if released immediately. - * @protected - */ - protected updateCursorDuringBlockDrag_(): void; - /** - * 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 {!Coordinate} pixelCoord A coordinate with x and y - * values in CSS pixel units. - * @return {!Coordinate} The input coordinate divided by the - * workspace scale. - * @protected - */ - protected pixelsToWorkspaceUnits_(pixelCoord: Coordinate): Coordinate; - /** - * Move all of the icons connected to this drag. - * @param {!Coordinate} dxy How far to move the icons from their - * original positions, in workspace units. - * @protected - */ - protected dragIcons_(dxy: Coordinate): void; - /** - * Get a list of the insertion markers that currently exist. Drags have 0, 1, - * or 2 insertion markers. - * @return {!Array} A possibly empty list of insertion - * marker blocks. - * @public - */ - public getInsertionMarkers(): Array; } - import { BlockSvg } from "block_svg"; - import { WorkspaceSvg } from "workspace_svg"; - import { InsertionMarkerManager } from "insertion_marker_manager"; - import { Coordinate } from "utils/coordinate"; + import { ISelectableToolboxItem } from "core/interfaces/i_selectable_toolbox_item"; + import { ToolboxItem } from "core/toolbox/toolbox_item"; + import { CategoryInfo } from "core/utils/toolbox"; + import { FlyoutDefinition } from "core/utils/toolbox"; } -declare module "gesture" { +declare module "core/toolbox/separator" { /** - * Note: In this file "start" refers to touchstart, mousedown, and pointerstart - * events. "End" refers to touchend, mouseup, and pointerend events. + * Class for a toolbox separator. This is the thin visual line that appears on + * the toolbox. This item is not interactable. + * @extends {ToolboxItem} + * @alias Blockly.ToolboxSeparator */ - export class Gesture { + export class ToolboxSeparator extends ToolboxItem { /** - * Is a drag or other gesture currently in progress on any workspace? - * @return {boolean} True if gesture is occurring. + * @param {!toolbox.SeparatorInfo} separatorDef The information + * needed to create a separator. + * @param {!IToolbox} toolbox The parent toolbox for the separator. */ - static inProgress(): boolean; + constructor(separatorDef: toolbox.SeparatorInfo, toolbox: () => void); /** - * Class for one gesture. - * @param {!Event} e The event that kicked off this gesture. - * @param {!WorkspaceSvg} creatorWorkspace The workspace that created - * this gesture and has a reference to it. - * @constructor - * @alias Blockly.Gesture - */ - constructor(e: Event, creatorWorkspace: WorkspaceSvg); - /** - * The position of the mouse when the gesture started. Units are CSS pixels, - * with (0, 0) at the top left of the browser window (mouseEvent clientX/Y). - * @type {Coordinate} - * @private - */ - private mouseDownXY_; - /** - * How far the mouse has moved during this drag, in pixel units. - * (0, 0) is at this.mouseDownXY_. - * @type {!Coordinate} - * @private - */ - private currentDragDeltaXY_; - /** - * The bubble that the gesture started on, or null if it did not start on a - * bubble. - * @type {IBubble} - * @private - */ - private startBubble_; - /** - * The field that the gesture started on, or null if it did not start on a - * field. - * @type {Field} - * @private - */ - private startField_; - /** - * The block that the gesture started on, or null if it did not start on a - * block. - * @type {BlockSvg} - * @private - */ - private startBlock_; - /** - * The block that this gesture targets. If the gesture started on a - * shadow block, this is the first non-shadow parent of the block. If the - * gesture started in the flyout, this is the root block of the block group - * that was clicked or dragged. - * @type {BlockSvg} - * @private - */ - private targetBlock_; - /** - * The workspace that the gesture started on. There may be multiple - * workspaces on a page; this is more accurate than using - * Blockly.common.getMainWorkspace(). - * @type {WorkspaceSvg} + * All the CSS class names that are used to create a separator. + * @type {!ToolboxSeparator.CssConfig} * @protected */ - protected startWorkspace_: WorkspaceSvg; + protected cssConfig_: ToolboxSeparator.CssConfig; /** - * The workspace that created this gesture. This workspace keeps a reference - * to the gesture, which will need to be cleared at deletion. - * This may be different from the start workspace. For instance, a flyout is - * a workspace, but its parent workspace manages gestures for it. - * @type {!WorkspaceSvg} + * @type {?HTMLDivElement} * @private */ - private creatorWorkspace_; + private htmlDiv_; /** - * Whether the pointer has at any point moved out of the drag radius. - * A gesture that exceeds the drag radius is a drag even if it ends exactly - * at its start point. - * @type {boolean} - * @private - */ - private hasExceededDragRadius_; - /** - * Whether the workspace is currently being dragged. - * @type {boolean} - * @private - */ - private isDraggingWorkspace_; - /** - * Whether the block is currently being dragged. - * @type {boolean} - * @private - */ - private isDraggingBlock_; - /** - * Whether the bubble is currently being dragged. - * @type {boolean} - * @private - */ - private isDraggingBubble_; - /** - * The event that most recently updated this gesture. - * @type {!Event} - * @private - */ - private mostRecentEvent_; - /** - * A handle to use to unbind a mouse move listener at the end of a drag. - * Opaque data returned from Blockly.bindEventWithChecks_. - * @type {?browserEvents.Data} + * Creates the DOM for a separator. + * @return {!HTMLDivElement} The parent element for the separator. * @protected */ - protected onMoveWrapper_: any[][] | null; - /** - * A handle to use to unbind a mouse up listener at the end of a drag. - * Opaque data returned from Blockly.bindEventWithChecks_. - * @type {?browserEvents.Data} - * @protected - */ - protected onUpWrapper_: any[][] | null; - /** - * The object tracking a bubble drag, or null if none is in progress. - * @type {BubbleDragger} - * @private - */ - private bubbleDragger_; - /** - * The object tracking a block drag, or null if none is in progress. - * @type {?IBlockDragger} - * @private - */ - private blockDragger_; - /** - * The object tracking a workspace or flyout workspace drag, or null if none - * is in progress. - * @type {WorkspaceDragger} - * @private - */ - private workspaceDragger_; - /** - * The flyout a gesture started in, if any. - * @type {IFlyout} - * @private - */ - private flyout_; - /** - * Boolean for sanity-checking that some code is only called once. - * @type {boolean} - * @private - */ - private calledUpdateIsDragging_; - /** - * Boolean for sanity-checking that some code is only called once. - * @type {boolean} - * @private - */ - private hasStarted_; - /** - * Boolean used internally to break a cycle in disposal. - * @type {boolean} - * @protected - */ - protected isEnding_: boolean; - /** - * Boolean used to indicate whether or not to heal the stack after - * disconnecting a block. - * @type {boolean} - * @private - */ - private healStack_; - /** - * Sever all links from this object. - * @package - */ - dispose(): void; - /** - * Update internal state based on an event. - * @param {!Event} e The most recent mouse or touch event. - * @private - */ - private updateFromEvent_; - /** - * DO MATH to set currentDragDeltaXY_ based on the most recent mouse position. - * @param {!Coordinate} currentXY The most recent mouse/pointer - * position, in pixel units, with (0, 0) at the window's top left corner. - * @return {boolean} True if the drag just exceeded the drag radius for the - * first time. - * @private - */ - private updateDragDelta_; - /** - * Update this gesture to record whether a block is being dragged from the - * flyout. - * This function should be called on a mouse/touch move event the first time the - * drag radius is exceeded. It should be called no more than once per gesture. - * If a block should be dragged from the flyout this function creates the new - * block on the main workspace and updates targetBlock_ and startWorkspace_. - * @return {boolean} True if a block is being dragged from the flyout. - * @private - */ - private updateIsDraggingFromFlyout_; - /** - * 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 - */ - private updateIsDraggingBubble_; - /** - * Update this gesture to record whether a block is being dragged. - * This function should be called on a mouse/touch move event the first time the - * drag radius is exceeded. It should be called no more than once per gesture. - * If a block should be dragged, either from the flyout or in the workspace, - * this function creates the necessary BlockDragger and starts the drag. - * @return {boolean} True if a block is being dragged. - * @private - */ - private updateIsDraggingBlock_; - /** - * Update this gesture to record whether a workspace is being dragged. - * This function should be called on a mouse/touch move event the first time the - * drag radius is exceeded. It should be called no more than once per gesture. - * If a workspace is being dragged this function creates the necessary - * WorkspaceDragger and starts the drag. - * @private - */ - private updateIsDraggingWorkspace_; - /** - * Update this gesture to record whether anything is being dragged. - * This function should be called on a mouse/touch move event the first time the - * drag radius is exceeded. It should be called no more than once per gesture. - * @private - */ - private updateIsDragging_; - /** - * Create a block dragger and start dragging the selected block. - * @private - */ - private startDraggingBlock_; - /** - * Create a bubble dragger and start dragging the selected bubble. - * @private - */ - private startDraggingBubble_; - /** - * Start a gesture: update the workspace to indicate that a gesture is in - * progress and bind mousemove and mouseup handlers. - * @param {!Event} e A mouse down or touch start event. - * @package - */ - doStart(e: Event): void; - /** - * Bind gesture events. - * @param {!Event} e A mouse down or touch start event. - * @package - */ - bindMouseEvents(e: Event): void; - /** - * Handle a mouse move or touch move event. - * @param {!Event} e A mouse move or touch move event. - * @package - */ - handleMove(e: Event): void; - /** - * Handle a mouse up or touch end event. - * @param {!Event} e A mouse up or touch end event. - * @package - */ - handleUp(e: Event): void; - /** - * Cancel an in-progress gesture. If a workspace or block drag is in progress, - * end the drag at the most recent location. - * @package - */ - cancel(): void; - /** - * Handle a real or faked right-click event by showing a context menu. - * @param {!Event} e A mouse move or touch move event. - * @package - */ - handleRightClick(e: Event): void; - /** - * Handle a mousedown/touchstart event on a workspace. - * @param {!Event} e A mouse down or touch start event. - * @param {!WorkspaceSvg} ws The workspace the event hit. - * @package - */ - handleWsStart(e: Event, ws: WorkspaceSvg): void; - /** - * Fires a workspace click event. - * @param {!WorkspaceSvg} ws The workspace that a user clicks on. - * @private - */ - private fireWorkspaceClick_; - /** - * Handle a mousedown/touchstart event on a flyout. - * @param {!Event} e A mouse down or touch start event. - * @param {!IFlyout} flyout The flyout the event hit. - * @package - */ - handleFlyoutStart(e: Event, flyout: IFlyout): void; - /** - * Handle a mousedown/touchstart event on a block. - * @param {!Event} e A mouse down or touch start event. - * @param {!BlockSvg} block The block the event hit. - * @package - */ - handleBlockStart(e: Event, block: BlockSvg): void; - /** - * Handle a mousedown/touchstart event on a bubble. - * @param {!Event} e A mouse down or touch start event. - * @param {!IBubble} bubble The bubble the event hit. - * @package - */ - handleBubbleStart(e: Event, bubble: IBubble): void; - /** - * Execute a bubble click. - * @private - */ - private doBubbleClick_; - /** - * Execute a field click. - * @private - */ - private doFieldClick_; - /** - * Execute a block click. - * @private - */ - private doBlockClick_; - /** - * Execute a workspace click. When in accessibility mode shift clicking will - * move the cursor. - * @param {!Event} _e A mouse up or touch end event. - * @private - */ - private doWorkspaceClick_; - /** - * Move the dragged/clicked block to the front of the workspace so that it is - * not occluded by other blocks. - * @private - */ - private bringBlockToFront_; - /** - * Record the field that a gesture started on. - * @param {Field} field The field the gesture started on. - * @package - */ - setStartField(field: Field): void; - /** - * Record the bubble that a gesture started on - * @param {IBubble} bubble The bubble the gesture started on. - * @package - */ - setStartBubble(bubble: IBubble): void; - /** - * Record the block that a gesture started on, and set the target block - * appropriately. - * @param {BlockSvg} block The block the gesture started on. - * @package - */ - setStartBlock(block: BlockSvg): void; - /** - * Record the block that a gesture targets, meaning the block that will be - * dragged if this turns into a drag. If this block is a shadow, that will be - * its first non-shadow parent. - * @param {BlockSvg} block The block the gesture targets. - * @private - */ - private setTargetBlock_; - /** - * Record the workspace that a gesture started on. - * @param {WorkspaceSvg} ws The workspace the gesture started on. - * @private - */ - private setStartWorkspace_; - /** - * Record the flyout that a gesture started on. - * @param {IFlyout} flyout The flyout the gesture started on. - * @private - */ - private setStartFlyout_; - /** - * 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 - */ - private isBubbleClick_; - /** - * Whether this gesture is a click on a block. This should only be called when - * ending a gesture (mouse up, touch end). - * @return {boolean} Whether this gesture was a click on a block. - * @private - */ - private isBlockClick_; - /** - * Whether this gesture is a click on a field. This should only be called when - * ending a gesture (mouse up, touch end). - * @return {boolean} Whether this gesture was a click on a field. - * @private - */ - private isFieldClick_; - /** - * Whether this gesture is a click on a workspace. This should only be called - * when ending a gesture (mouse up, touch end). - * @return {boolean} Whether this gesture was a click on a workspace. - * @private - */ - private isWorkspaceClick_; - /** - * Whether this gesture is a drag of either a workspace or block. - * This function is called externally to block actions that cannot be taken - * mid-drag (e.g. using the keyboard to delete the selected blocks). - * @return {boolean} True if this gesture is a drag of a workspace or block. - * @package - */ - isDragging(): boolean; - /** - * Whether this gesture has already been started. In theory every mouse down - * has a corresponding mouse up, but in reality it is possible to lose a - * mouse up, leaving an in-process gesture hanging. - * @return {boolean} Whether this gesture was a click on a workspace. - * @package - */ - hasStarted(): boolean; - /** - * Get a list of the insertion markers that currently exist. Block drags have - * 0, 1, or 2 insertion markers. - * @return {!Array} A possibly empty list of insertion - * marker blocks. - * @package - */ - getInsertionMarkers(): Array; - /** - * Gets the current dragger if an item is being dragged. Null if nothing is - * being dragged. - * @return {!WorkspaceDragger|!BubbleDragger|!IBlockDragger|null} - * The dragger that is currently in use or null if no drag is in progress. - */ - getCurrentDragger(): WorkspaceDragger | BubbleDragger | IBlockDragger | null; + protected createDom_(): HTMLDivElement; } - import { WorkspaceSvg } from "workspace_svg"; - import { IFlyout } from "interfaces/i_flyout"; - import { BlockSvg } from "block_svg"; - import { IBubble } from "interfaces/i_bubble"; - import { Field } from "field"; - import { WorkspaceDragger } from "workspace_dragger"; - import { BubbleDragger } from "bubble_dragger"; - import { IBlockDragger } from "interfaces/i_block_dragger"; + export namespace ToolboxSeparator { + const registrationName: string; + /** + * All the CSS class names that are used to create a separator. + */ + type CssConfig = { + container: (string | undefined); + }; + } + import { ToolboxItem } from "core/toolbox/toolbox_item"; + import * as toolbox from "core/utils/toolbox"; } -declare module "touch" { +declare module "core/utils/toolbox" { /** - * Whether touch is enabled in the browser. - * Copied from Closure's goog.events.BrowserFeature.TOUCH_ENABLED - * @const + * The information needed to create a block in the toolbox. + * Note that disabled has a different type for backwards compatibility. */ - export const TOUCH_ENABLED: boolean; - /** - * The TOUCH_MAP lookup dictionary specifies additional touch events to fire, - * in conjunction with mouse events. - * @type {Object} - * @alias Blockly.Touch.TOUCH_MAP - */ - export let TOUCH_MAP: any; - /** - * Context menus on touch devices are activated using a long-press. - * Unfortunately the contextmenu touch event is currently (2015) only supported - * by Chrome. This function is fired on any touchstart event, queues a task, - * which after about a second opens the context menu. The tasks is killed - * if the touch event terminates early. - * @param {!Event} e Touch start event. - * @param {Gesture} gesture The gesture that triggered this longStart. - * @alias Blockly.Touch.longStart - * @package - */ - export function longStart(e: Event, gesture: Gesture): void; - /** - * Nope, that's not a long-press. Either touchend or touchcancel was fired, - * or a drag hath begun. Kill the queued long-press task. - * @alias Blockly.Touch.longStop - * @package - */ - export function longStop(): void; - /** - * Clear the touch identifier that tracks which touch stream to pay attention - * to. This ends the current drag/gesture and allows other pointers to be - * captured. - * @alias Blockly.Touch.clearTouchIdentifier - */ - export function clearTouchIdentifier(): void; - /** - * Decide whether Blockly should handle or ignore this event. - * Mouse and touch events require special checks because we only want to deal - * with one touch stream at a time. All other events should always be handled. - * @param {!Event} e The event to check. - * @return {boolean} True if this event should be passed through to the - * registered handler; false if it should be blocked. - * @alias Blockly.Touch.shouldHandleEvent - */ - export function shouldHandleEvent(e: Event): boolean; - /** - * Get the touch identifier from the given event. If it was a mouse event, the - * identifier is the string 'mouse'. - * @param {!Event} e Mouse event or touch event. - * @return {string} The touch identifier from the first changed touch, if - * defined. Otherwise 'mouse'. - * @alias Blockly.Touch.getTouchIdentifierFromEvent - */ - export function getTouchIdentifierFromEvent(e: Event): string; - /** - * Check whether the touch identifier on the event matches the current saved - * identifier. If there is no identifier, that means it's a mouse event and - * we'll use the identifier "mouse". This means we won't deal well with - * multiple mice being used at the same time. That seems okay. - * If the current identifier was unset, save the identifier from the - * event. This starts a drag/gesture, during which touch events with other - * identifiers will be silently ignored. - * @param {!Event} e Mouse event or touch event. - * @return {boolean} Whether the identifier on the event matches the current - * saved identifier. - * @alias Blockly.Touch.checkTouchIdentifier - */ - export function checkTouchIdentifier(e: Event): boolean; - /** - * Set an event's clientX and clientY from its first changed touch. Use this to - * make a touch event work in a mouse event handler. - * @param {!Event} e A touch event. - * @alias Blockly.Touch.setClientFromTouch - */ - export function setClientFromTouch(e: Event): void; - /** - * Check whether a given event is a mouse or touch event. - * @param {!Event} e An event. - * @return {boolean} True if it is a mouse or touch event; false otherwise. - * @alias Blockly.Touch.isMouseOrTouchEvent - */ - export function isMouseOrTouchEvent(e: Event): boolean; - /** - * 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. - * @alias Blockly.Touch.isTouchEvent - */ - export function isTouchEvent(e: Event): boolean; - /** - * Split an event into an array of events, one per changed touch or mouse - * point. - * @param {!Event} e A mouse event or a touch event with one or more changed - * touches. - * @return {!Array} An array of mouse or touch events. Each touch - * event will have exactly one changed touch. - * @alias Blockly.Touch.splitEventByTouches - */ - export function splitEventByTouches(e: Event): Array; - import { Gesture } from "gesture"; -} -declare module "browser_events" { - /** - * Blockly opaque event data used to unbind events when using - * `bind` and `conditionalBind`. - */ - export type Data = Array; - /** - * Blockly opaque event data used to unbind events when using - * `bind` and `conditionalBind`. - * @typedef {!Array} - * @alias Blockly.browserEvents.Data - */ - export let Data: any; - /** - * Bind an event handler that can be ignored if it is not part of the active - * touch stream. - * Use this for events that either start or continue a multi-part gesture (e.g. - * mousedown or mousemove, which may be part of a drag or click). - * @param {!EventTarget} node Node upon which to listen. - * @param {string} name Event name to listen to (e.g. 'mousedown'). - * @param {?Object} thisObject The value of 'this' in the function. - * @param {!Function} func Function to call when event is triggered. - * @param {boolean=} opt_noCaptureIdentifier True if triggering on this event - * should not block execution of other event handlers on this touch or - * other simultaneous touches. False by default. - * @param {boolean=} opt_noPreventDefault True if triggering on this event - * should prevent the default handler. False by default. If - * opt_noPreventDefault is provided, opt_noCaptureIdentifier must also be - * provided. - * @return {!Data} Opaque data that can be passed to - * unbindEvent_. - * @alias Blockly.browserEvents.conditionalBind - */ - export function conditionalBind(node: EventTarget, name: string, thisObject: any | null, func: Function, opt_noCaptureIdentifier?: boolean | undefined, opt_noPreventDefault?: boolean | undefined): any[][]; - /** - * Bind an event handler that should be called regardless of whether it is part - * of the active touch stream. - * Use this for events that are not part of a multi-part gesture (e.g. - * mouseover for tooltips). - * @param {!EventTarget} node Node upon which to listen. - * @param {string} name Event name to listen to (e.g. 'mousedown'). - * @param {?Object} thisObject The value of 'this' in the function. - * @param {!Function} func Function to call when event is triggered. - * @return {!Data} Opaque data that can be passed to - * unbindEvent_. - * @alias Blockly.browserEvents.bind - */ - export function bind(node: EventTarget, name: string, thisObject: any | null, func: Function): any[][]; - /** - * Unbind one or more events event from a function call. - * @param {!Data} bindData Opaque data from bindEvent_. - * This list is emptied during the course of calling this function. - * @return {!Function} The function call. - * @alias Blockly.browserEvents.unbind - */ - export function unbind(bindData: any[][]): Function; - /** - * Returns true if this event is targeting a text input widget? - * @param {!Event} e An event. - * @return {boolean} True if text input. - * @alias Blockly.browserEvents.isTargetInput - */ - export function isTargetInput(e: Event): boolean; - /** - * Returns true this event is a right-click. - * @param {!Event} e Mouse event. - * @return {boolean} True if right-click. - * @alias Blockly.browserEvents.isRightButton - */ - export function isRightButton(e: Event): boolean; - /** - * Returns the converted coordinates of the given mouse event. - * The origin (0,0) is the top-left corner of the Blockly SVG. - * @param {!Event} e Mouse event. - * @param {!Element} svg SVG element. - * @param {?SVGMatrix} matrix Inverted screen CTM to use. - * @return {!SVGPoint} Object with .x and .y properties. - * @alias Blockly.browserEvents.mouseToSvg - */ - export function mouseToSvg(e: Event, svg: Element, matrix: SVGMatrix | null): SVGPoint; - /** - * Returns the scroll delta of a mouse event in pixel units. - * @param {!Event} e Mouse event. - * @return {{x: number, y: number}} Scroll delta object with .x and .y - * properties. - * @alias Blockly.browserEvents.getScrollDeltaPixels - */ - export function getScrollDeltaPixels(e: Event): { - x: number; - y: number; + export type BlockInfo = { + kind: string; + blockxml: (string | Node | undefined); + type: (string | undefined); + gap: (string | number | undefined); + disabled: (string | boolean | undefined); + enabled: (boolean | undefined); + id: (string | undefined); + x: (number | undefined); + y: (number | undefined); + collapsed: (boolean | undefined); + inline: (boolean | undefined); + data: (string | undefined); + 'extra-state': (any | undefined); + icons: ({ + [x: string]: any; + } | undefined); + fields: ({ + [x: string]: any; + } | undefined); + inputs: ({ + [x: string]: ConnectionState; + } | undefined); + next: (ConnectionState | undefined); }; -} -declare module "tooltip" { /** - * A type which can define a tooltip. - * Either a string, an object containing a tooltip property, or a function which - * returns either a string, or another arbitrarily nested function which - * eventually unwinds to a string. + * The information needed to create a block in the toolbox. + * Note that disabled has a different type for backwards compatibility. + * @typedef {{ + * kind:string, + * blockxml:(string|!Node|undefined), + * type:(string|undefined), + * gap:(string|number|undefined), + * disabled: (string|boolean|undefined), + * enabled: (boolean|undefined), + * id: (string|undefined), + * x: (number|undefined), + * y: (number|undefined), + * collapsed: (boolean|undefined), + * inline: (boolean|undefined), + * data: (string|undefined), + * 'extra-state': (*|undefined), + * icons: (!Object|undefined), + * fields: (!Object|undefined), + * inputs: (!Object|undefined), + * next: (!ConnectionState|undefined) + * }} + * @alias Blockly.utils.toolbox.BlockInfo */ - export type TipInfo = string | { - tooltip: any; - } | (() => (string | Function)); + export let BlockInfo: any; /** - * A type which can define a tooltip. - * Either a string, an object containing a tooltip property, or a function which - * returns either a string, or another arbitrarily nested function which - * eventually unwinds to a string. - * @typedef {string|{tooltip}|function(): (string|!Function)} - * @alias Blockly.Tooltip.TipInfo + * The information needed to create a separator in the toolbox. */ - export let TipInfo: any; + export type SeparatorInfo = { + kind: string; + id: (string | undefined); + gap: (number | undefined); + cssconfig: (ToolboxSeparator.CssConfig | undefined); + }; /** - * Returns whether or not a tooltip is showing - * @returns {boolean} True if a tooltip is showing - * @alias Blockly.Tooltip.isVisible + * The information needed to create a separator in the toolbox. + * @typedef {{ + * kind:string, + * id:(string|undefined), + * gap:(number|undefined), + * cssconfig:(!ToolboxSeparator.CssConfig|undefined) + * }} + * @alias Blockly.utils.toolbox.SeparatorInfo */ - export function isVisible(): boolean; + export let SeparatorInfo: any; /** - * Maximum width (in characters) of a tooltip. - * @alias Blockly.Tooltip.LIMIT + * The information needed to create a button in the toolbox. */ - export const LIMIT: 50; + export type ButtonInfo = { + kind: string; + text: string; + callbackkey: string; + }; /** - * Horizontal offset between mouse cursor and tooltip. - * @alias Blockly.Tooltip.OFFSET_X + * The information needed to create a button in the toolbox. + * @typedef {{ + * kind:string, + * text:string, + * callbackkey:string + * }} + * @alias Blockly.utils.toolbox.ButtonInfo */ - export const OFFSET_X: 0; + export let ButtonInfo: any; /** - * Vertical offset between mouse cursor and tooltip. - * @alias Blockly.Tooltip.OFFSET_Y + * The information needed to create a label in the toolbox. */ - export const OFFSET_Y: 10; + export type LabelInfo = { + kind: string; + text: string; + id: (string | undefined); + }; /** - * Radius mouse can move before killing tooltip. - * @alias Blockly.Tooltip.RADIUS_OK + * The information needed to create a label in the toolbox. + * @typedef {{ + * kind:string, + * text:string, + * id:(string|undefined) + * }} + * @alias Blockly.utils.toolbox.LabelInfo */ - export const RADIUS_OK: 10; + export let LabelInfo: any; /** - * Delay before tooltip appears. - * @alias Blockly.Tooltip.HOVER_MS + * The information needed to create either a button or a label in the flyout. */ - export const HOVER_MS: 750; + export type ButtonOrLabelInfo = ButtonInfo | LabelInfo; /** - * Horizontal padding between tooltip and screen edge. - * @alias Blockly.Tooltip.MARGINS + * The information needed to create either a button or a label in the flyout. + * @typedef {ButtonInfo| + * LabelInfo} + * @alias Blockly.utils.toolbox.ButtonOrLabelInfo */ - export const MARGINS: 5; + export let ButtonOrLabelInfo: any; /** - * Returns the HTML tooltip container. - * @returns {Element} The HTML tooltip container. - * @alias Blockly.Tooltip.getDiv + * The information needed to create a category in the toolbox. */ - export function getDiv(): Element; + export type StaticCategoryInfo = { + kind: string; + name: string; + contents: Array; + id: (string | undefined); + categorystyle: (string | undefined); + colour: (string | undefined); + cssconfig: (ToolboxCategory.CssConfig | undefined); + hidden: (string | undefined); + }; /** - * Returns the tooltip text for the given element. - * @param {?Object} object The object to get the tooltip text of. - * @return {string} The tooltip text of the element. - * @alias Blockly.Tooltip.getTooltipOfObject + * The information needed to create a category in the toolbox. + * @typedef {{ + * kind:string, + * name:string, + * contents:!Array, + * id:(string|undefined), + * categorystyle:(string|undefined), + * colour:(string|undefined), + * cssconfig:(!ToolboxCategory.CssConfig|undefined), + * hidden:(string|undefined) + * }} + * @alias Blockly.utils.toolbox.StaticCategoryInfo */ - export function getTooltipOfObject(object: any | null): string; + export let StaticCategoryInfo: any; /** - * Create the tooltip div and inject it onto the page. - * @alias Blockly.Tooltip.createDom + * The information needed to create a custom category. */ - export function createDom(): void; + export type DynamicCategoryInfo = { + kind: string; + custom: string; + id: (string | undefined); + categorystyle: (string | undefined); + colour: (string | undefined); + cssconfig: (ToolboxCategory.CssConfig | undefined); + hidden: (string | undefined); + }; /** - * Binds the required mouse events onto an SVG element. - * @param {!Element} element SVG element onto which tooltip is to be bound. - * @alias Blockly.Tooltip.bindMouseEvents + * The information needed to create a custom category. + * @typedef {{ + * kind:string, + * custom:string, + * id:(string|undefined), + * categorystyle:(string|undefined), + * colour:(string|undefined), + * cssconfig:(!ToolboxCategory.CssConfig|undefined), + * hidden:(string|undefined) + * }} + * @alias Blockly.utils.toolbox.DynamicCategoryInfo */ - export function bindMouseEvents(element: Element): void; + export let DynamicCategoryInfo: any; /** - * Unbinds tooltip mouse events from the SVG element. - * @param {!Element} element SVG element onto which tooltip is bound. - * @alias Blockly.Tooltip.unbindMouseEvents + * The information needed to create either a dynamic or static category. */ - export function unbindMouseEvents(element: Element): void; + export type CategoryInfo = StaticCategoryInfo | DynamicCategoryInfo; /** - * Dispose of the tooltip. - * @alias Blockly.Tooltip.dispose + * The information needed to create either a dynamic or static category. + * @typedef {StaticCategoryInfo| + * DynamicCategoryInfo} + * @alias Blockly.utils.toolbox.CategoryInfo + */ + export let CategoryInfo: any; + /** + * Any information that can be used to create an item in the toolbox. + */ + export type ToolboxItemInfo = FlyoutItemInfo | StaticCategoryInfo; + /** + * Any information that can be used to create an item in the toolbox. + * @typedef {FlyoutItemInfo| + * StaticCategoryInfo} + * @alias Blockly.utils.toolbox.ToolboxItemInfo + */ + export let ToolboxItemInfo: any; + /** + * All the different types that can be displayed in a flyout. + */ + export type FlyoutItemInfo = BlockInfo | SeparatorInfo | ButtonInfo | LabelInfo | DynamicCategoryInfo; + /** + * All the different types that can be displayed in a flyout. + * @typedef {BlockInfo| + * SeparatorInfo| + * ButtonInfo| + * LabelInfo| + * DynamicCategoryInfo} + * @alias Blockly.utils.toolbox.FlyoutItemInfo + */ + export let FlyoutItemInfo: any; + /** + * The JSON definition of a toolbox. + */ + export type ToolboxInfo = { + kind: (string | undefined); + contents: Array; + }; + /** + * The JSON definition of a toolbox. + * @typedef {{ + * kind:(string|undefined), + * contents:!Array + * }} + * @alias Blockly.utils.toolbox.ToolboxInfo + */ + export let ToolboxInfo: any; + /** + * An array holding flyout items. + * @typedef { + * Array + * } + * @alias Blockly.utils.toolbox.FlyoutItemInfoArray + */ + export let FlyoutItemInfoArray: any; + /** + * All of the different types that can create a toolbox. + */ + export type ToolboxDefinition = Node | ToolboxInfo | string; + /** + * All of the different types that can create a toolbox. + * @typedef {Node| + * ToolboxInfo| + * string} + * @alias Blockly.utils.toolbox.ToolboxDefinition + */ + export let ToolboxDefinition: any; + /** + * All of the different types that can be used to show items in a flyout. + */ + export type FlyoutDefinition = any | NodeList | ToolboxInfo | Array; + /** + * All of the different types that can be used to show items in a flyout. + * @typedef {FlyoutItemInfoArray| + * NodeList| + * ToolboxInfo| + * Array} + * @alias Blockly.utils.toolbox.FlyoutDefinition + */ + export let FlyoutDefinition: any; + /** + * * + */ + export type Position = number; + export namespace Position { + const TOP: number; + const BOTTOM: number; + const LEFT: number; + const RIGHT: number; + } + /** + * Converts the toolbox definition into toolbox JSON. + * @param {?ToolboxDefinition} toolboxDef The definition + * of the toolbox in one of its many forms. + * @return {?ToolboxInfo} Object holding information + * for creating a toolbox. + * @alias Blockly.utils.toolbox.convertToolboxDefToJson * @package */ - export function dispose(): void; + export function convertToolboxDefToJson(toolboxDef: ToolboxDefinition | null): ToolboxInfo | null; /** - * Hide the tooltip. - * @alias Blockly.Tooltip.hide - */ - export function hide(): void; - /** - * Hide any in-progress tooltips and block showing new tooltips until the next - * call to unblock(). - * @alias Blockly.Tooltip.block + * Converts the flyout definition into a list of flyout items. + * @param {?FlyoutDefinition} flyoutDef The definition of + * the flyout in one of its many forms. + * @return {!FlyoutItemInfoArray} A list of flyout items. + * @alias Blockly.utils.toolbox.convertFlyoutDefToJsonArray * @package */ - export function block(): void; + export function convertFlyoutDefToJsonArray(flyoutDef: FlyoutDefinition | null): any; /** - * Unblock tooltips: allow them to be scheduled and shown according to their own - * logic. - * @alias Blockly.Tooltip.unblock + * Whether or not the toolbox definition has categories. + * @param {?ToolboxInfo} toolboxJson Object holding + * information for creating a toolbox. + * @return {boolean} True if the toolbox has categories. + * @alias Blockly.utils.toolbox.hasCategories * @package */ - export function unblock(): void; + export function hasCategories(toolboxJson: ToolboxInfo | null): boolean; + /** + * Whether or not the category is collapsible. + * @param {!CategoryInfo} categoryInfo Object holing + * information for creating a category. + * @return {boolean} True if the category has subcategories. + * @alias Blockly.utils.toolbox.isCategoryCollapsible + * @package + */ + export function isCategoryCollapsible(categoryInfo: CategoryInfo): boolean; + /** + * Parse the provided toolbox tree into a consistent DOM format. + * @param {?Node|?string} toolboxDef DOM tree of blocks, or text representation + * of same. + * @return {?Node} DOM tree of blocks, or null. + * @alias Blockly.utils.toolbox.parseToolboxTree + */ + export function parseToolboxTree(toolboxDef: (Node | (string | null)) | null): Node | null; + import { ConnectionState } from "core/serialization/blocks"; + import { ToolboxSeparator } from "core/toolbox/separator"; + import { ToolboxCategory } from "core/toolbox/category"; } -declare module "interfaces/i_ast_node_location_with_block" { +declare module "core/connection_checker" { /** - * An AST node location that has an associated block. - * @interface - * @extends {IASTNodeLocation} - * @alias Blockly.IASTNodeLocationWithBlock + * Class for connection type checking logic. + * @implements {IConnectionChecker} + * @alias Blockly.ConnectionChecker */ - export class IASTNodeLocationWithBlock { - } -} -declare module "shortcut_registry" { - export class ShortcutRegistry { + export class ConnectionChecker implements IConnectionChecker { /** - * Registry of all keyboard shortcuts, keyed by name of shortcut. - * @type {!Object} - * @private - */ - private registry_; - /** - * Map of key codes to an array of shortcut names. - * @type {!Object>} - * @private - */ - private keyMap_; - /** - * Registers a keyboard shortcut. - * @param {!ShortcutRegistry.KeyboardShortcut} shortcut The - * shortcut for this key code. - * @param {boolean=} opt_allowOverrides True to prevent a warning when - * overriding an already registered item. - * @throws {Error} if a shortcut with the same name already exists. + * Check whether the current connection can connect with the target + * connection. + * @param {Connection} a Connection to check compatibility with. + * @param {Connection} b Connection to check compatibility with. + * @param {boolean} isDragging True if the connection is being made by + * dragging a block. + * @param {number=} opt_distance The max allowable distance between the + * connections for drag checks. + * @return {boolean} Whether the connection is legal. * @public */ - public register(shortcut: ShortcutRegistry.KeyboardShortcut, opt_allowOverrides?: boolean | undefined): void; - /** - * Unregisters a keyboard shortcut registered with the given key code. This will - * also remove any key mappings that reference this shortcut. - * @param {string} shortcutName The name of the shortcut to unregister. - * @return {boolean} True if an item was unregistered, false otherwise. - * @public - */ - public unregister(shortcutName: string): boolean; - /** - * Adds a mapping between a keycode and a keyboard shortcut. - * @param {string|KeyCodes} keyCode The key code for the keyboard - * shortcut. If registering a key code with a modifier (ex: ctrl+c) use - * ShortcutRegistry.registry.createSerializedKey; - * @param {string} shortcutName The name of the shortcut to execute when the - * given keycode is pressed. - * @param {boolean=} opt_allowCollision True to prevent an error when adding a - * shortcut to a key that is already mapped to a shortcut. - * @throws {Error} if the given key code is already mapped to a shortcut. - * @public - */ - public addKeyMapping(keyCode: string | KeyCodes, shortcutName: string, opt_allowCollision?: boolean | undefined): void; - /** - * Removes a mapping between a keycode and a keyboard shortcut. - * @param {string} keyCode The key code for the keyboard shortcut. If - * registering a key code with a modifier (ex: ctrl+c) use - * ShortcutRegistry.registry.createSerializedKey; - * @param {string} shortcutName The name of the shortcut to execute when the - * given keycode is pressed. - * @param {boolean=} opt_quiet True to not console warn when there is no - * shortcut to remove. - * @return {boolean} True if a key mapping was removed, false otherwise. - * @public - */ - public removeKeyMapping(keyCode: string, shortcutName: string, opt_quiet?: boolean | undefined): boolean; - /** - * Removes all the key mappings for a shortcut with the given name. - * Useful when changing the default key mappings and the key codes registered to - * the shortcut are unknown. - * @param {string} shortcutName The name of the shortcut to remove from the key - * map. - * @public - */ - public removeAllKeyMappings(shortcutName: string): void; - /** - * Sets the key map. Setting the key map will override any default key mappings. - * @param {!Object>} keyMap The object with key code to - * shortcut names. - * @public - */ - public setKeyMap(keyMap: { - [x: string]: Array; - }): void; - /** - * Gets the current key map. - * @return {!Object>} - * The object holding key codes to ShortcutRegistry.KeyboardShortcut. - * @public - */ - public getKeyMap(): { - [x: string]: Array; - }; - /** - * Gets the registry of keyboard shortcuts. - * @return {!Object} - * The registry of keyboard shortcuts. - * @public - */ - public getRegistry(): { - [x: string]: ShortcutRegistry.KeyboardShortcut; - }; - /** - * Handles key down events. - * @param {!Workspace} workspace The main workspace where the event was - * captured. - * @param {!Event} e The key down event. - * @return {boolean} True if the event was handled, false otherwise. - * @public - */ - public onKeyDown(workspace: Workspace, e: Event): boolean; - /** - * Gets the shortcuts registered to the given key code. - * @param {string} keyCode The serialized key code. - * @return {!Array|undefined} The list of shortcuts to call when the - * given keyCode is used. Undefined if no shortcuts exist. - * @public - */ - public getShortcutNamesByKeyCode(keyCode: string): Array | undefined; - /** - * Gets the serialized key codes that the shortcut with the given name is - * registered under. - * @param {string} shortcutName The name of the shortcut. - * @return {!Array} An array with all the key codes the shortcut is - * registered under. - * @public - */ - public getKeyCodesByShortcutName(shortcutName: string): Array; - /** - * Serializes a key event. - * @param {!Event} e A key down event. - * @return {string} The serialized key code for the given event. - * @private - */ - private serializeKeyEvent_; - /** - * Checks whether any of the given modifiers are not valid. - * @param {!Array} modifiers List of modifiers to be used with the key. - * @throws {Error} if the modifier is not in the valid modifiers list. - * @private - */ - private checkModifiers_; - /** - * Creates the serialized key code that will be used in the key map. - * @param {number} keyCode Number code representing the key. - * @param {?Array} modifiers List of modifier key codes to be used with - * the key. All valid modifiers can be found in the - * ShortcutRegistry.modifierKeys. - * @return {string} The serialized key code for the given modifiers and key. - * @public - */ - public createSerializedKey(keyCode: number, modifiers: Array | null): string; - } - export namespace ShortcutRegistry { - namespace modifierKeys { - const Shift: KeyCodes; - const Control: KeyCodes; - const Alt: KeyCodes; - const Meta: KeyCodes; - } - /** - * A keyboard shortcut. - */ - type KeyboardShortcut = { - callback: (((arg0: Workspace, arg1: Event, arg2: ShortcutRegistry.KeyboardShortcut) => boolean) | undefined); - name: string; - preconditionFn: (((arg0: Workspace) => boolean) | undefined); - metadata: (any | undefined); - }; - } - import { KeyCodes } from "utils/keycodes"; - import { Workspace } from "workspace"; -} -declare module "interfaces/i_keyboard_accessible" { - /** - * An interface for an object that handles keyboard shortcuts. - * @interface - * @alias Blockly.IKeyboardAccessible - */ - export class IKeyboardAccessible { - } -} -declare module "field" { - export class Field { - /** - * Abstract class for an editable field. - * @param {*} value The initial value of the field. - * @param {?Function=} opt_validator A function that is called to validate - * changes to the field's value. Takes in a value & returns a validated - * value, or null to abort the change. - * @param {Object=} opt_config A map of options used to configure the field. See - * the individual field's documentation for a list of properties this - * parameter supports. - * @constructor - * @abstract - * @implements {IASTNodeLocationSvg} - * @implements {IASTNodeLocationWithBlock} - * @implements {IKeyboardAccessible} - * @implements {IRegistrable} - * @alias Blockly.Field - */ - constructor(value: any, opt_validator?: (Function | null) | undefined, opt_config?: any | undefined); - /** - * A generic value possessed by the field. - * Should generally be non-null, only null when the field is created. - * @type {*} - * @protected - */ - protected value_: any; - /** - * Validation function called when user edits an editable field. - * @type {Function} - * @protected - */ - protected validator_: Function; - /** - * Used to cache the field's tooltip value if setTooltip is called when the - * field is not yet initialized. Is *not* guaranteed to be accurate. - * @type {?Tooltip.TipInfo} - * @private - */ - private tooltip_; - /** - * The size of the area rendered by the field. - * @type {!Size} - * @protected - */ - protected size_: Size; - /** - * Holds the cursors svg element when the cursor is attached to the field. - * This is null if there is no cursor on the field. - * @type {SVGElement} - * @private - */ - private cursorSvg_; - /** - * Holds the markers svg element when the marker is attached to the field. - * This is null if there is no marker on the field. - * @type {SVGElement} - * @private - */ - private markerSvg_; - /** - * The rendered field's SVG group element. - * @type {SVGGElement} - * @protected - */ - protected fieldGroup_: SVGGElement; - /** - * The rendered field's SVG border element. - * @type {SVGRectElement} - * @protected - */ - protected borderRect_: SVGRectElement; - /** - * The rendered field's SVG text element. - * @type {SVGTextElement} - * @protected - */ - protected textElement_: SVGTextElement; - /** - * The rendered field's text content element. - * @type {Text} - * @protected - */ - protected textContent_: Text; - /** - * Mouse down event listener data. - * @type {?browserEvents.Data} - * @private - */ - private mouseDownWrapper_; - /** - * Constants associated with the source block's renderer. - * @type {ConstantProvider} - * @protected - */ - protected constants_: ConstantProvider; - /** - * Process the configuration map passed to the field. - * @param {!Object} config A map of options used to configure the field. See - * the individual field's documentation for a list of properties this - * parameter supports. - * @protected - */ - protected configure_(config: any): void; - /** - * Attach this field to a block. - * @param {!Block} block The block containing this field. - */ - setSourceBlock(block: Block): void; - sourceBlock_: Block; - /** - * Get the renderer constant provider. - * @return {?ConstantProvider} The renderer constant - * provider. - */ - getConstants(): ConstantProvider | null; - /** - * Get the block this field is attached to. - * @return {Block} The block containing this field. - */ - getSourceBlock(): Block; - /** - * Initialize everything to render this field. Override - * methods initModel and initView rather than this method. - * @package - */ - init(): void; - /** - * Create the block UI for this field. - * @package - */ - initView(): void; - /** - * Initializes the model of the field after it has been installed on a block. - * No-op by default. - * @package - */ - initModel(): void; - /** - * Create a field border rect element. Not to be overridden by subclasses. - * Instead modify the result of the function inside initView, or create a - * separate function to call. - * @protected - */ - protected createBorderRect_(): void; - /** - * Create a field text element. Not to be overridden by subclasses. Instead - * modify the result of the function inside initView, or create a separate - * function to call. - * @protected - */ - protected createTextElement_(): void; - /** - * Bind events to the field. Can be overridden by subclasses if they need to do - * custom input handling. - * @protected - */ - protected bindEvents_(): void; - /** - * Sets the field's value based on the given XML element. Should only be called - * by Blockly.Xml. - * @param {!Element} fieldElement The element containing info about the - * field's state. - * @package - */ - fromXml(fieldElement: Element): void; - /** - * Serializes this field's value to XML. Should only be called by Blockly.Xml. - * @param {!Element} fieldElement The element to populate with info about the - * field's state. - * @return {!Element} The element containing info about the field's state. - * @package - */ - toXml(fieldElement: Element): Element; - /** - * Saves this fields value as something which can be serialized to JSON. Should - * only be called by the serialization system. - * @param {boolean=} _doFullSerialization If true, this signals to the field - * that if it normally just saves a reference to some state (eg variable - * fields) it should instead serialize the full state of the thing being - * referenced. - * @return {*} JSON serializable state. - * @package - */ - saveState(_doFullSerialization?: boolean | undefined): any; - /** - * Sets the field's state based on the given state value. Should only be called - * by the serialization system. - * @param {*} state The state we want to apply to the field. - * @package - */ - loadState(state: any): void; - /** - * Returns a stringified version of the XML state, if it should be used. - * Otherwise this returns null, to signal the field should use its own - * serialization. - * @param {*} callingClass The class calling this method. - * Used to see if `this` has overridden any relevant hooks. - * @return {?string} The stringified version of the XML state, or null. - * @protected - */ - protected saveLegacyState(callingClass: any): string | null; - /** - * Loads the given state using either the old XML hoooks, if they should be - * used. Returns true to indicate loading has been handled, false otherwise. - * @param {*} callingClass The class calling this method. - * Used to see if `this` has overridden any relevant hooks. - * @param {*} state The state to apply to the field. - * @return {boolean} Whether the state was applied or not. - */ - loadLegacyState(callingClass: any, state: any): boolean; - /** - * Dispose of all DOM objects and events belonging to this editable field. - * @package - */ - dispose(): void; - disposed: boolean; - /** - * Add or remove the UI indicating if this field is editable or not. - */ - updateEditable(): void; - /** - * Set whether this field's value can be changed using the editor when the - * source block is editable. - * @param {boolean} enabled True if enabled. - */ - setEnabled(enabled: boolean): void; - enabled_: boolean; - /** - * Check whether this field's value can be changed using the editor when the - * source block is editable. - * @return {boolean} Whether this field is enabled. - */ - isEnabled(): boolean; - /** - * Check whether this field defines the showEditor_ function. - * @return {boolean} Whether this field is clickable. - */ - isClickable(): boolean; - /** - * Check whether this field is currently editable. Some fields are never - * EDITABLE (e.g. text labels). Other fields may be EDITABLE but may exist on - * non-editable blocks or be currently disabled. - * @return {boolean} Whether this field is currently enabled, editable and on - * an editable block. - */ - isCurrentlyEditable(): boolean; - /** - * Check whether this field should be serialized by the XML renderer. - * Handles the logic for backwards compatibility and incongruous states. - * @return {boolean} Whether this field should be serialized or not. - */ - isSerializable(): boolean; - /** - * Gets whether this editable field is visible or not. - * @return {boolean} True if visible. - */ - isVisible(): boolean; - /** - * Sets whether this editable field is visible or not. Should only be called - * by input.setVisible. - * @param {boolean} visible True if visible. - * @package - */ - setVisible(visible: boolean): void; - visible_: any; - /** - * Sets a new validation function for editable fields, or clears a previously - * set validator. - * - * The validator function takes in the new field value, and returns - * validated value. The validated value could be the input value, a modified - * version of the input value, or null to abort the change. - * - * If the function does not return anything (or returns undefined) the new - * value is accepted as valid. This is to allow for fields using the - * validated function as a field-level change event notification. - * - * @param {Function} handler The validator function - * or null to clear a previous validator. - */ - setValidator(handler: Function): void; - /** - * Gets the validation function for editable fields, or null if not set. - * @return {?Function} Validation function, or null. - */ - getValidator(): Function | null; - /** - * Gets the group element for this editable field. - * Used for measuring the size and for positioning. - * @return {!SVGGElement} The group element. - */ - getSvgRoot(): SVGGElement; - /** - * Updates the field to match the colour/style of the block. Should only be - * called by BlockSvg.applyColour(). - * @package - */ - applyColour(): void; - /** - * Used by getSize() to move/resize any DOM elements, and get the new size. - * - * All rendering that has an effect on the size/shape of the block should be - * done here, and should be triggered by getSize(). - * @protected - */ - protected render_(): void; - /** - * Show an editor when the field is clicked only if the field is clickable. - * @param {Event=} opt_e Optional mouse event that triggered the field to open, - * or undefined if triggered programmatically. - * @package - */ - showEditor(opt_e?: Event | undefined): void; - /** - * Updates the size of the field based on the text. - * @param {number=} opt_margin margin to use when positioning the text element. - * @protected - */ - protected updateSize_(opt_margin?: number | undefined): void; - /** - * Position a field's text element after a size change. This handles both LTR - * and RTL positioning. - * @param {number} xOffset x offset to use when positioning the text element. - * @param {number} contentWidth The content width. - * @protected - */ - protected positionTextElement_(xOffset: number, contentWidth: number): void; - /** - * Position a field's border rect after a size change. - * @protected - */ - protected positionBorderRect_(): void; - /** - * Returns the height and width of the field. - * - * This should *in general* be the only place render_ gets called from. - * @return {!Size} Height and width. - */ - getSize(): Size; - isDirty_: boolean; - /** - * Returns the bounding box of the rendered field, accounting for workspace - * scaling. - * @return {!Rect} An object with top, bottom, left, and right in - * pixels relative to the top left corner of the page (window coordinates). - * @package - */ - getScaledBBox(): Rect; - /** - * Get the text from this field to display on the block. May differ from - * ``getText`` due to ellipsis, and other formatting. - * @return {string} Text to display. - * @protected - */ - protected getDisplayText_(): string; - /** - * Get the text from this field. - * @return {string} Current text. - */ - getText(): string; - /** - * Force a rerender of the block that this field is installed on, which will - * rerender this field and adjust for any sizing changes. - * Other fields on the same block will not rerender, because their sizes have - * already been recorded. - * @package - */ - markDirty(): void; - /** - * Force a rerender of the block that this field is installed on, which will - * rerender this field and adjust for any sizing changes. - * Other fields on the same block will not rerender, because their sizes have - * already been recorded. - * @package - */ - forceRerender(): void; - /** - * Used to change the value of the field. Handles validation and events. - * Subclasses should override doClassValidation_ and doValueUpdate_ rather - * than this method. - * @param {*} newValue New value. - */ - setValue(newValue: any): void; - /** - * Process the result of validation. - * @param {*} newValue New value. - * @param {*} validatedValue Validated value. - * @return {*} New value, or an Error object. - * @private - */ - private processValidation_; - /** - * Get the current value of the field. - * @return {*} Current value. - */ - getValue(): any; - /** - * Used to validate a value. Returns input by default. Can be overridden by - * subclasses, see FieldDropdown. - * @param {*=} opt_newValue The value to be validated. - * @return {*} The validated value, same as input by default. - * @protected - */ - protected doClassValidation_(opt_newValue?: any | undefined): any; - /** - * Used to update the value of a field. Can be overridden by subclasses to do - * custom storage of values/updating of external things. - * @param {*} newValue The value to be saved. - * @protected - */ - protected doValueUpdate_(newValue: any): void; - /** - * Used to notify the field an invalid value was input. Can be overridden by - * subclasses, see FieldTextInput. - * No-op by default. - * @param {*} _invalidValue The input value that was determined to be invalid. - * @protected - */ - protected doValueInvalid_(_invalidValue: any): void; - /** - * Handle a mouse down event on a field. - * @param {!Event} e Mouse down event. - * @protected - */ - protected onMouseDown_(e: Event): void; - /** - * Sets the tooltip for this field. - * @param {?Tooltip.TipInfo} newTip The - * text for the tooltip, a function that returns the text for the tooltip, a - * parent object whose tooltip will be used, or null to display the tooltip - * of the parent block. To not display a tooltip pass the empty string. - */ - setTooltip(newTip: Tooltip.TipInfo | null): void; - /** - * Returns the tooltip text for this field. - * @return {string} The tooltip text for this field. - */ - getTooltip(): string; - /** - * The element to bind the click handler to. If not set explicitly, defaults - * to the SVG root of the field. When this element is - * clicked on an editable field, the editor will open. - * @return {!Element} Element to bind click handler to. - * @protected - */ - protected getClickTarget_(): Element; - /** - * Return the absolute coordinates of the top-left corner of this field. - * The origin (0,0) is the top-left corner of the page body. - * @return {!Coordinate} Object with .x and .y properties. - * @protected - */ - protected getAbsoluteXY_(): Coordinate; - /** - * Whether this field references any Blockly variables. If true it may need to - * be handled differently during serialization and deserialization. Subclasses - * may override this. - * @return {boolean} True if this field has any variable references. - * @package - */ - referencesVariables(): boolean; - /** - * Search through the list of inputs and their fields in order to find the - * parent input of a field. - * @return {Input} The input that the field belongs to. - * @package - */ - getParentInput(): Input; - /** - * Returns whether or not we should flip the field in RTL. - * @return {boolean} True if we should flip in RTL. - */ - getFlipRtl(): boolean; - /** - * Returns whether or not the field is tab navigable. - * @return {boolean} True if the field is tab navigable. - */ - isTabNavigable(): boolean; - /** - * Handles the given keyboard shortcut. - * @param {!ShortcutRegistry.KeyboardShortcut} _shortcut The shortcut to be - * handled. - * @return {boolean} True if the shortcut has been handled, false otherwise. - * @public - */ - public onShortcut(_shortcut: ShortcutRegistry.KeyboardShortcut): boolean; - /** - * Add the cursor SVG to this fields SVG group. - * @param {SVGElement} cursorSvg The SVG root of the cursor to be added to the - * field group. - * @package - */ - setCursorSvg(cursorSvg: SVGElement): void; - /** - * Add the marker SVG to this fields SVG group. - * @param {SVGElement} markerSvg The SVG root of the marker to be added to the - * field group. - * @package - */ - setMarkerSvg(markerSvg: SVGElement): void; - /** - * Redraw any attached marker or cursor svgs if needed. - * @protected - */ - protected updateMarkers_(): void; - /** - * The default value for this field. - * @type {*} - * @protected - */ - protected DEFAULT_VALUE: any; - /** - * Name of field. Unique within each block. - * Static labels are usually unnamed. - * @type {string|undefined} - */ - name: string | undefined; - /** - * Maximum characters of text to display before adding an ellipsis. - * @type {number} - */ - maxDisplayLength: number; - /** - * The element the click handler is bound to. - * @type {Element} - * @protected - */ - protected clickTarget_: Element; - /** - * Editable fields usually show some sort of UI indicating they are editable. - * They will also be saved by the XML renderer. - * @type {boolean} - */ - EDITABLE: boolean; - /** - * Serializable fields are saved by the XML renderer, non-serializable fields - * are not. Editable fields should also be serializable. This is not the - * case by default so that SERIALIZABLE is backwards compatible. - * @type {boolean} - */ - SERIALIZABLE: boolean; - } - export namespace Field { - const NBSP: string; - } - import { Size } from "utils/size"; - import { ConstantProvider } from "renderers/common/constants"; - import { Block } from "block"; - import { Rect } from "utils/rect"; - import * as Tooltip from "tooltip"; - import { Coordinate } from "utils/coordinate"; - import { Input } from "input"; - import { ShortcutRegistry } from "shortcut_registry"; -} -declare module "xml" { - /** - * Encode a block tree as XML. - * @param {!Workspace} workspace The workspace containing blocks. - * @param {boolean=} opt_noId True if the encoder should skip the block IDs. - * @return {!Element} XML DOM element. - * @alias Blockly.Xml.workspaceToDom - */ - export function workspaceToDom(workspace: Workspace, opt_noId?: boolean | undefined): Element; - /** - * Encode a list of variables as XML. - * @param {!Array} variableList List of all variable - * models. - * @return {!Element} Tree of XML elements. - * @alias Blockly.Xml.variablesToDom - */ - export function variablesToDom(variableList: Array): Element; - /** - * Encode a block subtree as XML with XY coordinates. - * @param {!Block} block The root block to encode. - * @param {boolean=} opt_noId True if the encoder should skip the block ID. - * @return {!Element|!DocumentFragment} Tree of XML elements or an empty - * document fragment if the block was an insertion marker. - * @alias Blockly.Xml.blockToDomWithXY - */ - export function blockToDomWithXY(block: Block, opt_noId?: boolean | undefined): Element | DocumentFragment; - /** - * Encode a block subtree as XML. - * @param {!Block} block The root block to encode. - * @param {boolean=} opt_noId True if the encoder should skip the block ID. - * @return {!Element|!DocumentFragment} Tree of XML elements or an empty - * document fragment if the block was an insertion marker. - * @alias Blockly.Xml.blockToDom - */ - export function blockToDom(block: Block, opt_noId?: boolean | undefined): Element | DocumentFragment; - /** - * Converts a DOM structure into plain text. - * Currently the text format is fairly ugly: all one line with no whitespace, - * unless the DOM itself has whitespace built-in. - * @param {!Node} dom A tree of XML nodes. - * @return {string} Text representation. - * @alias Blockly.Xml.domToText - */ - export function domToText(dom: Node): string; - /** - * Converts a DOM structure into properly indented text. - * @param {!Node} dom A tree of XML elements. - * @return {string} Text representation. - * @alias Blockly.Xml.domToPrettyText - */ - export function domToPrettyText(dom: Node): string; - /** - * Converts an XML string into a DOM structure. - * @param {string} text An XML string. - * @return {!Element} A DOM object representing the singular child of the - * document element. - * @throws if the text doesn't parse. - * @alias Blockly.Xml.textToDom - */ - export function textToDom(text: string): Element; - /** - * Clear the given workspace then decode an XML DOM and - * create blocks on the workspace. - * @param {!Element} xml XML DOM. - * @param {!Workspace} workspace The workspace. - * @return {!Array} An array containing new block IDs. - * @alias Blockly.Xml.clearWorkspaceAndLoadFromXml - */ - export function clearWorkspaceAndLoadFromXml(xml: Element, workspace: Workspace): Array; - /** - * Decode an XML DOM and create blocks on the workspace. - * @param {!Element} xml XML DOM. - * @param {!Workspace} workspace The workspace. - * @return {!Array} An array containing new block IDs. - * @suppress {strictModuleDepCheck} Suppress module check while workspace - * comments are not bundled in. - * @alias Blockly.Xml.domToWorkspace - */ - export function domToWorkspace(xml: Element, workspace: Workspace): Array; - /** - * Decode an XML DOM and create blocks on the workspace. Position the new - * blocks immediately below prior blocks, aligned by their starting edge. - * @param {!Element} xml The XML DOM. - * @param {!Workspace} workspace The workspace to add to. - * @return {!Array} An array containing new block IDs. - * @alias Blockly.Xml.appendDomToWorkspace - */ - export function appendDomToWorkspace(xml: Element, workspace: Workspace): Array; - /** - * Decode an XML block tag and create a block (and possibly sub blocks) on the - * workspace. - * @param {!Element} xmlBlock XML block element. - * @param {!Workspace} workspace The workspace. - * @return {!Block} The root block created. - * @alias Blockly.Xml.domToBlock - */ - export function domToBlock(xmlBlock: Element, workspace: Workspace): Block; - /** - * Decode an XML list of variables and add the variables to the workspace. - * @param {!Element} xmlVariables List of XML variable elements. - * @param {!Workspace} workspace The workspace to which the variable - * should be added. - * @alias Blockly.Xml.domToVariables - */ - export function domToVariables(xmlVariables: Element, workspace: Workspace): void; - /** - * Remove any 'next' block (statements in a stack). - * @param {!Element|!DocumentFragment} xmlBlock XML block element or an empty - * DocumentFragment if the block was an insertion marker. - * @alias Blockly.Xml.deleteNext - */ - export function deleteNext(xmlBlock: Element | DocumentFragment): void; - import { Workspace } from "workspace"; - import { VariableModel } from "variable_model"; - import { Block } from "block"; -} -declare module "connection" { - export class Connection { - /** - * Returns the connection (starting at the startBlock) which will accept - * the given connection. This includes compatible connection types and - * connection checks. - * @param {!Block} startBlock The block on which to start the search. - * @param {!Connection} orphanConnection The connection that is looking - * for a home. - * @return {?Connection} The suitable connection point on the chain of - * blocks, or null. - */ - static getConnectionForOrphanedConnection(startBlock: Block, orphanConnection: Connection): Connection | null; - /** - * Class for a connection between blocks. - * @param {!Block} source The block establishing this connection. - * @param {number} type The type of the connection. - * @constructor - * @implements {IASTNodeLocationWithBlock} - * @alias Blockly.Connection - */ - constructor(source: Block, type: number); - /** - * @type {!Block} - * @protected - */ - protected sourceBlock_: Block; - /** @type {number} */ - type: number; - /** - * Connect two connections together. This is the connection on the superior - * block. - * @param {!Connection} childConnection Connection on inferior block. - * @protected - */ - protected connect_(childConnection: Connection): void; - /** - * Dispose of this connection and deal with connected blocks. - * @package - */ - dispose(): void; - disposed: boolean; - /** - * Get the source block for this connection. - * @return {!Block} The source block. - */ - getSourceBlock(): Block; - /** - * Does the connection belong to a superior block (higher in the source stack)? - * @return {boolean} True if connection faces down or right. - */ - isSuperior(): boolean; - /** - * Is the connection connected? - * @return {boolean} True if connection is connected to another connection. - */ - isConnected(): boolean; + public canConnect(a: Connection, b: Connection, isDragging: boolean, opt_distance?: number | undefined): boolean; /** * Checks whether the current connection can connect with the target - * connection. - * @param {Connection} target Connection to check compatibility with. + * connection, and return an error code if there are problems. + * @param {Connection} a Connection to check compatibility with. + * @param {Connection} b Connection to check compatibility with. + * @param {boolean} isDragging True if the connection is being made by + * dragging a block. + * @param {number=} opt_distance The max allowable distance between the + * connections for drag checks. * @return {number} Connection.CAN_CONNECT if the connection is legal, * an error code otherwise. - * @deprecated July 2020. Will be deleted July 2021. Use the workspace's - * connectionChecker instead. + * @public */ - canConnectWithReason(target: Connection): number; + public canConnectWithReason(a: Connection, b: Connection, isDragging: boolean, opt_distance?: number | undefined): number; /** - * Checks whether the current connection and target connection are compatible - * and throws an exception if they are not. - * @param {Connection} target The connection to check compatibility - * with. - * @package - * @deprecated July 2020. Will be deleted July 2021. Use the workspace's - * connectionChecker instead. + * Helper method that translates a connection error code into a string. + * @param {number} errorCode The error code. + * @param {Connection} a One of the two connections being checked. + * @param {Connection} b The second of the two connections being + * checked. + * @return {string} A developer-readable error string. + * @public */ - checkConnection(target: Connection): void; + public getErrorMessage(errorCode: number, a: Connection, b: Connection): string; /** - * Get the workspace's connection type checker object. - * @return {!IConnectionChecker} The connection type checker for the - * source block's workspace. - * @package + * Check that connecting the given connections is safe, meaning that it would + * not break any of Blockly's basic assumptions (e.g. no self connections). + * @param {Connection} a The first of the connections to check. + * @param {Connection} b The second of the connections to check. + * @return {number} An enum with the reason this connection is safe or unsafe. + * @public */ - getConnectionChecker(): IConnectionChecker; + public doSafetyChecks(a: Connection, b: Connection): number; /** - * Check if the two connections can be dragged to connect to each other. - * @param {!Connection} candidate A nearby connection to check. + * Check whether this connection is compatible with another connection with + * respect to the value type system. E.g. square_root("Hello") is not + * compatible. + * @param {!Connection} a Connection to compare. + * @param {!Connection} b Connection to compare against. + * @return {boolean} True if the connections share a type. + * @public + */ + public doTypeChecks(a: Connection, b: Connection): boolean; + /** + * Check whether this connection can be made by dragging. + * @param {!RenderedConnection} a Connection to compare. + * @param {!RenderedConnection} b Connection to compare against. + * @param {number} distance The maximum allowable distance between + * connections. + * @return {boolean} True if the connection is allowed during a drag. + * @public + */ + public doDragChecks(a: RenderedConnection, b: RenderedConnection, distance: number): boolean; + /** + * Helper function for drag checking. + * @param {!Connection} a The connection to check, which must be a + * statement input or next connection. + * @param {!Connection} b A nearby connection to check, which + * must be a previous connection. * @return {boolean} True if the connection is allowed, false otherwise. - * @deprecated July 2020. Will be deleted July 2021. Use the workspace's - * connectionChecker instead. - */ - isConnectionAllowed(candidate: Connection): boolean; - /** - * Called when an attempted connection fails. NOP by default (i.e. for headless - * workspaces). - * @param {!Connection} _otherConnection Connection that this connection - * failed to connect to. - * @package - */ - onFailedConnect(_otherConnection: Connection): void; - /** - * Connect this connection to another connection. - * @param {!Connection} otherConnection Connection to connect to. - * @return {boolean} Whether the the blocks are now connected or not. - */ - connect(otherConnection: Connection): boolean; - /** - * Disconnect this connection. - */ - disconnect(): void; - /** - * Disconnect two blocks that are connected by this connection. - * @param {!Block} parentBlock The superior block. - * @param {!Block} childBlock The inferior block. * @protected */ - protected disconnectInternal_(parentBlock: Block, childBlock: Block): void; - targetConnection: Connection; - /** - * Respawn the shadow block if there was one connected to the this connection. - * @protected - */ - protected respawnShadow_(): void; - /** - * Returns the block that this connection connects to. - * @return {?Block} The connected block or null if none is connected. - */ - targetBlock(): Block | null; - /** - * Is this connection compatible with another connection with respect to the - * value type system. E.g. square_root("Hello") is not compatible. - * @param {!Connection} otherConnection Connection to compare against. - * @return {boolean} True if the connections share a type. - * @deprecated July 2020. Will be deleted July 2021. Use the workspace's - * connectionChecker instead. - */ - checkType(otherConnection: Connection): boolean; - /** - * Is this connection compatible with another connection with respect to the - * value type system. E.g. square_root("Hello") is not compatible. - * @param {!Connection} otherConnection Connection to compare against. - * @return {boolean} True if the connections share a type. - * @private - * @deprecated October 2019. Will be deleted January 2021. Use the workspace's - * connectionChecker instead. - * @suppress {unusedPrivateMembers} - */ - private checkType_; - /** - * Function to be called when this connection's compatible types have changed. - * @protected - */ - protected onCheckChanged_(): void; - /** - * Change a connection's compatibility. - * @param {?(string|!Array)} check Compatible value type or list of - * value types. Null if all types are compatible. - * @return {!Connection} The connection being modified - * (to allow chaining). - */ - setCheck(check: (string | Array) | null): Connection; - check_: any[]; - /** - * Get a connection's compatibility. - * @return {?Array} List of compatible value types. - * Null if all types are compatible. - * @public - */ - public getCheck(): any[] | null; - /** - * Changes the connection's shadow block. - * @param {?Element} shadowDom DOM representation of a block or null. - */ - setShadowDom(shadowDom: Element | null): void; - /** - * Returns the xml representation of the connection's shadow block. - * @param {boolean=} returnCurrent If true, and the shadow block is currently - * attached to this connection, this serializes the state of that block - * and returns it (so that field values are correct). Otherwise the saved - * shadowDom is just returned. - * @return {?Element} Shadow DOM representation of a block or null. - */ - getShadowDom(returnCurrent?: boolean | undefined): Element | null; - /** - * Changes the connection's shadow block. - * @param {?blocks.State} shadowState An state represetation of the block or - * null. - */ - setShadowState(shadowState: any | null): void; - /** - * Returns the serialized object representation of the connection's shadow - * block. - * @param {boolean=} returnCurrent If true, and the shadow block is currently - * attached to this connection, this serializes the state of that block - * and returns it (so that field values are correct). Otherwise the saved - * state is just returned. - * @return {?blocks.State} Serialized object representation of the block, or - * null. - */ - getShadowState(returnCurrent?: boolean | undefined): any | null; - /** - * Find all nearby compatible connections to this connection. - * Type checking does not apply, since this function is used for bumping. - * - * Headless configurations (the default) do not have neighboring connection, - * and always return an empty list (the default). - * {@link Blockly.RenderedConnection} overrides this behavior with a list - * computed from the rendered positioning. - * @param {number} _maxLimit The maximum radius to another connection. - * @return {!Array} List of connections. - * @package - */ - neighbours(_maxLimit: number): Array; - /** - * Get the parent input of a connection. - * @return {?Input} The input that the connection belongs to or null if - * no parent exists. - * @package - */ - getParentInput(): Input | null; - /** - * This method returns a string describing this Connection in developer terms - * (English only). Intended to on be used in console logs and errors. - * @return {string} The description. - */ - toString(): string; - /** - * Returns the state of the shadowDom_ and shadowState_ properties, then - * temporarily sets those properties to null so no shadow respawns. - * @return {{shadowDom: ?Element, shadowState: ?blocks.State}} The state of both - * the shadowDom_ and shadowState_ properties. - * @private - */ - private stashShadowState_; - shadowDom_: Element; - shadowState_: any; - /** - * Reapplies the stashed state of the shadowDom_ and shadowState_ properties. - * @param {{shadowDom: ?Element, shadowState: ?blocks.State}} param0 The state - * to reapply to the shadowDom_ and shadowState_ properties. - * @private - */ - private applyShadowState_; - /** - * Sets the state of the shadow of this connection. - * @param {{shadowDom: (?Element|undefined), shadowState: - * (?blocks.State|undefined)}=} param0 The state to set the shadow of this - * connection to. - * @private - */ - private setShadowStateInternal_; - /** - * Creates a shadow block based on the current shadowState_ or shadowDom_. - * shadowState_ gets priority. - * @param {boolean} attemptToConnect Whether to try to connect the shadow block - * to this connection or not. - * @return {?Block} The shadow block that was created, or null if both the - * shadowState_ and shadowDom_ are null. - * @private - */ - private createShadowBlock_; - /** - * Saves the given shadow block to both the shadowDom_ and shadowState_ - * properties, in their respective serialized forms. - * @param {?Block} shadow The shadow to serialize, or null. - * @private - */ - private serializeShadow_; - /** - * Horizontal location of this connection. - * @type {number} - * @package - */ - x: number; - /** - * Vertical location of this connection. - * @type {number} - * @package - */ - y: number; + protected canConnectToPrevious_(a: Connection, b: Connection): boolean; } - export namespace Connection { - const CAN_CONNECT: number; - const REASON_SELF_CONNECTION: number; - const REASON_WRONG_TYPE: number; - const REASON_TARGET_NULL: number; - const REASON_CHECKS_FAILED: number; - const REASON_DIFFERENT_WORKSPACES: number; - const REASON_SHADOW_PARENT: number; - const REASON_DRAG_CHECKS_FAILED: number; - } - import { Block } from "block"; - import { IConnectionChecker } from "interfaces/i_connection_checker"; - import { Input } from "input"; + import { IConnectionChecker } from "core/interfaces/i_connection_checker"; + import { Connection } from "core/connection"; + import { RenderedConnection } from "core/rendered_connection"; } -declare module "keyboard_nav/ast_node" { - export class ASTNode { +declare module "core/workspace" { + /** + * Class for a workspace. This is a data structure that contains blocks. + * There is no UI, and can be created headlessly. + * @implements {IASTNodeLocation} + * @alias Blockly.Workspace + */ + export class Workspace implements IASTNodeLocation { /** - * Whether an AST node of the given type points to a connection. - * @param {string} type The type to check. One of ASTNode.types. - * @return {boolean} True if a node of the given type points to a connection. - * @private + * Find the workspace with the specified ID. + * @param {string} id ID of workspace to find. + * @return {?Workspace} The sought after workspace or null if not found. */ - private static isConnectionType_; + static getById(id: string): Workspace | null; /** - * Create an AST node pointing to a field. - * @param {Field} field The location of the AST node. - * @return {ASTNode} An AST node pointing to a field. + * Find all workspaces. + * @return {!Array} Array of workspaces. */ - static createFieldNode(field: Field): ASTNode; + static getAll(): Array; /** - * Creates an AST node pointing to a connection. If the connection has a parent - * input then create an AST node of type input that will hold the connection. - * @param {Connection} connection This is the connection the node will - * point to. - * @return {ASTNode} An AST node pointing to a connection. + * @param {!Options=} opt_options Dictionary of options. */ - static createConnectionNode(connection: Connection): ASTNode; + constructor(opt_options?: Options | undefined); + /** @type {string} */ + id: string; + /** @type {!Options} */ + options: Options; + /** @type {boolean} */ + RTL: boolean; + /** @type {boolean} */ + horizontalLayout: boolean; + /** @type {toolbox.Position} */ + toolboxPosition: toolbox.Position; /** - * Creates an AST node pointing to an input. Stores the input connection as the - * location. - * @param {Input} input The input used to create an AST node. - * @return {ASTNode} An AST node pointing to a input. - */ - static createInputNode(input: Input): ASTNode; - /** - * Creates an AST node pointing to a block. - * @param {Block} block The block used to create an AST node. - * @return {ASTNode} An AST node pointing to a block. - */ - static createBlockNode(block: Block): ASTNode; - /** - * Create an AST node of type stack. A stack, represented by its top block, is - * the set of all blocks connected to a top block, including the top block. - * @param {Block} topBlock A top block has no parent and can be found - * in the list returned by workspace.getTopBlocks(). - * @return {ASTNode} An AST node of type stack that points to the top - * block on the stack. - */ - static createStackNode(topBlock: Block): ASTNode; - /** - * Creates an AST node pointing to a workspace. - * @param {!Workspace} workspace The workspace that we are on. - * @param {Coordinate} wsCoordinate The position on the workspace - * for this node. - * @return {ASTNode} An AST node pointing to a workspace and a position - * on the workspace. - */ - static createWorkspaceNode(workspace: Workspace, wsCoordinate: Coordinate): ASTNode; - /** - * Creates an AST node for the top position on a block. - * This is either an output connection, previous connection, or block. - * @param {!Block} block The block to find the top most AST node on. - * @return {ASTNode} The AST node holding the top most position on the - * block. - */ - static createTopNode(block: Block): ASTNode; - /** - * Class for an AST node. - * It is recommended that you use one of the createNode methods instead of - * creating a node directly. - * @param {string} type The type of the location. - * Must be in ASTNode.types. - * @param {!IASTNodeLocation} location The position in the AST. - * @param {!ASTNode.Params=} opt_params Optional dictionary of options. - * @constructor - * @alias Blockly.ASTNode - */ - constructor(type: string, location: IASTNodeLocation, opt_params?: ASTNode.Params | undefined); - /** - * The type of the location. - * One of ASTNode.types - * @type {string} - * @private - */ - private type_; - /** - * Whether the location points to a connection. + * Returns `true` if the workspace is visible and `false` if it's headless. * @type {boolean} - * @private */ - private isConnection_; + rendered: boolean; /** - * The location of the AST node. - * @type {!IASTNodeLocation} - * @private + * Is this workspace the surface for a flyout? + * @type {boolean} */ - private location_; + isFlyout: boolean; /** - * The coordinate on the workspace. - * @type {Coordinate} - * @private - */ - private wsCoordinate_; - /** - * Parse the optional parameters. - * @param {?ASTNode.Params} params The user specified parameters. - * @private - */ - private processParams_; - /** - * Gets the value pointed to by this node. - * It is the callers responsibility to check the node type to figure out what - * type of object they get back from this. - * @return {!IASTNodeLocation} The current field, connection, workspace, or - * block the cursor is on. - */ - getLocation(): IASTNodeLocation; - /** - * The type of the current location. - * One of ASTNode.types - * @return {string} The type of the location. - */ - getType(): string; - /** - * The coordinate on the workspace. - * @return {Coordinate} The workspace coordinate or null if the - * location is not a workspace. - */ - getWsCoordinate(): Coordinate; - /** - * Whether the node points to a connection. - * @return {boolean} [description] + * Is this workspace the surface for a mutator? + * @type {boolean} * @package */ - isConnection(): boolean; + isMutator: boolean; /** - * Given an input find the next editable field or an input with a non null - * connection in the same block. The current location must be an input - * connection. - * @return {ASTNode} The AST node holding the next field or connection - * or null if there is no editable field or input connection after the given - * input. + * Returns `true` if the workspace is currently in the process of a bulk + * clear. + * @type {boolean} + * @package + */ + isClearing: boolean; + /** + * Maximum number of undo events in stack. `0` turns off undo, `Infinity` + * sets it to unlimited. + * @type {number} + */ + MAX_UNDO: number; + /** + * Set of databases for rapid lookup of connection locations. + * @type {Array} + */ + connectionDBList: Array; + /** + * An object that encapsulates logic for safety, type, and dragging checks. + * @type {!IConnectionChecker} + */ + connectionChecker: () => void; + /** + * @type {!Array} * @private */ - private findNextForInput_; + private topBlocks_; /** - * Given a field find the next editable field or an input with a non null - * connection in the same block. The current location must be a field. - * @return {ASTNode} The AST node pointing to the next field or - * connection or null if there is no editable field or input connection - * after the given input. + * @type {!Array} * @private */ - private findNextForField_; + private topComments_; /** - * Given an input find the previous editable field or an input with a non null - * connection in the same block. The current location must be an input - * connection. - * @return {ASTNode} The AST node holding the previous field or - * connection. + * @type {!Object} * @private */ - private findPrevForInput_; + private commentDB_; /** - * Given a field find the previous editable field or an input with a non null - * connection in the same block. The current location must be a field. - * @return {ASTNode} The AST node holding the previous input or field. + * @type {!Array} * @private */ - private findPrevForField_; + private listeners_; /** - * Navigate between stacks of blocks on the workspace. - * @param {boolean} forward True to go forward. False to go backwards. - * @return {ASTNode} The first block of the next stack or null if there - * are no blocks on the workspace. + * @type {!Array} + * @protected + */ + protected undoStack_: Array; + /** + * @type {!Array} + * @protected + */ + protected redoStack_: Array; + /** + * @type {!Object} * @private */ - private navigateBetweenStacks_; + private blockDB_; /** - * Finds the top most AST node for a given block. - * This is either the previous connection, output connection or block depending - * on what kind of connections the block has. - * @param {!Block} block The block that we want to find the top - * connection on. - * @return {!ASTNode} The AST node containing the top connection. + * @type {!Object} * @private */ - private findTopASTNodeForBlock_; + private typedBlocksDB_; /** - * Get the AST node pointing to the input that the block is nested under or if - * the block is not nested then get the stack AST node. - * @param {Block} block The source block of the current location. - * @return {ASTNode} The AST node pointing to the input connection or - * the top block of the stack this block is in. + * 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. + * @type {!VariableMap} * @private */ - private getOutAstNodeForBlock_; + private variableMap_; /** - * Find the first editable field or input with a connection on a given block. - * @param {!Block} block The source block of the current location. - * @return {ASTNode} An AST node pointing to the first field or input. - * Null if there are no editable fields or inputs with connections on the block. + * 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 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 {?VariableMap} * @private */ - private findFirstFieldOrInput_; + private potentialVariableMap_; /** - * Finds the source block of the location of this node. - * @return {Block} The source block of the location, or null if the node - * is of type workspace. + * Dispose of this workspace. + * Unlink from all DOM elements to prevent memory leaks. + * @suppress {checkTypes} */ - getSourceBlock(): Block; + dispose(): void; /** - * Find the element to the right of the current element in the AST. - * @return {ASTNode} An AST node that wraps the next field, connection, - * block, or workspace. Or null if there is no node to the right. + * Compare function for sorting objects (blocks, comments, etc) by position; + * top to bottom (with slight LTR or RTL bias). + * @param {!Block | !WorkspaceComment} a The first object to + * compare. + * @param {!Block | !WorkspaceComment} b The second object to + * compare. + * @return {number} The comparison value. This tells Array.sort() how to + * change object a's index. + * @private */ - next(): ASTNode; + private sortObjects_; /** - * Find the element one level below and all the way to the left of the current - * location. - * @return {ASTNode} An AST node that wraps the next field, connection, - * workspace, or block. Or null if there is nothing below this node. + * Adds a block to the list of top blocks. + * @param {!Block} block Block to add. */ - in(): ASTNode; + addTopBlock(block: Block): void; /** - * Find the element to the left of the current element in the AST. - * @return {ASTNode} An AST node that wraps the previous field, - * connection, workspace or block. Or null if no node exists to the left. - * null. + * Removes a block from the list of top blocks. + * @param {!Block} block Block to remove. */ - prev(): ASTNode; + removeTopBlock(block: Block): void; /** - * Find the next element that is one position above and all the way to the left - * of the current location. - * @return {ASTNode} An AST node that wraps the next field, connection, - * workspace or block. Or null if we are at the workspace level. + * Finds the top-level blocks and returns them. Blocks are optionally sorted + * by position; top to bottom (with slight LTR or RTL bias). + * @param {boolean} ordered Sort the list if true. + * @return {!Array} The top-level block objects. */ - out(): ASTNode; + getTopBlocks(ordered: boolean): Array; + /** + * Add a block to the list of blocks keyed by type. + * @param {!Block} block Block to add. + */ + addTypedBlock(block: Block): void; + /** + * Remove a block from the list of blocks keyed by type. + * @param {!Block} block Block to remove. + */ + removeTypedBlock(block: Block): void; + /** + * Finds the blocks with the associated type and returns them. Blocks are + * optionally sorted by position; top to bottom (with slight LTR or RTL bias). + * @param {string} type The type of block to search for. + * @param {boolean} ordered Sort the list if true. + * @return {!Array} The blocks of the given type. + */ + getBlocksByType(type: string, ordered: boolean): Array; + /** + * Adds a comment to the list of top comments. + * @param {!WorkspaceComment} comment comment to add. + * @package + */ + addTopComment(comment: WorkspaceComment): void; + /** + * Removes a comment from the list of top comments. + * @param {!WorkspaceComment} comment comment to remove. + * @package + */ + removeTopComment(comment: WorkspaceComment): void; + /** + * Finds the top-level comments and returns them. Comments are optionally + * sorted by position; top to bottom (with slight LTR or RTL bias). + * @param {boolean} ordered Sort the list if true. + * @return {!Array} The top-level comment objects. + * @package + */ + getTopComments(ordered: boolean): Array; + /** + * Find all blocks in workspace. Blocks are optionally sorted + * by position; top to bottom (with slight LTR or RTL bias). + * @param {boolean} ordered Sort the list if true. + * @return {!Array} Array of blocks. + */ + getAllBlocks(ordered: boolean): Array; + /** + * Dispose of all blocks and comments in workspace. + */ + clear(): void; + /** + * 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. + */ + renameVariableById(id: string, newName: string): void; + /** + * Create a variable with a given name, optional type, and optional ID. + * @param {string} name The name of the variable. This must be unique across + * variables and procedures. + * @param {?string=} opt_type The type of the variable like 'int' or 'string'. + * Does not need to be unique. Field_variable can filter variables based + * on their type. This will default to '' which is a specific type. + * @param {?string=} opt_id The unique ID of the variable. This will default + * to a UUID. + * @return {!VariableModel} The newly created variable. + */ + createVariable(name: string, opt_type?: (string | null) | undefined, opt_id?: (string | null) | undefined): VariableModel; + /** + * 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. + */ + getVariableUsesById(id: string): Array; + /** + * 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. + */ + deleteVariableById(id: string): void; + /** + * Find the variable by the given name and return it. Return null if 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 {?VariableModel} The variable with the given name. + */ + getVariable(name: string, opt_type?: string | undefined): VariableModel | null; + /** + * Find the variable by the given ID and return it. Return null if not found. + * @param {string} id The ID to check for. + * @return {?VariableModel} The variable with the given ID. + */ + getVariableById(id: string): VariableModel | null; + /** + * 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. + */ + getVariablesOfType(type: string | null): Array; + /** + * Return all variable types. + * @return {!Array} List of variable types. + * @package + */ + getVariableTypes(): Array; + /** + * Return all variables of all types. + * @return {!Array} List of variable models. + */ + getAllVariables(): Array; + /** + * Returns all variable names of all types. + * @return {!Array} List of all variable names of all types. + */ + getAllVariableNames(): Array; + /** + * Returns the horizontal offset of the workspace. + * Intended for LTR/RTL compatibility in XML. + * Not relevant for a headless workspace. + * @return {number} Width. + */ + getWidth(): number; + /** + * Obtain a newly created block. + * @param {!string} prototypeName Name of the language object containing + * type-specific functions for this block. + * @param {string=} opt_id Optional ID. Use this ID if provided, otherwise + * create a new ID. + * @return {!Block} The created block. + */ + newBlock(prototypeName: string, opt_id?: string | undefined): Block; + /** + * The number of blocks that may be added to the workspace before reaching + * the maxBlocks. + * @return {number} Number of blocks left. + */ + remainingCapacity(): number; + /** + * The number of blocks of the given type that may be added to the workspace + * before reaching the maxInstances allowed for that type. + * @param {string} type Type of block to return capacity for. + * @return {number} Number of blocks of type left. + */ + remainingCapacityOfType(type: string): number; + /** + * Check if there is remaining capacity for blocks of the given counts to be + * created. If the total number of blocks represented by the map is more + * than the total remaining capacity, it returns false. If a type count is + * more than the remaining capacity for that type, it returns false. + * @param {!Object} typeCountsMap A map of types to counts (usually + * representing + * blocks to be created). + * @return {boolean} True if there is capacity for the given map, + * false otherwise. + */ + isCapacityAvailable(typeCountsMap: Object): boolean; + /** + * Checks if the workspace has any limits on the maximum number of blocks, + * or the maximum number of blocks of specific types. + * @return {boolean} True if it has block limits, false otherwise. + */ + hasBlockLimits(): boolean; + /** + * Gets the undo stack for workplace. + * @return {!Array} undo stack + * @package + */ + getUndoStack(): Array; + /** + * Gets the redo stack for workplace. + * @return {!Array} redo stack + * @package + */ + getRedoStack(): Array; + /** + * Undo or redo the previous action. + * @param {boolean} redo False if undo, true if redo. + */ + undo(redo: boolean): void; + /** + * Clear the undo/redo stacks. + */ + clearUndo(): void; + /** + * When something in this workspace changes, call a function. + * Note that there may be a few recent events already on the stack. Thus the + * new change listener might be called with events that occurred a few + * milliseconds before the change listener was added. + * @param {!Function} func Function to call. + * @return {!Function} Obsolete return value, ignore. + */ + addChangeListener(func: Function): Function; + /** + * Stop listening for this workspace's changes. + * @param {!Function} func Function to stop calling. + */ + removeChangeListener(func: Function): void; + /** + * Fire a change event. + * @param {!Abstract} event Event to fire. + */ + fireChangeListener(event: Abstract): void; + /** + * Find the block on this workspace with the specified ID. + * @param {string} id ID of block to find. + * @return {?Block} The sought after block, or null if not found. + */ + getBlockById(id: string): Block | null; + /** + * Set a block on this workspace with the specified ID. + * @param {string} id ID of block to set. + * @param {Block} block The block to set. + * @package + */ + setBlockById(id: string, block: Block): void; + /** + * Delete a block off this workspace with the specified ID. + * @param {string} id ID of block to delete. + * @package + */ + removeBlockById(id: string): void; + /** + * Find the comment on this workspace with the specified ID. + * @param {string} id ID of comment to find. + * @return {?WorkspaceComment} The sought after comment, or null if not + * found. + * @package + */ + getCommentById(id: string): WorkspaceComment | null; + /** + * Checks whether all value and statement inputs in the workspace are filled + * with blocks. + * @param {boolean=} opt_shadowBlocksAreFilled An optional argument + * controlling whether shadow blocks are counted as filled. Defaults to + * true. + * @return {boolean} True if all inputs are filled, false otherwise. + */ + allInputsFilled(opt_shadowBlocksAreFilled?: boolean | undefined): boolean; + /** + * Return the variable map that contains "potential" variables. + * These exist in the flyout but not in the workspace. + * @return {?VariableMap} The potential variable map. + * @package + */ + getPotentialVariableMap(): VariableMap | null; + /** + * Create and store the potential variable map for this workspace. + * @package + */ + createPotentialVariableMap(): void; + /** + * Return the map of all variables on the workspace. + * @return {!VariableMap} The variable map. + */ + getVariableMap(): VariableMap; + /** + * Set the map of all variables on the workspace. + * @param {!VariableMap} variableMap The variable map. + * @package + */ + setVariableMap(variableMap: VariableMap): void; } - export namespace ASTNode { - namespace types { - const FIELD: string; - const BLOCK: string; - const INPUT: string; - const OUTPUT: string; - const NEXT: string; - const PREVIOUS: string; - const STACK: string; - const WORKSPACE: string; - } - /** - * Object holding different types for an AST node. - */ - type types = string; - const NAVIGATE_ALL_FIELDS: boolean; - const DEFAULT_OFFSET_Y: number; - type Params = { - wsCoordinate: Coordinate; - }; + export namespace Workspace { + const SCAN_ANGLE: number; } - import { IASTNodeLocation } from "interfaces/i_ast_node_location"; - import { Coordinate } from "utils/coordinate"; - import { Block } from "block"; - import { Field } from "field"; - import { Connection } from "connection"; - import { Input } from "input"; - import { Workspace } from "workspace"; + import { IASTNodeLocation } from "core/interfaces/i_ast_node_location"; + import { Options } from "core/options"; + import * as toolbox from "core/utils/toolbox"; + import { ConnectionDB } from "core/connection_db"; + import { Abstract } from "core/events/events_abstract"; + import { Block } from "core/block"; + import { WorkspaceComment } from "core/workspace_comment"; + import { VariableModel } from "core/variable_model"; + import { VariableMap } from "core/variable_map"; } -declare module "keyboard_nav/cursor" { - export class Cursor extends Marker { +declare module "core/events/events_abstract" { + /** + * Abstract class for an event. + * @abstract + * @alias Blockly.Events.Abstract + */ + export class Abstract { /** - * @override + * Whether or not the event is blank (to be populated by fromJson). + * @type {?boolean} */ - override type: string; + isBlank: boolean | null; /** - * Find the next connection, field, or block. - * @return {ASTNode} The next element, or null if the current node is - * not set or there is no next value. - * @public + * The workspace identifier for this event. + * @type {string|undefined} */ - public next(): ASTNode; + workspaceId: string | undefined; /** - * Find the in connection or field. - * @return {ASTNode} The in element, or null if the current node is - * not set or there is no in value. - * @public + * The event group id for the group this event belongs to. Groups define + * events that should be treated as an single action from the user's + * perspective, and should be undone together. + * @type {string} */ - public in(): ASTNode; + group: string; /** - * Find the previous connection, field, or block. - * @return {ASTNode} The previous element, or null if the current node - * is not set or there is no previous value. - * @public + * Sets whether the event should be added to the undo stack. + * @type {boolean} */ - public prev(): ASTNode; + recordUndo: boolean; /** - * Find the out connection, field, or block. - * @return {ASTNode} The out element, or null if the current node is - * not set or there is no out value. - * @public + * Whether or not the event is a UI event. + * @type {boolean} */ - public out(): ASTNode; + isUiEvent: boolean; + /** + * Type of this event. + * @type {string|undefined} + */ + type: string | undefined; + /** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ + toJson(): Object; + /** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ + fromJson(json: Object): void; + /** + * Does this event record any change of state? + * @return {boolean} True if null, false if something changed. + */ + isNull(): boolean; + /** + * Run an event. + * @param {boolean} _forward True if run forward, false if run backward + * (undo). + */ + run(_forward: boolean): void; + /** + * Get workspace the event belongs to. + * @return {!Workspace} The workspace the event belongs to. + * @throws {Error} if workspace is null. + * @protected + */ + protected getEventWorkspace_(): Workspace; } - import { ASTNode } from "keyboard_nav/ast_node"; - import { Marker } from "keyboard_nav/marker"; + import { Workspace } from "core/workspace"; } -declare module "registry" { +declare module "core/registry" { export namespace TEST_ONLY { export { typeMap }; } @@ -18450,7 +19879,7 @@ declare module "registry" { */ const typeMap: { [x: string]: { - [x: string]: ((new () => unknown) | any); + [x: string]: ((new () => unknown) | Object); }; }; /** @@ -18459,13 +19888,14 @@ declare module "registry" { * @alias Blockly.registry.DEFAULT */ export const DEFAULT: string; - export class Type { + /** + * A name with the type of the element stored in the generic. + * @template T + * @alias Blockly.registry.Type + */ + export class Type { /** - * A name with the type of the element stored in the generic. * @param {string} name The name of the registry type. - * @constructor - * @template T - * @alias Blockly.registry.Type */ constructor(name: string); /** @@ -18476,24 +19906,23 @@ declare module "registry" { /** * Returns the name of the type. * @return {string} The name. - * @override */ toString(): string; } export namespace Type { - const CONNECTION_CHECKER: any; - const CURSOR: any; - const EVENT: any; - const FIELD: any; - const RENDERER: any; - const TOOLBOX: any; - const THEME: any; - const TOOLBOX_ITEM: any; - const FLYOUTS_VERTICAL_TOOLBOX: any; - const FLYOUTS_HORIZONTAL_TOOLBOX: any; - const METRICS_MANAGER: any; - const BLOCK_DRAGGER: any; - const SERIALIZER: any; + const CONNECTION_CHECKER: Type<() => void>; + const CURSOR: Type; + const EVENT: Type; + const FIELD: Type; + const RENDERER: Type; + const TOOLBOX: Type<() => void>; + const THEME: Type; + const TOOLBOX_ITEM: Type; + const FLYOUTS_VERTICAL_TOOLBOX: Type; + const FLYOUTS_HORIZONTAL_TOOLBOX: Type; + const METRICS_MANAGER: Type<() => void>; + const BLOCK_DRAGGER: Type<() => void>; + const SERIALIZER: Type; } /** * Registers a class based on a type and name. @@ -18510,7 +19939,7 @@ declare module "registry" { * @template T * @alias Blockly.registry.register */ - export function register(type: any, name: string, registryItem: any, opt_allowOverrides?: boolean | undefined): void; + export function register(type: string | Type, name: string, registryItem: Object | (new (...arg1: unknown[]) => T) | null, opt_allowOverrides?: boolean | undefined): void; /** * Unregisters the registry item with the given type and name. * @param {string|!Type} type The type of the plugin. @@ -18519,7 +19948,7 @@ declare module "registry" { * @template T * @alias Blockly.registry.unregister */ - export function unregister(type: any, name: string): void; + export function unregister(type: string | Type, name: string): void; /** * Returns whether or not the registry contains an item with the given type and * name. @@ -18531,7 +19960,7 @@ declare module "registry" { * @template T * @alias Blockly.registry.hasItem */ - export function hasItem(type: any, name: string): boolean; + export function hasItem(type: string | Type, name: string): boolean; /** * Gets the class for the given name and type. * @param {string|!Type} type The type of the plugin. @@ -18544,7 +19973,7 @@ declare module "registry" { * @template T * @alias Blockly.registry.getClass */ - export function getClass(type: any, name: string, opt_throwIfMissing?: boolean | undefined): new (...arg1: unknown[]) => T; + export function getClass(type: string | Type, name: string, opt_throwIfMissing?: boolean | undefined): (new (...arg1: unknown[]) => T) | null; /** * Gets the object for the given name and type. * @param {string|!Type} type The type of the plugin. @@ -18556,7 +19985,7 @@ declare module "registry" { * @template T * @alias Blockly.registry.getObject */ - export function getObject(type: any, name: string, opt_throwIfMissing?: boolean | undefined): T; + export function getObject(type: string | Type, name: string, opt_throwIfMissing?: boolean | undefined): T | null; /** * Returns a map of items registered with the given type. * @param {string|!Type} type The type of the plugin. (e.g. Category) @@ -18569,9 +19998,9 @@ declare module "registry" { * @template T * @alias Blockly.registry.getAllItems */ - export function getAllItems(type: any, opt_cased: boolean, opt_throwIfMissing?: boolean | undefined): { - [x: string]: T | (new (...arg1: unknown[]) => T); - }; + export function getAllItems(type: string | Type, opt_cased: boolean, opt_throwIfMissing?: boolean | undefined): { + [x: string]: T | (new (...arg1: unknown[]) => T) | null; + } | null; /** * Gets the class from Blockly options for the given type. * This is used for plugins that override a built in feature. (e.g. Toolbox) @@ -18584,14 +20013,23 @@ declare module "registry" { * @template T * @alias Blockly.registry.getClassFromOptions */ - export function getClassFromOptions(type: any, options: Options, opt_throwIfMissing?: boolean | undefined): new (...arg1: unknown[]) => T; - import { Options } from "options"; + export function getClassFromOptions(type: Type, options: Options, opt_throwIfMissing?: boolean | undefined): (new (...arg1: unknown[]) => T) | null; + import { Cursor } from "core/keyboard_nav/cursor"; + import { Abstract } from "core/events/events_abstract"; + import { Field } from "core/field"; + import { Renderer } from "core/renderers/common/renderer"; + import { Theme } from "core/theme"; + import { ToolboxItem } from "core/toolbox/toolbox_item"; + import { IFlyout } from "core/interfaces/i_flyout"; + import { ISerializer } from "core/interfaces/i_serializer"; + import { Options } from "core/options"; export {}; } -declare module "events/utils" { +declare module "core/events/utils" { export namespace TEST_ONLY { export { FIRE_QUEUE }; export { fireNow }; + export { setFireStub }; } /** * Sets whether events should be added to the undo stack. @@ -18794,7 +20232,7 @@ declare module "events/utils" { * @param {!Abstract} event Custom data for event. * @alias Blockly.Events.utils.fire */ - export function fire(event: typeof Abstract): void; + export function fire(event: Abstract): void; /** * Filter the queued events and merge duplicates. * @param {!Array} queueIn Array of events. @@ -18802,7 +20240,7 @@ declare module "events/utils" { * @return {!Array} Array of filtered events. * @alias Blockly.Events.utils.filter */ - export function filter(queueIn: Array, forward: boolean): Array; + export function filter(queueIn: Array, forward: boolean): Array; /** * Modify pending undo events so that when they are fired they don't land * in the undo stack. Called by Workspace.clearUndo. @@ -18855,7 +20293,7 @@ declare module "events/utils" { * @throws {Error} if an event type is not found in the registry. * @alias Blockly.Events.utils.fromJson */ - export function fromJson(json: any, workspace: Workspace): typeof Abstract; + export function fromJson(json: Object, workspace: Workspace): Abstract; /** * Gets the class for a specific event type from the registry. * @param {string} eventType The type of the event to get. @@ -18863,7 +20301,7 @@ declare module "events/utils" { * the given type or null if none exists. * @alias Blockly.Events.utils.get */ - export function get(eventType: string): (new (...args: unknown[]) => typeof Abstract) | null; + export function get(eventType: string): (new (...args: unknown[]) => Abstract) | null; /** * Enable/disable a block depending on whether it is properly connected. * Use this on applications where all blocks should be connected to a top block. @@ -18872,91 +20310,1523 @@ declare module "events/utils" { * @param {!Abstract} event Custom data for event. * @alias Blockly.Events.utils.disableOrphans */ - export function disableOrphans(event: typeof Abstract): void; + export function disableOrphans(event: Abstract): void; /** * List of events queued for firing. + * @type {!Array} */ - const FIRE_QUEUE: any[]; + const FIRE_QUEUE: Array; /** * Fire all queued events. */ function fireNow(): void; - import { BlockCreate } from "events/events_block_create"; - import { BlockMove } from "events/events_block_move"; - import { CommentCreate } from "events/events_comment_create"; - import { CommentMove } from "events/events_comment_move"; - import * as Abstract from "events/events_abstract"; - import { Block } from "block"; - import { Workspace } from "workspace"; + function setFireStub(stub: any): void; + import { BlockCreate } from "core/events/events_block_create"; + import { BlockMove } from "core/events/events_block_move"; + import { CommentCreate } from "core/events/events_comment_create"; + import { CommentMove } from "core/events/events_comment_move"; + import { Abstract } from "core/events/events_abstract"; + import { Block } from "core/block"; + import { Workspace } from "core/workspace"; export {}; } -declare module "events/events_abstract" { - export {}; +declare module "core/xml" { + /** + * Encode a block tree as XML. + * @param {!Workspace} workspace The workspace containing blocks. + * @param {boolean=} opt_noId True if the encoder should skip the block IDs. + * @return {!Element} XML DOM element. + * @alias Blockly.Xml.workspaceToDom + */ + export function workspaceToDom(workspace: Workspace, opt_noId?: boolean | undefined): Element; + /** + * Encode a list of variables as XML. + * @param {!Array} variableList List of all variable + * models. + * @return {!Element} Tree of XML elements. + * @alias Blockly.Xml.variablesToDom + */ + export function variablesToDom(variableList: Array): Element; + /** + * Encode a block subtree as XML with XY coordinates. + * @param {!Block} block The root block to encode. + * @param {boolean=} opt_noId True if the encoder should skip the block ID. + * @return {!Element|!DocumentFragment} Tree of XML elements or an empty + * document fragment if the block was an insertion marker. + * @alias Blockly.Xml.blockToDomWithXY + */ + export function blockToDomWithXY(block: Block, opt_noId?: boolean | undefined): Element | DocumentFragment; + /** + * Encode a block subtree as XML. + * @param {!Block} block The root block to encode. + * @param {boolean=} opt_noId True if the encoder should skip the block ID. + * @return {!Element|!DocumentFragment} Tree of XML elements or an empty + * document fragment if the block was an insertion marker. + * @alias Blockly.Xml.blockToDom + */ + export function blockToDom(block: Block, opt_noId?: boolean | undefined): Element | DocumentFragment; + /** + * Converts a DOM structure into plain text. + * Currently the text format is fairly ugly: all one line with no whitespace, + * unless the DOM itself has whitespace built-in. + * @param {!Node} dom A tree of XML nodes. + * @return {string} Text representation. + * @alias Blockly.Xml.domToText + */ + export function domToText(dom: Node): string; + /** + * Converts a DOM structure into properly indented text. + * @param {!Node} dom A tree of XML elements. + * @return {string} Text representation. + * @alias Blockly.Xml.domToPrettyText + */ + export function domToPrettyText(dom: Node): string; + /** + * Converts an XML string into a DOM structure. + * @param {string} text An XML string. + * @return {!Element} A DOM object representing the singular child of the + * document element. + * @throws if the text doesn't parse. + * @alias Blockly.Xml.textToDom + */ + export function textToDom(text: string): Element; + /** + * Clear the given workspace then decode an XML DOM and + * create blocks on the workspace. + * @param {!Element} xml XML DOM. + * @param {!WorkspaceSvg} workspace The workspace. + * @return {!Array} An array containing new block IDs. + * @alias Blockly.Xml.clearWorkspaceAndLoadFromXml + */ + export function clearWorkspaceAndLoadFromXml(xml: Element, workspace: WorkspaceSvg): Array; + /** + * Decode an XML DOM and create blocks on the workspace. + * @param {!Element} xml XML DOM. + * @param {!Workspace} workspace The workspace. + * @return {!Array} An array containing new block IDs. + * @suppress {strictModuleDepCheck} Suppress module check while workspace + * comments are not bundled in. + * @alias Blockly.Xml.domToWorkspace + */ + export function domToWorkspace(xml: Element, workspace: Workspace): Array; + /** + * Decode an XML DOM and create blocks on the workspace. Position the new + * blocks immediately below prior blocks, aligned by their starting edge. + * @param {!Element} xml The XML DOM. + * @param {!Workspace} workspace The workspace to add to. + * @return {!Array} An array containing new block IDs. + * @alias Blockly.Xml.appendDomToWorkspace + */ + export function appendDomToWorkspace(xml: Element, workspace: Workspace): Array; + /** + * Decode an XML block tag and create a block (and possibly sub blocks) on the + * workspace. + * @param {!Element} xmlBlock XML block element. + * @param {!Workspace} workspace The workspace. + * @return {!Block} The root block created. + * @alias Blockly.Xml.domToBlock + */ + export function domToBlock(xmlBlock: Element, workspace: Workspace): Block; + /** + * Decode an XML list of variables and add the variables to the workspace. + * @param {!Element} xmlVariables List of XML variable elements. + * @param {!Workspace} workspace The workspace to which the variable + * should be added. + * @alias Blockly.Xml.domToVariables + */ + export function domToVariables(xmlVariables: Element, workspace: Workspace): void; + /** + * Remove any 'next' block (statements in a stack). + * @param {!Element|!DocumentFragment} xmlBlock XML block element or an empty + * DocumentFragment if the block was an insertion marker. + * @alias Blockly.Xml.deleteNext + */ + export function deleteNext(xmlBlock: Element | DocumentFragment): void; + import { Workspace } from "core/workspace"; + import { VariableModel } from "core/variable_model"; + import { Block } from "core/block"; + import { WorkspaceSvg } from "core/workspace_svg"; } -declare module "events/events_block_delete" { - export class BlockDelete { +declare module "core/connection" { + /** + * Class for a connection between blocks. + * @implements {IASTNodeLocationWithBlock} + * @alias Blockly.Connection + */ + export class Connection implements IASTNodeLocationWithBlock { /** - * Class for a block deletion event. - * @param {!Block=} opt_block The deleted block. Undefined for a blank - * event. - * @extends {BlockBase} - * @constructor - * @alias Blockly.Events.BlockDelete + * Returns the connection (starting at the startBlock) which will accept + * the given connection. This includes compatible connection types and + * connection checks. + * @param {!Block} startBlock The block on which to start the search. + * @param {!Connection} orphanConnection The connection that is looking + * for a home. + * @return {?Connection} The suitable connection point on the chain of + * blocks, or null. */ - constructor(opt_block?: Block | undefined); - recordUndo: boolean; - oldXml: Element | DocumentFragment; - ids: string[]; + static getConnectionForOrphanedConnection(startBlock: Block, orphanConnection: Connection): Connection | null; /** - * Was the block that was just deleted a shadow? + * @param {!Block} source The block establishing this connection. + * @param {number} type The type of the connection. + */ + constructor(source: Block, type: number); + /** + * @type {!Block} + * @protected + */ + protected sourceBlock_: Block; + /** @type {number} */ + type: number; + /** + * Connection this connection connects to. Null if not connected. + * @type {Connection} + */ + targetConnection: Connection; + /** + * Has this connection been disposed of? * @type {boolean} + * @package */ - wasShadow: boolean; + disposed: boolean; /** - * JSON representation of the block that was just deleted. - * @type {!blocks.State} + * List of compatible value types. Null if all types are compatible. + * @type {Array} + * @private */ - oldJson: any; + private check_; /** - * Encode the event as JSON. - * @return {!Object} JSON representation. + * DOM representation of a shadow block, or null if none. + * @type {Element} + * @private */ - toJson(): any; + private shadowDom_; /** - * Decode the JSON event. - * @param {!Object} json JSON representation. + * Horizontal location of this connection. + * @type {number} + * @package */ - fromJson(json: any): void; + x: number; /** - * Run a deletion event. - * @param {boolean} forward True if run forward, false if run backward (undo). + * Vertical location of this connection. + * @type {number} + * @package */ - run(forward: boolean): void; + y: number; /** - * Type of this event. - * @type {string} + * @type {?blocks.State} + * @private */ - type: string; + private shadowState_; + /** + * Connect two connections together. This is the connection on the superior + * block. + * @param {!Connection} childConnection Connection on inferior block. + * @protected + */ + protected connect_(childConnection: Connection): void; + /** + * Dispose of this connection and deal with connected blocks. + * @package + */ + dispose(): void; + /** + * Get the source block for this connection. + * @return {!Block} The source block. + */ + getSourceBlock(): Block; + /** + * Does the connection belong to a superior block (higher in the source + * stack)? + * @return {boolean} True if connection faces down or right. + */ + isSuperior(): boolean; + /** + * Is the connection connected? + * @return {boolean} True if connection is connected to another connection. + */ + isConnected(): boolean; + /** + * Get the workspace's connection type checker object. + * @return {!IConnectionChecker} The connection type checker for the + * source block's workspace. + * @package + */ + getConnectionChecker(): () => void; + /** + * Called when an attempted connection fails. NOP by default (i.e. for + * headless workspaces). + * @param {!Connection} _otherConnection Connection that this connection + * failed to connect to. + * @package + */ + onFailedConnect(_otherConnection: Connection): void; + /** + * Connect this connection to another connection. + * @param {!Connection} otherConnection Connection to connect to. + * @return {boolean} Whether the the blocks are now connected or not. + */ + connect(otherConnection: Connection): boolean; + /** + * Disconnect this connection. + */ + disconnect(): void; + /** + * Disconnect two blocks that are connected by this connection. + * @param {!Block} parentBlock The superior block. + * @param {!Block} childBlock The inferior block. + * @protected + */ + protected disconnectInternal_(parentBlock: Block, childBlock: Block): void; + /** + * Respawn the shadow block if there was one connected to the this connection. + * @protected + */ + protected respawnShadow_(): void; + /** + * Returns the block that this connection connects to. + * @return {?Block} The connected block or null if none is connected. + */ + targetBlock(): Block | null; + /** + * Function to be called when this connection's compatible types have changed. + * @protected + */ + protected onCheckChanged_(): void; + /** + * Change a connection's compatibility. + * @param {?(string|!Array)} check Compatible value type or list of + * value types. Null if all types are compatible. + * @return {!Connection} The connection being modified + * (to allow chaining). + */ + setCheck(check: (string | Array) | null): Connection; + /** + * Get a connection's compatibility. + * @return {?Array} List of compatible value types. + * Null if all types are compatible. + * @public + */ + public getCheck(): any[] | null; + /** + * Changes the connection's shadow block. + * @param {?Element} shadowDom DOM representation of a block or null. + */ + setShadowDom(shadowDom: Element | null): void; + /** + * Returns the xml representation of the connection's shadow block. + * @param {boolean=} returnCurrent If true, and the shadow block is currently + * attached to this connection, this serializes the state of that block + * and returns it (so that field values are correct). Otherwise the saved + * shadowDom is just returned. + * @return {?Element} Shadow DOM representation of a block or null. + */ + getShadowDom(returnCurrent?: boolean | undefined): Element | null; + /** + * Changes the connection's shadow block. + * @param {?blocks.State} shadowState An state represetation of the block or + * null. + */ + setShadowState(shadowState: any | null): void; + /** + * Returns the serialized object representation of the connection's shadow + * block. + * @param {boolean=} returnCurrent If true, and the shadow block is currently + * attached to this connection, this serializes the state of that block + * and returns it (so that field values are correct). Otherwise the saved + * state is just returned. + * @return {?blocks.State} Serialized object representation of the block, or + * null. + */ + getShadowState(returnCurrent?: boolean | undefined): any | null; + /** + * Find all nearby compatible connections to this connection. + * Type checking does not apply, since this function is used for bumping. + * + * Headless configurations (the default) do not have neighboring connection, + * and always return an empty list (the default). + * {@link Blockly.RenderedConnection} overrides this behavior with a list + * computed from the rendered positioning. + * @param {number} _maxLimit The maximum radius to another connection. + * @return {!Array} List of connections. + * @package + */ + neighbours(_maxLimit: number): Array; + /** + * Get the parent input of a connection. + * @return {?Input} The input that the connection belongs to or null if + * no parent exists. + * @package + */ + getParentInput(): Input | null; + /** + * This method returns a string describing this Connection in developer terms + * (English only). Intended to on be used in console logs and errors. + * @return {string} The description. + */ + toString(): string; + /** + * Returns the state of the shadowDom_ and shadowState_ properties, then + * temporarily sets those properties to null so no shadow respawns. + * @return {{shadowDom: ?Element, shadowState: ?blocks.State}} The state of + * both the shadowDom_ and shadowState_ properties. + * @private + */ + private stashShadowState_; + /** + * Reapplies the stashed state of the shadowDom_ and shadowState_ properties. + * @param {{shadowDom: ?Element, shadowState: ?blocks.State}} param0 The state + * to reapply to the shadowDom_ and shadowState_ properties. + * @private + */ + private applyShadowState_; + /** + * Sets the state of the shadow of this connection. + * @param {{shadowDom: (?Element|undefined), shadowState: + * (?blocks.State|undefined)}=} param0 The state to set the shadow of this + * connection to. + * @private + */ + private setShadowStateInternal_; + /** + * Creates a shadow block based on the current shadowState_ or shadowDom_. + * shadowState_ gets priority. + * @param {boolean} attemptToConnect Whether to try to connect the shadow + * block to this connection or not. + * @return {?Block} The shadow block that was created, or null if both the + * shadowState_ and shadowDom_ are null. + * @private + */ + private createShadowBlock_; + /** + * Saves the given shadow block to both the shadowDom_ and shadowState_ + * properties, in their respective serialized forms. + * @param {?Block} shadow The shadow to serialize, or null. + * @private + */ + private serializeShadow_; } - import { Block } from "block"; + export namespace Connection { + const CAN_CONNECT: number; + const REASON_SELF_CONNECTION: number; + const REASON_WRONG_TYPE: number; + const REASON_TARGET_NULL: number; + const REASON_CHECKS_FAILED: number; + const REASON_DIFFERENT_WORKSPACES: number; + const REASON_SHADOW_PARENT: number; + const REASON_DRAG_CHECKS_FAILED: number; + const REASON_PREVIOUS_AND_OUTPUT: number; + } + import { IASTNodeLocationWithBlock } from "core/interfaces/i_ast_node_location_with_block"; + import { Block } from "core/block"; + import { Input } from "core/input"; } -declare module "block" { - export class Block { +declare module "core/common" { + /** + * All of the connections on blocks that are currently being dragged. + * @type {!Array} + */ + export const draggingConnections: Array; + /** + * Returns the last used top level workspace (based on focus). Try not to use + * this function, particularly if there are multiple Blockly instances on a + * page. + * @return {!Workspace} The main workspace. + * @alias Blockly.common.getMainWorkspace + */ + export function getMainWorkspace(): Workspace; + /** + * Sets last used main workspace. + * @param {!Workspace} workspace The most recently used top level workspace. + * @alias Blockly.common.setMainWorkspace + */ + export function setMainWorkspace(workspace: Workspace): void; + /** + * Returns the currently selected block. + * @return {?ICopyable} The currently selected block. + * @alias Blockly.common.getSelected + */ + export function getSelected(): { + (): void; + CopyData: ICopyable.CopyData; + } | null; + /** + * Sets the currently selected block. This function does not visually mark the + * block as selected or fire the required events. If you wish to + * programmatically select a block, use `BlockSvg#select`. + * @param {?ICopyable} newSelection The newly selected block. + * @alias Blockly.common.setSelected + * @package + */ + export function setSelected(newSelection: { + (): void; + CopyData: ICopyable.CopyData; + } | null): void; + /** + * Get the container element in which to render the WidgetDiv, DropDownDiv and\ + * Tooltip. + * @return {?Element} The parent container. + * @alias Blockly.common.getParentContainer + */ + export function getParentContainer(): Element | null; + /** + * Set the parent container. This is the container element that the WidgetDiv, + * DropDownDiv, and Tooltip are rendered into the first time `Blockly.inject` + * is called. + * This method is a NOP if called after the first ``Blockly.inject``. + * @param {!Element} newParent The container element. + * @alias Blockly.common.setParentContainer + */ + export function setParentContainer(newParent: Element): void; + /** + * Size the SVG image to completely fill its container. Call this when the view + * actually changes sizes (e.g. on a window resize/device orientation change). + * See workspace.resizeContents to resize the workspace when the contents + * change (e.g. when a block is added or removed). + * Record the height/width of the SVG image. + * @param {!WorkspaceSvg} workspace Any workspace in the SVG. + * @alias Blockly.common.svgResize + */ + export function svgResize(workspace: WorkspaceSvg): void; + import { Connection } from "core/connection"; + /** + * Get a map of all the block's descendants mapping their type to the number of + * children with that type. + * @param {!Block} block The block to map. + * @param {boolean=} opt_stripFollowing Optionally ignore all following + * statements (blocks that are not inside a value or statement input + * of the block). + * @return {!Object} Map of types to type counts for descendants of the bock. + * @alias Blockly.common.getBlockTypeCounts + */ + export function getBlockTypeCounts(block: Block, opt_stripFollowing?: boolean | undefined): Object; + /** + * Define blocks from an array of JSON block definitions, as might be generated + * by the Blockly Developer Tools. + * @param {!Array} jsonArray An array of JSON block definitions. + * @alias Blockly.common.defineBlocksWithJsonArray + */ + export function defineBlocksWithJsonArray(jsonArray: Array): void; + /** + * Define blocks from an array of JSON block definitions, as might be generated + * by the Blockly Developer Tools. + * @param {!Array} jsonArray An array of JSON block definitions. + * @return {!Object} A map of the block + * definitions created. + * @alias Blockly.common.defineBlocksWithJsonArray + */ + export function createBlockDefinitionsFromJsonArray(jsonArray: Array): { + [x: string]: BlockDefinition; + }; + /** + * Add the specified block definitions to the block definitions + * dictionary (Blockly.Blocks). + * @param {!Object} blocks A map of block + * type names to block definitions. + * @alias Blockly.common.defineBlocks + */ + export function defineBlocks(blocks: { + [x: string]: BlockDefinition; + }): void; + import { Workspace } from "core/workspace"; + import { ICopyable } from "core/interfaces/i_copyable"; + import { WorkspaceSvg } from "core/workspace_svg"; + import { Block } from "core/block"; + import { BlockDefinition } from "core/blocks"; +} +declare module "core/dropdowndiv" { + /** + * Arrow size in px. Should match the value in CSS + * (need to position pre-render). + * @type {number} + * @const + */ + export const ARROW_SIZE: number; + /** + * Drop-down border size in px. Should match the value in CSS (need to position + * the arrow). + * @type {number} + * @const + */ + export const BORDER_SIZE: number; + /** + * Amount the arrow must be kept away from the edges of the main drop-down div, + * in px. + * @type {number} + * @const + */ + export const ARROW_HORIZONTAL_PADDING: number; + /** + * Amount drop-downs should be padded away from the source, in px. + * @type {number} + * @const + */ + export const PADDING_Y: number; + /** + * Length of animations in seconds. + * @type {number} + * @const + */ + export const ANIMATION_TIME: number; + /** + * Dropdown bounds info object used to encapsulate sizing information about a + * bounding element (bounding box and width/height). + */ + export type BoundsInfo = { + top: number; + left: number; + bottom: number; + right: number; + width: number; + height: number; + }; + /** + * Dropdown bounds info object used to encapsulate sizing information about a + * bounding element (bounding box and width/height). + * @typedef {{ + * top:number, + * left:number, + * bottom:number, + * right:number, + * width:number, + * height:number + * }} + */ + export let BoundsInfo: any; + /** + * Dropdown position metrics. + */ + export type PositionMetrics = { + initialX: number; + initialY: number; + finalX: number; + finalY: number; + arrowX: number | null; + arrowY: number | null; + arrowAtTop: boolean | null; + arrowVisible: boolean; + }; + /** + * Dropdown position metrics. + * @typedef {{ + * initialX:number, + * initialY:number, + * finalX:number, + * finalY:number, + * arrowX:?number, + * arrowY:?number, + * arrowAtTop:?boolean, + * arrowVisible:boolean + * }} + */ + export let PositionMetrics: any; + /** + * Create and insert the DOM element for this div. + * @package + */ + export function createDom(): void; + /** + * Set an element to maintain bounds within. Drop-downs will appear + * within the box of this element if possible. + * @param {?Element} boundsElem Element to bind drop-down to. + */ + export function setBoundsElement(boundsElem: Element | null): void; + /** + * Provide the div for inserting content into the drop-down. + * @return {!Element} Div to populate with content. + */ + export function getContentDiv(): Element; + /** + * Clear the content of the drop-down. + */ + export function clearContent(): void; + /** + * Set the colour for the drop-down. + * @param {string} backgroundColour Any CSS colour for the background. + * @param {string} borderColour Any CSS colour for the border. + */ + export function setColour(backgroundColour: string, borderColour: string): void; + /** + * Shortcut to show and place the drop-down with positioning determined + * by a particular block. The primary position will be below the block, + * and the secondary position above the block. Drop-down will be + * constrained to the block's workspace. + * @param {!Field} field The field showing the drop-down. + * @param {!BlockSvg} block Block to position the drop-down around. + * @param {Function=} opt_onHide Optional callback for when the drop-down is + * hidden. + * @param {number=} opt_secondaryYOffset Optional Y offset for above-block + * positioning. + * @return {boolean} True if the menu rendered below block; false if above. + */ + export function showPositionedByBlock(field: Field, block: BlockSvg, opt_onHide?: Function | undefined, opt_secondaryYOffset?: number | undefined): boolean; + /** + * Shortcut to show and place the drop-down with positioning determined + * by a particular field. The primary position will be below the field, + * and the secondary position above the field. Drop-down will be + * constrained to the block's workspace. + * @param {!Field} field The field to position the dropdown against. + * @param {Function=} opt_onHide Optional callback for when the drop-down is + * hidden. + * @param {number=} opt_secondaryYOffset Optional Y offset for above-block + * positioning. + * @return {boolean} True if the menu rendered below block; false if above. + */ + export function showPositionedByField(field: Field, opt_onHide?: Function | undefined, opt_secondaryYOffset?: number | undefined): boolean; + /** + * Show and place the drop-down. + * The drop-down is placed with an absolute "origin point" (x, y) - i.e., + * the arrow will point at this origin and box will positioned below or above + * it. If we can maintain the container bounds at the primary point, the arrow + * will point there, and the container will be positioned below it. + * If we can't maintain the container bounds at the primary point, fall-back to + * the secondary point and position above. + * @param {?Object} newOwner The object showing the drop-down + * @param {boolean} rtl Right-to-left (true) or left-to-right (false). + * @param {number} primaryX Desired origin point x, in absolute px. + * @param {number} primaryY Desired origin point y, in absolute px. + * @param {number} secondaryX Secondary/alternative origin point x, in absolute + * px. + * @param {number} secondaryY Secondary/alternative origin point y, in absolute + * px. + * @param {Function=} opt_onHide Optional callback for when the drop-down is + * hidden. + * @return {boolean} True if the menu rendered at the primary origin point. + * @package + */ + export function show(newOwner: Object | null, rtl: boolean, primaryX: number, primaryY: number, secondaryX: number, secondaryY: number, opt_onHide?: Function | undefined): boolean; + /** + * Get the x positions for the left side of the DropDownDiv and the arrow, + * accounting for the bounds of the workspace. + * @param {number} sourceX Desired origin point x, in absolute px. + * @param {number} boundsLeft The left edge of the bounding element, in + * absolute px. + * @param {number} boundsRight The right edge of the bounding element, in + * absolute px. + * @param {number} divWidth The width of the div in px. + * @return {{divX: number, arrowX: number}} An object containing metrics for + * the x positions of the left side of the DropDownDiv and the arrow. + * @package + */ + export function getPositionX(sourceX: number, boundsLeft: number, boundsRight: number, divWidth: number): { + divX: number; + arrowX: number; + }; + /** + * Is the container visible? + * @return {boolean} True if visible. + */ + export function isVisible(): boolean; + /** + * Hide the menu only if it is owned by the provided object. + * @param {?Object} divOwner Object which must be owning the drop-down to hide. + * @param {boolean=} opt_withoutAnimation True if we should hide the dropdown + * without animating. + * @return {boolean} True if hidden. + */ + export function hideIfOwner(divOwner: Object | null, opt_withoutAnimation?: boolean | undefined): boolean; + /** + * Hide the menu, triggering animation. + */ + export function hide(): void; + /** + * Hide the menu, without animation. + */ + export function hideWithoutAnimation(): void; + /** + * Repositions the dropdownDiv on window resize. If it doesn't know how to + * calculate the new position, it will just hide it instead. + * @package + */ + export function repositionForWindowResize(): void; + namespace internal { + /** + * Get sizing info about the bounding element. + * @return {!BoundsInfo} An object containing size + * information about the bounding element (bounding box and width/height). + */ + function getBoundsInfo(): BoundsInfo; + /** + * Helper to position the drop-down and the arrow, maintaining bounds. + * See explanation of origin points in show. + * @param {number} primaryX Desired origin point x, in absolute px. + * @param {number} primaryY Desired origin point y, in absolute px. + * @param {number} secondaryX Secondary/alternative origin point x, + * in absolute px. + * @param {number} secondaryY Secondary/alternative origin point y, + * in absolute px. + * @return {!PositionMetrics} Various final metrics, + * including rendered positions for drop-down and arrow. + */ + function getPositionMetrics(primaryX: number, primaryY: number, secondaryX: number, secondaryY: number): PositionMetrics; + } + import { Field } from "core/field"; + import { BlockSvg } from "core/block_svg"; + export { internal as TEST_ONLY }; +} +declare module "core/field_dropdown" { + /** + * Class for an editable dropdown field. + * @extends {Field} + * @alias Blockly.FieldDropdown + */ + export class FieldDropdown extends Field { + /** + * Construct a FieldDropdown from a JSON arg object. + * @param {!Object} options A JSON object with options (options). + * @return {!FieldDropdown} The new field instance. + * @package + * @nocollapse + */ + static fromJson(options: Object): FieldDropdown; + /** + * Use the calculated prefix and suffix lengths to trim all of the options in + * the given array. + * @param {!Array} options Array of option tuples: + * (human-readable text or image, language-neutral name). + * @param {number} prefixLength The length of the common prefix. + * @param {number} suffixLength The length of the common suffix + * @return {!Array} A new array with all of the option text trimmed. + */ + static applyTrim_(options: Array, prefixLength: number, suffixLength: number): Array; + /** + * @param {(!Array|!Function|!Sentinel)} menuGenerator + * A non-empty array of options for a dropdown list, or a function which + * generates these options. + * Also accepts Field.SKIP_SETUP if you wish to skip setup (only used by + * subclasses that want to handle configuration and setting the field + * value after their own constructors have run). + * @param {Function=} opt_validator A function that is called to validate + * changes to the field's value. Takes in a language-neutral dropdown + * option & returns a validated language-neutral dropdown option, or null + * to abort the change. + * @param {Object=} opt_config A map of options used to configure the field. + * See the [field creation documentation]{@link + * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/dropdown#creation} + * for a list of properties this parameter supports. + * @throws {TypeError} If `menuGenerator` options are incorrectly structured. + */ + constructor(menuGenerator: (Array | Function | Sentinel), opt_validator?: Function | undefined, opt_config?: Object | undefined); + /** + * A reference to the currently selected menu item. + * @type {?MenuItem} + * @private + */ + private selectedMenuItem_; + /** + * The dropdown menu. + * @type {?Menu} + * @protected + */ + protected menu_: { + menuItems_: { + content_: string | HTMLElement; + value_: string | undefined; + enabled_: boolean; + element_: HTMLDivElement | null; + rightToLeft_: boolean; + roleName_: string | null; + checkable_: boolean; + checked_: boolean; + highlight_: boolean; + actionHandler_: Function | null; + createDom(): Element; + dispose(): void; + getElement(): Element | null; + getId(): string; + getValue(): any; + setRightToLeft(rtl: boolean): void; + setRole(roleName: string): void; + setCheckable(checkable: boolean): void; + setChecked(checked: boolean): void; + setHighlighted(highlight: boolean): void; + isEnabled(): boolean; + setEnabled(enabled: boolean): void; + performAction(): void; + onAction(fn: (arg0: any) => any, obj: Object): void; + }[]; + openingCoords: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + } | null; + highlightedItem_: { + content_: string | HTMLElement; + value_: string | undefined; + enabled_: boolean; + element_: HTMLDivElement | null; + rightToLeft_: boolean; + roleName_: string | null; + checkable_: boolean; + checked_: boolean; + highlight_: boolean; + actionHandler_: Function | null; + createDom(): Element; + dispose(): void; + getElement(): Element | null; + getId(): string; + getValue(): any; + setRightToLeft(rtl: boolean): void; + setRole(roleName: string): void; + setCheckable(checkable: boolean): void; + setChecked(checked: boolean): void; + setHighlighted(highlight: boolean): void; + isEnabled(): boolean; + setEnabled(enabled: boolean): void; + performAction(): void; + onAction(fn: (arg0: any) => any, obj: Object): void; + } | null; + mouseOverHandler_: any[][] | null; + clickHandler_: any[][] | null; + mouseEnterHandler_: any[][] | null; + mouseLeaveHandler_: any[][] | null; + onKeyDownHandler_: any[][] | null; + element_: HTMLDivElement | null; + roleName_: string | null; + addChild(menuItem: { + content_: string | HTMLElement; + value_: string | undefined; + enabled_: boolean; + element_: HTMLDivElement | null; + rightToLeft_: boolean; + roleName_: string | null; + checkable_: boolean; + checked_: boolean; + highlight_: boolean; + actionHandler_: Function | null; + createDom(): Element; + dispose(): void; + getElement(): Element | null; + getId(): string; + getValue(): any; + setRightToLeft(rtl: boolean): void; + setRole(roleName: string): void; + setCheckable(checkable: boolean): void; + setChecked(checked: boolean): void; + setHighlighted(highlight: boolean): void; + isEnabled(): boolean; + setEnabled(enabled: boolean): void; + performAction(): void; + onAction(fn: (arg0: any) => any, obj: Object): void; + }): void; + render(container: Element): void; + getElement(): Element | null; + focus(): void; + blur_(): void; /** + * The currently selected option. The field is initialized with the + * first option selected. + * @type {!Array} + * @private + */ + setRole(roleName: string): void; + dispose(): void; + getMenuItem_(elem: Element): { + content_: string | HTMLElement; + value_: string | undefined; + enabled_: boolean; + element_: HTMLDivElement | null; + rightToLeft_: boolean; + roleName_: string | null; + checkable_: boolean; + checked_: boolean; + highlight_: boolean; + actionHandler_: Function | null; + createDom(): Element; + dispose(): void; + getElement(): Element | null; + getId(): string; + getValue(): any; + setRightToLeft(rtl: boolean): void; + setRole(roleName: string): void; + setCheckable(checkable: boolean): void; + setChecked(checked: boolean): void; + setHighlighted(highlight: boolean): void; + isEnabled(): boolean; + setEnabled(enabled: boolean): void; + performAction(): void; + onAction(fn: (arg0: any) => any, obj: Object): void; + } | null; + /** + * Create a tspan based arrow. + * @protected + */ + setHighlighted(item: { + content_: string | HTMLElement; + value_: string | undefined; + enabled_: boolean; + element_: HTMLDivElement | null; + rightToLeft_: boolean; + roleName_: string | null; + checkable_: boolean; + checked_: boolean; + highlight_: boolean; + actionHandler_: Function | null; + createDom(): Element; + dispose(): void; + getElement(): Element | null; + getId(): string; + getValue(): any; + setRightToLeft(rtl: boolean): void; + setRole(roleName: string): void; + setCheckable(checkable: boolean): void; + setChecked(checked: boolean): void; + setHighlighted(highlight: boolean): void; + isEnabled(): boolean; + setEnabled(enabled: boolean): void; + performAction(): void; + onAction(fn: (arg0: any) => any, obj: Object): void; + } | null): void; + highlightNext(): void; + highlightPrevious(): void; + highlightFirst_(): void; + highlightLast_(): void; + highlightHelper_(startIndex: number, delta: number): void; + handleMouseOver_(e: Event): void; + handleClick_(e: Event): void; + handleMouseEnter_(_e: Event): void; + handleMouseLeave_(_e: Event): void; + handleKeyEvent_(e: Event): void; + getSize(): { + width: number; + height: number; + }; + } | null; + /** + * SVG image element if currently selected option is an image, or null. + * @type {?SVGImageElement} + * @private + */ + private imageElement_; + /** + * Tspan based arrow element. + * @type {?SVGTSpanElement} + * @private + */ + private arrow_; + /** + * SVG based arrow element. + * @type {?SVGElement} + * @private + */ + private svgArrow_; + /** + * An array of options for a dropdown list, + * or a function which generates these options. + * @type {(!Array|!function(this:FieldDropdown): !Array)} + * @protected + */ + protected menuGenerator_: any[][] | ((this: FieldDropdown) => Array); + /** + * A cache of the most recently generated options. + * @type {Array>} + * @private + */ + private generatedOptions_; + /** + * The currently selected option. The field is initialized with the + * first option selected. + * @type {!Array} + * @private + */ + private selectedOption_; + /** + * Whether or not the dropdown should add a border rect. + * @return {boolean} True if the dropdown field should add a border rect. + * @protected + */ + protected shouldAddBorderRect_(): boolean; + /** + * Create a tspan based arrow. + * @protected + */ + protected createTextArrow_(): void; + /** + * Create an SVG based arrow. + * @protected + */ + protected createSVGArrow_(): void; + /** + * Create the dropdown editor. + * @private + */ + private dropdownCreate_; + /** + * Disposes of events and DOM-references belonging to the dropdown editor. + * @private + */ + private dropdownDispose_; + /** + * Handle an action in the dropdown menu. + * @param {!MenuItem} menuItem The MenuItem selected within menu. + * @private + */ + private handleMenuActionEvent_; + /** + * Handle the selection of an item in the dropdown menu. + * @param {!Menu} menu The Menu component clicked. + * @param {!MenuItem} menuItem The MenuItem selected within menu. + * @protected + */ + protected onItemSelected_(menu: { + menuItems_: { + content_: string | HTMLElement; + value_: string | undefined; + enabled_: boolean; + element_: HTMLDivElement | null; + rightToLeft_: boolean; + roleName_: string | null; + checkable_: boolean; + checked_: boolean; + highlight_: boolean; + actionHandler_: Function | null; + createDom(): Element; + dispose(): void; + getElement(): Element | null; + getId(): string; + getValue(): any; + setRightToLeft(rtl: boolean): void; + setRole(roleName: string): void; + setCheckable(checkable: boolean): void; + setChecked(checked: boolean): void; + setHighlighted(highlight: boolean): void; + isEnabled(): boolean; + setEnabled(enabled: boolean): void; + performAction(): void; + onAction(fn: (arg0: any) => any, obj: Object): void; + }[]; + openingCoords: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + } | null; + highlightedItem_: { + content_: string | HTMLElement; + value_: string | undefined; + enabled_: boolean; + element_: HTMLDivElement | null; + rightToLeft_: boolean; + roleName_: string | null; + checkable_: boolean; + checked_: boolean; + highlight_: boolean; + actionHandler_: Function | null; + createDom(): Element; + dispose(): void; + getElement(): Element | null; + getId(): string; + getValue(): any; + setRightToLeft(rtl: boolean): void; + setRole(roleName: string): void; + setCheckable(checkable: boolean): void; + setChecked(checked: boolean): void; + setHighlighted(highlight: boolean): void; + isEnabled(): boolean; + setEnabled(enabled: boolean): void; + performAction(): void; + onAction(fn: (arg0: any) => any, obj: Object): void; + } | null; + mouseOverHandler_: any[][] | null; + clickHandler_: any[][] | null; + mouseEnterHandler_: any[][] | null; + mouseLeaveHandler_: any[][] | null; + onKeyDownHandler_: any[][] | null; + element_: HTMLDivElement | null; + roleName_: string | null; + addChild(menuItem: { + content_: string | HTMLElement; + value_: string | undefined; + enabled_: boolean; + element_: HTMLDivElement | null; + rightToLeft_: boolean; + roleName_: string | null; + checkable_: boolean; + checked_: boolean; + highlight_: boolean; + actionHandler_: Function | null; + createDom(): Element; + dispose(): void; + getElement(): Element | null; + getId(): string; + getValue(): any; + setRightToLeft(rtl: boolean): void; + setRole(roleName: string): void; + setCheckable(checkable: boolean): void; + setChecked(checked: boolean): void; + setHighlighted(highlight: boolean): void; + isEnabled(): boolean; + setEnabled(enabled: boolean): void; + performAction(): void; + onAction(fn: (arg0: any) => any, obj: Object): void; + }): void; + render(container: Element): void; + getElement(): Element | null; + focus(): void; + blur_(): void; /** + * The currently selected option. The field is initialized with the + * first option selected. + * @type {!Array} + * @private + */ + setRole(roleName: string): void; + dispose(): void; + getMenuItem_(elem: Element): { + content_: string | HTMLElement; + value_: string | undefined; + enabled_: boolean; + element_: HTMLDivElement | null; + rightToLeft_: boolean; + roleName_: string | null; + checkable_: boolean; + checked_: boolean; + highlight_: boolean; + actionHandler_: Function | null; + createDom(): Element; + dispose(): void; + getElement(): Element | null; + getId(): string; + getValue(): any; + setRightToLeft(rtl: boolean): void; + setRole(roleName: string): void; + setCheckable(checkable: boolean): void; + setChecked(checked: boolean): void; + setHighlighted(highlight: boolean): void; + isEnabled(): boolean; + setEnabled(enabled: boolean): void; + performAction(): void; + onAction(fn: (arg0: any) => any, obj: Object): void; + } | null; + /** + * Create a tspan based arrow. + * @protected + */ + setHighlighted(item: { + content_: string | HTMLElement; + value_: string | undefined; + enabled_: boolean; + element_: HTMLDivElement | null; + rightToLeft_: boolean; + roleName_: string | null; + checkable_: boolean; + checked_: boolean; + highlight_: boolean; + actionHandler_: Function | null; + createDom(): Element; + dispose(): void; + getElement(): Element | null; + getId(): string; + getValue(): any; + setRightToLeft(rtl: boolean): void; + setRole(roleName: string): void; + setCheckable(checkable: boolean): void; + setChecked(checked: boolean): void; + setHighlighted(highlight: boolean): void; + isEnabled(): boolean; + setEnabled(enabled: boolean): void; + performAction(): void; + onAction(fn: (arg0: any) => any, obj: Object): void; + } | null): void; + highlightNext(): void; + highlightPrevious(): void; + highlightFirst_(): void; + highlightLast_(): void; + highlightHelper_(startIndex: number, delta: number): void; + handleMouseOver_(e: Event): void; + handleClick_(e: Event): void; + handleMouseEnter_(_e: Event): void; + handleMouseLeave_(_e: Event): void; + handleKeyEvent_(e: Event): void; + getSize(): { + width: number; + height: number; + }; + }, menuItem: { + content_: string | HTMLElement; + value_: string | undefined; + enabled_: boolean; + element_: HTMLDivElement | null; + rightToLeft_: boolean; + roleName_: string | null; + checkable_: boolean; + checked_: boolean; + highlight_: boolean; + actionHandler_: Function | null; + createDom(): Element; + dispose(): void; + getElement(): Element | null; + getId(): string; + getValue(): any; + setRightToLeft(rtl: boolean): void; + setRole(roleName: string): void; + setCheckable(checkable: boolean): void; + setChecked(checked: boolean): void; + setHighlighted(highlight: boolean): void; + isEnabled(): boolean; + setEnabled(enabled: boolean): void; + performAction(): void; + onAction(fn: (arg0: any) => any, obj: Object): void; + }): void; + /** + * Factor out common words in statically defined options. + * Create prefix and/or suffix labels. + * @private + */ + private trimOptions_; + /** + * @return {boolean} True if the option list is generated by a function. + * Otherwise false. + */ + isOptionListDynamic(): boolean; + /** + * Return a list of the options for this dropdown. + * @param {boolean=} opt_useCache For dynamic options, whether or not to use + * the cached options or to re-generate them. + * @return {!Array} A non-empty array of option tuples: + * (human-readable text or image, language-neutral name). + * @throws {TypeError} If generated options are incorrectly structured. + */ + getOptions(opt_useCache?: boolean | undefined): Array; + /** + * Renders the selected option, which must be an image. + * @param {!ImageProperties} imageJson Selected + * option that must be an image. + * @private + */ + private renderSelectedImage_; + /** + * Renders the selected option, which must be text. + * @private + */ + private renderSelectedText_; + /** + * Position a drop-down arrow at the appropriate location at render-time. + * @param {number} x X position the arrow is being rendered at, in px. + * @param {number} y Y position the arrow is being rendered at, in px. + * @return {number} Amount of space the arrow is taking up, in px. + * @private + */ + private positionSVGArrow_; + } + export namespace FieldDropdown { + const CHECKMARK_OVERHANG: number; + const MAX_MENU_HEIGHT_VH: number; + const ARROW_CHAR: string; + } + import { Field } from "core/field"; + import { Sentinel } from "core/utils/sentinel"; +} +declare module "core/extensions" { + export namespace TEST_ONLY { + export { allExtensions }; + } + /** + * The set of all registered extensions, keyed by extension name/id. + * @private + */ + const allExtensions: any; + /** + * Registers a new extension function. Extensions are functions that help + * initialize blocks, usually adding dynamic behavior such as onchange + * handlers and mutators. These are applied using Block.applyExtension(), or + * the JSON "extensions" array attribute. + * @param {string} name The name of this extension. + * @param {Function} initFn The function to initialize an extended block. + * @throws {Error} if the extension name is empty, the extension is already + * registered, or extensionFn is not a function. + * @alias Blockly.Extensions.register + */ + export function register(name: string, initFn: Function): void; + /** + * Registers a new extension function that adds all key/value of mixinObj. + * @param {string} name The name of this extension. + * @param {!Object} mixinObj The values to mix in. + * @throws {Error} if the extension name is empty or the extension is already + * registered. + * @alias Blockly.Extensions.registerMixin + */ + export function registerMixin(name: string, mixinObj: Object): void; + /** + * Registers a new extension function that adds a mutator to the block. + * At register time this performs some basic sanity checks on the mutator. + * The wrapper may also add a mutator dialog to the block, if both compose and + * decompose are defined on the mixin. + * @param {string} name The name of this mutator extension. + * @param {!Object} mixinObj The values to mix in. + * @param {(function())=} opt_helperFn An optional function to apply after + * mixing in the object. + * @param {!Array=} opt_blockList A list of blocks to appear in the + * flyout of the mutator dialog. + * @throws {Error} if the mutation is invalid or can't be applied to the block. + * @alias Blockly.Extensions.registerMutator + */ + export function registerMutator(name: string, mixinObj: Object, opt_helperFn?: (() => any) | undefined, opt_blockList?: Array | undefined): void; + /** + * Unregisters the extension registered with the given name. + * @param {string} name The name of the extension to unregister. + * @alias Blockly.Extensions.unregister + */ + export function unregister(name: string): void; + /** + * Returns whether an extension is registered with the given name. + * @param {string} name The name of the extension to check for. + * @return {boolean} True if the extension is registered. False if it is + * not registered. + * @alias Blockly.Extensions.isRegistered + */ + export function isRegistered(name: string): boolean; + /** + * Applies an extension method to a block. This should only be called during + * block construction. + * @param {string} name The name of the extension. + * @param {!Block} block The block to apply the named extension to. + * @param {boolean} isMutator True if this extension defines a mutator. + * @throws {Error} if the extension is not found. + * @alias Blockly.Extensions.apply + */ + export function apply(name: string, block: Block, isMutator: boolean): void; + /** + * Calls a function after the page has loaded, possibly immediately. + * @param {function()} fn Function to run. + * @throws Error Will throw if no global document can be found (e.g., Node.js). + * @package + */ + export function runAfterPageLoad(fn: () => any): void; + /** + * Builds an extension function that will map a dropdown value to a tooltip + * string. + * + * This method includes multiple checks to ensure tooltips, dropdown options, + * and message references are aligned. This aims to catch errors as early as + * possible, without requiring developers to manually test tooltips under each + * option. After the page is loaded, each tooltip text string will be checked + * for matching message keys in the internationalized string table. Deferring + * this until the page is loaded decouples loading dependencies. Later, upon + * loading the first block of any given type, the extension will validate every + * dropdown option has a matching tooltip in the lookupTable. Errors are + * reported as warnings in the console, and are never fatal. + * @param {string} dropdownName The name of the field whose value is the key + * to the lookup table. + * @param {!Object} lookupTable The table of field values to + * tooltip text. + * @return {!Function} The extension function. + * @alias Blockly.Extensions.buildTooltipForDropdown + */ + export function buildTooltipForDropdown(dropdownName: string, lookupTable: { + [x: string]: string; + }): Function; + /** + * Builds an extension function that will install a dynamic tooltip. The + * tooltip message should include the string '%1' and that string will be + * replaced with the text of the named field. + * @param {string} msgTemplate The template form to of the message text, with + * %1 placeholder. + * @param {string} fieldName The field with the replacement text. + * @return {!Function} The extension function. + * @alias Blockly.Extensions.buildTooltipWithFieldText + */ + export function buildTooltipWithFieldText(msgTemplate: string, fieldName: string): Function; + import { Block } from "core/block"; + export {}; +} +declare module "core/block" { + /** + * Class for one block. + * Not normally called directly, workspace.newBlock() is preferred. + * @implements {IASTNodeLocation} + * @implements {IDeletable} + * @unrestricted + * @alias Blockly.Block + */ + export class Block implements IASTNodeLocation, IDeletable { /** - * Class for one block. - * Not normally called directly, workspace.newBlock() is preferred. * @param {!Workspace} workspace The block's workspace. * @param {!string} prototypeName Name of the language object containing * type-specific functions for this block. * @param {string=} opt_id Optional ID. Use this ID if provided, otherwise * create a new ID. - * @constructor - * @implements {IASTNodeLocation} - * @implements {IDeletable} * @throws When the prototypeName is not valid or not allowed. - * @alias Blockly.Block */ constructor(workspace: Workspace, prototypeName: string, opt_id?: string | undefined); + /** + * Optional text data that round-trips between blocks and XML. + * Has no effect. May be used by 3rd parties for meta information. + * @type {?string} + */ + data: string | null; + /** + * Has this block been disposed of? + * @type {boolean} + * @package + */ + disposed: boolean; + /** + * Colour of the block as HSV hue value (0-360) + * This may be null if the block colour was not set via a hue number. + * @type {?number} + * @private + */ + private hue_; + /** + * Colour of the block in '#RRGGBB' format. + * @type {string} + * @protected + */ + protected colour_: string; + /** + * Name of the block style. + * @type {string} + * @protected + */ + protected styleName_: string; + /** + * An optional method called during initialization. + * @type {undefined|?function()} + */ + init: (() => any) | null | undefined; + /** + * An optional serialization method for defining how to serialize the + * mutation state to XML. This must be coupled with defining + * `domToMutation`. + * @type {undefined|?function(...):!Element} + */ + mutationToDom: ((...arg0: any[]) => Element) | null | undefined; + /** + * An optional deserialization method for defining how to deserialize the + * mutation state from XML. This must be coupled with defining + * `mutationToDom`. + * @type {undefined|?function(!Element)} + */ + domToMutation: ((arg0: Element) => any) | null | undefined; + /** + * An optional serialization method for defining how to serialize the + * block's extra state (eg mutation state) to something JSON compatible. + * This must be coupled with defining `loadExtraState`. + * @type {undefined|?function(): *} + */ + saveExtraState: (() => any) | null | undefined; + /** + * An optional serialization method for defining how to deserialize the + * block's extra state (eg mutation state) from something JSON compatible. + * This must be coupled with defining `saveExtraState`. + * @type {undefined|?function(*)} + */ + loadExtraState: ((arg0: any) => any) | null | undefined; + /** + * An optional property for suppressing adding STATEMENT_PREFIX and + * STATEMENT_SUFFIX to generated code. + * @type {?boolean} + */ + suppressPrefixSuffix: boolean | null; + /** + * An optional property for declaring developer variables. Return a list of + * variable names for use by generators. Developer variables are never + * shown to the user, but are declared as global variables in the generated + * code. + * @type {undefined|?function():!Array} + */ + getDeveloperVariables: (() => Array) | null | undefined; /** @type {string} */ id: string; /** @type {Connection} */ @@ -19058,6 +21928,17 @@ declare module "block" { hat: string | undefined; /** @type {?boolean} */ rendered: boolean | null; + /** + * String for block help, or function that returns a URL. Null for no help. + * @type {string|Function} + */ + helpUrl: string | Function; + /** + * A bound callback function to use when the parent workspace changes. + * @type {?function(Abstract)} + * @private + */ + private onchangeWrapper_; /** * A count of statement inputs on the block. * @type {number} @@ -19066,6 +21947,11 @@ declare module "block" { statementInputCount: number; /** @type {string} */ type: string; + /** + * Calls the init() function and handles associated event firing, etc. + * @protected + */ + protected doInit_(): void; /** @type {boolean|undefined} */ inputsInlineDefault: boolean | undefined; /** @@ -19073,15 +21959,15 @@ declare module "block" { * @param {boolean} healStack If true, then try to heal any gap by connecting * the next statement with the previous statement. Otherwise, dispose of * all children of this block. + * @param {boolean=} _animate If true, show a disposal animation and sound. * @suppress {checkTypes} */ - dispose(healStack: boolean): void; - disposed: boolean; + dispose(healStack: boolean, _animate?: boolean | undefined): void; /** * 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 + * 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 @@ -19103,9 +21989,9 @@ declare module "block" { */ private unplugFromRow_; /** - * Returns the connection on the value input that is connected to another block. - * When an insertion marker is connected to a connection with a block already - * attached, the connected block is attached to the insertion marker. + * Returns the connection on the value input that is connected to another + * block. When an insertion marker is connected to a connection with a block + * already attached, the connected block is attached to the insertion marker. * Since only one block can be displaced and attached to the insertion marker * this should only ever return one connection. * @@ -19129,7 +22015,8 @@ declare module "block" { */ getConnections_(_all: boolean): Array; /** - * Walks down a stack of blocks and finds the last next connection on the stack. + * Walks down a stack of blocks and finds the last next connection on the + * stack. * @param {boolean} ignoreShadows If true,the last connection on a non-shadow * block will be returned. If false, this will follow shadows to find the * last connection. @@ -19143,10 +22030,10 @@ declare module "block" { */ bumpNeighbours(): void; /** - * Return the parent block or null if this block is at the top level. The parent - * block is either the block connected to the previous connection (for a - * statement block) or the block connected to the output connection (for a value - * block). + * Return the parent block or null if this block is at the top level. The + * parent block is either the block connected to the previous connection (for + * a statement block) or the block connected to the output connection (for a + * value block). * @return {?Block} The block (if any) that holds the current block. */ getParent(): Block | null; @@ -19160,7 +22047,8 @@ declare module "block" { /** * Return the parent block that surrounds the current block, or null if this * block has no surrounding block. A parent block might just be the previous - * statement, whereas the surrounding block is an if statement, while loop, etc. + * statement, whereas the surrounding block is an if statement, while loop, + * etc. * @return {?Block} The block (if any) that surrounds the current block. */ getSurroundParent(): Block | null; @@ -19175,8 +22063,8 @@ declare module "block" { */ getPreviousBlock(): Block | null; /** - * Return the connection on the first statement input on this block, or null if - * there are none. + * Return the connection on the first statement input on this block, or null + * if there are none. * @return {?Connection} The first statement connection or null. * @package */ @@ -19302,7 +22190,6 @@ declare module "block" { * returns a URL. Null for no help. */ setHelpUrl(url: string | Function): void; - helpUrl: string | Function; /** * Sets the tooltip for this block. * @param {!Tooltip.TipInfo} newTip The text for the tooltip, a function @@ -19336,14 +22223,11 @@ declare module "block" { * or a message reference string pointing to one of those two values. */ setColour(colour: number | string): void; - hue_: number | null; - colour_: string; /** * Set the style and colour values of a block. * @param {string} blockStyleName Name of the block style. */ setStyle(blockStyleName: string): void; - styleName_: string; /** * Sets a callback function to use whenever the block's parent workspace * changes, replacing any prior onchange handler. This is usually only called @@ -19353,9 +22237,8 @@ declare module "block" { * when the block's workspace changes. * @throws {Error} if onchangeFn is not falsey and not a function. */ - setOnChange(onchangeFn: (arg0: typeof Abstract) => any): void; - onchange: ((arg0: typeof Abstract) => any) | null; - onchangeWrapper_: any; + setOnChange(onchangeFn: (arg0: Abstract) => any): void; + onchange: undefined | (((arg0: Abstract) => any) | null); /** * Returns the named field from a block. * @param {string} name The name of the field. @@ -19384,8 +22267,8 @@ declare module "block" { * 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. + * @param {string} newId ID of new variable. May be the same as oldId, but + * with an updated name. */ renameVarById(oldId: string, newId: string): void; /** @@ -19404,14 +22287,16 @@ declare module "block" { * Set whether this block can chain onto the bottom of another block. * @param {boolean} newBoolean True if there can be a previous statement. * @param {(string|Array|null)=} opt_check Statement type or - * list of statement types. Null/undefined if any type could be connected. + * list of statement types. Null/undefined if any type could be + * connected. */ setPreviousStatement(newBoolean: boolean, opt_check?: (string | Array | null) | undefined): void; /** * Set whether another block can chain onto the bottom of this block. * @param {boolean} newBoolean True if there can be a next statement. * @param {(string|Array|null)=} opt_check Statement type or - * list of statement types. Null/undefined if any type could be connected. + * list of statement types. Null/undefined if any type could be + * connected. */ setNextStatement(newBoolean: boolean, opt_check?: (string | Array | null) | undefined): void; /** @@ -19478,22 +22363,22 @@ declare module "block" { toString(opt_maxLength?: number | undefined, opt_emptyToken?: string | undefined): string; /** * Shortcut for appending a value input row. - * @param {string} name Language-neutral identifier which may used to find this - * input again. Should be unique to this block. + * @param {string} name Language-neutral identifier which may used to find + * this input again. Should be unique to this block. * @return {!Input} The input object created. */ appendValueInput(name: string): Input; /** * Shortcut for appending a statement input row. - * @param {string} name Language-neutral identifier which may used to find this - * input again. Should be unique to this block. + * @param {string} name Language-neutral identifier which may used to find + * this input again. Should be unique to this block. * @return {!Input} The input object created. */ appendStatementInput(name: string): Input; /** * Shortcut for appending a dummy input row. - * @param {string=} opt_name Language-neutral identifier which may used to find - * this input again. Should be unique to this block. + * @param {string=} opt_name Language-neutral identifier which may used to + * find this input again. Should be unique to this block. * @return {!Input} The input object created. */ appendDummyInput(opt_name?: string | undefined): Input; @@ -19502,8 +22387,7 @@ declare module "block" { * JSON description. * @param {!Object} json Structured data describing the block. */ - jsonInit(json: any): void; - suppressPrefixSuffix: boolean | null; + jsonInit(json: Object): void; /** * Initialize the colour of this block from the JSON description. * @param {!Object} json Structured data describing the block. @@ -19527,7 +22411,7 @@ declare module "block" { * @param {!Object} mixinObj The key/values pairs to add to this block object. * @param {boolean=} opt_disableCheck Option flag to disable overwrite checks. */ - mixin(mixinObj: any, opt_disableCheck?: boolean | undefined): void; + mixin(mixinObj: Object, opt_disableCheck?: boolean | undefined): void; /** * Interpolate a message description onto the block. * @param {string} message Text contains interpolation tokens (%1, %2, ...) @@ -19540,22 +22424,22 @@ declare module "block" { */ private interpolate_; /** - * Validates that the tokens are within the correct bounds, with no duplicates, - * and that all of the arguments are referred to. Throws errors if any of these - * things are not true. + * Validates that the tokens are within the correct bounds, with no + * duplicates, and that all of the arguments are referred to. Throws errors if + * any of these things are not true. * @param {!Array} tokens An array of tokens to validate * @param {number} argsCount The number of args that need to be referred to. * @private */ private validateTokens_; /** - * Inserts args in place of numerical tokens. String args are converted to JSON - * that defines a label field. If necessary an extra dummy input is added to - * the end of the elements. + * Inserts args in place of numerical tokens. String args are converted to + * JSON that defines a label field. If necessary an extra dummy input is added + * to the end of the elements. * @param {!Array} tokens The tokens to interpolate * @param {!Array} args The arguments to insert. - * @param {string|undefined} lastDummyAlign The alignment the added dummy input - * should have, if we are required to add one. + * @param {string|undefined} lastDummyAlign The alignment the added dummy + * input should have, if we are required to add one. * @return {!Array} The JSON definitions of field and inputs to add * to the block. * @private @@ -19573,8 +22457,8 @@ declare module "block" { */ private fieldFromJson_; /** - * Creates an input from the JSON definition of an input. Sets the input's check - * and alignment if they are provided. + * Creates an input from the JSON definition of an input. Sets the input's + * check and alignment if they are provided. * @param {!Object} element The JSON to turn into an input. * @param {string} warningPrefix The prefix to add to warnings to help the * developer debug. @@ -19586,15 +22470,16 @@ declare module "block" { /** * Returns true if the given string matches one of the input keywords. * @param {string} str The string to check. - * @return {boolean} True if the given string matches one of the input keywords, - * false otherwise. + * @return {boolean} True if the given string matches one of the input + * keywords, false otherwise. * @private */ private isInputKeyword_; /** * Turns a string into the JSON definition of a label field. If the string * becomes an empty string when trimmed, this returns null. - * @param {string} str String to turn into the JSON definition of a label field. + * @param {string} str String to turn into the JSON definition of a label + * field. * @return {?{text: string, type: string}} The JSON definition or null. * @private */ @@ -19602,8 +22487,8 @@ declare module "block" { /** * Add a value input, statement input or local variable to this block. * @param {number} type One of Blockly.inputTypes. - * @param {string} name Language-neutral identifier which may used to find this - * input again. Should be unique to this block. + * @param {string} name Language-neutral identifier which may used to find + * this input again. Should be unique to this block. * @return {!Input} The input object created. * @protected */ @@ -19611,20 +22496,23 @@ declare module "block" { /** * Move a named input to a different location on this block. * @param {string} name The name of the input to move. - * @param {?string} refName Name of input that should be after the moved input, + * @param {?string} refName Name of input that should be after the moved + * input, * or null to be the input at the end. */ moveInputBefore(name: string, refName: string | null): void; /** * Move a numbered input to a different location on this block. * @param {number} inputIndex Index of the input to move. - * @param {number} refIndex Index of input that should be after the moved input. + * @param {number} refIndex Index of input that should be after the moved + * input. */ moveNumberedInputBefore(inputIndex: number, refIndex: number): void; /** * Remove an input from this block. * @param {string} name The name of the input. - * @param {boolean=} opt_quiet True to prevent an error if input is not present. + * @param {boolean=} opt_quiet True to prevent an error if input is not + * present. * @return {boolean} True if operation succeeds, false if input is not present * and opt_quiet is true. * @throws {Error} if the input is not present and opt_quiet is not true. @@ -19671,7 +22559,13 @@ declare module "block" { * drawing surface's origin (0,0), in workspace units. * @return {!Coordinate} Object with .x and .y properties. */ - getRelativeToSurfaceXY(): Coordinate; + getRelativeToSurfaceXY(): { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }; /** * Move a block by a relative offset. * @param {number} dx Horizontal offset, in workspace units. @@ -19688,8 +22582,9 @@ declare module "block" { /** * Recursively checks whether all statement and value inputs are filled with * blocks. Also checks all following statement blocks in this stack. - * @param {boolean=} opt_shadowBlocksAreFilled An optional argument controlling - * whether shadow blocks are counted as filled. Defaults to true. + * @param {boolean=} opt_shadowBlocksAreFilled An optional argument + * controlling whether shadow blocks are counted as filled. Defaults to + * true. * @return {boolean} True if all inputs are filled, false otherwise. */ allInputsFilled(opt_shadowBlocksAreFilled?: boolean | undefined): boolean; @@ -19697,142 +22592,72 @@ declare module "block" { * This method returns a string describing this Block in developer terms (type * name and ID; English only). * - * Intended to on be used in console logs and errors. If you need a string that - * uses the user's native language (including block text, field values, and - * child blocks), use [toString()]{@link Block#toString}. + * Intended to on be used in console logs and errors. If you need a string + * that uses the user's native language (including block text, field values, + * and child blocks), use [toString()]{@link Block#toString}. * @return {string} The description. */ toDevString(): string; - /** - * Optional text data that round-trips between blocks and XML. - * Has no effect. May be used by 3rd parties for meta information. - * @type {?string} - */ - data: string | null; - /** - * An optional method called during initialization. - * @type {?function()} - */ - init: (() => any) | null; - /** - * An optional serialization method for defining how to serialize the - * mutation state to XML. This must be coupled with defining `domToMutation`. - * @type {?function(...):!Element} - */ - mutationToDom: ((...args: any[]) => Element) | null; - /** - * An optional deserialization method for defining how to deserialize the - * mutation state from XML. This must be coupled with defining `mutationToDom`. - * @type {?function(!Element)} - */ - domToMutation: ((arg0: Element) => any) | null; - /** - * An optional serialization method for defining how to serialize the block's - * extra state (eg mutation state) to something JSON compatible. This must be - * coupled with defining `loadExtraState`. - * @type {?function(): *} - */ - saveExtraState: (() => any) | null; - /** - * An optional serialization method for defining how to deserialize the block's - * extra state (eg mutation state) from something JSON compatible. This must be - * coupled with defining `saveExtraState`. - * @type {?function(*)} - */ - loadExtraState: ((arg0: any) => any) | null; - /** - * An optional property for declaring developer variables. Return a list of - * variable names for use by generators. Developer variables are never shown to - * the user, but are declared as global variables in the generated code. - * @type {?function():!Array} - */ - getDeveloperVariables: (() => Array) | null; } export namespace Block { - const COLLAPSED_INPUT_NAME: any; - const COLLAPSED_FIELD_NAME: any; + const COLLAPSED_INPUT_NAME: string; + const COLLAPSED_FIELD_NAME: string; type CommentModel = { text: string | null; pinned: boolean; - size: Size; + size: { + width: number; + height: number; + }; }; } - import { Connection } from "connection"; - import { Input } from "input"; - import * as Tooltip from "tooltip"; - import { Comment } from "comment"; - import { Workspace } from "workspace"; - import * as Abstract from "events/events_abstract"; - import { Field } from "field"; - import { VariableModel } from "variable_model"; - import { Mutator } from "mutator"; - import { Coordinate } from "utils/coordinate"; - import { Size } from "utils/size"; + import { IASTNodeLocation } from "core/interfaces/i_ast_node_location"; + import { IDeletable } from "core/interfaces/i_deletable"; + import { Connection } from "core/connection"; + import { Input } from "core/input"; + import * as Tooltip from "core/tooltip"; + import { Comment } from "core/comment"; + import { Workspace } from "core/workspace"; + import { Abstract } from "core/events/events_abstract"; + import { Field } from "core/field"; + import { VariableModel } from "core/variable_model"; + import { Mutator } from "core/mutator"; } -declare module "events/workspace_events" { +declare module "core/events/workspace_events" { /** * Class for a finished loading event. * Used to notify the developer when the workspace has finished loading (i.e * domToWorkspace). * Finished loading events do not record undo or redo. - * @param {!Workspace=} opt_workspace The workspace that has finished - * loading. Undefined for a blank event. - * @extends {Abstract} - * @constructor + * @extends {AbstractEvent} * @alias Blockly.Events.FinishedLoading */ - export class FinishedLoading { - constructor(opt_workspace: any); + export class FinishedLoading extends AbstractEvent { /** - * Whether or not the event is blank (to be populated by fromJson). - * @type {boolean} + * @param {!Workspace=} opt_workspace The workspace that has finished + * loading. Undefined for a blank event. */ - isBlank: boolean; - /** - * The workspace identifier for this event. - * @type {string} - */ - workspaceId: string; - /** - * The event group ID for the group this event belongs to. Groups define - * events that should be treated as an single action from the user's - * perspective, and should be undone together. - * @type {string} - */ - group: string; - recordUndo: boolean; - /** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ - toJson(): any; - /** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ - fromJson(json: any): void; - /** - * Type of this event. - * @type {string} - */ - type: string; + constructor(opt_workspace?: Workspace | undefined); } + import { Abstract as AbstractEvent } from "core/events/events_abstract"; + import { Workspace } from "core/workspace"; } -declare module "events/events_toolbox_item_select" { +declare module "core/events/events_toolbox_item_select" { /** * Class for a toolbox item select event. - * @param {?string=} opt_oldItem The previously selected toolbox item. Undefined - * for a blank event. - * @param {?string=} opt_newItem The newly selected toolbox item. Undefined for - * a blank event. - * @param {string=} opt_workspaceId The workspace identifier for this event. - * Undefined for a blank event. * @extends {UiBase} - * @constructor * @alias Blockly.Events.ToolboxItemSelect */ - export class ToolboxItemSelect { - constructor(opt_oldItem: any, opt_newItem: any, opt_workspaceId: any); + export class ToolboxItemSelect extends UiBase { + /** + * @param {?string=} opt_oldItem The previously selected toolbox item. + * Undefined for a blank event. + * @param {?string=} opt_newItem The newly selected toolbox item. Undefined + * for a blank event. + * @param {string=} opt_workspaceId The workspace identifier for this event. + * Undefined for a blank event. + */ + constructor(opt_oldItem?: (string | null) | undefined, opt_newItem?: (string | null) | undefined, opt_workspaceId?: string | undefined); /** * The previously selected toolbox item. * @type {?string|undefined} @@ -19843,61 +22668,35 @@ declare module "events/events_toolbox_item_select" { * @type {?string|undefined} */ newItem: (string | undefined) | null; - /** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ - toJson(): any; - /** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ - fromJson(json: any): void; - /** - * Type of this event. - * @type {string} - */ - type: string; } + import { UiBase } from "core/events/events_ui_base"; } -declare module "events/events_ui" { +declare module "core/events/events_ui" { /** * Class for a UI event. - * @param {?Block=} opt_block The affected block. Null for UI events - * that do not have an associated block. Undefined for a blank event. - * @param {string=} opt_element One of 'selected', 'comment', 'mutatorOpen', - * etc. - * @param {*=} opt_oldValue Previous value of element. - * @param {*=} opt_newValue New value of element. * @extends {UiBase} * @deprecated December 2020. Instead use a more specific UI event. - * @constructor * @alias Blockly.Events.Ui */ - export class Ui { - constructor(opt_block: any, opt_element: any, opt_oldValue: any, opt_newValue: any); - blockId: any; - element: any; + export class Ui extends UiBase { + /** + * @param {?Block=} opt_block The affected block. Null for UI events + * that do not have an associated block. Undefined for a blank event. + * @param {string=} opt_element One of 'selected', 'comment', 'mutatorOpen', + * etc. + * @param {*=} opt_oldValue Previous value of element. + * @param {*=} opt_newValue New value of element. + */ + constructor(opt_block?: (Block | null) | undefined, opt_element?: string | undefined, opt_oldValue?: any | undefined, opt_newValue?: any | undefined); + blockId: string | null; + element: string; oldValue: any; newValue: any; - /** - * Encode the event as JSON. - * @return {!Object} JSON representation. - */ - toJson(): any; - /** - * Decode the JSON event. - * @param {!Object} json JSON representation. - */ - fromJson(json: any): void; - /** - * Type of this event. - * @type {string} - */ - type: string; } + import { UiBase } from "core/events/events_ui_base"; + import { Block } from "core/block"; } -declare module "events/events" { +declare module "core/events/events" { export const BLOCK_CHANGE: "change"; export const BLOCK_CREATE: "create"; export const BLOCK_DELETE: "delete"; @@ -19929,47 +22728,47 @@ declare module "events/events" { export const clearPendingUndo: () => void; export const disable: () => void; export const enable: () => void; - export const filter: (queueIn: (typeof Abstract)[], forward: boolean) => (typeof Abstract)[]; - export const fire: (event: typeof Abstract) => void; - export const fromJson: (json: any, workspace: import("workspace").Workspace) => typeof Abstract; - export const getDescendantIds: (block: import("block").Block) => string[]; - export const get: (eventType: string) => new (...arg1: any[]) => typeof Abstract; + export const filter: (queueIn: AbstractEvent[], forward: boolean) => AbstractEvent[]; + export const fire: (event: AbstractEvent) => void; + export const fromJson: (json: Object, workspace: import("core/workspace").Workspace) => AbstractEvent; + export const getDescendantIds: (block: import("core/block").Block) => string[]; + export const get: (eventType: string) => (new (...arg1: any[]) => AbstractEvent) | null; export const getGroup: () => string; export const getRecordUndo: () => boolean; export const isEnabled: () => boolean; export const setGroup: (state: string | boolean) => void; export const setRecordUndo: (newValue: boolean) => void; - export const disableOrphans: (event: typeof Abstract) => void; - import * as Abstract from "events/events_abstract"; - import { BubbleOpen } from "events/events_bubble_open"; - import { BlockBase } from "events/events_block_base"; - import { BlockChange } from "events/events_block_change"; - import { BlockCreate } from "events/events_block_create"; - import { BlockDelete } from "events/events_block_delete"; - import { BlockDrag } from "events/events_block_drag"; - import { BlockMove } from "events/events_block_move"; - import { Click } from "events/events_click"; - import { CommentBase } from "events/events_comment_base"; - import { CommentChange } from "events/events_comment_change"; - import { CommentCreate } from "events/events_comment_create"; - import { CommentDelete } from "events/events_comment_delete"; - import { CommentMove } from "events/events_comment_move"; - import { FinishedLoading } from "events/workspace_events"; - import { MarkerMove } from "events/events_marker_move"; - import { Selected } from "events/events_selected"; - import { ThemeChange } from "events/events_theme_change"; - import { ToolboxItemSelect } from "events/events_toolbox_item_select"; - import { TrashcanOpen } from "events/events_trashcan_open"; - import { Ui } from "events/events_ui"; - import { UiBase } from "events/events_ui_base"; - import { VarBase } from "events/events_var_base"; - import { VarCreate } from "events/events_var_create"; - import { VarDelete } from "events/events_var_delete"; - import { VarRename } from "events/events_var_rename"; - import { ViewportChange } from "events/events_viewport"; - export { Abstract, BubbleOpen, BlockBase, BlockChange, BlockCreate, BlockDelete, BlockDrag, BlockMove, Click, CommentBase, CommentChange, CommentCreate, CommentDelete, CommentMove, FinishedLoading, MarkerMove, Selected, ThemeChange, ToolboxItemSelect, TrashcanOpen, Ui, UiBase, VarBase, VarCreate, VarDelete, VarRename, ViewportChange }; + export const disableOrphans: (event: AbstractEvent) => void; + import { Abstract as AbstractEvent } from "core/events/events_abstract"; + import { BubbleOpen } from "core/events/events_bubble_open"; + import { BlockBase } from "core/events/events_block_base"; + import { BlockChange } from "core/events/events_block_change"; + import { BlockCreate } from "core/events/events_block_create"; + import { BlockDelete } from "core/events/events_block_delete"; + import { BlockDrag } from "core/events/events_block_drag"; + import { BlockMove } from "core/events/events_block_move"; + import { Click } from "core/events/events_click"; + import { CommentBase } from "core/events/events_comment_base"; + import { CommentChange } from "core/events/events_comment_change"; + import { CommentCreate } from "core/events/events_comment_create"; + import { CommentDelete } from "core/events/events_comment_delete"; + import { CommentMove } from "core/events/events_comment_move"; + import { FinishedLoading } from "core/events/workspace_events"; + import { MarkerMove } from "core/events/events_marker_move"; + import { Selected } from "core/events/events_selected"; + import { ThemeChange } from "core/events/events_theme_change"; + import { ToolboxItemSelect } from "core/events/events_toolbox_item_select"; + import { TrashcanOpen } from "core/events/events_trashcan_open"; + import { Ui } from "core/events/events_ui"; + import { UiBase } from "core/events/events_ui_base"; + import { VarBase } from "core/events/events_var_base"; + import { VarCreate } from "core/events/events_var_create"; + import { VarDelete } from "core/events/events_var_delete"; + import { VarRename } from "core/events/events_var_rename"; + import { ViewportChange } from "core/events/events_viewport"; + export { AbstractEvent as Abstract, BubbleOpen, BlockBase, BlockChange, BlockCreate, BlockDelete, BlockDrag, BlockMove, Click, CommentBase, CommentChange, CommentCreate, CommentDelete, CommentMove, FinishedLoading, MarkerMove, Selected, ThemeChange, ToolboxItemSelect, TrashcanOpen, Ui, UiBase, VarBase, VarCreate, VarDelete, VarRename, ViewportChange }; } -declare module "contextmenu_items" { +declare module "core/contextmenu_items" { /** * Option to undo previous action. * @alias Blockly.ContextMenuItems.registerUndo @@ -20043,7 +22842,7 @@ declare module "contextmenu_items" { */ export function registerDefaultOptions(): void; } -declare module "shortcut_items" { +declare module "core/shortcut_items" { /** * * */ @@ -20101,33 +22900,27 @@ declare module "shortcut_items" { */ export function registerDefaultShortcuts(): void; } -declare module "theme/zelos" { +declare module "core/theme/zelos" { /** * Zelos theme. * @type {Theme} * @alias Blockly.Themes.Zelos */ export const Zelos: Theme; - import { Theme } from "theme"; + import { Theme } from "core/theme"; } -declare module "theme/themes" { - import { Classic } from "theme/classic"; - import { Zelos } from "theme/zelos"; +declare module "core/theme/themes" { + import { Classic } from "core/theme/classic"; + import { Zelos } from "core/theme/zelos"; export { Classic, Zelos }; } -declare module "renderers/geras/constants" { +declare module "core/renderers/geras/constants" { /** * An object that provides constants for rendering blocks in Geras mode. - * @constructor - * @package * @extends {BaseConstantProvider} * @alias Blockly.geras.ConstantProvider */ export class ConstantProvider extends BaseConstantProvider { - /** - * @override - */ - override FIELD_TEXT_BASELINE_CENTER: boolean; DARK_PATH_OFFSET: number; /** * The maximum width of a bottom row that follows a statement input and has @@ -20135,31 +22928,24 @@ declare module "renderers/geras/constants" { * @type {number} */ MAX_BOTTOM_WIDTH: number; - /** - * @override - */ - override STATEMENT_BOTTOM_SPACER: number; - /** - * @override - */ - override getCSS_(selector: any): any; } - import { ConstantProvider as BaseConstantProvider } from "renderers/common/constants"; + import { ConstantProvider as BaseConstantProvider } from "core/renderers/common/constants"; } -declare module "renderers/geras/highlight_constants" { +declare module "core/renderers/geras/highlight_constants" { /** * An object that provides constants for rendering highlights on blocks. * Some highlights are simple offsets of the parent paths and can be generated * programmatically. Others, especially on curves, are just made out of piles * of constants and are hard to tweak. - * @param {!ConstantProvider} constants The rendering - * constants provider. - * @constructor - * @package * @alias Blockly.geras.HighlightConstantProvider */ export class HighlightConstantProvider { - constructor(constants: any); + /** + * @param {!ConstantProvider} constants The rendering + * constants provider. + * @package + */ + constructor(constants: ConstantProvider); /** * The renderer's constant provider. * @type {!ConstantProvider} @@ -20186,125 +22972,107 @@ declare module "renderers/geras/highlight_constants" { * highlights. * @type {!Object} */ - INSIDE_CORNER: any; + INSIDE_CORNER: Object | undefined; /** * An object containing sizing and path information about outside corner * highlights. * @type {!Object} */ - OUTSIDE_CORNER: any; + OUTSIDE_CORNER: Object | undefined; /** * An object containing sizing and path information about puzzle tab * highlights. * @type {!Object} */ - PUZZLE_TAB: any; + PUZZLE_TAB: Object | undefined; /** * An object containing sizing and path information about notch highlights. * @type {!Object} */ - NOTCH: any; + NOTCH: Object | undefined; /** * An object containing sizing and path information about highlights for * collapsed block indicators. * @type {!Object} */ - JAGGED_TEETH: any; + JAGGED_TEETH: Object | undefined; /** * An object containing sizing and path information about start hat * highlights. * @type {!Object} */ - START_HAT: any; + START_HAT: Object | undefined; /** * @return {!Object} An object containing sizing and path information about * inside corner highlights. * @package */ - makeInsideCorner(): any; + makeInsideCorner(): Object; /** * @return {!Object} An object containing sizing and path information about * outside corner highlights. * @package */ - makeOutsideCorner(): any; + makeOutsideCorner(): Object; /** * @return {!Object} An object containing sizing and path information about * puzzle tab highlights. * @package */ - makePuzzleTab(): any; + makePuzzleTab(): Object; /** * @return {!Object} An object containing sizing and path information about * notch highlights. * @package */ - makeNotch(): any; + makeNotch(): Object; /** * @return {!Object} An object containing sizing and path information about * collapsed block edge highlights. * @package */ - makeJaggedTeeth(): any; + makeJaggedTeeth(): Object; /** * @return {!Object} An object containing sizing and path information about * start highlights. * @package */ - makeStartHat(): any; + makeStartHat(): Object; } - import { ConstantProvider } from "renderers/common/constants"; + import { ConstantProvider } from "core/renderers/common/constants"; } -declare module "renderers/geras/measurables/inline_input" { +declare module "core/renderers/geras/measurables/inline_input" { /** * An object containing information about the space an inline input takes up - * during rendering - * @param {!ConstantProvider} constants The rendering - * constants provider. - * @param {!Input} input The inline input to measure and store - * information for. - * @package - * @constructor + * during rendering. * @extends {BaseInlineInput} * @alias Blockly.geras.InlineInput */ - export class InlineInput { - constructor(constants: any, input: any); + export class InlineInput extends BaseInlineInput { } + import { InlineInput as BaseInlineInput } from "core/renderers/measurables/inline_input"; } -declare module "renderers/geras/path_object" { +declare module "core/renderers/geras/path_object" { /** * An object that handles creating and setting each of the SVG elements * used by the renderer. - * @param {!SVGElement} root The root SVG element. - * @param {!Theme.BlockStyle} style The style object to use for - * colouring. - * @param {!ConstantProvider} constants The renderer's constants. - * @constructor - * @extends {BasePathObject} - * @package * @alias Blockly.geras.PathObject */ export class PathObject extends BasePathObject { - constructor(root: any, style: any, constants: any); /** - * The renderer's constant provider. - * @type {!ConstantProvider} + * @param {!SVGElement} root The root SVG element. + * @param {!Theme.BlockStyle} style The style object to use for + * colouring. + * @param {!ConstantProvider} constants The renderer's constants. + * @package */ - constants: ConstantProvider; - svgRoot: any; + constructor(root: SVGElement, style: Theme.BlockStyle, constants: ConstantProvider); /** * The dark path of the block. * @type {SVGElement} * @package */ svgPathDark: SVGElement; - /** - * The primary path of the block. - * @type {!SVGElement} - * @package - */ - svgPath: SVGElement; /** * The light path of the block. * @type {SVGElement} @@ -20317,245 +23085,104 @@ declare module "renderers/geras/path_object" { * @package */ colourDark: string; - /** - * The style object to use when colouring block paths. - * @type {!Theme.BlockStyle} - * @package - */ - style: Theme.BlockStyle; - /** - * @override - */ - override setPath(mainPath: any): void; /** * Set the highlight path generated by the renderer onto the SVG element. * @param {string} highlightPath The highlight path. * @package */ setHighlightPath(highlightPath: string): void; - /** - * @override - */ - override flipRTL(): void; - /** - * @override - */ - override applyColour(block: any): void; - /** - * @override - */ - override setStyle(blockStyle: any): void; - /** - * @override - */ - override updateHighlighted(highlighted: any): void; - /** - * @override - */ - override updateShadow_(shadow: any): void; - /** - * @override - */ - override updateDisabled_(disabled: any): void; } - import { ConstantProvider } from "renderers/geras/constants"; - import { Theme } from "theme"; - import { PathObject as BasePathObject } from "renderers/common/block_rendering"; + import { PathObject as BasePathObject } from "core/renderers/common/path_object"; + import { Theme } from "core/theme"; + import { ConstantProvider } from "core/renderers/geras/constants"; } -declare module "renderers/geras/renderer" { +declare module "core/renderers/geras/renderer" { /** * The geras renderer. - * @param {string} name The renderer name. - * @package - * @constructor * @extends {BaseRenderer} * @alias Blockly.geras.Renderer */ - export class Renderer extends BaseRenderer { - constructor(name: any); + export class Renderer extends blockRendering.Renderer { /** * The renderer's highlight constant provider. * @type {HighlightConstantProvider} * @private */ private highlightConstants_; - /** - * Initialize the renderer. Geras has a highlight provider in addition to - * the normal constant provider. - * @package - * @override - */ - override init(theme: any, opt_rendererOverrides: any): void; - /** - * @override - */ - override refreshDom(svg: any, theme: any): void; - /** - * @override - */ - override makeConstants_(): ConstantProvider; - /** - * Create a new instance of the renderer's render info object. - * @param {!BlockSvg} block The block to measure. - * @return {!RenderInfo} The render info object. - * @protected - * @override - */ - protected override makeRenderInfo_(block: BlockSvg): RenderInfo; - /** - * Create a new instance of the renderer's drawer. - * @param {!BlockSvg} block The block to render. - * @param {!BaseRenderInfo} info An object containing all - * information needed to render this block. - * @return {!Drawer} The drawer. - * @protected - * @override - */ - protected override makeDrawer_(block: BlockSvg, info: BaseRenderInfo): Drawer; - /** - * Create a new instance of a renderer path object. - * @param {!SVGElement} root The root SVG element. - * @param {!Theme.BlockStyle} style The style object to use for - * colouring. - * @return {!PathObject} The renderer path object. - * @package - * @override - */ - override makePathObject(root: SVGElement, style: Theme.BlockStyle): PathObject; /** * Create a new instance of the renderer's highlight constant provider. - * @return {!HighlightConstantProvider} The highlight constant - * provider. + * @return {!HighlightConstantProvider} The highlight constant provider. * @protected */ protected makeHighlightConstants_(): HighlightConstantProvider; /** - * Get the renderer's highlight constant provider. We assume that when this is - * called, the renderer has already been initialized. - * @return {!HighlightConstantProvider} The highlight constant - * provider. + * Get the renderer's highlight constant provider. We assume that when this + * is called, the renderer has already been initialized. + * @return {!HighlightConstantProvider} The highlight constant provider. * @package */ getHighlightConstants(): HighlightConstantProvider; } - import { ConstantProvider } from "renderers/geras/constants"; - import { BlockSvg } from "block_svg"; - import { RenderInfo } from "renderers/geras/info"; - import { RenderInfo as BaseRenderInfo } from "renderers/common/info"; - import { Drawer } from "renderers/geras/drawer"; - import { Theme } from "theme"; - import { PathObject } from "renderers/geras/path_object"; - import { HighlightConstantProvider } from "renderers/geras/highlight_constants"; - import { Renderer as BaseRenderer } from "renderers/common/block_rendering"; + import * as blockRendering from "core/renderers/common/block_rendering"; + import { HighlightConstantProvider } from "core/renderers/geras/highlight_constants"; } -declare module "renderers/geras/measurables/statement_input" { +declare module "core/renderers/geras/measurables/statement_input" { /** * An object containing information about the space a statement input takes up - * during rendering - * @param {!ConstantProvider} constants The rendering - * constants provider. - * @param {!Input} input The statement input to measure and store - * information for. - * @package - * @constructor + * during rendering. * @extends {BaseStatementInput} * @alias Blockly.geras.StatementInput */ - export class StatementInput { - constructor(constants: any, input: any); + export class StatementInput extends BaseStatementInput { } + import { StatementInput as BaseStatementInput } from "core/renderers/measurables/statement_input"; } -declare module "renderers/geras/info" { +declare module "core/renderers/geras/info" { + /** + * An object containing all sizing information needed to draw this block. + * + * This measure pass does not propagate changes to the block (although fields + * may choose to rerender when getSize() is called). However, calling it + * repeatedly may be expensive. + * @extends {BaseRenderInfo} + * @alias Blockly.geras.RenderInfo + */ export class RenderInfo extends BaseRenderInfo { /** - * An object containing all sizing information needed to draw this block. - * - * This measure pass does not propagate changes to the block (although fields - * may choose to rerender when getSize() is called). However, calling it - * repeatedly may be expensive. - * * @param {!Renderer} renderer The renderer in use. * @param {!BlockSvg} block The block to measure. - * @constructor * @package - * @extends {BaseRenderInfo} - * @alias Blockly.geras.RenderInfo */ constructor(renderer: Renderer, block: BlockSvg); - /** - * Get the block renderer in use. - * @return {!Renderer} The block renderer in use. - * @package - */ - getRenderer(): Renderer; - /** - * @override - */ - override populateBottomRow_(): void; - /** - * @override - */ - override addInput_(input: any, activeRow: any): void; - /** - * @override - */ - override addElemSpacing_(): void; - /** - * @override - */ - override getInRowSpacing_(prev: any, next: any): any; - /** - * @override - */ - override getSpacerRowHeight_(prev: any, next: any): any; - /** - * @override - */ - override getElemCenterline_(row: any, elem: any): any; - /** - * @override - */ - override alignRowElements_(): void; - /** - * @override - */ - override getDesiredRowWidth_(row: any): any; - /** - * @override - */ - override finalize_(): void; - widthWithChildren: any; - height: any; - startY: any; } - import { Renderer } from "renderers/geras/renderer"; - import { BlockSvg } from "block_svg"; - import { RenderInfo as BaseRenderInfo } from "renderers/common/block_rendering"; + import { RenderInfo as BaseRenderInfo } from "core/renderers/common/info"; + import { Renderer } from "core/renderers/geras/renderer"; + import { BlockSvg } from "core/block_svg"; } -declare module "renderers/geras/highlighter" { +declare module "core/renderers/geras/highlighter" { + /** + * An object that adds highlights to a block based on the given rendering + * information. + * + * Highlighting is interesting because the highlights do not fully enclose the + * block. Instead, they are positioned based on a light source in the top left. + * This means that rendering highlights requires exact information about the + * position of each part of the block. The resulting paths are not continuous + * or closed paths. The highlights for tabs and notches are loosely based on + * tab and notch shapes, but are not exactly the same. + * @alias Blockly.geras.Highlighter + */ export class Highlighter { /** - * An object that adds highlights to a block based on the given rendering - * information. - * - * Highlighting is interesting because the highlights do not fully enclose the - * block. Instead, they are positioned based on a light source in the top left. - * This means that rendering highlights requires exact information about the - * position of each part of the block. The resulting paths are not continuous - * or closed paths. The highlights for tabs and notches are loosely based on - * tab and notch shapes, but are not exactly the same. - * * @param {!RenderInfo} info An object containing all * information needed to render this block. * @package - * @constructor - * @alias Blockly.geras.Highlighter */ constructor(info: RenderInfo); info_: RenderInfo; steps_: string; inlineSteps_: string; - RTL_: any; + RTL_: boolean; /** * The renderer's constant provider. * @type {!ConstantProvider} @@ -20571,1699 +23198,285 @@ declare module "renderers/geras/highlighter" { * @private */ private highlightOffset_; - outsideCornerPaths_: any; - insideCornerPaths_: any; - puzzleTabPaths_: any; - notchPaths_: any; - startPaths_: any; - jaggedTeethPaths_: any; + outsideCornerPaths_: Object | undefined; + insideCornerPaths_: Object | undefined; + puzzleTabPaths_: Object | undefined; + notchPaths_: Object | undefined; + startPaths_: Object | undefined; + jaggedTeethPaths_: Object | undefined; /** * Get the steps for the highlight path. * @return {string} The steps for the highlight path. * @package */ getPath(): string; - drawTopCorner(row: any): void; - drawJaggedEdge_(row: any): void; - drawValueInput(row: any): void; - drawStatementInput(row: any): void; - drawRightSideRow(row: any): void; - drawBottomRow(row: any): void; + /** + * Add a highlight to the top corner of a block. + * @param {!TopRow} row The top row of the block. + * @package + */ + drawTopCorner(row: TopRow): void; + /** + * Add a highlight on a jagged edge for a collapsed block. + * @param {!Row} row The row to highlight. + * @package + */ + drawJaggedEdge_(row: Row): void; + /** + * Add a highlight on a value input. + * @param {!Row} row The row the input belongs to. + * @package + */ + drawValueInput(row: Row): void; + /** + * Add a highlight on a statement input. + * @param {!Row} row The row to highlight. + * @package + */ + drawStatementInput(row: Row): void; + /** + * Add a highlight on the right side of a row. + * @param {!Row} row The row to highlight. + * @package + */ + drawRightSideRow(row: Row): void; + /** + * Add a highlight to the bottom row. + * @param {!BottomRow} row The row to highlight. + * @package + */ + drawBottomRow(row: BottomRow): void; + /** + * Draw the highlight on the left side of the block. + * @package + */ drawLeft(): void; - drawInlineInput(input: any): void; + /** + * Add a highlight to an inline input. + * @param {!InlineInput} input The input to highlight. + * @package + */ + drawInlineInput(input: InlineInput): void; } - import { RenderInfo } from "renderers/geras/info"; - import { ConstantProvider } from "renderers/common/constants"; - import { HighlightConstantProvider } from "renderers/geras/highlight_constants"; + import { RenderInfo } from "core/renderers/geras/info"; + import { ConstantProvider } from "core/renderers/common/constants"; + import { HighlightConstantProvider } from "core/renderers/geras/highlight_constants"; + import { TopRow } from "core/renderers/measurables/top_row"; + import { Row } from "core/renderers/measurables/row"; + import { BottomRow } from "core/renderers/measurables/bottom_row"; + import { InlineInput } from "core/renderers/geras/measurables/inline_input"; } -declare module "renderers/geras/drawer" { +declare module "core/renderers/geras/drawer" { + /** + * An object that draws a block based on the given rendering information. + * @extends {BaseDrawer} + * @alias Blockly.geras.Drawer + */ export class Drawer extends BaseDrawer { /** - * An object that draws a block based on the given rendering information. * @param {!BlockSvg} block The block to render. * @param {!RenderInfo} info An object containing all * information needed to render this block. * @package - * @constructor - * @extends {BaseDrawer} - * @alias Blockly.geras.Drawer */ constructor(block: BlockSvg, info: RenderInfo); highlighter_: Highlighter; - /** - * @override - */ - override draw(): void; - /** - * @override - */ - override drawTop_(): void; - /** - * @override - */ - override drawJaggedEdge_(row: any): void; - /** - * @override - */ - override drawValueInput_(row: any): void; - /** - * @override - */ - override drawStatementInput_(row: any): void; - /** - * @override - */ - override drawRightSideRow_(row: any): void; - /** - * @override - */ - override drawBottom_(): void; - /** - * Add steps for the left side of the block, which may include an output - * connection - * @protected - * @override - */ - protected override drawLeft_(): void; - /** - * @override - */ - override drawInlineInput_(input: any): void; - /** - * @override - */ - override positionInlineInputConnection_(input: any): void; - /** - * @override - */ - override positionStatementInputConnection_(row: any): void; - /** - * @override - */ - override positionExternalValueConnection_(row: any): void; - /** - * @override - */ - override positionNextConnection_(): void; } - import { Highlighter } from "renderers/geras/highlighter"; - import { BlockSvg } from "block_svg"; - import { RenderInfo } from "renderers/geras/info"; - import { Drawer as BaseDrawer } from "renderers/common/drawer"; + import { Drawer as BaseDrawer } from "core/renderers/common/drawer"; + import { Highlighter } from "core/renderers/geras/highlighter"; + import { BlockSvg } from "core/block_svg"; + import { RenderInfo } from "core/renderers/geras/info"; } -declare module "renderers/geras/geras" { - import { ConstantProvider } from "renderers/geras/constants"; - import { Drawer } from "renderers/geras/drawer"; - import { HighlightConstantProvider } from "renderers/geras/highlight_constants"; - import { Highlighter } from "renderers/geras/highlighter"; - import { InlineInput } from "renderers/geras/measurables/inline_input"; - import { PathObject } from "renderers/geras/path_object"; - import { RenderInfo } from "renderers/geras/info"; - import { Renderer } from "renderers/geras/renderer"; - import { StatementInput } from "renderers/geras/measurables/statement_input"; +declare module "core/renderers/geras/geras" { + import { ConstantProvider } from "core/renderers/geras/constants"; + import { Drawer } from "core/renderers/geras/drawer"; + import { HighlightConstantProvider } from "core/renderers/geras/highlight_constants"; + import { Highlighter } from "core/renderers/geras/highlighter"; + import { InlineInput } from "core/renderers/geras/measurables/inline_input"; + import { PathObject } from "core/renderers/geras/path_object"; + import { RenderInfo } from "core/renderers/geras/info"; + import { Renderer } from "core/renderers/geras/renderer"; + import { StatementInput } from "core/renderers/geras/measurables/statement_input"; export { ConstantProvider, Drawer, HighlightConstantProvider, Highlighter, InlineInput, PathObject, RenderInfo, Renderer, StatementInput }; } -declare module "renderers/minimalist/constants" { +declare module "core/renderers/minimalist/constants" { /** * An object that provides constants for rendering blocks in the sample. - * @constructor - * @package * @extends {BaseConstantProvider} * @alias Blockly.minimalist.ConstantProvider */ export class ConstantProvider extends BaseConstantProvider { } - - import { ConstantProvider as BaseConstantProvider } from "renderers/common/block_rendering"; + import { ConstantProvider as BaseConstantProvider } from "core/renderers/common/constants"; } -declare module "renderers/minimalist/renderer" { +declare module "core/renderers/minimalist/renderer" { /** * The minimalist renderer. - * @param {string} name The renderer name. - * @package - * @constructor * @extends {BaseRenderer} * @alias Blockly.minimalist.Renderer */ - export class Renderer extends BaseRenderer { - constructor(name: any); - /** - * Create a new instance of the renderer's constant provider. - * @return {!ConstantProvider} The constant provider. - * @protected - * @override - */ - protected override makeConstants_(): ConstantProvider; - /** - * Create a new instance of the renderer's render info object. - * @param {!BlockSvg} block The block to measure. - * @return {!RenderInfo} The render info object. - * @protected - * @override - */ - protected override makeRenderInfo_(block: BlockSvg): RenderInfo; - /** - * Create a new instance of the renderer's drawer. - * @param {!BlockSvg} block The block to render. - * @param {!BaseRenderInfo} info An object containing all - * information needed to render this block. - * @return {!Drawer} The drawer. - * @protected - * @override - */ - protected override makeDrawer_(block: BlockSvg, info: BaseRenderInfo): Drawer; + export class Renderer extends blockRendering.Renderer { } - import { ConstantProvider } from "renderers/minimalist/constants"; - import { BlockSvg } from "block_svg"; - import { RenderInfo } from "renderers/minimalist/info"; - import { RenderInfo as BaseRenderInfo } from "renderers/common/info"; - import { Drawer } from "renderers/minimalist/drawer"; - import { Renderer as BaseRenderer } from "renderers/common/block_rendering"; + import * as blockRendering from "core/renderers/common/block_rendering"; } -declare module "renderers/minimalist/info" { +declare module "core/renderers/minimalist/info" { /** * An object containing all sizing information needed to draw this block. * * This measure pass does not propagate changes to the block (although fields * may choose to rerender when getSize() is called). However, calling it * repeatedly may be expensive. - * - * @param {!Renderer} renderer The renderer in use. - * @param {!BlockSvg} block The block to measure. - * @constructor - * @package * @extends {BaseRenderInfo} * @alias Blockly.minimalist.RenderInfo */ export class RenderInfo extends BaseRenderInfo { - constructor(renderer: any, block: any); /** - * Get the block renderer in use. - * @return {!Renderer} The block renderer in use. + * @param {!Renderer} renderer The renderer in use. + * @param {!BlockSvg} block The block to measure. * @package */ - getRenderer(): Renderer; + constructor(renderer: Renderer, block: BlockSvg); } - import { Renderer } from "renderers/minimalist/renderer"; - import { RenderInfo as BaseRenderInfo } from "renderers/common/block_rendering"; + import { RenderInfo as BaseRenderInfo } from "core/renderers/common/info"; + import { Renderer } from "core/renderers/minimalist/renderer"; + import { BlockSvg } from "core/block_svg"; } -declare module "renderers/minimalist/drawer" { +declare module "core/renderers/minimalist/drawer" { /** * An object that draws a block based on the given rendering information. - * @param {!BlockSvg} block The block to render. - * @param {!RenderInfo} info An object containing all - * information needed to render this block. - * @package - * @constructor * @extends {BaseDrawer} * @alias Blockly.minimalist.Drawer */ export class Drawer extends BaseDrawer { - constructor(block: any, info: any); - } - - import { Drawer as BaseDrawer } from "renderers/common/drawer"; -} -declare module "renderers/minimalist/minimalist" { - import { ConstantProvider } from "renderers/minimalist/constants"; - import { Drawer } from "renderers/minimalist/drawer"; - import { RenderInfo } from "renderers/minimalist/info"; - import { Renderer } from "renderers/minimalist/renderer"; - export { ConstantProvider, Drawer, RenderInfo, Renderer }; -} -declare module "renderers/thrasos/renderer" { - /** - * The thrasos renderer. - * @param {string} name The renderer name. - * @package - * @constructor - * @extends {BaseRenderer} - * @alias Blockly.thrasos.Renderer - */ - export class Renderer extends BaseRenderer { - constructor(name: any); /** - * Create a new instance of the renderer's render info object. - * @param {!BlockSvg} block The block to measure. - * @return {!RenderInfo} The render info object. - * @protected - * @override - */ - protected override makeRenderInfo_(block: BlockSvg): RenderInfo; - } - import { BlockSvg } from "block_svg"; - import { RenderInfo } from "renderers/thrasos/info"; - import { Renderer as BaseRenderer } from "renderers/common/block_rendering"; -} -declare module "renderers/thrasos/info" { - export class RenderInfo extends BaseRenderInfo { - /** - * An object containing all sizing information needed to draw this block. - * - * This measure pass does not propagate changes to the block (although fields - * may choose to rerender when getSize() is called). However, calling it - * repeatedly may be expensive. - * - * @param {!Renderer} renderer The renderer in use. - * @param {!BlockSvg} block The block to measure. - * @constructor - * @package - * @extends {BaseRenderInfo} - * @alias Blockly.thrasos.RenderInfo - */ - constructor(renderer: Renderer, block: BlockSvg); - /** - * Get the block renderer in use. - * @return {!Renderer} The block renderer in use. - * @package - */ - getRenderer(): Renderer; - /** - * @override - */ - override addElemSpacing_(): void; - /** - * @override - */ - override getInRowSpacing_(prev: any, next: any): any; - /** - * @override - */ - override getSpacerRowHeight_(prev: any, next: any): any; - /** - * @override - */ - override getElemCenterline_(row: any, elem: any): any; - /** - * @override - */ - override finalize_(): void; - widthWithChildren: any; - height: number; - startY: any; - } - import { Renderer } from "renderers/thrasos/renderer"; - import { BlockSvg } from "block_svg"; - import { RenderInfo as BaseRenderInfo } from "renderers/common/block_rendering"; -} -declare module "renderers/thrasos/thrasos" { - import { RenderInfo } from "renderers/thrasos/info"; - import { Renderer } from "renderers/thrasos/renderer"; - export { RenderInfo, Renderer }; -} -declare module "renderers/zelos/measurables/bottom_row" { - /** - * An object containing information about what elements are in the bottom row of - * a block as well as spacing information for the top row. - * Elements in a bottom row can consist of corners, spacers and next - * connections. - * @param {!ConstantProvider} constants The rendering - * constants provider. - * @package - * @constructor - * @extends {BaseBottomRow} - * @alias Blockly.zelos.BottomRow - */ - export class BottomRow extends BaseBottomRow { - constructor(constants: any); - /** - * @override - */ - override endsWithElemSpacer(): boolean; - /** - * Render a round corner unless the block has an output connection. - * @override - */ - override hasLeftSquareCorner(block: any): boolean; - /** - * Render a round corner unless the block has an output connection. - * @override - */ - override hasRightSquareCorner(block: any): boolean; - } - import { BottomRow as BaseBottomRow } from "renderers/common/block_rendering"; -} -declare module "renderers/zelos/constants" { - /** - * An object that provides constants for rendering blocks in Zelos mode. - * @constructor - * @package - * @extends {BaseConstantProvider} - * @alias Blockly.zelos.ConstantProvider - */ - export class ConstantProvider extends BaseConstantProvider { - GRID_UNIT: number; - /** - * @override - */ - override SMALL_PADDING: number; - /** - * @override - */ - override MEDIUM_PADDING: number; - /** - * @override - */ - override MEDIUM_LARGE_PADDING: number; - /** - * @override - */ - override LARGE_PADDING: number; - /** - * @override - */ - override CORNER_RADIUS: number; - /** - * @override - */ - override NOTCH_WIDTH: number; - /** - * @override - */ - override NOTCH_HEIGHT: number; - /** - * @override - */ - override NOTCH_OFFSET_LEFT: number; - /** - * @override - */ - override STATEMENT_INPUT_NOTCH_OFFSET: number; - /** - * @override - */ - override MIN_BLOCK_WIDTH: number; - /** - * @override - */ - override MIN_BLOCK_HEIGHT: number; - /** - * @override - */ - override EMPTY_STATEMENT_INPUT_HEIGHT: number; - /** - * @override - */ - override TAB_OFFSET_FROM_TOP: number; - /** - * @override - */ - override TOP_ROW_MIN_HEIGHT: number; - /** - * @override - */ - override TOP_ROW_PRECEDES_STATEMENT_MIN_HEIGHT: number; - /** - * @override - */ - override BOTTOM_ROW_MIN_HEIGHT: number; - /** - * @override - */ - override BOTTOM_ROW_AFTER_STATEMENT_MIN_HEIGHT: number; - /** - * @override - */ - override STATEMENT_BOTTOM_SPACER: number; - /** - * Minimum statement input spacer width. - * @type {number} - */ - STATEMENT_INPUT_SPACER_MIN_WIDTH: number; - /** - * @override - */ - override STATEMENT_INPUT_PADDING_LEFT: number; - /** - * @override - */ - override EMPTY_INLINE_INPUT_PADDING: number; - /** - * @override - */ - override EMPTY_INLINE_INPUT_HEIGHT: number; - /** - * @override - */ - override DUMMY_INPUT_MIN_HEIGHT: number; - /** - * @override - */ - override DUMMY_INPUT_SHADOW_MIN_HEIGHT: number; - /** - * @override - */ - override CURSOR_WS_WIDTH: number; - /** - * @override - */ - override CURSOR_COLOUR: string; - /** - * Radius of the cursor for input and output connections. - * @type {number} - * @package - */ - CURSOR_RADIUS: number; - /** - * @override - */ - override JAGGED_TEETH_HEIGHT: number; - /** - * @override - */ - override JAGGED_TEETH_WIDTH: number; - /** - * @override - */ - override START_HAT_HEIGHT: number; - /** - * @override - */ - override START_HAT_WIDTH: number; - /** - * @enum {number} - * @override - */ - override SHAPES: { - HEXAGONAL: number; - ROUND: number; - SQUARE: number; - PUZZLE: number; - NOTCH: number; - }; - /** - * Map of output/input shapes and the amount they should cause a block to be - * padded. Outer key is the outer shape, inner key is the inner shape. - * When a block with the outer shape contains an input block with the inner - * shape on its left or right edge, the block elements are aligned such that - * the padding specified is reached. - * @package - */ - SHAPE_IN_SHAPE_PADDING: { - 1: { - 0: number; - 1: number; - 2: number; - 3: number; - }; - 2: { - 0: number; - 1: number; - 2: number; - 3: number; - }; - 3: { - 0: number; - 1: number; - 2: number; - 3: number; - }; - }; - /** - * @override - */ - override FULL_BLOCK_FIELDS: boolean; - /** - * @override - */ - override FIELD_TEXT_FONTSIZE: number; - /** - * @override - */ - override FIELD_TEXT_FONTWEIGHT: string; - /** - * @override - */ - override FIELD_TEXT_FONTFAMILY: string; - /** - * @override - */ - override FIELD_BORDER_RECT_RADIUS: number; - /** - * @override - */ - override FIELD_BORDER_RECT_X_PADDING: number; - /** - * @override - */ - override FIELD_BORDER_RECT_Y_PADDING: number; - /** - * @override - */ - override FIELD_BORDER_RECT_HEIGHT: number; - /** - * @override - */ - override FIELD_DROPDOWN_BORDER_RECT_HEIGHT: number; - /** - * @override - */ - override FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW: boolean; - /** - * @override - */ - override FIELD_DROPDOWN_COLOURED_DIV: boolean; - /** - * @override - */ - override FIELD_DROPDOWN_SVG_ARROW: boolean; - /** - * @override - */ - override FIELD_DROPDOWN_SVG_ARROW_PADDING: number; - /** - * @override - */ - override FIELD_TEXTINPUT_BOX_SHADOW: boolean; - /** - * @override - */ - override FIELD_COLOUR_FULL_BLOCK: boolean; - /** - * @override - */ - override FIELD_COLOUR_DEFAULT_WIDTH: number; - /** - * @override - */ - override FIELD_COLOUR_DEFAULT_HEIGHT: number; - /** - * @override - */ - override FIELD_CHECKBOX_X_OFFSET: number; - /** - * The maximum width of a dynamic connection shape. - * @type {number} - */ - MAX_DYNAMIC_CONNECTION_SHAPE_WIDTH: number; - /** - * The selected glow colour. - * @type {string} - */ - SELECTED_GLOW_COLOUR: string; - /** - * The size of the selected glow. - * @type {number} - */ - SELECTED_GLOW_SIZE: number; - /** - * The replacement glow colour. - * @type {string} - */ - REPLACEMENT_GLOW_COLOUR: string; - /** - * The size of the selected glow. - * @type {number} - */ - REPLACEMENT_GLOW_SIZE: number; - /** - * The ID of the selected glow filter, or the empty string if no filter is - * set. - * @type {string} - * @package - */ - selectedGlowFilterId: string; - /** - * The element to use for a selected glow, or null if not set. - * @type {SVGElement} - * @private - */ - private selectedGlowFilter_; - /** - * The ID of the replacement glow filter, or the empty string if no filter is - * set. - * @type {string} - * @package - */ - replacementGlowFilterId: string; - /** - * The element to use for a replacement glow, or null if not set. - * @type {SVGElement} - * @private - */ - private replacementGlowFilter_; - /** - * @override - */ - override setFontConstants_(theme: any): void; - /** - * @override - */ - override init(): void; - HEXAGONAL: any; - ROUNDED: any; - SQUARED: any; - /** - * @override - */ - override setDynamicProperties_(theme: any): void; - /** - * @override - */ - override dispose(): void; - /** - * @override - */ - override makeStartHat(): { - height: number; - width: number; - path: string; - }; - /** - * Create sizing and path information about a hexagonal shape. - * @return {!Object} An object containing sizing and path information about - * a hexagonal shape for connections. - * @package - */ - makeHexagonal(): any; - /** - * Create sizing and path information about a rounded shape. - * @return {!Object} An object containing sizing and path information about - * a rounded shape for connections. - * @package - */ - makeRounded(): any; - /** - * Create sizing and path information about a squared shape. - * @return {!Object} An object containing sizing and path information about - * a squared shape for connections. - * @package - */ - makeSquared(): any; - /** - * @override - */ - override shapeFor(connection: any): any; - /** - * @override - */ - override makeNotch(): { - type: number; - width: number; - height: number; - pathLeft: string; - pathRight: string; - }; - /** - * @override - */ - override makeInsideCorners(): { - width: number; - height: number; - pathTop: string; - pathBottom: string; - rightWidth: number; - rightHeight: number; - pathTopRight: string; - pathBottomRight: string; - }; - /** - * @override - */ - override generateSecondaryColour_(colour: any): any; - /** - * @override - */ - override generateTertiaryColour_(colour: any): any; - /** - * @override - */ - override createDom(svg: any, tagName: any, selector: any): void; - /** - * @override - */ - override getCSS_(selector: any): string[]; - } - import { ConstantProvider as BaseConstantProvider } from "renderers/common/block_rendering"; -} -declare module "renderers/zelos/path_object" { - export class PathObject extends BasePathObject { - /** - * An object that handles creating and setting each of the SVG elements - * used by the renderer. - * @param {!SVGElement} root The root SVG element. - * @param {!Theme.BlockStyle} style The style object to use for - * colouring. - * @param {!ConstantProvider} constants The renderer's constants. - * @constructor - * @extends {BasePathObject} - * @package - * @alias Blockly.zelos.PathObject - */ - constructor(root: SVGElement, style: Theme.BlockStyle, constants: ConstantProvider); - /** - * The renderer's constant provider. - * @type {!ConstantProvider} - */ - constants: ConstantProvider; - /** - * The selected path of the block. - * @type {?SVGElement} - * @private - */ - private svgPathSelected_; - /** - * The outline paths on the block. - * @type {!Object} - * @private - */ - private outlines_; - /** - * A set used to determine which outlines were used during a draw pass. The - * set is initialized with a reference to all the outlines in - * `this.outlines_`. Every time we use an outline during the draw pass, the - * reference is removed from this set. - * @type {Object} - * @private - */ - private remainingOutlines_; - /** - * The type of block's output connection shape. This is set when a block with - * an output connection is drawn. - * @package - */ - outputShapeType: any; - /** - * @override - */ - override setPath(pathString: any): void; - /** - * @override - */ - override applyColour(block: any): void; - /** - * @override - */ - override flipRTL(): void; - /** - * @override - */ - override updateSelected(enable: any): void; - /** - * @override - */ - override updateReplacementFade(enable: any): void; - /** - * @override - */ - override updateShapeForInputHighlight(conn: any, enable: any): void; - /** - * Method that's called when the drawer is about to draw the block. - * @package - */ - beginDrawing(): void; - /** - * Method that's called when the drawer is done drawing. - * @package - */ - endDrawing(): void; - /** - * Set the path generated by the renderer for an outline path on the respective - * outline path SVG element. - * @param {string} name The input name. - * @param {string} pathString The path. - * @package - */ - setOutlinePath(name: string, pathString: string): void; - /** - * Create's an outline path for the specified input. - * @param {string} name The input name. - * @return {!SVGElement} The SVG outline path. - * @private - */ - private getOutlinePath_; - /** - * Remove an outline path that is associated with the specified input. - * @param {string} name The input name. - * @private - */ - private removeOutlinePath_; - } - import { ConstantProvider } from "renderers/zelos/constants"; - import { Theme } from "theme"; - import { PathObject as BasePathObject } from "renderers/common/block_rendering"; -} -declare module "field_image" { - /** - * Class for an image on a block. - * @param {string} src The URL of the image. - * @param {!(string|number)} width Width of the image. - * @param {!(string|number)} height Height of the image. - * @param {string=} opt_alt Optional alt text for when block is collapsed. - * @param {function(!FieldImage)=} opt_onClick Optional function to be - * called when the image is clicked. If opt_onClick is defined, opt_alt must - * also be defined. - * @param {boolean=} opt_flipRtl Whether to flip the icon in RTL. - * @param {Object=} opt_config A map of options used to configure the field. - * See the [field creation documentation]{@link - * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/image#creation} - * for a list of properties this parameter supports. - * @extends {Field} - * @constructor - * @alias Blockly.FieldImage - */ - export class FieldImage extends 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, - * alt, and flipRtl). - * @return {!FieldImage} The new field instance. - * @package - * @nocollapse - */ - static fromJson(options: any): FieldImage; - constructor(src: any, width: any, height: any, opt_alt: any, opt_onClick: any, opt_flipRtl: any, opt_config: any); - /** - * Whether to flip this image in RTL. - * @type {boolean} - * @private - */ - private flipRtl_; - /** - * Alt text of this image. - * @type {string} - * @private - */ - private altText_; - /** - * The size of the area rendered by the field. - * @type {Size} - * @protected - * @override - */ - protected override size_: Size; - /** - * Store the image height, since it is different from the field height. - * @type {number} - * @private - */ - private imageHeight_; - /** - * The function to be called when this field is clicked. - * @type {?function(!FieldImage)} - * @private - */ - private clickHandler_; - /** - * The rendered field's image element. - * @type {SVGImageElement} - * @private - */ - private imageElement_; - /** - * Configure the field based on the given map of options. - * @param {!Object} config A map of options to configure the field based on. - * @protected - * @override - */ - protected override configure_(config: any): void; - /** - * Create the block UI for this image. - * @package - */ - initView(): void; - /** - * @override - */ - override updateSize_(): void; - /** - * Ensure that the input value (the source URL) is a string. - * @param {*=} opt_newValue The input value. - * @return {?string} A string, or null if invalid. - * @protected - */ - protected doClassValidation_(opt_newValue?: any | undefined): string | null; - /** - * Update the value of this image field, and update the displayed image. - * @param {*} newValue The value to be saved. The default validator guarantees - * that this is a string. - * @protected - */ - protected doValueUpdate_(newValue: any): void; - value_: any; - /** - * Get whether to flip this image in RTL - * @return {boolean} True if we should flip in RTL. - * @override - */ - override getFlipRtl(): boolean; - /** - * Set the alt text of this image. - * @param {?string} alt New alt text. - * @public - */ - public setAlt(alt: string | null): void; - /** - * If field click is called, and click handler defined, - * call the handler. - * @protected - */ - protected showEditor_(): void; - /** - * Set the function that is called when this image is clicked. - * @param {?function(!FieldImage)} func The function that is called - * when the image is clicked, or null to remove. - */ - setOnClickHandler(func: (arg0: FieldImage) => any): void; - /** - * Use the `getText_` developer hook to override the field's text - * representation. - * Return the image alt text instead. - * @return {?string} The image alt text. - * @protected - * @override - */ - protected getText_(): string | null; - /** - * The default value for this field. - * @type {*} - * @protected - */ - protected DEFAULT_VALUE: any; - /** - * Editable fields usually show some sort of UI indicating they are - * editable. This field should not. - * @type {boolean} - */ - EDITABLE: boolean; - /** - * Used to tell if the field needs to be rendered the next time the block is - * rendered. Image fields are statically sized, and only need to be - * rendered at initialization. - * @type {boolean} - * @protected - */ - public isDirty_: boolean; - } - export namespace FieldImage { - const Y_PADDING: number; - } - import { Size } from "utils/size"; - import { Field } from "field"; -} -declare module "field_textinput" { - export class FieldTextInput extends Field { - /** - * Construct a FieldTextInput from a JSON arg object, - * dereferencing any string table references. - * @param {!Object} options A JSON object with options (text, and spellcheck). - * @return {!FieldTextInput} The new field instance. - * @package - * @nocollapse - */ - static fromJson(options: any): FieldTextInput; - /** - * Class for an editable text field. - * @param {string=} opt_value The initial value of the field. Should cast to a - * string. Defaults to an empty string if null or undefined. - * @param {?Function=} opt_validator A function that is called to validate - * changes to the field's value. Takes in a string & returns a validated - * string, or null to abort the change. - * @param {Object=} opt_config A map of options used to configure the field. - * See the [field creation documentation]{@link - * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/text-input#creation} - * for a list of properties this parameter supports. - * @extends {Field} - * @constructor - * @alias Blockly.FieldTextInput - */ - constructor(opt_value?: string | undefined, opt_validator?: (Function | null) | undefined, opt_config?: any | undefined); - /** - * Allow browser to spellcheck this field. - * @type {boolean} - * @protected - */ - protected spellcheck_: boolean; - /** - * The HTML input element. - * @type {HTMLElement} - */ - htmlInput_: HTMLElement; - /** - * Key down event data. - * @type {?browserEvents.Data} - * @private - */ - private onKeyDownWrapper_; - /** - * Key input event data. - * @type {?browserEvents.Data} - * @private - */ - private onKeyInputWrapper_; - /** - * Whether the field should consider the whole parent block to be its click - * target. - * @type {?boolean} - */ - fullBlockClickTarget_: boolean | null; - /** - * The workspace that this field belongs to. - * @type {?WorkspaceSvg} - * @protected - */ - protected workspace_: WorkspaceSvg | null; - /** - * @override - */ - override configure_(config: any): void; - /** - * @override - */ - override initView(): void; - clickTarget_: any; - /** - * Ensure that the input value casts to a valid string. - * @param {*=} opt_newValue The input value. - * @return {*} A valid string, or null if invalid. - * @protected - */ - protected doClassValidation_(opt_newValue?: any | undefined): any; - /** - * Called by setValue if the text input is not valid. If the field is - * currently being edited it reverts value of the field to the previous - * value while allowing the display text to be handled by the htmlInput_. - * @param {*} _invalidValue The input value that was determined to be invalid. - * This is not used by the text input because its display value is stored on - * the htmlInput_. - * @protected - */ - protected doValueInvalid_(_invalidValue: any): void; - isTextValid_: boolean; - value_: any; - /** - * Called by setValue if the text input is valid. Updates the value of the - * field, and updates the text of the field if it is not currently being - * edited (i.e. handled by the htmlInput_). - * @param {*} newValue The value to be saved. The default validator guarantees - * that this is a string. - * @protected - */ - protected doValueUpdate_(newValue: any): void; - isDirty_: boolean; - /** - * Updates text field to match the colour/style of the block. - * @package - */ - applyColour(): void; - /** - * Updates the colour of the htmlInput given the current validity of the - * field's value. - * @protected - */ - protected render_(): void; - /** - * Set whether this field is spellchecked by the browser. - * @param {boolean} check True if checked. - */ - setSpellcheck(check: boolean): void; - /** - * Show the inline free-text editor on top of the text. - * @param {Event=} _opt_e Optional mouse event that triggered the field to open, - * or undefined if triggered programmatically. - * @param {boolean=} opt_quietInput True if editor should be created without - * focus. Defaults to false. - * @protected - */ - protected showEditor_(_opt_e?: Event | undefined, opt_quietInput?: boolean | undefined): void; - /** - * Create and show a text input editor that is a prompt (usually a popup). - * Mobile browsers have issues with in-line textareas (focus and keyboards). - * @private - */ - private showPromptEditor_; - /** - * Create and show a text input editor that sits directly over the text input. - * @param {boolean} quietInput True if editor should be created without - * focus. - * @private - */ - private showInlineEditor_; - isBeingEdited_: boolean; - /** - * Create the text input editor widget. - * @return {!HTMLElement} The newly created text input editor. - * @protected - */ - protected widgetCreate_(): HTMLElement; - /** - * Closes the editor, saves the results, and disposes of any events or - * DOM-references belonging to the editor. - * @protected - */ - protected widgetDispose_(): void; - /** - * Bind handlers for user input on the text input field's editor. - * @param {!HTMLElement} htmlInput The htmlInput to which event - * handlers will be bound. - * @protected - */ - protected bindInputEvents_(htmlInput: HTMLElement): void; - /** - * Unbind handlers for user input and workspace size changes. - * @protected - */ - protected unbindInputEvents_(): void; - /** - * Handle key down to the editor. - * @param {!Event} e Keyboard event. - * @protected - */ - protected onHtmlInputKeyDown_(e: Event): void; - /** - * Handle a change to the editor. - * @param {!Event} _e Keyboard event. - * @private - */ - private onHtmlInputChange_; - /** - * Set the HTML input value and the field's internal value. The difference - * between this and ``setValue`` is that this also updates the HTML input - * value whilst editing. - * @param {*} newValue New value. - * @protected - */ - protected setEditorValue_(newValue: any): void; - /** - * Resize the editor to fit the text. - * @protected - */ - protected resizeEditor_(): void; - /** - * Returns whether or not the field is tab navigable. - * @return {boolean} True if the field is tab navigable. - * @override - */ - override isTabNavigable(): boolean; - /** - * Use the `getText_` developer hook to override the field's text - * representation. When we're currently editing, return the current HTML value - * instead. Otherwise, return null which tells the field to use the default - * behaviour (which is a string cast of the field's value). - * @return {?string} The HTML value if we're editing, otherwise null. - * @protected - * @override - */ - protected getText_(): string | null; - /** - * Transform the provided value into a text to show in the HTML input. - * Override this method if the field's HTML input representation is different - * than the field's value. This should be coupled with an override of - * `getValueFromEditorText_`. - * @param {*} value The value stored in this field. - * @return {string} The text to show on the HTML input. - * @protected - */ - protected getEditorText_(value: any): string; - /** - * Transform the text received from the HTML input into a value to store - * in this field. - * Override this method if the field's HTML input representation is different - * than the field's value. This should be coupled with an override of - * `getEditorText_`. - * @param {string} text Text received from the HTML input. - * @return {*} The value to store. - * @protected - */ - protected getValueFromEditorText_(text: string): any; - /** - * The default value for this field. - * @type {*} - * @protected - */ - protected DEFAULT_VALUE: any; - /** - * Serializable fields are saved by the XML renderer, non-serializable fields - * are not. Editable fields should also be serializable. - * @type {boolean} - */ - SERIALIZABLE: boolean; - /** - * Mouse cursor style when over the hotspot that initiates the editor. - */ - CURSOR: string; - } - export namespace FieldTextInput { - const BORDERRADIUS: number; - } - import { WorkspaceSvg } from "workspace_svg"; - import { Field } from "field"; -} -declare module "renderers/zelos/marker_svg" { - export class MarkerSvg extends BaseMarkerSvg { - /** - * Class to draw a marker. - * @param {!WorkspaceSvg} workspace The workspace the marker belongs to. - * @param {!ConstantProvider} constants The constants for - * the renderer. - * @param {!Marker} marker The marker to draw. - * @constructor - * @extends {BaseMarkerSvg} - * @alias Blockly.zelos.MarkerSvg - */ - constructor(workspace: WorkspaceSvg, constants: ConstantProvider, marker: Marker); - /** - * Position and display the marker for an input or an output connection. - * @param {!ASTNode} curNode The node to draw the marker for. - * @private - */ - private showWithInputOutput_; - /** - * @override - */ - override showWithOutput_(curNode: any): void; - /** - * @override - */ - override showWithInput_(curNode: any): void; - /** - * Draw a rectangle around the block. - * @param {!ASTNode} curNode The current node of the marker. - */ - showWithBlock_(curNode: ASTNode): void; - /** - * Position the circle we use for input and output connections. - * @param {number} x The x position of the circle. - * @param {number} y The y position of the circle. - * @private - */ - private positionCircle_; - currentMarkerSvg: SVGCircleElement; - /** - * @override - */ - override hide(): void; - /** - * @override - */ - override createDomInternal_(): any; - markerCircle_: SVGCircleElement; - /** - * @override - */ - override applyColour_(curNode: any): void; - } - import { ASTNode } from "keyboard_nav/ast_node"; - import { WorkspaceSvg } from "workspace_svg"; - import { ConstantProvider } from "renderers/common/constants"; - import { Marker } from "keyboard_nav/marker"; - import { MarkerSvg as BaseMarkerSvg } from "renderers/common/marker_svg"; -} -declare module "renderers/zelos/renderer" { - /** - * The zelos renderer. - * @param {string} name The renderer name. - * @package - * @constructor - * @extends {BaseRenderer} - * @alias Blockly.zelos.Renderer - */ - export class Renderer extends BaseRenderer { - constructor(name: any); - /** - * Create a new instance of the renderer's constant provider. - * @return {!ConstantProvider} The constant provider. - * @protected - * @override - */ - protected override makeConstants_(): ConstantProvider; - /** - * Create a new instance of the renderer's render info object. - * @param {!BlockSvg} block The block to measure. - * @return {!RenderInfo} The render info object. - * @protected - * @override - */ - protected override makeRenderInfo_(block: BlockSvg): RenderInfo; - /** - * Create a new instance of the renderer's drawer. - * @param {!BlockSvg} block The block to render. - * @param {!BaseRenderInfo} info An object containing all - * information needed to render this block. - * @return {!Drawer} The drawer. - * @protected - * @override - */ - protected override makeDrawer_(block: BlockSvg, info: BaseRenderInfo): Drawer; - /** - * Create a new instance of the renderer's cursor drawer. - * @param {!WorkspaceSvg} workspace The workspace the cursor belongs to. - * @param {!Marker} marker The marker. - * @return {!MarkerSvg} The object in charge of drawing - * the marker. - * @package - * @override - */ - override makeMarkerDrawer(workspace: WorkspaceSvg, marker: Marker): MarkerSvg; - /** - * Create a new instance of a renderer path object. - * @param {!SVGElement} root The root SVG element. - * @param {!Theme.BlockStyle} style The style object to use for - * colouring. - * @return {!PathObject} The renderer path object. - * @package - * @override - */ - override makePathObject(root: SVGElement, style: Theme.BlockStyle): PathObject; - /** - * @override - */ - override shouldHighlightConnection(conn: any): boolean; - /** - * @override - */ - override getConnectionPreviewMethod(closest: any, local: any, topBlock: any): any; - } - import { ConstantProvider } from "renderers/zelos/constants"; - import { BlockSvg } from "block_svg"; - import { RenderInfo } from "renderers/zelos/info"; - import { RenderInfo as BaseRenderInfo } from "renderers/common/info"; - import { Drawer } from "renderers/zelos/drawer"; - import { WorkspaceSvg } from "workspace_svg"; - import { Marker } from "keyboard_nav/marker"; - import { MarkerSvg } from "renderers/zelos/marker_svg"; - import { Theme } from "theme"; - import { PathObject } from "renderers/zelos/path_object"; - import { Renderer as BaseRenderer } from "renderers/common/block_rendering"; -} -declare module "renderers/zelos/measurables/row_elements" { - /** - * An object containing information about the space a right connection shape - * takes up during rendering. - * @param {!ConstantProvider} constants The rendering - * constants provider. - * @package - * @constructor - * @extends {Measurable} - * @alias Blockly.zelos.RightConnectionShape - */ - export class RightConnectionShape extends Measurable{ - constructor(constants: any); - height: number; - width: number; - } - - import { Measurable } from "renderers/common/block_rendering"; -} -declare module "renderers/zelos/measurables/inputs" { - /** - * An object containing information about the space a statement input takes up - * during rendering - * @param {!ConstantProvider} constants The rendering constants provider. - * @param {!Input} input The statement input to measure and store information - * for. - * @package - * @constructor - * @extends {BaseStatementInput} - * @alias Blockly.zelos.StatementInput - */ - export class StatementInput { - constructor(constants: any, input: any); - height: any; - connectedBottomNextConnection: boolean; - } -} -declare module "renderers/zelos/measurables/top_row" { - /** - * An object containing information about what elements are in the top row of a - * block as well as sizing information for the top row. - * Elements in a top row can consist of corners, hats, spacers, and previous - * connections. - * After this constructor is called, the row will contain all non-spacer - * elements it needs. - * @param {!ConstantProvider} constants The rendering - * constants provider. - * @package - * @constructor - * @extends {BaseTopRow} - * @alias Blockly.zelos.TopRow - */ - export class TopRow extends BaseTopRow { - constructor(constants: any); - /** - * @override - */ - override endsWithElemSpacer(): boolean; - /** - * Render a round corner unless the block has an output connection. - * @override - */ - override hasLeftSquareCorner(block: any): boolean; - /** - * Render a round corner unless the block has an output connection. - * @override - */ - override hasRightSquareCorner(block: any): boolean; - } - import { TopRow as BaseTopRow } from "renderers/common/block_rendering"; -} -declare module "renderers/zelos/info" { - export class RenderInfo extends BaseRenderInfo { - /** - * An object containing all sizing information needed to draw this block. - * - * This measure pass does not propagate changes to the block (although fields - * may choose to rerender when getSize() is called). However, calling it - * repeatedly may be expensive. - * - * @param {!Renderer} renderer The renderer in use. - * @param {!BlockSvg} block The block to measure. - * @constructor - * @package - * @extends {BaseRenderInfo} - * @alias Blockly.zelos.RenderInfo - */ - constructor(renderer: Renderer, block: BlockSvg); - /** - * An object with rendering information about the top row of the block. - * @type {!TopRow} - * @override - */ - override topRow: TopRow; - /** - * An object with rendering information about the bottom row of the block. - * @type {!BottomRow} - * @override - */ - override bottomRow: BottomRow; - /** - * @override - */ - override isInline: boolean; - /** - * Whether the block should be rendered as a multi-line block, either because - * it's not inline or because it has been collapsed. - * @type {boolean} - */ - isMultiRow: boolean; - /** - * Whether or not the block has a statement input in one of its rows. - * @type {boolean} - */ - hasStatementInput: boolean; - /** - * An object with rendering information about the right connection shape. - * @type {RightConnectionShape} - */ - rightSide: RightConnectionShape; - /** - * Get the block renderer in use. - * @return {!Renderer} The block renderer in use. - * @package - */ - getRenderer(): Renderer; - /** - * @override - */ - override measure(): void; - /** - * @override - */ - override shouldStartNewRow_(input: any, lastInput: any): boolean; - /** - * @override - */ - override getDesiredRowWidth_(row: any): any; - /** - * @override - */ - override getInRowSpacing_(prev: any, next: any): any; - /** - * @override - */ - override getSpacerRowHeight_(prev: any, next: any): any; - /** - * @override - */ - override getSpacerRowWidth_(prev: any, next: any): number; - /** - * @override - */ - override getElemCenterline_(row: any, elem: any): any; - /** - * @override - */ - override addInput_(input: any, activeRow: any): void; - /** - * @override - */ - override addAlignmentPadding_(row: any, missingSpace: any): void; - /** - * Adjust the x position of fields to bump all non-label fields in the first row - * past the notch position. This must be called before ``computeBounds`` is - * called. - * @protected - */ - protected adjustXPosition_(): void; - /** - * Finalize the output connection info. In particular, set the height of the - * output connection to match that of the block. For the right side, add a - * right connection shape element and have it match the dimensions of the - * output connection. - * @protected - */ - protected finalizeOutputConnection_(): void; - height: number; - startX: any; - /** - * Finalize horizontal alignment of elements on the block. In particular, - * reduce the implicit spacing created by the left and right output connection - * shapes by adding setting negative spacing onto the leftmost and rightmost - * spacers. - * @protected - */ - protected finalizeHorizontalAlignment_(): void; - /** - * Calculate the spacing to reduce the left and right edges by based on the - * outer and inner connection shape. - * @param {Measurable} elem The first or last element on - * a block. - * @return {number} The amount of spacing to reduce the first or last spacer. - * @protected - */ - protected getNegativeSpacing_(elem: Measurable): number; - /** - * Finalize vertical alignment of rows on a block. In particular, reduce the - * implicit spacing when a non-shadow block is connected to any of an input - * row's inline inputs. - * @protected - */ - protected finalizeVerticalAlignment_(): void; - /** - * @override - */ - override finalize_(): void; - } - import { TopRow } from "renderers/zelos/measurables/top_row"; - import { BottomRow } from "renderers/zelos/measurables/bottom_row"; - import { RightConnectionShape } from "renderers/zelos/measurables/row_elements"; - import { Renderer } from "renderers/zelos/renderer"; - import { Measurable } from "renderers/measurables/base"; - import { BlockSvg } from "block_svg"; - import { RenderInfo as BaseRenderInfo } from "renderers/common/block_rendering"; -} -declare module "renderers/zelos/drawer" { - export class Drawer extends BaseDrawer { - /** - * An object that draws a block based on the given rendering information. * @param {!BlockSvg} block The block to render. * @param {!RenderInfo} info An object containing all * information needed to render this block. * @package - * @constructor - * @extends {BaseDrawer} - * @alias Blockly.zelos.Drawer */ constructor(block: BlockSvg, info: RenderInfo); - /** - * @override - */ - override draw(): void; - /** - * @override - */ - override drawOutline_(): void; - /** - * @override - */ - override drawLeft_(): void; - /** - * Add steps for the right side of a row that does not have value or - * statement input connections. - * @param {!Row} row The row to draw the - * side of. - * @protected - */ - protected drawRightSideRow_(row: Row): void; - /** - * Add steps to draw the right side of an output with a dynamic connection. - * @protected - */ - protected drawRightDynamicConnection_(): void; - /** - * Add steps to draw the left side of an output with a dynamic connection. - * @protected - */ - protected drawLeftDynamicConnection_(): void; - /** - * Add steps to draw a flat top row. - * @protected - */ - protected drawFlatTop_(): void; - /** - * Add steps to draw a flat bottom row. - * @protected - */ - protected drawFlatBottom_(): void; - /** - * @override - */ - override drawInlineInput_(input: any): void; - /** - * @override - */ - override drawStatementInput_(row: any): void; } - import { Row } from "renderers/measurables/row"; - import { BlockSvg } from "block_svg"; - import { RenderInfo } from "renderers/zelos/info"; - import { Drawer as BaseDrawer } from "renderers/common/drawer"; + import { Drawer as BaseDrawer } from "core/renderers/common/drawer"; + import { BlockSvg } from "core/block_svg"; + import { RenderInfo } from "core/renderers/minimalist/info"; } -declare module "renderers/zelos/zelos" { - import { BottomRow } from "renderers/zelos/measurables/bottom_row"; - import { ConstantProvider } from "renderers/zelos/constants"; - import { Drawer } from "renderers/zelos/drawer"; - import { MarkerSvg } from "renderers/zelos/marker_svg"; - import { PathObject } from "renderers/zelos/path_object"; - import { RenderInfo } from "renderers/zelos/info"; - import { Renderer } from "renderers/zelos/renderer"; - import { RightConnectionShape } from "renderers/zelos/measurables/row_elements"; - import { StatementInput } from "renderers/zelos/measurables/inputs"; - import { TopRow } from "renderers/zelos/measurables/top_row"; +declare module "core/renderers/minimalist/minimalist" { + import { ConstantProvider } from "core/renderers/minimalist/constants"; + import { Drawer } from "core/renderers/minimalist/drawer"; + import { RenderInfo } from "core/renderers/minimalist/info"; + import { Renderer } from "core/renderers/minimalist/renderer"; + export { ConstantProvider, Drawer, RenderInfo, Renderer }; +} +declare module "core/serialization/variables" { + /** + * Represents the state of a given variable. + */ + export type State = { + name: string; + id: string; + type: (string | undefined); + }; + /** + * Represents the state of a given variable. + * @typedef {{ + * name: string, + * id: string, + * type: (string|undefined) + * }} + * @alias Blockly.serialization.variables.State + */ + export let State: any; +} +declare module "core/serialization/workspaces" { + /** + * Returns the state of the workspace as a plain JavaScript object. + * @param {!Workspace} workspace The workspace to serialize. + * @return {!Object} The serialized state of the workspace. + * @alias Blockly.serialization.workspaces.save + */ + export function save(workspace: Workspace): { + [x: string]: any; + }; + /** + * Loads the variable represented by the given state into the given workspace. + * @param {!Object} state The state of the workspace to deserialize + * into the workspace. + * @param {!Workspace} workspace The workspace to add the new state to. + * @param {{recordUndo: (boolean|undefined)}=} param1 + * recordUndo: If true, events triggered by this function will be undo-able + * by the user. False by default. + * @alias Blockly.serialization.workspaces.load + */ + export function load(state: { + [x: string]: any; + }, workspace: Workspace, { recordUndo }?: { + recordUndo: (boolean | undefined); + } | undefined): void; + import { Workspace } from "core/workspace"; +} +declare module "core/renderers/thrasos/renderer" { + /** + * The thrasos renderer. + * @extends {BaseRenderer} + * @alias Blockly.thrasos.Renderer + */ + export class Renderer extends blockRendering.Renderer { + } + import * as blockRendering from "core/renderers/common/block_rendering"; +} +declare module "core/renderers/thrasos/info" { + /** + * An object containing all sizing information needed to draw this block. + * + * This measure pass does not propagate changes to the block (although fields + * may choose to rerender when getSize() is called). However, calling it + * repeatedly may be expensive. + * @extends {BaseRenderInfo} + * @alias Blockly.thrasos.RenderInfo + */ + export class RenderInfo extends BaseRenderInfo { + /** + * @param {!Renderer} renderer The renderer in use. + * @param {!BlockSvg} block The block to measure. + * @package + */ + constructor(renderer: Renderer, block: BlockSvg); + } + import { RenderInfo as BaseRenderInfo } from "core/renderers/common/info"; + import { Renderer } from "core/renderers/thrasos/renderer"; + import { BlockSvg } from "core/block_svg"; +} +declare module "core/renderers/thrasos/thrasos" { + import { RenderInfo } from "core/renderers/thrasos/info"; + import { Renderer } from "core/renderers/thrasos/renderer"; + export { RenderInfo, Renderer }; +} +declare module "core/renderers/zelos/zelos" { + import { BottomRow } from "core/renderers/zelos/measurables/bottom_row"; + import { ConstantProvider } from "core/renderers/zelos/constants"; + import { Drawer } from "core/renderers/zelos/drawer"; + import { MarkerSvg } from "core/renderers/zelos/marker_svg"; + import { PathObject } from "core/renderers/zelos/path_object"; + import { RenderInfo } from "core/renderers/zelos/info"; + import { Renderer } from "core/renderers/zelos/renderer"; + import { RightConnectionShape } from "core/renderers/zelos/measurables/row_elements"; + import { StatementInput } from "core/renderers/zelos/measurables/inputs"; + import { TopRow } from "core/renderers/zelos/measurables/top_row"; export { BottomRow, ConstantProvider, Drawer, MarkerSvg, PathObject, RenderInfo, Renderer, RightConnectionShape, StatementInput, TopRow }; } -declare module "toolbox/collapsible_category" { - export class CollapsibleToolboxCategory extends ToolboxCategory { - /** - * Class for a category in a toolbox that can be collapsed. - * @param {!toolbox.CategoryInfo} categoryDef The information needed - * to create a category in the toolbox. - * @param {!IToolbox} toolbox The parent toolbox for the category. - * @param {ICollapsibleToolboxItem=} opt_parent The parent category or null if - * the category does not have a parent. - * @constructor - * @extends {ToolboxCategory} - * @implements {ICollapsibleToolboxItem} - * @alias Blockly.CollapsibleToolboxCategory - */ - constructor(categoryDef: toolbox.CategoryInfo, toolbox: IToolbox, opt_parent?: ICollapsibleToolboxItem | undefined); +declare module "core/toolbox/collapsible_category" { + /** + * Class for a category in a toolbox that can be collapsed. + * @implements {ICollapsibleToolboxItem} + * @alias Blockly.CollapsibleToolboxCategory + */ + export class CollapsibleToolboxCategory extends ToolboxCategory implements ICollapsibleToolboxItem { /** * Container for any child categories. - * @type {?Element} + * @type {?HTMLDivElement} * @protected */ - protected subcategoriesDiv_: Element | null; + protected subcategoriesDiv_: HTMLDivElement | null; /** * Whether or not the category should display its subcategories. * @type {boolean} @@ -22275,16 +23488,7 @@ declare module "toolbox/collapsible_category" { * @type {!Array} * @protected */ - protected toolboxItems_: Array; - /** - * @override - */ - override makeDefaultCssConfig_(): any; - /** - * @override - */ - override parseContents_(categoryDef: any): void; - flyoutItems_: any; + protected toolboxItems_: Array<() => void>; /** * Creates a toolbox item and adds it to the list of toolbox items. * @param {!toolbox.ToolboxItemInfo} itemDef The information needed @@ -22292,36 +23496,19 @@ declare module "toolbox/collapsible_category" { * @private */ private createToolboxItem_; - /** - * @override - */ - override init(): void; - /** - * @override - */ - override createDom_(): any; - /** - * @override - */ - override createIconDom_(): HTMLSpanElement; /** * Create the DOM for all subcategories. * @param {!Array} subcategories The subcategories. - * @return {!Element} The div holding all the subcategories. + * @return {!HTMLDivElement} The div holding all the subcategories. * @protected */ - protected createSubCategoriesDom_(subcategories: Array): Element; + protected createSubCategoriesDom_(subcategories: Array<() => void>): HTMLDivElement; /** * Opens or closes the current category. * @param {boolean} isExpanded True to expand the category, false to close. * @public */ public setExpanded(isExpanded: boolean): void; - /** - * @override - */ - override setVisible_(isVisible: any): void; - isHidden_: boolean; /** * Whether the category is expanded to show its child subcategories. * @return {boolean} True if the toolbox item shows its children, false if it @@ -22329,28 +23516,16 @@ declare module "toolbox/collapsible_category" { * @public */ public isExpanded(): boolean; - /** - * @override - */ - override isCollapsible(): boolean; - /** - * @override - */ - override onClick(_e: any): void; /** * Toggles whether or not the category is expanded. * @public */ public toggleExpanded(): void; - /** - * @override - */ - override getDiv(): any; /** * Gets any children toolbox items. (ex. Gets the subcategories) * @return {!Array} The child toolbox items. */ - getChildToolboxItems(): Array; + getChildToolboxItems(): Array<() => void>; } export namespace CollapsibleToolboxCategory { const registrationName: string; @@ -22370,13 +23545,15 @@ declare module "toolbox/collapsible_category" { contents: string | null; }; } - import { IToolboxItem } from "interfaces/i_toolbox_item"; - import * as toolbox from "utils/toolbox"; - import { IToolbox } from "interfaces/i_toolbox"; - import { ICollapsibleToolboxItem } from "interfaces/i_collapsible_toolbox_item"; - import { ToolboxCategory } from "toolbox/category"; + import { ICollapsibleToolboxItem } from "core/interfaces/i_collapsible_toolbox_item"; + import { ToolboxCategory } from "core/toolbox/category"; } -declare module "field_angle" { +declare module "core/field_angle" { + /** + * Class for an editable angle field. + * @extends {FieldTextInput} + * @alias Blockly.FieldAngle + */ export class FieldAngle extends FieldTextInput { /** * Construct a FieldAngle from a JSON arg object. @@ -22384,24 +23561,24 @@ declare module "field_angle" { * @return {!FieldAngle} The new field instance. * @package * @nocollapse + * @override */ - static fromJson(options: any): FieldAngle; + static override fromJson(options: Object): FieldAngle; /** - * Class for an editable angle field. - * @param {string|number=} opt_value The initial value of the field. Should cast - * to a number. Defaults to 0. + * @param {(string|number|!Sentinel)=} opt_value The initial value of + * the field. Should cast to a number. Defaults to 0. + * Also accepts Field.SKIP_SETUP if you wish to skip setup (only used by + * subclasses that want to handle configuration and setting the field + * value after their own constructors have run). * @param {Function=} opt_validator A function that is called to validate - * changes to the field's value. Takes in a number & returns a - * validated number, or null to abort the change. + * changes to the field's value. Takes in a number & returns a + * validated number, or null to abort the change. * @param {Object=} opt_config A map of options used to configure the field. - * See the [field creation documentation]{@link - * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/angle#creation} - * for a list of properties this parameter supports. - * @extends {FieldTextInput} - * @constructor - * @alias Blockly.FieldAngle + * See the [field creation documentation]{@link + * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/angle#creation} + * for a list of properties this parameter supports. */ - constructor(opt_value?: (string | number) | undefined, opt_validator?: Function | undefined, opt_config?: any | undefined); + constructor(opt_value?: (string | number | Sentinel) | undefined, opt_validator?: Function | undefined, opt_config?: Object | undefined); /** * Should the angle increase as the angle picker is moved clockwise (true) * or counterclockwise (false) @@ -22447,6 +23624,12 @@ declare module "field_angle" { * @type {?SVGElement} */ line_: SVGElement | null; + /** + * The degree symbol for this field. + * @type {SVGTSpanElement} + * @protected + */ + protected symbol_: SVGTSpanElement; /** * Wrapper click event data. * @type {?browserEvents.Data} @@ -22465,32 +23648,6 @@ declare module "field_angle" { * @private */ private moveSurfaceWrapper_; - /** - * Configure the field based on the given map of options. - * @param {!Object} config A map of options to configure the field based on. - * @protected - * @override - */ - override configure_(config: any): void; - /** - * Create the block UI for this field. - * @package - */ - initView(): void; - symbol_: SVGTSpanElement; - /** - * Updates the graph when the field rerenders. - * @protected - * @override - */ - protected override render_(): void; - /** - * Create and show the angle field's editor. - * @param {Event=} opt_e Optional mouse event that triggered the field to open, - * or undefined if triggered programmatically. - * @protected - */ - protected showEditor_(opt_e?: Event | undefined): void; /** * Create the angle dropdown editor. * @private @@ -22525,21 +23682,6 @@ declare module "field_angle" { * @private */ private updateGraph_; - /** - * Handle key down to the editor. - * @param {!Event} e Keyboard event. - * @protected - * @override - */ - protected override onHtmlInputKeyDown_(e: Event): void; - /** - * Ensure that the input value is a valid angle. - * @param {*=} opt_newValue The input value. - * @return {?number} A valid angle, or null if invalid. - * @protected - * @override - */ - protected override doClassValidation_(opt_newValue?: any | undefined): number | null; /** * Wraps the value so that it is in the range (-360 + wrap, wrap). * @param {number} value The value to wrap. @@ -22547,18 +23689,6 @@ declare module "field_angle" { * @private */ private wrapValue_; - /** - * The default value for this field. - * @type {*} - * @protected - */ - protected DEFAULT_VALUE: any; - /** - * Serializable fields are saved by the XML renderer, non-serializable fields - * are not. Editable fields should also be serializable. - * @type {boolean} - */ - SERIALIZABLE: boolean; } export namespace FieldAngle { const ROUND: number; @@ -22568,24 +23698,13 @@ declare module "field_angle" { const WRAP: number; const RADIUS: number; } - - import { FieldTextInput } from "field_textinput"; + import { FieldTextInput } from "core/field_textinput"; + import { Sentinel } from "core/utils/sentinel"; } -declare module "field_checkbox" { +declare module "core/field_checkbox" { /** * Class for a checkbox field. - * @param {string|boolean=} opt_value The initial value of the field. Should - * either be 'TRUE', 'FALSE' or a boolean. Defaults to 'FALSE'. - * @param {Function=} opt_validator A function that is called to validate - * changes to the field's value. Takes in a value ('TRUE' or 'FALSE') & - * returns a validated value ('TRUE' or 'FALSE'), or null to abort the - * change. - * @param {Object=} opt_config A map of options used to configure the field. - * See the [field creation documentation]{@link - * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/checkbox#creation} - * for a list of properties this parameter supports. * @extends {Field} - * @constructor * @alias Blockly.FieldCheckbox */ export class FieldCheckbox extends Field { @@ -22596,84 +23715,42 @@ declare module "field_checkbox" { * @package * @nocollapse */ - static fromJson(options: any): FieldCheckbox; - constructor(opt_value: any, opt_validator: any, opt_config: any); + static fromJson(options: Object): FieldCheckbox; + /** + * @param {(string|boolean|!Sentinel)=} opt_value The initial value of + * the field. Should either be 'TRUE', 'FALSE' or a boolean. Defaults to + * 'FALSE'. + * Also accepts Field.SKIP_SETUP if you wish to skip setup (only used by + * subclasses that want to handle configuration and setting the field + * value after their own constructors have run). + * @param {Function=} opt_validator A function that is called to validate + * changes to the field's value. Takes in a value ('TRUE' or 'FALSE') & + * returns a validated value ('TRUE' or 'FALSE'), or null to abort the + * change. + * @param {Object=} opt_config A map of options used to configure the field. + * See the [field creation documentation]{@link + * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/checkbox#creation} + * for a list of properties this parameter supports. + */ + constructor(opt_value?: (string | boolean | Sentinel) | undefined, opt_validator?: Function | undefined, opt_config?: Object | undefined); /** * Character for the check mark. Used to apply a different check mark * character to individual fields. - * @type {?string} + * @type {string} * @private */ private checkChar_; - /** - * Configure the field based on the given map of options. - * @param {!Object} config A map of options to configure the field based on. - * @protected - * @override - */ - protected override configure_(config: any): void; - /** - * Saves this field's value. - * @return {*} The boolean value held by this field. - * @override - * @package - */ - override saveState(): any; - /** - * Create the block UI for this checkbox. - * @package - */ - initView(): void; - /** - * @override - */ - override render_(): void; - /** - * @override - */ - override getDisplayText_(): string; /** * Set the character used for the check mark. * @param {?string} character The character to use for the check mark, or * null to use the default. */ setCheckCharacter(character: string | null): void; - /** - * Toggle the state of the checkbox on click. - * @protected - */ - protected showEditor_(): void; - /** - * Ensure that the input value is valid ('TRUE' or 'FALSE'). - * @param {*=} opt_newValue The input value. - * @return {?string} A valid value ('TRUE' or 'FALSE), or null if invalid. - * @protected - */ - protected doClassValidation_(opt_newValue?: any | undefined): string | null; - /** - * Update the value of the field, and update the checkElement. - * @param {*} newValue The value to be saved. The default validator guarantees - * that this is a either 'TRUE' or 'FALSE'. - * @protected - */ - protected doValueUpdate_(newValue: any): void; - value_: boolean; - /** - * Get the value of this field, either 'TRUE' or 'FALSE'. - * @return {string} The value of this field. - */ - getValue(): string; /** * Get the boolean value of this field. * @return {boolean} The boolean value of this field. */ getValueBoolean(): boolean; - /** - * Get the text of this field. Used when the block is collapsed. - * @return {string} Text representing the value of this field - * ('true' or 'false'). - */ - getText(): string; /** * Convert a value into a pure boolean. * @@ -22684,30 +23761,19 @@ declare module "field_checkbox" { * @private */ private convertValueToBool_; - /** - * The default value for this field. - * @type {*} - * @protected - */ - protected DEFAULT_VALUE: any; - /** - * Serializable fields are saved by the XML renderer, non-serializable fields - * are not. Editable fields should also be serializable. - * @type {boolean} - */ - SERIALIZABLE: boolean; - /** - * Mouse cursor style when over the hotspot that initiates editability. - */ - CURSOR: string; } export namespace FieldCheckbox { const CHECK_CHAR: string; } - - import { Field } from "field"; + import { Field } from "core/field"; + import { Sentinel } from "core/utils/sentinel"; } -declare module "field_colour" { +declare module "core/field_colour" { + /** + * Class for a colour input field. + * @extends {Field} + * @alias Blockly.FieldColour + */ export class FieldColour extends Field { /** * Construct a FieldColour from a JSON arg object. @@ -22716,24 +23782,24 @@ declare module "field_colour" { * @package * @nocollapse */ - static fromJson(options: any): FieldColour; + static fromJson(options: Object): FieldColour; /** - * Class for a colour input field. - * @param {string=} opt_value The initial value of the field. Should be in - * '#rrggbb' format. Defaults to the first value in the default colour array. + * @param {(string|!Sentinel)=} opt_value The initial value of the + * field. Should be in '#rrggbb' format. Defaults to the first value in + * the default colour array. + * Also accepts Field.SKIP_SETUP if you wish to skip setup (only used by + * subclasses that want to handle configuration and setting the field + * value after their own constructors have run). * @param {Function=} opt_validator A function that is called to validate - * changes to the field's value. Takes in a colour string & returns a - * validated colour string ('#rrggbb' format), or null to abort the - * change.Blockly. + * changes to the field's value. Takes in a colour string & returns a + * validated colour string ('#rrggbb' format), or null to abort the + * change.Blockly. * @param {Object=} opt_config A map of options used to configure the field. - * See the [field creation documentation]{@link - * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/colour} - * for a list of properties this parameter supports. - * @extends {Field} - * @constructor - * @alias Blockly.FieldColour + * See the [field creation documentation]{@link + * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/colour} + * for a list of properties this parameter supports. */ - constructor(opt_value?: string | undefined, opt_validator?: Function | undefined, opt_config?: any | undefined); + constructor(opt_value?: (string | Sentinel) | undefined, opt_validator?: Function | undefined, opt_config?: Object | undefined); /** * The field's colour picker element. * @type {?Element} @@ -22777,46 +23843,25 @@ declare module "field_colour" { */ private onKeyDownWrapper_; /** - * Configure the field based on the given map of options. - * @param {!Object} config A map of options to configure the field based on. - * @protected - * @override + * Array of colours used by this field. If null, use the global list. + * @type {Array} + * @private */ - protected override configure_(config: any): void; - colours_: Array; - titles_: Array; - columns_: number; + private colours_; /** - * Create the block UI for this colour field. - * @package + * Array of colour tooltips used by this field. If null, use the global + * list. + * @type {Array} + * @private */ - initView(): void; - size_: Size; - clickTarget_: any; + private titles_; /** - * @override + * Number of colour columns used by this field. If 0, use the global + * setting. By default use the global constants for columns. + * @type {number} + * @private */ - override applyColour(): void; - /** - * Ensure that the input value is a valid colour. - * @param {*=} opt_newValue The input value. - * @return {?string} A valid colour, or null if invalid. - * @protected - */ - protected doClassValidation_(opt_newValue?: any | undefined): string | null; - /** - * Update the value of this colour field, and update the displayed colour. - * @param {*} newValue The value to be saved. The default validator guarantees - * that this is a colour in '#rrggbb' format. - * @protected - */ - protected doValueUpdate_(newValue: any): void; - value_: any; - /** - * Get the text for this field. Used when the block is collapsed. - * @return {string} Text representing the value of this field. - */ - getText(): string; + private columns_; /** * Set a custom colour grid for this field. * @param {Array} colours Array of colours for this block, @@ -22833,11 +23878,6 @@ declare module "field_colour" { * @return {!FieldColour} Returns itself (for method chaining). */ setColumns(columns: number): FieldColour; - /** - * Create and show the colour field's editor. - * @protected - */ - protected showEditor_(): void; /** * Handle a click on a colour cell. * @param {!MouseEvent} e Mouse event. @@ -22898,352 +23938,41 @@ declare module "field_colour" { * @private */ private dropdownDispose_; - /** - * Serializable fields are saved by the XML renderer, non-serializable fields - * are not. Editable fields should also be serializable. - * @type {boolean} - */ - SERIALIZABLE: boolean; - /** - * Mouse cursor style when over the hotspot that initiates the editor. - */ - CURSOR: string; - /** - * Used to tell if the field needs to be rendered the next time the block is - * rendered. Colour fields are statically sized, and only need to be - * rendered at initialization. - * @type {boolean} - * @protected - */ - isDirty_: boolean; - /** - * The default value for this field. - * @type {*} - * @protected - */ - protected DEFAULT_VALUE: any; } export namespace FieldColour { const COLOURS: Array; const TITLES: Array; const COLUMNS: number; } - import { Size } from "utils/size"; - import { Field } from "field"; + import { Field } from "core/field"; + import { Sentinel } from "core/utils/sentinel"; } -declare module "field_dropdown" { - export class FieldDropdown extends Field { - /** - * Construct a FieldDropdown from a JSON arg object. - * @param {!Object} options A JSON object with options (options). - * @return {!FieldDropdown} The new field instance. - * @package - * @nocollapse - */ - static fromJson(options: any): FieldDropdown; - /** - * Use the calculated prefix and suffix lengths to trim all of the options in - * the given array. - * @param {!Array} options Array of option tuples: - * (human-readable text or image, language-neutral name). - * @param {number} prefixLength The length of the common prefix. - * @param {number} suffixLength The length of the common suffix - * @return {!Array} A new array with all of the option text trimmed. - */ - static applyTrim_(options: Array, prefixLength: number, suffixLength: number): Array; - /** - * Class for an editable dropdown field. - * @param {(!Array|!Function)} menuGenerator A non-empty array of - * options for a dropdown list, or a function which generates these options. - * @param {Function=} opt_validator A function that is called to validate - * changes to the field's value. Takes in a language-neutral dropdown - * option & returns a validated language-neutral dropdown option, or null to - * abort the change. - * @param {Object=} opt_config A map of options used to configure the field. - * See the [field creation documentation]{@link - * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/dropdown#creation} - * for a list of properties this parameter supports. - * @extends {Field} - * @constructor - * @throws {TypeError} If `menuGenerator` options are incorrectly structured. - * @alias Blockly.FieldDropdown - */ - constructor(menuGenerator: (Array | Function), opt_validator?: Function | undefined, opt_config?: any | undefined); - /** - * An array of options for a dropdown list, - * or a function which generates these options. - * @type {(!Array| - * !function(this:FieldDropdown): !Array)} - * @protected - */ - protected menuGenerator_: any[][] | ((this: FieldDropdown) => Array); - /** - * A cache of the most recently generated options. - * @type {Array>} - * @private - */ - private generatedOptions_; - /** - * The prefix field label, of common words set after options are trimmed. - * @type {?string} - * @package - */ - prefixField: string | null; - /** - * The suffix field label, of common words set after options are trimmed. - * @type {?string} - * @package - */ - suffixField: string | null; - /** - * The currently selected option. The field is initialized with the - * first option selected. - * @type {!Object} - * @private - */ - private selectedOption_; - /** - * A reference to the currently selected menu item. - * @type {?MenuItem} - * @private - */ - private selectedMenuItem_; - /** - * The dropdown menu. - * @type {?Menu} - * @protected - */ - protected menu_: Menu | null; - /** - * SVG image element if currently selected option is an image, or null. - * @type {?SVGImageElement} - * @private - */ - private imageElement_; - /** - * Tspan based arrow element. - * @type {?SVGTSpanElement} - * @private - */ - private arrow_; - /** - * SVG based arrow element. - * @type {?SVGElement} - * @private - */ - private svgArrow_; - /** - * Sets the field's value based on the given XML element. Should only be - * called by Blockly.Xml. - * @param {!Element} fieldElement The element containing info about the - * field's state. - * @package - */ - fromXml(fieldElement: Element): void; - /** - * Sets the field's value based on the given state. - * @param {*} state The state to apply to the dropdown field. - * @override - * @package - */ - override loadState(state: any): void; - /** - * Create the block UI for this dropdown. - * @package - */ - initView(): void; - clickTarget_: any; - /** - * Whether or not the dropdown should add a border rect. - * @return {boolean} True if the dropdown field should add a border rect. - * @protected - */ - protected shouldAddBorderRect_(): boolean; - /** - * Create a tspan based arrow. - * @protected - */ - protected createTextArrow_(): void; - /** - * Create an SVG based arrow. - * @protected - */ - protected createSVGArrow_(): void; - /** - * Create a dropdown menu under the text. - * @param {Event=} opt_e Optional mouse event that triggered the field to open, - * or undefined if triggered programmatically. - * @protected - */ - protected showEditor_(opt_e?: Event | undefined): void; - /** - * Create the dropdown editor. - * @private - */ - private dropdownCreate_; - /** - * Disposes of events and DOM-references belonging to the dropdown editor. - * @private - */ - private dropdownDispose_; - /** - * Handle an action in the dropdown menu. - * @param {!MenuItem} menuItem The MenuItem selected within menu. - * @private - */ - private handleMenuActionEvent_; - /** - * Handle the selection of an item in the dropdown menu. - * @param {!Menu} menu The Menu component clicked. - * @param {!MenuItem} menuItem The MenuItem selected within menu. - * @protected - */ - protected onItemSelected_(menu: Menu, menuItem: MenuItem): void; - /** - * Factor out common words in statically defined options. - * Create prefix and/or suffix labels. - * @private - */ - private trimOptions_; - /** - * @return {boolean} True if the option list is generated by a function. - * Otherwise false. - */ - isOptionListDynamic(): boolean; - /** - * Return a list of the options for this dropdown. - * @param {boolean=} opt_useCache For dynamic options, whether or not to use the - * cached options or to re-generate them. - * @return {!Array} A non-empty array of option tuples: - * (human-readable text or image, language-neutral name). - * @throws {TypeError} If generated options are incorrectly structured. - */ - getOptions(opt_useCache?: boolean | undefined): Array; - /** - * Ensure that the input value is a valid language-neutral option. - * @param {*=} opt_newValue The input value. - * @return {?string} A valid language-neutral option, or null if invalid. - * @protected - */ - protected doClassValidation_(opt_newValue?: any | undefined): string | null; - /** - * Update the value of this dropdown field. - * @param {*} newValue The value to be saved. The default validator guarantees - * that this is one of the valid dropdown options. - * @protected - */ - protected doValueUpdate_(newValue: any): void; - /** - * Updates the dropdown arrow to match the colour/style of the block. - * @package - */ - applyColour(): void; - /** - * Draws the border with the correct width. - * @protected - */ - protected render_(): void; - /** - * Renders the selected option, which must be an image. - * @param {!FieldDropdown.ImageProperties} imageJson Selected - * option that must be an image. - * @private - */ - private renderSelectedImage_; - /** - * Renders the selected option, which must be text. - * @private - */ - private renderSelectedText_; - /** - * Position a drop-down arrow at the appropriate location at render-time. - * @param {number} x X position the arrow is being rendered at, in px. - * @param {number} y Y position the arrow is being rendered at, in px. - * @return {number} Amount of space the arrow is taking up, in px. - * @private - */ - private positionSVGArrow_; - /** - * Use the `getText_` developer hook to override the field's text - * representation. Get the selected option text. If the selected option is an - * image we return the image alt text. - * @return {?string} Selected option text. - * @protected - * @override - */ - protected getText_(): string | null; - /** - * Serializable fields are saved by the XML renderer, non-serializable fields - * are not. Editable fields should also be serializable. - * @type {boolean} - */ - SERIALIZABLE: boolean; - /** - * Mouse cursor style when over the hotspot that initiates the editor. - */ - CURSOR: string; - } - export namespace FieldDropdown { - const CHECKMARK_OVERHANG: number; - const MAX_MENU_HEIGHT_VH: number; - const ARROW_CHAR: string; - /** - * Dropdown image properties. - */ - type ImageProperties = { - src: string; - alt: string; - width: number; - height: number; - }; - } - import { Menu } from "menu"; - import { MenuItem } from "menuitem"; - import { Field } from "field"; -} -declare module "field_label_serializable" { +declare module "core/field_label_serializable" { /** * Class for a non-editable, serializable text field. - * @param {*} opt_value The initial value of the field. Should cast to a - * string. Defaults to an empty string if null or undefined. - * @param {string=} opt_class Optional CSS class for the field's text. - * @param {Object=} opt_config A map of options used to configure the field. - * See the [field creation documentation]{@link - * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/label-serializable#creation} - * for a list of properties this parameter supports. * @extends {FieldLabel} - * @constructor - * * @alias Blockly.FieldLabelSerializable */ export class FieldLabelSerializable extends FieldLabel { /** - * Construct a FieldLabelSerializable from a JSON arg object, - * dereferencing any string table references. - * @param {!Object} options A JSON object with options (text, and class). - * @return {!FieldLabelSerializable} The new field instance. - * @package - * @nocollapse + * @param {string=} opt_value The initial value of the field. Should cast to a + * string. Defaults to an empty string if null or undefined. + * @param {string=} opt_class Optional CSS class for the field's text. + * @param {Object=} opt_config A map of options used to configure the field. + * See the [field creation documentation]{@link + * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/label-serializable#creation} + * for a list of properties this parameter supports. */ - static fromJson(options: any): FieldLabelSerializable; - constructor(opt_value: any, opt_class: any, opt_config: any); - /** - * Editable fields usually show some sort of UI indicating they are - * editable. This field should not. - * @type {boolean} - */ - EDITABLE: boolean; - /** - * Serializable fields are saved by the XML renderer, non-serializable fields - * are not. This field should be serialized, but only edited programmatically. - * @type {boolean} - */ - SERIALIZABLE: boolean; + constructor(opt_value?: string | undefined, opt_class?: string | undefined, opt_config?: Object | undefined); } - - import { FieldLabel } from "field_label"; + import { FieldLabel } from "core/field_label"; } -declare module "field_multilineinput" { +declare module "core/field_multilineinput" { + /** + * Class for an editable text area field. + * @extends {FieldTextInput} + * @alias Blockly.FieldMultilineInput + */ export class FieldMultilineInput extends FieldTextInput { /** * Construct a FieldMultilineInput from a JSON arg object, @@ -23252,25 +23981,26 @@ declare module "field_multilineinput" { * @return {!FieldMultilineInput} The new field instance. * @package * @nocollapse + * @override */ - static fromJson(options: any): FieldMultilineInput; + static override fromJson(options: Object): FieldMultilineInput; /** - * Class for an editable text area field. - * @param {string=} opt_value The initial content of the field. Should cast to a - * string. Defaults to an empty string if null or undefined. + * @param {(string|!Sentinel)=} opt_value The initial content of the + * field. Should cast to a string. Defaults to an empty string if null or + * undefined. + * Also accepts Field.SKIP_SETUP if you wish to skip setup (only used by + * subclasses that want to handle configuration and setting the field + * value after their own constructors have run). * @param {Function=} opt_validator An optional function that is called * to validate any constraints on what the user entered. Takes the new * text as an argument and returns either the accepted text, a replacement * text, or null to abort the change. * @param {Object=} opt_config A map of options used to configure the field. - * See the [field creation documentation]{@link - * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/multiline-text-input#creation} - * for a list of properties this parameter supports. - * @extends {FieldTextInput} - * @constructor - * @alias Blockly.FieldMultilineInput + * See the [field creation documentation]{@link + * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/multiline-text-input#creation} + * for a list of properties this parameter supports. */ - constructor(opt_value?: string | undefined, opt_validator?: Function | undefined, opt_config?: any | undefined); + constructor(opt_value?: (string | Sentinel) | undefined, opt_validator?: Function | undefined, opt_config?: Object | undefined); /** * The SVG group element that will contain a text element for each text row * when initialized. @@ -23290,89 +24020,6 @@ declare module "field_multilineinput" { * @protected */ protected isOverflowedY_: boolean; - /** - * @override - */ - override configure_(config: any): void; - /** - * Serializes this field's value to XML. Should only be called by Blockly.Xml. - * @param {!Element} fieldElement The element to populate with info about the - * field's state. - * @return {!Element} The element containing info about the field's state. - * @package - */ - toXml(fieldElement: Element): Element; - /** - * Sets the field's value based on the given XML element. Should only be - * called by Blockly.Xml. - * @param {!Element} fieldElement The element containing info about the - * field's state. - * @package - */ - fromXml(fieldElement: Element): void; - /** - * Saves this field's value. - * @return {*} The state of this field. - * @package - */ - saveState(): any; - /** - * Sets the field's value based on the given state. - * @param {*} state The state of the variable to assign to this variable field. - * @override - * @package - */ - override loadState(state: any): void; - /** - * Create the block UI for this field. - * @package - */ - initView(): void; - /** - * Get the text from this field as displayed on screen. May differ from getText - * due to ellipsis, and other formatting. - * @return {string} Currently displayed text. - * @protected - * @override - */ - protected override getDisplayText_(): string; - /** - * Called by setValue if the text input is valid. Updates the value of the - * field, and updates the text of the field if it is not currently being - * edited (i.e. handled by the htmlInput_). Is being redefined here to update - * overflow state of the field. - * @param {*} newValue The value to be saved. The default validator guarantees - * that this is a string. - * @protected - */ - protected doValueUpdate_(newValue: any): void; - /** - * Updates the text of the textElement. - * @protected - */ - protected render_(): void; - /** - * Updates the size of the field based on the text. - * @protected - */ - protected updateSize_(): void; - /** - * Show the inline free-text editor on top of the text. - * Overrides the default behaviour to force rerender in order to - * correct block size, based on editor text. - * @param {Event=} _opt_e Optional mouse event that triggered the field to open, - * or undefined if triggered programmatically. - * @param {boolean=} opt_quietInput True if editor should be created without - * focus. Defaults to false. - * @override - */ - override showEditor_(_opt_e?: Event | undefined, opt_quietInput?: boolean | undefined): void; - /** - * Create the text input editor widget. - * @return {!HTMLTextAreaElement} The newly created text input editor. - * @protected - */ - protected widgetCreate_(): HTMLTextAreaElement; /** * Sets the maxLines config for this field. * @param {number} maxLines Defines the maximum number of lines allowed, @@ -23384,17 +24031,16 @@ declare module "field_multilineinput" { * @return {number} The maxLines config value. */ getMaxLines(): number; - /** - * Handle key down to the editor. Override the text input definition of this - * so as to not close the editor when enter is typed in. - * @param {!Event} e Keyboard event. - * @protected - */ - protected onHtmlInputKeyDown_(e: Event): void; } - import { FieldTextInput } from "field_textinput"; + import { FieldTextInput } from "core/field_textinput"; + import { Sentinel } from "core/utils/sentinel"; } -declare module "field_number" { +declare module "core/field_number" { + /** + * Class for an editable number field. + * @extends {FieldTextInput} + * @alias Blockly.FieldNumber + */ export class FieldNumber extends FieldTextInput { /** * Construct a FieldNumber from a JSON arg object. @@ -23403,27 +24049,30 @@ declare module "field_number" { * @return {!FieldNumber} The new field instance. * @package * @nocollapse + * @override */ - static fromJson(options: any): FieldNumber; + static override fromJson(options: Object): FieldNumber; /** - * Class for an editable number field. - * @param {string|number=} opt_value The initial value of the field. Should cast - * to a number. Defaults to 0. - * @param {?(string|number)=} opt_min Minimum value. - * @param {?(string|number)=} opt_max Maximum value. - * @param {?(string|number)=} opt_precision Precision for value. + * @param {(string|number|!Sentinel)=} opt_value The initial value of + * the field. Should cast to a number. Defaults to 0. + * Also accepts Field.SKIP_SETUP if you wish to skip setup (only used by + * subclasses that want to handle configuration and setting the field + * value after their own constructors have run). + * @param {?(string|number)=} opt_min Minimum value. Will only be used if + * opt_config is not provided. + * @param {?(string|number)=} opt_max Maximum value. Will only be used if + * opt_config is not provided. + * @param {?(string|number)=} opt_precision Precision for value. Will only be + * used if opt_config is not provided. * @param {?Function=} opt_validator A function that is called to validate - * changes to the field's value. Takes in a number & returns a validated - * number, or null to abort the change. + * changes to the field's value. Takes in a number & returns a validated + * number, or null to abort the change. * @param {Object=} opt_config A map of options used to configure the field. - * See the [field creation documentation]{@link - * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/number#creation} - * for a list of properties this parameter supports. - * @extends {FieldTextInput} - * @constructor - * @alias Blockly.FieldNumber + * See the [field creation documentation]{@link + * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/number#creation} + * for a list of properties this parameter supports. */ - constructor(opt_value?: (string | number) | undefined, opt_min?: ((string | number) | null) | undefined, opt_max?: ((string | number) | null) | undefined, opt_precision?: ((string | number) | null) | undefined, opt_validator?: (Function | null) | undefined, opt_config?: any | undefined); + constructor(opt_value?: (string | number | Sentinel) | undefined, opt_min?: ((string | number) | null) | undefined, opt_max?: ((string | number) | null) | undefined, opt_precision?: ((string | number) | null) | undefined, opt_validator?: (Function | null) | undefined, opt_config?: Object | undefined); /** * The minimum value this number field can contain. * @type {number} @@ -23449,27 +24098,22 @@ declare module "field_number" { * @private */ private decimalPlaces_; - /** - * Configure the field based on the given map of options. - * @param {!Object} config A map of options to configure the field based on. - * @protected - * @override - */ - override configure_(config: any): void; /** * Set the maximum, minimum and precision constraints on this field. * Any of these properties may be undefined or NaN to be disabled. * Setting precision (usually a power of 10) enforces a minimum step between * values. That is, the user's value will rounded to the closest multiple of - * precision. The least significant digit place is inferred from the precision. - * Integers values can be enforces by choosing an integer precision. + * precision. The least significant digit place is inferred from the + * precision. Integers values can be enforces by choosing an integer + * precision. * @param {?(number|string|undefined)} min Minimum value. * @param {?(number|string|undefined)} max Maximum value. * @param {?(number|string|undefined)} precision Precision for value. */ setConstraints(min: (number | string | undefined) | null, max: (number | string | undefined) | null, precision: (number | string | undefined) | null): void; /** - * Sets the minimum value this field can contain. Updates the value to reflect. + * Sets the minimum value this field can contain. Updates the value to + * reflect. * @param {?(number|string|undefined)} min Minimum value. */ setMin(min: (number | string | undefined) | null): void; @@ -23487,7 +24131,8 @@ declare module "field_number" { */ getMin(): number; /** - * Sets the maximum value this field can contain. Updates the value to reflect. + * Sets the maximum value this field can contain. Updates the value to + * reflect. * @param {?(number|string|undefined)} max Maximum value. */ setMax(max: (number | string | undefined) | null): void; @@ -23526,39 +24171,16 @@ declare module "field_number" { * @return {number} The number to which this field's value is rounded. */ getPrecision(): number; - /** - * Ensure that the input value is a valid number (must fulfill the - * constraints placed on the field). - * @param {*=} opt_newValue The input value. - * @return {?number} A valid number, or null if invalid. - * @protected - * @override - */ - protected override doClassValidation_(opt_newValue?: any | undefined): number | null; - /** - * Create the number input editor widget. - * @return {!HTMLElement} The newly created number input editor. - * @protected - * @override - */ - protected override widgetCreate_(): HTMLElement; - /** - * The default value for this field. - * @type {*} - * @protected - */ - protected DEFAULT_VALUE: any; - /** - * Serializable fields are saved by the XML renderer, non-serializable fields - * are not. Editable fields should also be serializable. - * @type {boolean} - */ - SERIALIZABLE: boolean; } - - import { FieldTextInput } from "field_textinput"; + import { FieldTextInput } from "core/field_textinput"; + import { Sentinel } from "core/utils/sentinel"; } -declare module "field_variable" { +declare module "core/field_variable" { + /** + * Class for a variable's dropdown field. + * @extends {FieldDropdown} + * @alias Blockly.FieldVariable + */ export class FieldVariable extends FieldDropdown { /** * Construct a FieldVariable from a JSON arg object, @@ -23568,8 +24190,9 @@ declare module "field_variable" { * @return {!FieldVariable} The new field instance. * @package * @nocollapse + * @override */ - static fromJson(options: any): FieldVariable; + static override fromJson(options: Object): FieldVariable; /** * Return a sorted list of variable names for variable dropdown menus. * Include a special option at the end for creating a new variable name. @@ -23578,33 +24201,26 @@ declare module "field_variable" { */ static dropdownCreate(): Array; /** - * Class for a variable's dropdown field. - * @param {?string} varName The default name for the variable. If null, - * a unique variable name will be generated. + * @param {?string|!Sentinel} varName The default name for the variable. + * If null, a unique variable name will be generated. + * Also accepts Field.SKIP_SETUP if you wish to skip setup (only used by + * subclasses that want to handle configuration and setting the field + * value after their own constructors have run). * @param {Function=} opt_validator A function that is called to validate * changes to the field's value. Takes in a variable ID & returns a * validated variable ID, or null to abort the change. * @param {Array=} opt_variableTypes A list of the types of variables - * to include in the dropdown. + * to include in the dropdown. Will only be used if opt_config is not + * provided. * @param {string=} opt_defaultType The type of variable to create if this - * field's value is not explicitly set. Defaults to ''. + * field's value is not explicitly set. Defaults to ''. Will only be used + * if opt_config is not provided. * @param {Object=} opt_config A map of options used to configure the field. * See the [field creation documentation]{@link * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/variable#creation} * for a list of properties this parameter supports. - * @extends {FieldDropdown} - * @constructor - * @alias Blockly.FieldVariable */ - constructor(varName: string | null, opt_validator?: Function | undefined, opt_variableTypes?: Array | undefined, opt_defaultType?: string | undefined, opt_config?: any | undefined); - /** - * An array of options for a dropdown list, - * or a function which generates these options. - * @type {(!Array| - * !function(this:FieldDropdown): !Array)} - * @protected - */ - protected menuGenerator_: any[][] | ((this: FieldDropdown) => Array); + constructor(varName: (string | Sentinel) | null, opt_validator?: Function | undefined, opt_variableTypes?: Array | undefined, opt_defaultType?: string | undefined, opt_config?: Object | undefined); /** * The initial variable name passed to this field's constructor, or an * empty string if a name wasn't provided. Used to create the initial @@ -23613,75 +24229,23 @@ declare module "field_variable" { */ defaultVariableName: string; /** - * The size of the area rendered by the field. - * @type {Size} - * @protected - * @override + * The type of the default variable for this field. + * @type {string} + * @private */ - protected override size_: Size; + private defaultType_; /** - * Configure the field based on the given map of options. - * @param {!Object} config A map of options to configure the field based on. - * @protected + * All of the types of variables that will be available in this field's + * dropdown. + * @type {?Array} */ - protected configure_(config: any): void; + variableTypes: Array | null; /** - * 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 + * The variable model associated with this field. + * @type {?VariableModel} + * @private */ - initModel(): void; - /** - * @override - */ - override shouldAddBorderRect_(): boolean; - /** - * Initialize this field based on the given XML. - * @param {!Element} fieldElement The element containing information about the - * variable field's state. - */ - fromXml(fieldElement: Element): void; - /** - * Serialize this field to XML. - * @param {!Element} fieldElement The element to populate with info about the - * field's state. - * @return {!Element} The element containing info about the field's state. - */ - toXml(fieldElement: Element): Element; - /** - * Saves this field's value. - * @param {boolean=} doFullSerialization If true, the variable field will - * serialize the full state of the field being referenced (ie ID, name, - * and type) rather than just a reference to it (ie ID). - * @return {*} The state of the variable field. - * @override - * @package - */ - override saveState(doFullSerialization?: boolean | undefined): any; - /** - * Sets the field's value based on the given state. - * @param {*} state The state of the variable to assign to this variable field. - * @override - * @package - */ - override loadState(state: any): void; - /** - * Attach this field to a block. - * @param {!Block} block The block containing this field. - */ - setSourceBlock(block: Block): void; - /** - * Get the variable's ID. - * @return {string} Current variable's ID. - */ - getValue(): string; - /** - * 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. - */ - getText(): string; + private variable_; /** * Get the variable model for the selected variable. * Not guaranteed to be in the variable map on the workspace (e.g. if accessed @@ -23691,31 +24255,6 @@ declare module "field_variable" { * @package */ getVariable(): VariableModel | null; - /** - * Gets the validation function for this field, or null if not set. - * Returns null if the variable is not set, because validators should not - * run on the initial setValue call, because the field won't be attached to - * a block and workspace at that point. - * @return {?Function} Validation function, or null. - */ - getValidator(): Function | null; - /** - * Ensure that the ID belongs to a valid variable of an allowed type. - * @param {*=} opt_newValue The ID of the new variable to set. - * @return {?string} The validated ID, or null if invalid. - * @protected - */ - protected doClassValidation_(opt_newValue?: any | undefined): string | null; - /** - * Update the value of this variable field, as well as its variable and text. - * - * The variable ID should be valid at this point, but if a variable field - * validator returns a bad ID, this could break. - * @param {*} newId The value to be saved. - * @protected - */ - protected doValueUpdate_(newId: any): void; - variable_: VariableModel; /** * Check whether the given variable type is allowed on this field. * @param {string} type The type to check. @@ -23731,67 +24270,34 @@ declare module "field_variable" { */ private getVariableTypes_; /** - * Parse the optional arguments representing the allowed variable types and the - * default variable type. + * 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. + * 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 */ private setTypes_; - defaultType_: string; - variableTypes: string[]; - /** - * Refreshes the name of the variable by grabbing the name of the model. - * Used when a variable gets renamed, but the ID stays the same. Should only - * be called by the block. - * @package - */ - refreshVariableName(): void; - /** - * Handle the selection of an item in the variable dropdown menu. - * Special case the 'Rename variable...' and 'Delete variable...' options. - * In the rename case, prompt the user for a new name. - * @param {!Menu} menu The Menu component clicked. - * @param {!MenuItem} menuItem The MenuItem selected within menu. - * @protected - */ - protected onItemSelected_(menu: Menu, menuItem: MenuItem): void; - /** - * Overrides referencesVariables(), indicating this field refers to a variable. - * @return {boolean} True. - * @package - * @override - */ - override referencesVariables(): boolean; - /** - * Serializable fields are saved by the XML renderer, non-serializable fields - * are not. Editable fields should also be serializable. - * @type {boolean} - */ - SERIALIZABLE: boolean; } - import { FieldDropdown } from "field_dropdown"; - import { Size } from "utils/size"; - import { Block } from "block"; - import { VariableModel } from "variable_model"; - import { Menu } from "menu"; - import { MenuItem } from "menuitem"; + import { FieldDropdown } from "core/field_dropdown"; + import { VariableModel } from "core/variable_model"; + import { Sentinel } from "core/utils/sentinel"; } -declare module "flyout_metrics_manager" { +declare module "core/flyout_metrics_manager" { /** * Calculates metrics for a flyout's workspace. * The metrics are mainly used to size scrollbars for the flyout. - * @param {!WorkspaceSvg} workspace The flyout's workspace. - * @param {!IFlyout} flyout The flyout. * @extends {MetricsManager} - * @constructor * @alias Blockly.FlyoutMetricsManager */ export class FlyoutMetricsManager extends MetricsManager { - constructor(workspace: any, flyout: any); + /** + * @param {!WorkspaceSvg} workspace The flyout's workspace. + * @param {!IFlyout} flyout The flyout. + */ + constructor(workspace: WorkspaceSvg, flyout: IFlyout); /** * The flyout that owns the workspace to calculate metrics for. * @type {!IFlyout} @@ -23801,44 +24307,28 @@ declare module "flyout_metrics_manager" { /** * Gets the bounding box of the blocks on the flyout's workspace. * This is in workspace coordinates. - * @return {!SVGRect|{height: number, y: number, width: number, x: number}} The - * bounding box of the blocks on the workspace. + * @return {!SVGRect|{height: number, y: number, width: number, x: number}} + * The bounding box of the blocks on the workspace. * @private */ private getBoundingBox_; - /** - * @override - */ - override getContentMetrics(opt_getWorkspaceCoordinates: any): { - height: number; - width: number; - top: number; - left: number; - }; - /** - * @override - */ - override getScrollMetrics(opt_getWorkspaceCoordinates: any, opt_viewMetrics: any, opt_contentMetrics: any): { - height: number; - width: number; - top: number; - left: number; - }; } - import { IFlyout } from "interfaces/i_flyout"; - import { MetricsManager } from "metrics_manager"; + import { MetricsManager } from "core/metrics_manager"; + import { IFlyout } from "core/interfaces/i_flyout"; + import { WorkspaceSvg } from "core/workspace_svg"; } -declare module "flyout_base" { - export class Flyout { +declare module "core/flyout_base" { + /** + * Class for a flyout. + * @abstract + * @implements {IFlyout} + * @extends {DeleteArea} + * @alias Blockly.Flyout + */ + export class Flyout extends DeleteArea implements IFlyout { /** - * Class for a flyout. * @param {!Options} workspaceOptions Dictionary of options for the * workspace. - * @constructor - * @abstract - * @implements {IFlyout} - * @extends {DeleteArea} - * @alias Blockly.Flyout */ constructor(workspaceOptions: Options); /** @@ -23875,6 +24365,21 @@ declare module "flyout_base" { * @private */ private eventWrappers_; + /** + * Function that will be registered as a change listener on the workspace + * to reflow when blocks in the flyout workspace change. + * @type {?Function} + * @private + */ + private reflowWrapper_; + /** + * Function that disables blocks in the flyout based on max block counts + * allowed in the target workspace. Registered as a change listener on the + * target workspace. + * @type {?Function} + * @private + */ + private filterWrapper_; /** * List of background mats that lurk behind each block to catch clicks * landing in the blocks' lakes and bays. @@ -23919,6 +24424,103 @@ declare module "flyout_base" { * @private */ private recycledBlocks_; + /** + * Does the flyout automatically close when a block is created? + * @type {boolean} + */ + autoClose: boolean; + /** + * Whether the flyout is visible. + * @type {boolean} + * @private + */ + private isVisible_; + /** + * Whether the workspace containing this flyout is visible. + * @type {boolean} + * @private + */ + private containerVisible_; + /** + * A map from blocks to the rects which are beneath them to act as input + * targets. + * @type {!WeakMap} + * @private + */ + private rectMap_; + /** + * Corner radius of the flyout background. + * @type {number} + * @const + */ + CORNER_RADIUS: number; + /** + * Margin around the edges of the blocks in the flyout. + * @type {number} + * @const + */ + MARGIN: number; + /** + * Gap between items in horizontal flyouts. Can be overridden with the "sep" + * element. + * @const {number} + */ + GAP_X: number; + /** + * Gap between items in vertical flyouts. Can be overridden with the "sep" + * element. + * @const {number} + */ + GAP_Y: number; + /** + * Top/bottom padding between scrollbar and edge of flyout background. + * @type {number} + * @const + */ + SCROLLBAR_MARGIN: number; + /** + * Width of flyout. + * @type {number} + * @protected + */ + protected width_: number; + /** + * Height of flyout. + * @type {number} + * @protected + */ + protected height_: number; + /** + * Range of a drag angle from a flyout considered "dragging toward + * workspace". Drags that are within the bounds of this many degrees from + * the orthogonal line to the flyout edge are considered to be "drags toward + * the workspace". + * Example: + * Flyout Edge Workspace + * [block] / <-within this angle, drags "toward workspace" | + * [block] ---- orthogonal to flyout boundary ---- | + * [block] \ | + * The angle is given in degrees from the orthogonal. + * + * This is used to know when to create a new block and when to scroll the + * flyout. Setting it to 360 means that all drags create a new block. + * @type {number} + * @protected + */ + protected dragAngleRange_: number; + /** + * The path around the background of the flyout, which will be filled with a + * background colour. + * @type {?SVGPathElement} + * @protected + */ + protected svgBackground_: SVGPathElement | null; + /** + * The root SVG group for the button or label. + * @type {?SVGGElement} + * @protected + */ + protected svgGroup_: SVGGElement | null; /** * Creates the flyout's DOM. Only needs to be called once. The flyout can * either exist as its own SVG element or be a g element nested inside a @@ -23930,15 +24532,12 @@ declare module "flyout_base" { * @return {!SVGElement} The flyout's SVG group. */ createDom(tagName: string | Svg | Svg): SVGElement; - svgGroup_: SVGGElement; - svgBackground_: SVGPathElement; /** * Initializes the flyout. * @param {!WorkspaceSvg} targetWorkspace The workspace in which to * create new blocks. */ init(targetWorkspace: WorkspaceSvg): void; - filterWrapper_: any; /** * Dispose of this flyout. * Unlink from all DOM elements to prevent memory leaks. @@ -23973,18 +24572,17 @@ declare module "flyout_base" { */ isVisible(): boolean; /** - * 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. + * 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. */ setVisible(visible: boolean): void; - isVisible_: boolean; /** * Set whether this flyout's container is visible. * @param {boolean} visible Whether the container is visible. */ setContainerVisible(visible: boolean): void; - containerVisible_: boolean; /** * Update the display property of the flyout based whether it thinks it should * be visible and whether its containing workspace is visible. @@ -24004,7 +24602,6 @@ declare module "flyout_base" { * Hide and empty the flyout. */ hide(): void; - reflowWrapper_: any; /** * Show and populate the flyout. * @param {!toolbox.FlyoutDefinition|string} flyoutDef Contents to display @@ -24012,8 +24609,6 @@ declare module "flyout_base" { * toolbox definition, or a string with the name of the dynamic category. */ show(flyoutDef: toolbox.FlyoutDefinition | string): void; - height_: number; - width_: number; /** * Create the contents array and gaps array necessary to create the layout for * the flyout. @@ -24063,7 +24658,8 @@ declare module "flyout_base" { * Adds a gap in the flyout based on block info. * @param {!toolbox.BlockInfo} blockInfo Information about a block. * @param {!Array} gaps The list of gaps between items in the flyout. - * @param {number} defaultGap The default gap between one element and the next. + * @param {number} defaultGap The default gap between one element and the + * next. * @private */ private addBlockGap_; @@ -24155,12 +24751,12 @@ declare module "flyout_base" { * @param {!BlockSvg} block The block to associate the rect to. * @param {number} x The x position of the cursor during this layout pass. * @param {number} y The y position of the cursor during this layout pass. - * @param {!{height: number, width: number}} blockHW The height and width of the - * block. - * @param {number} index The index into the mats list where this rect should be - * placed. - * @return {!SVGElement} Newly created SVG element for the rectangle behind the - * block. + * @param {!{height: number, width: number}} blockHW The height and width of + * the block. + * @param {number} index The index into the mats list where this rect should + * be placed. + * @return {!SVGElement} Newly created SVG element for the rectangle behind + * the block. * @protected */ protected createRect_(block: BlockSvg, x: number, y: number, blockHW: { @@ -24177,8 +24773,9 @@ declare module "flyout_base" { protected moveRectToBlock_(rect: SVGElement, block: BlockSvg): void; /** * Filter the blocks on the flyout to disable the ones that are above the - * capacity limit. For instance, if the user may only place two more blocks on - * the workspace, an "a + b" block that has two shadow blocks would be disabled. + * capacity limit. For instance, if the user may only place two more blocks + * on the workspace, an "a + b" block that has two shadow blocks would be + * disabled. * @private */ private filterForCapacity_; @@ -24187,8 +24784,8 @@ declare module "flyout_base" { */ reflow(): void; /** - * @return {boolean} True if this flyout may be scrolled with a scrollbar or by - * dragging. + * @return {boolean} True if this flyout may be scrolled with a scrollbar or + * by dragging. * @package */ isScrollable(): boolean; @@ -24206,213 +24803,38 @@ declare module "flyout_base" { * @private */ private positionNewBlock_; - /** - * Does the flyout automatically close when a block is created? - * @type {boolean} - */ - autoClose: boolean; - /** - * Corner radius of the flyout background. - * @type {number} - * @const - */ - CORNER_RADIUS: number; - /** - * Margin around the edges of the blocks in the flyout. - * @type {number} - * @const - */ - MARGIN: number; - /** - * Gap between items in horizontal flyouts. Can be overridden with the "sep" - * element. - * @const {number} - */ - GAP_X: number; - /** - * Gap between items in vertical flyouts. Can be overridden with the "sep" - * element. - * @const {number} - */ - GAP_Y: number; - /** - * Top/bottom padding between scrollbar and edge of flyout background. - * @type {number} - * @const - */ - SCROLLBAR_MARGIN: number; - /** - * Range of a drag angle from a flyout considered "dragging toward workspace". - * Drags that are within the bounds of this many degrees from the orthogonal - * line to the flyout edge are considered to be "drags toward the workspace". - * Example: - * Flyout Edge Workspace - * [block] / <-within this angle, drags "toward workspace" | - * [block] ---- orthogonal to flyout boundary ---- | - * [block] \ | - * The angle is given in degrees from the orthogonal. - * - * This is used to know when to create a new block and when to scroll the - * flyout. Setting it to 360 means that all drags create a new block. - * @type {number} - * @protected - */ - protected dragAngleRange_: number; } - import { WorkspaceSvg } from "workspace_svg"; - import { FlyoutButton } from "flyout_button"; - import { Svg } from "utils/svg"; - import * as toolbox from "utils/toolbox"; - import { BlockSvg } from "block_svg"; - import { Options } from "options"; + import { IFlyout } from "core/interfaces/i_flyout"; + import { DeleteArea } from "core/delete_area"; + import { WorkspaceSvg } from "core/workspace_svg"; + import { FlyoutButton } from "core/flyout_button"; + import { Svg } from "core/utils/svg"; + import * as toolbox from "core/utils/toolbox"; + import { BlockSvg } from "core/block_svg"; + import { Options } from "core/options"; } -declare module "generator" { +declare module "core/generator" { + /** + * Class for a code generator that translates the blocks into a language. + * @unrestricted + * @alias Blockly.Generator + */ export class Generator { /** - * Class for a code generator that translates the blocks into a language. * @param {string} name Language name of this generator. - * @constructor - * @alias Blockly.Generator */ constructor(name: string); name_: string; + /** + * This is used as a placeholder in functions defined using + * Generator.provideFunction_. It must not be legal code that could + * legitimately appear in a function definition (or comment), and it must + * not confuse the regular expression parser. + * @type {string} + * @protected + */ + protected FUNCTION_NAME_PLACEHOLDER_: string; FUNCTION_NAME_PLACEHOLDER_REGEXP_: RegExp; - /** - * Generate code for all blocks in the workspace to the specified language. - * @param {!Workspace=} workspace Workspace to generate code from. - * @return {string} Generated code. - */ - workspaceToCode(workspace?: Workspace | undefined): string; - /** - * Prepend a common prefix onto each line of code. - * Intended for indenting code or adding comment markers. - * @param {string} text The lines of code. - * @param {string} prefix The common prefix. - * @return {string} The prefixed lines of code. - */ - prefixLines(text: string, prefix: string): string; - /** - * Recursively spider a tree of blocks, returning all their comments. - * @param {!Block} block The block from which to start spidering. - * @return {string} Concatenated list of comments. - */ - allNestedComments(block: Block): string; - /** - * Generate code for the specified block (and attached blocks). - * The generator must be initialized before calling this function. - * @param {Block} block The block to generate code for. - * @param {boolean=} opt_thisOnly True to generate code for only this statement. - * @return {string|!Array} For statement blocks, the generated code. - * For value blocks, an array containing the generated code and an - * operator order value. Returns '' if block is null. - */ - blockToCode(block: Block, opt_thisOnly?: boolean | undefined): string | any[]; - /** - * Generate code representing the specified value input. - * @param {!Block} block The block containing the input. - * @param {string} name The name of the input. - * @param {number} outerOrder The maximum binding strength (minimum order value) - * of any operators adjacent to "block". - * @return {string} Generated code or '' if no blocks are connected or the - * specified input does not exist. - */ - valueToCode(block: Block, name: string, outerOrder: number): string; - /** - * Generate a code string representing the blocks attached to the named - * statement input. Indent the code. - * This is mainly used in generators. When trying to generate code to evaluate - * look at using workspaceToCode or blockToCode. - * @param {!Block} block The block containing the input. - * @param {string} name The name of the input. - * @return {string} Generated code or '' if no blocks are connected. - */ - statementToCode(block: Block, name: string): string; - /** - * Add an infinite loop trap to the contents of a loop. - * Add statement suffix at the start of the loop block (right after the loop - * statement executes), and a statement prefix to the end of the loop block - * (right before the loop statement executes). - * @param {string} branch Code for loop contents. - * @param {!Block} block Enclosing block. - * @return {string} Loop contents, with infinite loop trap added. - */ - addLoopTrap(branch: string, block: Block): string; - /** - * Inject a block ID into a message to replace '%1'. - * Used for STATEMENT_PREFIX, STATEMENT_SUFFIX, and INFINITE_LOOP_TRAP. - * @param {string} msg Code snippet with '%1'. - * @param {!Block} block Block which has an ID. - * @return {string} Code snippet with ID. - */ - injectId(msg: string, block: Block): string; - /** - * Add one or more words to the list of reserved words for this language. - * @param {string} words Comma-separated list of words to add to the list. - * No spaces. Duplicates are ok. - */ - addReservedWords(words: string): void; - /** - * Define a developer-defined function (not a user-defined procedure) to be - * included in the generated code. Used for creating private helper functions. - * The first time this is called with a given desiredName, the code is - * saved and an actual name is generated. Subsequent calls with the - * same desiredName have no effect but have the same return value. - * - * It is up to the caller to make sure the same desiredName is not - * used for different helper functions (e.g. use "colourRandom" and - * "listRandom", not "random"). There is no danger of colliding with reserved - * words, or user-defined variable or procedure names. - * - * The code gets output when Generator.finish() is called. - * - * @param {string} desiredName The desired name of the function - * (e.g. mathIsPrime). - * @param {!Array} code A list of statements. Use ' ' for indents. - * @return {string} The actual name of the new function. This may differ - * from desiredName if the former has already been taken by the user. - * @protected - */ - protected provideFunction_(desiredName: string, code: Array): string; - /** - * Hook for code to run before code generation starts. - * Subclasses may override this, e.g. to initialise the database of variable - * names. - * @param {!Workspace} _workspace Workspace to generate code from. - */ - init(_workspace: Workspace): void; - definitions_: any | undefined; - functionNames_: any | undefined; - /** - * Common tasks for generating code from blocks. This is called from - * blockToCode and is called on every block, not just top level blocks. - * Subclasses may override this, e.g. to generate code for statements following - * the block, or to handle comments for the specified block and any connected - * value blocks. - * @param {!Block} _block The current block. - * @param {string} code The code created for this block. - * @param {boolean=} _opt_thisOnly True to generate code for only this - * statement. - * @return {string} Code with comments and subsequent blocks added. - * @protected - */ - protected scrub_(_block: Block, code: string, _opt_thisOnly?: boolean | undefined): string; - /** - * Hook for code to run at end of code generation. - * Subclasses may override this, e.g. to prepend the generated code with import - * statements or variable definitions. - * @param {string} code Generated code. - * @return {string} Completed code. - */ - finish(code: string): string; - /** - * Naked values are top-level blocks with outputs that aren't plugged into - * anything. - * Subclasses may override this, e.g. if their language does not allow - * naked values. - * @param {string} line Line of generated code. - * @return {string} Legal line of code. - */ - scrubNakedValue(line: string): string; /** * Arbitrary code to inject into locations that risk causing infinite loops. * Any instances of '%1' will be replaced by the block ID that failed. @@ -24466,42 +24888,197 @@ declare module "generator" { */ protected RESERVED_WORDS_: string; /** - * This is used as a placeholder in functions defined using - * Generator.provideFunction_. It must not be legal code that could - * legitimately appear in a function definition (or comment), and it must - * not confuse the regular expression parser. - * @type {string} + * A dictionary of definitions to be printed before the code. + * @type {!Object|undefined} * @protected */ - protected FUNCTION_NAME_PLACEHOLDER_: string; + protected definitions_: Object | undefined; + /** + * A dictionary mapping desired function names in definitions_ to actual + * function names (to avoid collisions with user functions). + * @type {!Object|undefined} + * @protected + */ + protected functionNames_: Object | undefined; /** * A database of variable and procedure names. * @type {!Names|undefined} * @protected */ - protected nameDB_: Names | undefined; - } - import { Workspace } from "workspace"; - import { Block } from "block"; - import { Names } from "names"; -} -declare module "flyout_horizontal" { - export class HorizontalFlyout { + protected nameDB_: { + variablePrefix_: string; + reservedDict_: Object; + db_: { + [x: string]: { + [x: string]: string; + }; + }; + dbReverse_: { + [x: string]: boolean; + }; + variableMap_: import("core/variable_map").VariableMap | null; + reset(): void; + setVariableMap(map: import("core/variable_map").VariableMap): void; + getNameForUserVariable_(id: string): string | null; + populateVariables(workspace: Workspace): void; + populateProcedures(workspace: Workspace): void; + getName(nameOrId: string, type: string): string; + getUserNames(type: string): string[]; + getDistinctName(name: string, type: string): string; + safeName_(name: string): string; + } | undefined; /** - * Class for a flyout. - * @param {!Options} workspaceOptions Dictionary of options for the - * workspace. - * @extends {Flyout} - * @constructor - * @alias Blockly.HorizontalFlyout + * Generate code for all blocks in the workspace to the specified language. + * @param {!Workspace=} workspace Workspace to generate code from. + * @return {string} Generated code. */ - constructor(workspaceOptions: Options); - horizontalLayout: boolean; + workspaceToCode(workspace?: Workspace | undefined): string; + /** + * Prepend a common prefix onto each line of code. + * Intended for indenting code or adding comment markers. + * @param {string} text The lines of code. + * @param {string} prefix The common prefix. + * @return {string} The prefixed lines of code. + */ + prefixLines(text: string, prefix: string): string; + /** + * Recursively spider a tree of blocks, returning all their comments. + * @param {!Block} block The block from which to start spidering. + * @return {string} Concatenated list of comments. + */ + allNestedComments(block: Block): string; + /** + * Generate code for the specified block (and attached blocks). + * The generator must be initialized before calling this function. + * @param {?Block} block The block to generate code for. + * @param {boolean=} opt_thisOnly True to generate code for only this + * statement. + * @return {string|!Array} For statement blocks, the generated code. + * For value blocks, an array containing the generated code and an + * operator order value. Returns '' if block is null. + */ + blockToCode(block: Block | null, opt_thisOnly?: boolean | undefined): string | any[]; + /** + * Generate code representing the specified value input. + * @param {!Block} block The block containing the input. + * @param {string} name The name of the input. + * @param {number} outerOrder The maximum binding strength (minimum order + * value) of any operators adjacent to "block". + * @return {string} Generated code or '' if no blocks are connected or the + * specified input does not exist. + */ + valueToCode(block: Block, name: string, outerOrder: number): string; + /** + * Generate a code string representing the blocks attached to the named + * statement input. Indent the code. + * This is mainly used in generators. When trying to generate code to evaluate + * look at using workspaceToCode or blockToCode. + * @param {!Block} block The block containing the input. + * @param {string} name The name of the input. + * @return {string} Generated code or '' if no blocks are connected. + */ + statementToCode(block: Block, name: string): string; + /** + * Add an infinite loop trap to the contents of a loop. + * Add statement suffix at the start of the loop block (right after the loop + * statement executes), and a statement prefix to the end of the loop block + * (right before the loop statement executes). + * @param {string} branch Code for loop contents. + * @param {!Block} block Enclosing block. + * @return {string} Loop contents, with infinite loop trap added. + */ + addLoopTrap(branch: string, block: Block): string; + /** + * Inject a block ID into a message to replace '%1'. + * Used for STATEMENT_PREFIX, STATEMENT_SUFFIX, and INFINITE_LOOP_TRAP. + * @param {string} msg Code snippet with '%1'. + * @param {!Block} block Block which has an ID. + * @return {string} Code snippet with ID. + */ + injectId(msg: string, block: Block): string; + /** + * Add one or more words to the list of reserved words for this language. + * @param {string} words Comma-separated list of words to add to the list. + * No spaces. Duplicates are ok. + */ + addReservedWords(words: string): void; + /** + * Define a developer-defined function (not a user-defined procedure) to be + * included in the generated code. Used for creating private helper + * functions. The first time this is called with a given desiredName, the code + * is saved and an actual name is generated. Subsequent calls with the same + * desiredName have no effect but have the same return value. + * + * It is up to the caller to make sure the same desiredName is not + * used for different helper functions (e.g. use "colourRandom" and + * "listRandom", not "random"). There is no danger of colliding with reserved + * words, or user-defined variable or procedure names. + * + * The code gets output when Generator.finish() is called. + * + * @param {string} desiredName The desired name of the function + * (e.g. mathIsPrime). + * @param {!Array|string} code A list of statements or one multi-line + * code string. Use ' ' for indents (they will be replaced). + * @return {string} The actual name of the new function. This may differ + * from desiredName if the former has already been taken by the user. + * @protected + */ + protected provideFunction_(desiredName: string, code: Array | string): string; + /** + * Hook for code to run before code generation starts. + * Subclasses may override this, e.g. to initialise the database of variable + * names. + * @param {!Workspace} _workspace Workspace to generate code from. + */ + init(_workspace: Workspace): void; + /** + * Common tasks for generating code from blocks. This is called from + * blockToCode and is called on every block, not just top level blocks. + * Subclasses may override this, e.g. to generate code for statements + * following the block, or to handle comments for the specified block and any + * connected value blocks. + * @param {!Block} _block The current block. + * @param {string} code The code created for this block. + * @param {boolean=} _opt_thisOnly True to generate code for only this + * statement. + * @return {string} Code with comments and subsequent blocks added. + * @protected + */ + protected scrub_(_block: Block, code: string, _opt_thisOnly?: boolean | undefined): string; + /** + * Hook for code to run at end of code generation. + * Subclasses may override this, e.g. to prepend the generated code with + * import statements or variable definitions. + * @param {string} code Generated code. + * @return {string} Completed code. + */ + finish(code: string): string; + /** + * Naked values are top-level blocks with outputs that aren't plugged into + * anything. + * Subclasses may override this, e.g. if their language does not allow + * naked values. + * @param {string} line Line of generated code. + * @return {string} Legal line of code. + */ + scrubNakedValue(line: string): string; + } + import { Workspace } from "core/workspace"; + import { Block } from "core/block"; +} +declare module "core/flyout_horizontal" { + /** + * Class for a flyout. + * @extends {Flyout} + * @alias Blockly.HorizontalFlyout + */ + export class HorizontalFlyout extends Flyout { /** * Sets the translation of the flyout to match the scrollbars. - * @param {!{x:number,y:number}} xyRatio Contains a y property which is a float - * between 0 and 1 specifying the degree of scrolling and a - * similar x property. + * @param {!{x:number,y:number}} xyRatio Contains a y property which is a + * float between 0 and 1 specifying the degree of scrolling and a similar + * x property. * @protected */ protected setMetrics_(xyRatio: { @@ -24522,7 +25099,6 @@ declare module "flyout_horizontal" { * Move the flyout to the edge of the workspace. */ position(): void; - width_: any; /** * Create and set the path for the visible boundaries of the flyout. * @param {number} width The width of the flyout, not including the @@ -24548,7 +25124,7 @@ declare module "flyout_horizontal" { * @param {!Array} gaps The visible gaps between blocks. * @protected */ - protected layout_(contents: Array, gaps: Array): void; + protected layout_(contents: Array, gaps: Array): void; /** * Determine if a drag delta is toward the workspace, based on the position * and orientation of the flyout. This is used in determineDragIntention_ to @@ -24558,61 +25134,45 @@ declare module "flyout_horizontal" { * @return {boolean} True if the drag is toward the workspace. * @package */ - isDragTowardWorkspace(currentDragDeltaXY: Coordinate): boolean; - /** - * Returns the bounding rectangle of the drag target area in pixel units - * relative to viewport. - * @return {?Rect} The component's bounding box. Null if drag - * target area should be ignored. - */ - getClientRect(): Rect | null; + isDragTowardWorkspace(currentDragDeltaXY: { + x: number; + y: number; + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): boolean; /** * Compute height of flyout. toolbox.Position mat under each block. * For RTL: Lay out the blocks right-aligned. * @protected */ protected reflowInternal_(): void; - height_: any; } - import { Coordinate } from "utils/coordinate"; - import { Rect } from "utils/rect"; - import { Options } from "options"; + import { Flyout } from "core/flyout_base"; } -declare module "interfaces/i_styleable" { - /** - * @license - * Copyright 2020 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - /** - * @fileoverview The interface for an object that a style can be added to. - */ - /** - * The interface for an object that a style can be added to. - * @namespace Blockly.IStyleable - */ +declare module "core/interfaces/i_styleable" { /** * Interface for an object that a style can be added to. * @interface * @alias Blockly.IStyleable */ - export class IStyleable { - } + export class IStyleable {} } -declare module "toolbox/toolbox" { - export class Toolbox extends DeleteArea { +declare module "core/toolbox/toolbox" { + /** + * Class for a Toolbox. + * Creates the toolbox's DOM. + * @implements {IAutoHideable} + * @implements {IKeyboardAccessible} + * @implements {IStyleable} + * @implements {IToolbox} + * @extends {DeleteArea} + * @alias Blockly.Toolbox + */ + export class Toolbox extends DeleteArea implements IAutoHideable, IKeyboardAccessible, IStyleable, IToolbox { /** - * Class for a Toolbox. - * Creates the toolbox's DOM. * @param {!WorkspaceSvg} workspace The workspace in which to create new * blocks. - * @constructor - * @implements {IAutoHideable} - * @implements {IKeyboardAccessible} - * @implements {IStyleable} - * @implements {IToolbox} - * @extends {DeleteArea} - * @alias Blockly.Toolbox */ constructor(workspace: WorkspaceSvg); /** @@ -24641,15 +25201,15 @@ declare module "toolbox/toolbox" { private horizontalLayout_; /** * The html container for the toolbox. - * @type {?Element} + * @type {?HTMLDivElement} */ - HtmlDiv: Element | null; + HtmlDiv: HTMLDivElement | null; /** * The html container for the contents of a toolbox. - * @type {?Element} + * @type {?HTMLDivElement} * @protected */ - protected contentsDiv_: Element | null; + protected contentsDiv_: HTMLDivElement | null; /** * Whether the Toolbox is visible. * @type {boolean} @@ -24661,7 +25221,7 @@ declare module "toolbox/toolbox" { * @type {!Array} * @protected */ - protected contents_: Array; + protected contents_: Array<() => void>; /** * The width of the toolbox. * @type {number} @@ -24691,7 +25251,7 @@ declare module "toolbox/toolbox" { * @protected */ protected contentMap_: { - [x: string]: IToolboxItem; + [x: string]: () => void; }; /** * Position of the toolbox and flyout relative to the workspace. @@ -24703,13 +25263,13 @@ declare module "toolbox/toolbox" { * @type {?ISelectableToolboxItem} * @protected */ - protected selectedItem_: ISelectableToolboxItem | null; + protected selectedItem_: (() => void) | null; /** * The previously selected item. * @type {?ISelectableToolboxItem} * @protected */ - protected previouslySelectedItem_: ISelectableToolboxItem | null; + protected previouslySelectedItem_: (() => void) | null; /** * Array holding info needed to unbind event handlers. * Used for disposing. @@ -24734,30 +25294,30 @@ declare module "toolbox/toolbox" { /** * Creates the DOM for the toolbox. * @param {!WorkspaceSvg} workspace The workspace this toolbox is on. - * @return {!Element} The HTML container for the toolbox. + * @return {!HTMLDivElement} The HTML container for the toolbox. * @protected */ - protected createDom_(workspace: WorkspaceSvg): Element; + protected createDom_(workspace: WorkspaceSvg): HTMLDivElement; /** * Creates the container div for the toolbox. - * @return {!Element} The HTML container for the toolbox. + * @return {!HTMLDivElement} The HTML container for the toolbox. * @protected */ - protected createContainer_(): Element; + protected createContainer_(): HTMLDivElement; /** * Creates the container for all the contents in the toolbox. - * @return {!Element} The HTML container for the toolbox contents. + * @return {!HTMLDivElement} The HTML container for the toolbox contents. * @protected */ - protected createContentsContainer_(): Element; + protected createContentsContainer_(): HTMLDivElement; /** * Adds event listeners to the toolbox container div. - * @param {!Element} container The HTML container for the toolbox. - * @param {!Element} contentsContainer The HTML container for the contents - * of the toolbox. + * @param {!HTMLDivElement} container The HTML container for the toolbox. + * @param {!HTMLDivElement} contentsContainer The HTML container for the + * contents of the toolbox. * @protected */ - protected attachEvents_(container: Element, contentsContainer: Element): void; + protected attachEvents_(container: HTMLDivElement, contentsContainer: HTMLDivElement): void; /** * Handles on click events for when the toolbox or toolbox items are clicked. * @param {!Event} e Click event to handle. @@ -24806,13 +25366,13 @@ declare module "toolbox/toolbox" { * @param {!IToolboxItem} toolboxItem The item in the toolbox. * @protected */ - protected addToolboxItem_(toolboxItem: IToolboxItem): void; + protected addToolboxItem_(toolboxItem: () => void): void; /** * Gets the items in the toolbox. * @return {!Array} The list of items in the toolbox. * @public */ - public getToolboxItems(): Array; + public getToolboxItems(): Array<() => void>; /** * Adds a style on the toolbox. Usually used to change the cursor. * @param {string} style The name of the class to add. @@ -24825,57 +25385,6 @@ declare module "toolbox/toolbox" { * @package */ removeStyle(style: string): void; - /** - * Returns the bounding rectangle of the drag target area in pixel units - * relative to viewport. - * @return {?Rect} The component's bounding box. Null if drag - * target area should be ignored. - */ - getClientRect(): Rect | null; - /** - * Returns whether the provided block or bubble would be deleted if dropped on - * this area. - * This method should check if the element is deletable and is always called - * before onDragEnter/onDragOver/onDragExit. - * @param {!IDraggable} element The block or bubble currently being - * dragged. - * @param {boolean} _couldConnect Whether the element could could connect to - * another. - * @return {boolean} Whether the element provided would be deleted if dropped on - * this area. - * @override - */ - override wouldDelete(element: IDraggable, _couldConnect: boolean): boolean; - /** - * Handles when a cursor with a block or bubble enters this drag target. - * @param {!IDraggable} _dragElement The block or bubble currently being - * dragged. - * @override - */ - override onDragEnter(_dragElement: IDraggable): void; - /** - * Handles when a cursor with a block or bubble exits this drag target. - * @param {!IDraggable} _dragElement The block or bubble currently being - * dragged. - * @override - */ - override onDragExit(_dragElement: IDraggable): void; - /** - * Handles when a block or bubble is dropped on this component. - * Should not handle delete here. - * @param {!IDraggable} _dragElement The block or bubble currently being - * dragged. - * @override - */ - override onDrop(_dragElement: IDraggable): void; - /** - * Updates the internal wouldDelete_ state. - * @param {boolean} wouldDelete The new value for the wouldDelete state. - * @protected - * @override - */ - protected override updateWouldDelete_(wouldDelete: boolean): void; - wouldDelete_: any; /** * Adds or removes the CSS style of the cursor over the toolbox based whether * the block or bubble over it is expected to be deleted if dropped (using the @@ -24891,7 +25400,7 @@ declare module "toolbox/toolbox" { * if no item exists. * @public */ - public getToolboxItemById(id: string): IToolboxItem | null; + public getToolboxItemById(id: string): (() => void) | null; /** * Gets the width of the toolbox. * @return {number} The width of the toolbox. @@ -24922,24 +25431,24 @@ declare module "toolbox/toolbox" { * currently selected. * @public */ - public getSelectedItem(): ISelectableToolboxItem | null; + public getSelectedItem(): (() => void) | null; /** * Gets the previously selected item. - * @return {?ISelectableToolboxItem} The previously selected item, or null if no - * item was previously selected. + * @return {?ISelectableToolboxItem} The previously selected item, or null if + * no item was previously selected. * @public */ - public getPreviouslySelectedItem(): ISelectableToolboxItem | null; + public getPreviouslySelectedItem(): (() => void) | null; /** * Gets whether or not the toolbox is horizontal. - * @return {boolean} True if the toolbox is horizontal, false if the toolbox is - * vertical. + * @return {boolean} True if the toolbox is horizontal, false if the toolbox + * is vertical. * @public */ public isHorizontal(): boolean; /** - * Positions the toolbox based on whether it is a horizontal toolbox and whether - * the workspace is in rtl. + * Positions the toolbox based on whether it is a horizontal toolbox and + * whether the workspace is in rtl. * @public */ public position(): void; @@ -24959,8 +25468,8 @@ declare module "toolbox/toolbox" { */ refreshTheme(): void; /** - * Updates the flyout's content without closing it. Should be used in response - * to a change in one of the dynamic categories, such as variables or + * Updates the flyout's content without closing it. Should be used in + * response to a change in one of the dynamic categories, such as variables or * procedures. * @public */ @@ -24983,17 +25492,18 @@ declare module "toolbox/toolbox" { * @param {?IToolboxItem} newItem The toolbox item to select. * @public */ - public setSelectedItem(newItem: IToolboxItem | null): void; + public setSelectedItem(newItem: (() => void) | null): void; /** * Decides whether the old item should be deselected. * @param {?ISelectableToolboxItem} oldItem The previously selected * toolbox item. * @param {?ISelectableToolboxItem} newItem The newly selected toolbox * item. - * @return {boolean} True if the old item should be deselected, false otherwise. + * @return {boolean} True if the old item should be deselected, false + * otherwise. * @protected */ - protected shouldDeselectItem_(oldItem: ISelectableToolboxItem | null, newItem: ISelectableToolboxItem | null): boolean; + protected shouldDeselectItem_(oldItem: (() => void) | null, newItem: (() => void) | null): boolean; /** * Decides whether the new item should be selected. * @param {?ISelectableToolboxItem} oldItem The previously selected @@ -25003,14 +25513,14 @@ declare module "toolbox/toolbox" { * @return {boolean} True if the new item should be selected, false otherwise. * @protected */ - protected shouldSelectItem_(oldItem: ISelectableToolboxItem | null, newItem: ISelectableToolboxItem | null): boolean; + protected shouldSelectItem_(oldItem: (() => void) | null, newItem: (() => void) | null): boolean; /** * Deselects the given item, marks it as unselected, and updates aria state. * @param {!ISelectableToolboxItem} item The previously selected * toolbox item which should be deselected. * @protected */ - protected deselectItem_(item: ISelectableToolboxItem): void; + protected deselectItem_(item: () => void): void; /** * Selects the given item, marks it selected, and updates aria state. * @param {?ISelectableToolboxItem} oldItem The previously selected @@ -25019,7 +25529,7 @@ declare module "toolbox/toolbox" { * item. * @protected */ - protected selectItem_(oldItem: ISelectableToolboxItem | null, newItem: ISelectableToolboxItem): void; + protected selectItem_(oldItem: (() => void) | null, newItem: () => void): void; /** * Selects the toolbox item by its position in the list of toolbox items. * @param {number} position The position of the item to select. @@ -25033,7 +25543,7 @@ declare module "toolbox/toolbox" { * @param {?ISelectableToolboxItem} newItem The newly selected toolbox item. * @protected */ - protected updateFlyout_(oldItem: ISelectableToolboxItem | null, newItem: ISelectableToolboxItem | null): void; + protected updateFlyout_(oldItem: (() => void) | null, newItem: (() => void) | null): void; /** * Emits an event when a new toolbox item is selected. * @param {?ISelectableToolboxItem} oldItem The previously selected @@ -25064,7 +25574,8 @@ declare module "toolbox/toolbox" { private selectNext_; /** * Selects the previous visible toolbox item. - * @return {boolean} True if a previous category was selected, false otherwise. + * @return {boolean} True if a previous category was selected, false + * otherwise. * @private */ private selectPrevious_; @@ -25074,32 +25585,28 @@ declare module "toolbox/toolbox" { */ public dispose(): void; } - import { WorkspaceSvg } from "workspace_svg"; - import * as toolbox from "utils/toolbox"; - import { IToolboxItem } from "interfaces/i_toolbox_item"; - import { ISelectableToolboxItem } from "interfaces/i_selectable_toolbox_item"; - import { ShortcutRegistry } from "shortcut_registry"; - import { IFlyout } from "interfaces/i_flyout"; - import { Rect } from "utils/rect"; - import { IDraggable } from "interfaces/i_draggable"; - import { DeleteArea } from "delete_area"; + import { IAutoHideable } from "core/interfaces/i_autohideable"; + import { IKeyboardAccessible } from "core/interfaces/i_keyboard_accessible"; + import { IStyleable } from "core/interfaces/i_styleable"; + import { IToolbox } from "core/interfaces/i_toolbox"; + import { DeleteArea } from "core/delete_area"; + import { WorkspaceSvg } from "core/workspace_svg"; + import * as toolbox from "core/utils/toolbox"; + import { ShortcutRegistry } from "core/shortcut_registry"; + import { IFlyout } from "core/interfaces/i_flyout"; } -declare module "flyout_vertical" { - export class VerticalFlyout { - /** - * Class for a flyout. - * @param {!Options} workspaceOptions Dictionary of options for the - * workspace. - * @extends {Flyout} - * @constructor - * @alias Blockly.VerticalFlyout - */ - constructor(workspaceOptions: Options); +declare module "core/flyout_vertical" { + /** + * Class for a flyout. + * @extends {Flyout} + * @alias Blockly.VerticalFlyout + */ + export class VerticalFlyout extends Flyout { /** * Sets the translation of the flyout to match the scrollbars. - * @param {!{x:number,y:number}} xyRatio Contains a y property which is a float - * between 0 and 1 specifying the degree of scrolling and a - * similar x property. + * @param {!{x:number,y:number}} xyRatio Contains a y property which is a + * float between 0 and 1 specifying the degree of scrolling and a similar + * x property. * @protected */ protected setMetrics_(xyRatio: { @@ -25120,7 +25627,6 @@ declare module "flyout_vertical" { * Move the flyout to the edge of the workspace. */ position(): void; - height_: any; /** * Create and set the path for the visible boundaries of the flyout. * @param {number} width The width of the flyout, not including the @@ -25146,7 +25652,7 @@ declare module "flyout_vertical" { * @param {!Array} gaps The visible gaps between blocks. * @protected */ - protected layout_(contents: Array, gaps: Array): void; + protected layout_(contents: Array, gaps: Array): void; /** * Determine if a drag delta is toward the workspace, based on the position * and orientation of the flyout. This is used in determineDragIntention_ to @@ -25156,30 +25662,31 @@ declare module "flyout_vertical" { * @return {boolean} True if the drag is toward the workspace. * @package */ - isDragTowardWorkspace(currentDragDeltaXY: Coordinate): boolean; - /** - * Returns the bounding rectangle of the drag target area in pixel units - * relative to viewport. - * @return {?Rect} The component's bounding box. Null if drag - * target area should be ignored. - */ - getClientRect(): Rect | null; + isDragTowardWorkspace(currentDragDeltaXY: { + x: number; + y: number; + /** + * Class for a flyout. + * @extends {Flyout} + * @alias Blockly.VerticalFlyout + */ + clone(): any; + scale(s: number): any; + translate(tx: number, ty: number): any; + }): boolean; /** * Compute width of flyout. toolbox.Position mat under each block. * For RTL: Lay out the blocks and buttons to be right-aligned. * @protected */ protected reflowInternal_(): void; - width_: any; } export namespace VerticalFlyout { const registryName: string; } - import { Coordinate } from "utils/coordinate"; - import { Rect } from "utils/rect"; - import { Options } from "options"; + import { Flyout } from "core/flyout_base"; } -declare module "inject" { +declare module "core/inject" { /** * Inject a Blockly editor into the specified container element (usually a div). * @param {Element|string} container Containing element, or its ID, @@ -25188,11 +25695,10 @@ declare module "inject" { * @return {!WorkspaceSvg} Newly created main workspace. * @alias Blockly.inject */ - export function inject(container: Element | string, opt_options?: BlocklyOptions | undefined): WorkspaceSvg; - import { BlocklyOptions } from "blockly_options"; - import { WorkspaceSvg } from "workspace_svg"; + export function inject(container: Element | string, opt_options?: (() => void) | undefined): WorkspaceSvg; + import { WorkspaceSvg } from "core/workspace_svg"; } -declare module "blockly" { +declare module "core/blockly" { /** * Blockly core version. * This constant is overridden by the build script (npm run build) to the value @@ -25205,103 +25711,20 @@ declare module "blockly" { */ export const VERSION: "uncompiled"; /** - * Returns the dimensions of the specified SVG image. - * @param {!SVGElement} svg SVG image. - * @return {!Size} Contains width and height properties. - * @deprecated Use workspace.setCachedParentSvgSize. (2021 March 5) - * @alias Blockly.svgSize - */ - export const svgSize: (svg: SVGElement) => utils.Size; - /** - * Copy a block or workspace comment onto the local clipboard. - * @param {!ICopyable} toCopy Block or Workspace Comment to be copied. - * @package - * @alias Blockly.copy - */ - export const copy: (toCopy: ICopyable) => void; - /** - * Paste a block or workspace comment on to the main workspace. - * @return {boolean} True if the paste was successful, false otherwise. - * @package - * @alias Blockly.paste - */ - export const paste: () => boolean; - /** - * Duplicate this block and its children, or a workspace comment. - * @param {!ICopyable} toDuplicate Block or Workspace Comment to be - * copied. - * @package - * @alias Blockly.duplicate - */ - export const duplicate: (toDuplicate: ICopyable) => void; - /** - * Returns the main workspace. Returns the last used main workspace (based on - * focus). Try not to use this function, particularly if there are multiple - * Blockly instances on a page. - * @return {!Workspace} The main workspace. - * @alias Blockly.getMainWorkspace - */ - export const getMainWorkspace: () => Workspace; - /** - * Define blocks from an array of JSON block definitions, as might be generated - * by the Blockly Developer Tools. - * @param {!Array} jsonArray An array of JSON block definitions. - * @alias Blockly.defineBlocksWithJsonArray - */ - export const defineBlocksWithJsonArray: (jsonArray: any[]) => void; - /** - * Set the parent container. This is the container element that the WidgetDiv, - * DropDownDiv, and Tooltip are rendered into the first time `Blockly.inject` - * is called. - * This method is a NOP if called after the first ``Blockly.inject``. - * @param {!Element} container The container element. - * @alias Blockly.setParentContainer - */ - export const setParentContainer: (newParent: Element) => void; - /** Aliases. */ - /** - * @see colour.hueToHex - * @deprecated Use Blockly.utils.colour.hueToHex (September 2021). - * @alias Blockly.hueToHex - */ - export const hueToHex: (hue: number) => string; - /** - * @see browserEvents.bind - * @alias Blockly.bindEvent_ - */ - export const bindEvent_: (node: EventTarget, name: string, thisObject: any, func: Function) => any[][]; - /** - * @see browserEvents.unbind - * @alias Blockly.unbindEvent_ - */ - export const unbindEvent_: (bindData: any[][]) => Function; - /** - * @see browserEvents.conditionalBind - * @alias Blockly.bindEventWithChecks_ - */ - export const bindEventWithChecks_: (node: EventTarget, name: string, thisObject: any, func: Function, opt_noCaptureIdentifier?: boolean, opt_noPreventDefault?: boolean) => any[][]; - /** - * @see constants.ALIGN.LEFT + * @see Blockly.Input.Align.LEFT * @alias Blockly.ALIGN_LEFT */ - export const ALIGN_LEFT: any; + export const ALIGN_LEFT: number; /** - * @see constants.ALIGN.CENTRE + * @see Blockly.Input.Align.CENTRE * @alias Blockly.ALIGN_CENTRE */ - export const ALIGN_CENTRE: any; + export const ALIGN_CENTRE: number; /** - * @see constants.ALIGN.RIGHT + * @see Blockly.Input.Align.RIGHT * @alias Blockly.ALIGN_RIGHT */ - export const ALIGN_RIGHT: any; - /** - * @see common.svgResize - */ - export const svgResize: (workspace: WorkspaceSvg) => void; - /** - * Aliases for constants used for connection and input types. - */ + export const ALIGN_RIGHT: number; /** * @see ConnectionType.INPUT_VALUE * @alias Blockly.INPUT_VALUE @@ -25350,247 +25773,355 @@ declare module "blockly" { * @alias Blockly.TOOLBOX_AT_RIGHT */ export const TOOLBOX_AT_RIGHT: number; - export const LINE_MODE_MULTIPLIER: number; - export const PAGE_MODE_MULTIPLIER: number; - export const DRAG_RADIUS: 5; - export const FLYOUT_DRAG_RADIUS: 10; - export const SNAP_RADIUS: 28; - export const CONNECTING_SNAP_RADIUS: 28; - export const CURRENT_CONNECTION_PREFERENCE: 8; - export const BUMP_DELAY: 250; - export const BUMP_RANDOMNESS: 10; - export const COLLAPSE_CHARS: 30; - export const LONGPRESS: 750; - export const SOUND_LIMIT: 100; - export const DRAG_STACK: true; - export const HSV_SATURATION: 0.45; - export const HSV_VALUE: 0.65; - export const SPRITE: { + /** + * Size the SVG image to completely fill its container. Call this when the view + * actually changes sizes (e.g. on a window resize/device orientation change). + * See workspace.resizeContents to resize the workspace when the contents + * change (e.g. when a block is added or removed). + * Record the height/width of the SVG image. + * @param {!WorkspaceSvg} workspace Any workspace in the SVG. + * @see Blockly.common.svgResize + * @alias Blockly.svgResize + */ + export const svgResize: (workspace: WorkspaceSvg) => void; + /** + * Returns the main workspace. Returns the last used main workspace (based on + * focus). Try not to use this function, particularly if there are multiple + * Blockly instances on a page. + * @return {!Workspace} The main workspace. + * @see Blockly.common.getMainWorkspace + * @alias Blockly.getMainWorkspace + */ + export const getMainWorkspace: () => Workspace; + /** + * Define blocks from an array of JSON block definitions, as might be generated + * by the Blockly Developer Tools. + * @param {!Array} jsonArray An array of JSON block definitions. + * @see Blockly.common.defineBlocksWithJsonArray + * @alias Blockly.defineBlocksWithJsonArray + */ + export const defineBlocksWithJsonArray: (jsonArray: Object[]) => void; + /** + * Set the parent container. This is the container element that the WidgetDiv, + * dropDownDiv, and Tooltip are rendered into the first time `Blockly.inject` + * is called. + * This method is a NOP if called after the first ``Blockly.inject``. + * @param {!Element} container The container element. + * @see Blockly.common.setParentContainer + * @alias Blockly.setParentContainer + */ + export const setParentContainer: (newParent: Element) => void; + /** + * Returns the dimensions of the specified SVG image. + * @param {!SVGElement} svg SVG image. + * @return {!Size} Contains width and height properties. + * @deprecated Use workspace.setCachedParentSvgSize. (2021 March 5) + * @see Blockly.WorkspaceSvg.setCachedParentSvgSize + * @alias Blockly.svgSize + */ + export const svgSize: (svg: SVGElement) => { width: number; height: number; - url: string; }; - export const DRAG_NONE: 0; - export const DRAG_STICKY: 1; - export const DRAG_BEGIN: 1; - export const DRAG_FREE: 2; + export const COLLAPSE_CHARS: 30; + export const DRAG_STACK: true; export const OPPOSITE_TYPE: any[]; - export const VARIABLE_CATEGORY_NAME: "VARIABLE"; - export const VARIABLE_DYNAMIC_CATEGORY_NAME: "VARIABLE_DYNAMIC"; - export const PROCEDURE_CATEGORY_NAME: "PROCEDURE"; export const RENAME_VARIABLE_ID: "RENAME_VARIABLE_ID"; export const DELETE_VARIABLE_ID: "DELETE_VARIABLE_ID"; - export const COLLAPSED_INPUT_NAME: any; - export const COLLAPSED_FIELD_NAME: any; - import * as utils from "utils"; + export const COLLAPSED_INPUT_NAME: "_TEMP_COLLAPSED_INPUT"; + export const COLLAPSED_FIELD_NAME: "_TEMP_COLLAPSED_FIELD"; + /** + * 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} + * @alias Blockly.VARIABLE_CATEGORY_NAME + */ + export const 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} + * @alias Blockly.VARIABLE_DYNAMIC_CATEGORY_NAME + */ + export const VARIABLE_DYNAMIC_CATEGORY_NAME: "VARIABLE_DYNAMIC"; + /** + * String for use in the "custom" attribute of a category in toolbox XML. + * This string indicates that the category should be dynamically populated with + * procedure blocks. + * @const {string} + * @alias Blockly.PROCEDURE_CATEGORY_NAME + */ + export const PROCEDURE_CATEGORY_NAME: "PROCEDURE"; + export namespace serialization { + export { serializationBlocks as blocks }; + export { serializationExceptions as exceptions }; + export { serializationPriorities as priorities }; + export { serializationRegistry as registry }; + export { serializationVariables as variables }; + export { serializationWorkspaces as workspaces }; + export { ISerializer }; + } + import { WorkspaceSvg } from "core/workspace_svg"; + /** + * Close tooltips, context menus, dropdown selections, etc. + * @param {boolean=} opt_onlyClosePopups Whether only popups should be closed. + * @see Blockly.WorkspaceSvg.hideChaff + * @alias Blockly.hideChaff + */ + export function hideChaff(opt_onlyClosePopups?: boolean | undefined): void; + import { Workspace } from "core/workspace"; /** * Size the workspace when the contents change. This also updates * scrollbars accordingly. * @param {!WorkspaceSvg} workspace The workspace to resize. - * @deprecated + * @deprecated Use workspace.resizeContents. (2021 December) + * @see Blockly.WorkspaceSvg.resizeContents * @alias Blockly.resizeSvgContents */ function resizeSvgContentsLocal(workspace: WorkspaceSvg): void; - import { ICopyable } from "interfaces/i_copyable"; /** - * Close tooltips, context menus, dropdown selections, etc. - * @deprecated Use Blockly.common.getMainWorkspace().hideChaff() - * @param {boolean=} opt_onlyClosePopups Whether only popups should be closed. - * @alias Blockly.hideChaff + * Copy a block or workspace comment onto the local clipboard. + * @param {!ICopyable} toCopy Block or Workspace Comment to be copied. + * @deprecated Use Blockly.clipboard.copy(). (2021 December) + * @see Blockly.clipboard.copy + * @alias Blockly.copy */ - export function hideChaff(opt_onlyClosePopups?: boolean | undefined): void; - import { Workspace } from "workspace"; + export function copy(toCopy: { + (): void; + CopyData: ICopyable.CopyData; + }): void; + /** + * Paste a block or workspace comment on to the main workspace. + * @return {boolean} True if the paste was successful, false otherwise. + * @deprecated Use Blockly.clipboard.paste(). (2021 December) + * @see Blockly.clipboard.paste + * @alias Blockly.paste + */ + export function paste(): boolean; + /** + * Duplicate this block and its children, or a workspace comment. + * @param {!ICopyable} toDuplicate Block or Workspace Comment to be + * copied. + * @deprecated Use Blockly.clipboard.duplicate(). (2021 December) + * @see Blockly.clipboard.duplicate + * @alias Blockly.duplicate + */ + export function duplicate(toDuplicate: { + (): void; + CopyData: ICopyable.CopyData; + }): void; /** * Is the given string a number (includes negative and decimals). * @param {string} str Input string. * @return {boolean} True if number, false otherwise. - * @deprecated + * @deprecated Use Blockly.utils.string.isNumber(str). (2021 December) + * @see Blockly.utils.string.isNumber * @alias Blockly.isNumber */ export function isNumber(str: string): boolean; - import { WorkspaceSvg } from "workspace_svg"; - import { ASTNode } from "keyboard_nav/ast_node"; - import { BasicCursor } from "keyboard_nav/basic_cursor"; - import { Block } from "block"; - import { BlocklyOptions } from "blockly_options"; - import { BlockDragger } from "block_dragger"; - import { BlockDragSurfaceSvg } from "block_drag_surface"; - import { BlockSvg } from "block_svg"; - import { Blocks } from "blocks"; - import { Bubble } from "bubble"; - import { BubbleDragger } from "bubble_dragger"; - import { CollapsibleToolboxCategory } from "toolbox/collapsible_category"; - import { Comment } from "comment"; - import { ComponentManager } from "component_manager"; - import { Connection } from "connection"; - import { ConnectionType } from "connection_type"; - import { ConnectionChecker } from "connection_checker"; - import { ConnectionDB } from "connection_db"; - import * as ContextMenu from "contextmenu"; - import * as ContextMenuItems from "contextmenu_items"; - import { ContextMenuRegistry } from "contextmenu_registry"; - import * as Css from "css"; - import { Cursor } from "keyboard_nav/cursor"; - import { DeleteArea } from "delete_area"; - import { DragTarget } from "drag_target"; - import { DropDownDiv } from "dropdowndiv"; - import * as Events from "events/events"; - import * as Extensions from "extensions"; - import { Field } from "field"; - import { FieldAngle } from "field_angle"; - import { FieldCheckbox } from "field_checkbox"; - import { FieldColour } from "field_colour"; - import { FieldDropdown } from "field_dropdown"; - import { FieldImage } from "field_image"; - import { FieldLabel } from "field_label"; - import { FieldLabelSerializable } from "field_label_serializable"; - import { FieldMultilineInput } from "field_multilineinput"; - import { FieldNumber } from "field_number"; - import { FieldTextInput } from "field_textinput"; - import { FieldVariable } from "field_variable"; - import { Flyout } from "flyout_base"; - import { FlyoutButton } from "flyout_button"; - import { FlyoutMetricsManager } from "flyout_metrics_manager"; - import { Generator } from "generator"; - import { Gesture } from "gesture"; - import { Grid } from "grid"; - import { HorizontalFlyout } from "flyout_horizontal"; - import { IASTNodeLocation } from "interfaces/i_ast_node_location"; - import { IASTNodeLocationSvg } from "interfaces/i_ast_node_location_svg"; - import { IASTNodeLocationWithBlock } from "interfaces/i_ast_node_location_with_block"; - import { IAutoHideable } from "interfaces/i_autohideable"; - import { IBlockDragger } from "interfaces/i_block_dragger"; - import { IBoundedElement } from "interfaces/i_bounded_element"; - import { IBubble } from "interfaces/i_bubble"; - import { ICollapsibleToolboxItem } from "interfaces/i_collapsible_toolbox_item"; - import { IComponent } from "interfaces/i_component"; - import { IConnectionChecker } from "interfaces/i_connection_checker"; - import { IContextMenu } from "interfaces/i_contextmenu"; - import { Icon } from "icon"; - import { IDeletable } from "interfaces/i_deletable"; - import { IDeleteArea } from "interfaces/i_delete_area"; - import { IDragTarget } from "interfaces/i_drag_target"; - import { IDraggable } from "interfaces/i_draggable"; - import { IFlyout } from "interfaces/i_flyout"; - import { IKeyboardAccessible } from "interfaces/i_keyboard_accessible"; - import { IMetricsManager } from "interfaces/i_metrics_manager"; - import { IMovable } from "interfaces/i_movable"; - import { Input } from "input"; - import { InsertionMarkerManager } from "insertion_marker_manager"; - import { IPositionable } from "interfaces/i_positionable"; - import { IRegistrable } from "interfaces/i_registrable"; - import { IRegistrableField } from "interfaces/i_registrable_field"; - import { ISelectable } from "interfaces/i_selectable"; - import { ISelectableToolboxItem } from "interfaces/i_selectable_toolbox_item"; - import { IStyleable } from "interfaces/i_styleable"; - import { IToolbox } from "interfaces/i_toolbox"; - import { IToolboxItem } from "interfaces/i_toolbox_item"; - import { Marker } from "keyboard_nav/marker"; - import { MarkerManager } from "marker_manager"; - import { Menu } from "menu"; - import { MenuItem } from "menuitem"; - import { MetricsManager } from "metrics_manager"; - import { Mutator } from "mutator"; - import { Names } from "names"; - import { Options } from "options"; - import * as Procedures from "procedures"; - import { RenderedConnection } from "rendered_connection"; - import { Scrollbar } from "scrollbar"; - import { ScrollbarPair } from "scrollbar_pair"; - import * as ShortcutItems from "shortcut_items"; - import { ShortcutRegistry } from "shortcut_registry"; - import { TabNavigateCursor } from "keyboard_nav/tab_navigate_cursor"; - import { Theme } from "theme"; - import * as Themes from "theme/themes"; - import { ThemeManager } from "theme_manager"; - import { Toolbox } from "toolbox/toolbox"; - import { ToolboxCategory } from "toolbox/category"; - import { ToolboxItem } from "toolbox/toolbox_item"; - import { ToolboxSeparator } from "toolbox/separator"; - import * as Tooltip from "tooltip"; - import * as Touch from "touch"; - import { TouchGesture } from "touch_gesture"; - import { Trashcan } from "trashcan"; - import { VariableMap } from "variable_map"; - import { VariableModel } from "variable_model"; - import * as Variables from "variables"; - import * as VariablesDynamic from "variables_dynamic"; - import { VerticalFlyout } from "flyout_vertical"; - import { Warning } from "warning"; - import * as WidgetDiv from "widgetdiv"; - import { WorkspaceAudio } from "workspace_audio"; - import { WorkspaceComment } from "workspace_comment"; - import { WorkspaceCommentSvg } from "workspace_comment_svg"; - import { WorkspaceDragSurfaceSvg } from "workspace_drag_surface_svg"; - import { WorkspaceDragger } from "workspace_dragger"; - import * as Xml from "xml"; - import { ZoomControls } from "zoom_controls"; - import * as blockAnimations from "block_animations"; - import * as blockRendering from "renderers/common/block_rendering"; - import * as browserEvents from "browser_events"; - import * as bumpObjects from "bump_objects"; - import * as clipboard from "clipboard"; - import * as common from "common"; - import * as constants from "constants"; - import * as dialog from "dialog"; - import * as fieldRegistry from "field_registry"; - import * as geras from "renderers/geras/geras"; - import { inject } from "inject"; - import { inputTypes } from "input_types"; - import * as minimalist from "renderers/minimalist/minimalist"; - import * as registry from "registry"; - import * as thrasos from "renderers/thrasos/thrasos"; - import * as uiPosition from "positionable_helpers"; - import * as zelos from "renderers/zelos/zelos"; - export { resizeSvgContentsLocal as resizeSvgContents, ASTNode, BasicCursor, Block, BlocklyOptions, BlockDragger, BlockDragSurfaceSvg, BlockSvg, Blocks, Bubble, BubbleDragger, CollapsibleToolboxCategory, Comment, ComponentManager, Connection, ConnectionType, ConnectionChecker, ConnectionDB, ContextMenu, ContextMenuItems, ContextMenuRegistry, Css, Cursor, DeleteArea, DragTarget, DropDownDiv, Events, Extensions, Field, FieldAngle, FieldCheckbox, FieldColour, FieldDropdown, FieldImage, FieldLabel, FieldLabelSerializable, FieldMultilineInput, FieldNumber, FieldTextInput, FieldVariable, Flyout, FlyoutButton, FlyoutMetricsManager, Generator, Gesture, Grid, HorizontalFlyout, IASTNodeLocation, IASTNodeLocationSvg, IASTNodeLocationWithBlock, IAutoHideable, IBlockDragger, IBoundedElement, IBubble, ICollapsibleToolboxItem, IComponent, IConnectionChecker, IContextMenu, Icon, ICopyable, IDeletable, IDeleteArea, IDragTarget, IDraggable, IFlyout, IKeyboardAccessible, IMetricsManager, IMovable, Input, InsertionMarkerManager, IPositionable, IRegistrable, IRegistrableField, ISelectable, ISelectableToolboxItem, IStyleable, IToolbox, IToolboxItem, Marker, MarkerManager, Menu, MenuItem, MetricsManager, Mutator, Names, Options, Procedures, RenderedConnection, Scrollbar, ScrollbarPair, ShortcutItems, ShortcutRegistry, TabNavigateCursor, Theme, Themes, ThemeManager, Toolbox, ToolboxCategory, ToolboxItem, ToolboxSeparator, Tooltip, Touch, TouchGesture, Trashcan, VariableMap, VariableModel, Variables, VariablesDynamic, VerticalFlyout, Warning, WidgetDiv, Workspace, WorkspaceAudio, WorkspaceComment, WorkspaceCommentSvg, WorkspaceDragSurfaceSvg, WorkspaceDragger, WorkspaceSvg, Xml, ZoomControls, blockAnimations, blockRendering, browserEvents, bumpObjects, clipboard, common, ConnectionType as connectionTypes, constants, dialog, fieldRegistry, geras, inject, inputTypes, minimalist, registry, thrasos, uiPosition, utils, zelos }; -} -declare module "serialization/variables" { /** - * Represents the state of a given variable. + * Convert a hue (HSV model) into an RGB hex triplet. + * @param {number} hue Hue on a colour wheel (0-360). + * @return {string} RGB code, e.g. '#5ba65b'. + * @deprecated Use Blockly.utils.colour.hueToHex(). (2021 December) + * @see Blockly.utils.colour.hueToHex + * @alias Blockly.hueToHex */ - export type State = { - name: string; - id: string; - type: (string | undefined); - }; + export function hueToHex(hue: number): string; /** - * Represents the state of a given variable. - * @typedef {{ - * name: string, - * id: string, - * type: (string|undefined) - * }} - * @alias Blockly.serialization.variables.State + * Bind an event handler that should be called regardless of whether it is part + * of the active touch stream. + * Use this for events that are not part of a multi-part gesture (e.g. + * mouseover for tooltips). + * @param {!EventTarget} node Node upon which to listen. + * @param {string} name Event name to listen to (e.g. 'mousedown'). + * @param {?Object} thisObject The value of 'this' in the function. + * @param {!Function} func Function to call when event is triggered. + * @return {!browserEvents.Data} Opaque data that can be passed to + * unbindEvent_. + * @deprecated Use Blockly.browserEvents.bind(). (December 2021) + * @see Blockly.browserEvents.bind + * @alias Blockly.bindEvent_ */ - export let State: any; -} -declare module "serialization/workspaces" { + export function bindEvent_(node: EventTarget, name: string, thisObject: Object | null, func: Function): any[][]; /** - * Returns the state of the workspace as a plain JavaScript object. - * @param {!Workspace} workspace The workspace to serialize. - * @return {!Object} The serialized state of the workspace. - * @alias Blockly.serialization.workspaces.save + * Unbind one or more events event from a function call. + * @param {!browserEvents.Data} bindData Opaque data from bindEvent_. + * This list is emptied during the course of calling this function. + * @return {!Function} The function call. + * @deprecated Use Blockly.browserEvents.unbind(). (December 2021) + * @see browserEvents.unbind + * @alias Blockly.unbindEvent_ */ - export function save(workspace: Workspace): { - [x: string]: any; - }; + export function unbindEvent_(bindData: any[][]): Function; /** - * Loads the variable represented by the given state into the given workspace. - * @param {!Object} state The state of the workspace to deserialize - * into the workspace. - * @param {!Workspace} workspace The workspace to add the new state to. - * @param {{recordUndo: (boolean|undefined)}=} param1 - * recordUndo: If true, events triggered by this function will be undo-able - * by the user. False by default. - * @alias Blockly.serialization.workspaces.load + * Bind an event handler that can be ignored if it is not part of the active + * touch stream. + * Use this for events that either start or continue a multi-part gesture (e.g. + * mousedown or mousemove, which may be part of a drag or click). + * @param {!EventTarget} node Node upon which to listen. + * @param {string} name Event name to listen to (e.g. 'mousedown'). + * @param {?Object} thisObject The value of 'this' in the function. + * @param {!Function} func Function to call when event is triggered. + * @param {boolean=} opt_noCaptureIdentifier True if triggering on this event + * should not block execution of other event handlers on this touch or + * other simultaneous touches. False by default. + * @param {boolean=} opt_noPreventDefault True if triggering on this event + * should prevent the default handler. False by default. If + * opt_noPreventDefault is provided, opt_noCaptureIdentifier must also be + * provided. + * @return {!browserEvents.Data} Opaque data that can be passed to + * unbindEvent_. + * @deprecated Use Blockly.browserEvents.conditionalBind(). (December 2021) + * @see browserEvents.conditionalBind + * @alias Blockly.bindEventWithChecks_ */ - export function load(state: { - [x: string]: any; - }, workspace: Workspace, { recordUndo }?: { - recordUndo: (boolean | undefined); - } | undefined): void; - import { Workspace } from "workspace"; -} -declare module "requires" { - export {}; + export function bindEventWithChecks_(node: EventTarget, name: string, thisObject: Object | null, func: Function, opt_noCaptureIdentifier?: boolean | undefined, opt_noPreventDefault?: boolean | undefined): any[][]; + import { ASTNode } from "core/keyboard_nav/ast_node"; + import { BasicCursor } from "core/keyboard_nav/basic_cursor"; + import { Block } from "core/block"; + import { BlocklyOptions } from "core/blockly_options"; + import { BlockDragger } from "core/block_dragger"; + import { BlockDragSurfaceSvg } from "core/block_drag_surface"; + import { BlockSvg } from "core/block_svg"; + import { Blocks } from "core/blocks"; + import { Bubble } from "core/bubble"; + import { BubbleDragger } from "core/bubble_dragger"; + import { CollapsibleToolboxCategory } from "core/toolbox/collapsible_category"; + import { Comment } from "core/comment"; + import { ComponentManager } from "core/component_manager"; + import { Connection } from "core/connection"; + import { ConnectionType } from "core/connection_type"; + import { ConnectionChecker } from "core/connection_checker"; + import { ConnectionDB } from "core/connection_db"; + import * as ContextMenu from "core/contextmenu"; + import * as ContextMenuItems from "core/contextmenu_items"; + import { ContextMenuRegistry } from "core/contextmenu_registry"; + import * as Css from "core/css"; + import { Cursor } from "core/keyboard_nav/cursor"; + import { DeleteArea } from "core/delete_area"; + import { DragTarget } from "core/drag_target"; + import * as dropDownDiv from "core/dropdowndiv"; + import * as Events from "core/events/events"; + import * as Extensions from "core/extensions"; + import { Field } from "core/field"; + import { FieldAngle } from "core/field_angle"; + import { FieldCheckbox } from "core/field_checkbox"; + import { FieldColour } from "core/field_colour"; + import { FieldDropdown } from "core/field_dropdown"; + import { FieldImage } from "core/field_image"; + import { FieldLabel } from "core/field_label"; + import { FieldLabelSerializable } from "core/field_label_serializable"; + import { FieldMultilineInput } from "core/field_multilineinput"; + import { FieldNumber } from "core/field_number"; + import { FieldTextInput } from "core/field_textinput"; + import { FieldVariable } from "core/field_variable"; + import { Flyout } from "core/flyout_base"; + import { FlyoutButton } from "core/flyout_button"; + import { FlyoutMetricsManager } from "core/flyout_metrics_manager"; + import { Generator } from "core/generator"; + import { Gesture } from "core/gesture"; + import { Grid } from "core/grid"; + import { HorizontalFlyout } from "core/flyout_horizontal"; + import { IASTNodeLocation } from "core/interfaces/i_ast_node_location"; + import { IASTNodeLocationSvg } from "core/interfaces/i_ast_node_location_svg"; + import { IASTNodeLocationWithBlock } from "core/interfaces/i_ast_node_location_with_block"; + import { IAutoHideable } from "core/interfaces/i_autohideable"; + import { IBlockDragger } from "core/interfaces/i_block_dragger"; + import { IBoundedElement } from "core/interfaces/i_bounded_element"; + import { IBubble } from "core/interfaces/i_bubble"; + import { ICollapsibleToolboxItem } from "core/interfaces/i_collapsible_toolbox_item"; + import { IComponent } from "core/interfaces/i_component"; + import { IConnectionChecker } from "core/interfaces/i_connection_checker"; + import { IContextMenu } from "core/interfaces/i_contextmenu"; + import { Icon } from "core/icon"; + import { ICopyable } from "core/interfaces/i_copyable"; + import { IDeletable } from "core/interfaces/i_deletable"; + import { IDeleteArea } from "core/interfaces/i_delete_area"; + import { IDragTarget } from "core/interfaces/i_drag_target"; + import { IDraggable } from "core/interfaces/i_draggable"; + import { IFlyout } from "core/interfaces/i_flyout"; + import { IKeyboardAccessible } from "core/interfaces/i_keyboard_accessible"; + import { IMetricsManager } from "core/interfaces/i_metrics_manager"; + import { IMovable } from "core/interfaces/i_movable"; + import { Input } from "core/input"; + import { InsertionMarkerManager } from "core/insertion_marker_manager"; + import { IPositionable } from "core/interfaces/i_positionable"; + import { IRegistrable } from "core/interfaces/i_registrable"; + import { IRegistrableField } from "core/interfaces/i_registrable_field"; + import { ISelectable } from "core/interfaces/i_selectable"; + import { ISelectableToolboxItem } from "core/interfaces/i_selectable_toolbox_item"; + import { IStyleable } from "core/interfaces/i_styleable"; + import { IToolbox } from "core/interfaces/i_toolbox"; + import { IToolboxItem } from "core/interfaces/i_toolbox_item"; + import { Marker } from "core/keyboard_nav/marker"; + import { MarkerManager } from "core/marker_manager"; + import { Menu } from "core/menu"; + import { MenuItem } from "core/menuitem"; + import { MetricsManager } from "core/metrics_manager"; + import { Mutator } from "core/mutator"; + import { Msg } from "core/msg"; + import { Names } from "core/names"; + import { Options } from "core/options"; + import * as Procedures from "core/procedures"; + import { RenderedConnection } from "core/rendered_connection"; + import { Scrollbar } from "core/scrollbar"; + import { ScrollbarPair } from "core/scrollbar_pair"; + import * as ShortcutItems from "core/shortcut_items"; + import { ShortcutRegistry } from "core/shortcut_registry"; + import { TabNavigateCursor } from "core/keyboard_nav/tab_navigate_cursor"; + import { Theme } from "core/theme"; + import * as Themes from "core/theme/themes"; + import { ThemeManager } from "core/theme_manager"; + import { Toolbox } from "core/toolbox/toolbox"; + import { ToolboxCategory } from "core/toolbox/category"; + import { ToolboxItem } from "core/toolbox/toolbox_item"; + import { ToolboxSeparator } from "core/toolbox/separator"; + import * as Tooltip from "core/tooltip"; + import * as Touch from "core/touch"; + import { TouchGesture } from "core/touch_gesture"; + import { Trashcan } from "core/trashcan"; + import { VariableMap } from "core/variable_map"; + import { VariableModel } from "core/variable_model"; + import * as Variables from "core/variables"; + import * as VariablesDynamic from "core/variables_dynamic"; + import { VerticalFlyout } from "core/flyout_vertical"; + import { Warning } from "core/warning"; + import * as WidgetDiv from "core/widgetdiv"; + import { WorkspaceAudio } from "core/workspace_audio"; + import { WorkspaceComment } from "core/workspace_comment"; + import { WorkspaceCommentSvg } from "core/workspace_comment_svg"; + import { WorkspaceDragSurfaceSvg } from "core/workspace_drag_surface_svg"; + import { WorkspaceDragger } from "core/workspace_dragger"; + import * as Xml from "core/xml"; + import { ZoomControls } from "core/zoom_controls"; + import * as blockAnimations from "core/block_animations"; + import * as blockRendering from "core/renderers/common/block_rendering"; + import * as browserEvents from "core/browser_events"; + import * as bumpObjects from "core/bump_objects"; + import * as clipboard from "core/clipboard"; + import * as common from "core/common"; + import { config } from "core/config"; + import * as constants from "core/constants"; + import * as dialog from "core/dialog"; + import * as fieldRegistry from "core/field_registry"; + import * as geras from "core/renderers/geras/geras"; + import { inject } from "core/inject"; + import { inputTypes } from "core/input_types"; + import * as minimalist from "core/renderers/minimalist/minimalist"; + import * as registry from "core/registry"; + import * as serializationBlocks from "core/serialization/blocks"; + import * as serializationExceptions from "core/serialization/exceptions"; + import * as serializationPriorities from "core/serialization/priorities"; + import * as serializationRegistry from "core/serialization/registry"; + import * as serializationVariables from "core/serialization/variables"; + import * as serializationWorkspaces from "core/serialization/workspaces"; + import { ISerializer } from "core/interfaces/i_serializer"; + import * as thrasos from "core/renderers/thrasos/thrasos"; + import * as uiPosition from "core/positionable_helpers"; + import * as utils from "core/utils"; + import * as zelos from "core/renderers/zelos/zelos"; + export { resizeSvgContentsLocal as resizeSvgContents, ASTNode, BasicCursor, Block, BlocklyOptions, BlockDragger, BlockDragSurfaceSvg, BlockSvg, Blocks, Bubble, BubbleDragger, CollapsibleToolboxCategory, Comment, ComponentManager, Connection, ConnectionType, ConnectionChecker, ConnectionDB, ContextMenu, ContextMenuItems, ContextMenuRegistry, Css, Cursor, DeleteArea, DragTarget, dropDownDiv as DropDownDiv, Events, Extensions, Field, FieldAngle, FieldCheckbox, FieldColour, FieldDropdown, FieldImage, FieldLabel, FieldLabelSerializable, FieldMultilineInput, FieldNumber, FieldTextInput, FieldVariable, Flyout, FlyoutButton, FlyoutMetricsManager, Generator, Gesture, Grid, HorizontalFlyout, IASTNodeLocation, IASTNodeLocationSvg, IASTNodeLocationWithBlock, IAutoHideable, IBlockDragger, IBoundedElement, IBubble, ICollapsibleToolboxItem, IComponent, IConnectionChecker, IContextMenu, Icon, ICopyable, IDeletable, IDeleteArea, IDragTarget, IDraggable, IFlyout, IKeyboardAccessible, IMetricsManager, IMovable, Input, InsertionMarkerManager, IPositionable, IRegistrable, IRegistrableField, ISelectable, ISelectableToolboxItem, IStyleable, IToolbox, IToolboxItem, Marker, MarkerManager, Menu, MenuItem, MetricsManager, Mutator, Msg, Names, Options, Procedures, RenderedConnection, Scrollbar, ScrollbarPair, ShortcutItems, ShortcutRegistry, TabNavigateCursor, Theme, Themes, ThemeManager, Toolbox, ToolboxCategory, ToolboxItem, ToolboxSeparator, Tooltip, Touch, TouchGesture, Trashcan, VariableMap, VariableModel, Variables, VariablesDynamic, VerticalFlyout, Warning, WidgetDiv, Workspace, WorkspaceAudio, WorkspaceComment, WorkspaceCommentSvg, WorkspaceDragSurfaceSvg, WorkspaceDragger, WorkspaceSvg, Xml, ZoomControls, blockAnimations, blockRendering, browserEvents, bumpObjects, clipboard, common, config, ConnectionType as connectionTypes, constants, dialog, fieldRegistry, geras, inject, inputTypes, minimalist, registry, thrasos, uiPosition, utils, zelos }; }