blockly.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590
  1. /**
  2. * @license
  3. * Visual Blocks Editor
  4. *
  5. * Copyright 2011 Google Inc.
  6. * https://developers.google.com/blockly/
  7. *
  8. * Licensed under the Apache License, Version 2.0 (the "License");
  9. * you may not use this file except in compliance with the License.
  10. * You may obtain a copy of the License at
  11. *
  12. * http://www.apache.org/licenses/LICENSE-2.0
  13. *
  14. * Unless required by applicable law or agreed to in writing, software
  15. * distributed under the License is distributed on an "AS IS" BASIS,
  16. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17. * See the License for the specific language governing permissions and
  18. * limitations under the License.
  19. */
  20. /**
  21. * @fileoverview Core JavaScript library for Blockly.
  22. * @author fraser@google.com (Neil Fraser)
  23. */
  24. 'use strict';
  25. // Top level object for Blockly.
  26. goog.provide('Blockly');
  27. goog.require('Blockly.BlockSvg.render');
  28. goog.require('Blockly.Events');
  29. goog.require('Blockly.FieldAngle');
  30. goog.require('Blockly.FieldCheckbox');
  31. goog.require('Blockly.FieldColour');
  32. // Date picker commented out since it increases footprint by 60%.
  33. // Add it only if you need it.
  34. //goog.require('Blockly.FieldDate');
  35. goog.require('Blockly.FieldDropdown');
  36. goog.require('Blockly.FieldImage');
  37. goog.require('Blockly.FieldTextInput');
  38. goog.require('Blockly.FieldNumber');
  39. goog.require('Blockly.FieldVariable');
  40. goog.require('Blockly.Generator');
  41. goog.require('Blockly.Msg');
  42. goog.require('Blockly.Procedures');
  43. goog.require('Blockly.Toolbox');
  44. goog.require('Blockly.WidgetDiv');
  45. goog.require('Blockly.WorkspaceSvg');
  46. goog.require('Blockly.constants');
  47. goog.require('Blockly.inject');
  48. goog.require('Blockly.utils');
  49. goog.require('goog.color');
  50. goog.require('goog.userAgent');
  51. // Turn off debugging when compiled.
  52. var CLOSURE_DEFINES = {'goog.DEBUG': false};
  53. /**
  54. * The main workspace most recently used.
  55. * Set by Blockly.WorkspaceSvg.prototype.markFocused
  56. * @type {Blockly.Workspace}
  57. */
  58. Blockly.mainWorkspace = null;
  59. /**
  60. * Currently selected block.
  61. * @type {Blockly.Block}
  62. */
  63. Blockly.selected = null;
  64. /**
  65. * Currently highlighted connection (during a drag).
  66. * @type {Blockly.Connection}
  67. * @private
  68. */
  69. Blockly.highlightedConnection_ = null;
  70. /**
  71. * List of "backlight" blocks (drawn with highlighting, not selectable)
  72. * @type {Blockly.Block}
  73. * Added for BlocksCAD typing system
  74. */
  75. Blockly.backlight = [];
  76. /**
  77. * Currently highlighted illegal connection (during a drag). For BlocksCAD!
  78. * @type {Blockly.Connection}
  79. * @private
  80. */
  81. Blockly.highlightedConnectionBad_ = null;
  82. /**
  83. * Connection on dragged block that matches the highlighted connection.
  84. * @type {Blockly.Connection}
  85. * @private
  86. */
  87. Blockly.localConnection_ = null;
  88. /**
  89. * All of the connections on blocks that are currently being dragged.
  90. * @type {!Array.<!Blockly.Connection>}
  91. * @private
  92. */
  93. Blockly.draggingConnections_ = [];
  94. /**
  95. * Contents of the local clipboard.
  96. * @type {Element}
  97. * @private
  98. */
  99. Blockly.clipboardXml_ = null;
  100. /**
  101. * Source of the local clipboard.
  102. * @type {Blockly.WorkspaceSvg}
  103. * @private
  104. */
  105. Blockly.clipboardSource_ = null;
  106. /**
  107. * Is the mouse dragging a block?
  108. * DRAG_NONE - No drag operation.
  109. * DRAG_STICKY - Still inside the sticky DRAG_RADIUS.
  110. * DRAG_FREE - Freely draggable.
  111. * @private
  112. */
  113. Blockly.dragMode_ = Blockly.DRAG_NONE;
  114. /**
  115. * Wrapper function called when a touch mouseUp occurs during a drag operation.
  116. * @type {Array.<!Array>}
  117. * @private
  118. */
  119. Blockly.onTouchUpWrapper_ = null;
  120. /**
  121. * Convert a hue (HSV model) into an RGB hex triplet.
  122. * @param {number} hue Hue on a colour wheel (0-360).
  123. * @return {string} RGB code, e.g. '#5ba65b'.
  124. */
  125. Blockly.hueToRgb = function(hue) {
  126. return goog.color.hsvToHex(hue, Blockly.HSV_SATURATION,
  127. Blockly.HSV_VALUE * 255);
  128. };
  129. /**
  130. * Returns the dimensions of the specified SVG image.
  131. * @param {!Element} svg SVG image.
  132. * @return {!Object} Contains width and height properties.
  133. */
  134. Blockly.svgSize = function(svg) {
  135. return {width: svg.cachedWidth_,
  136. height: svg.cachedHeight_};
  137. };
  138. /**
  139. * Size the workspace when the contents change. This also updates
  140. * scrollbars accordingly.
  141. * @param {!Blockly.WorkspaceSvg} workspace The workspace to resize.
  142. */
  143. Blockly.resizeSvgContents = function(workspace) {
  144. workspace.resizeContents();
  145. };
  146. /**
  147. * Size the SVG image to completely fill its container. Call this when the view
  148. * actually changes sizes (e.g. on a window resize/device orientation change).
  149. * See Blockly.resizeSvgContents to resize the workspace when the contents
  150. * change (e.g. when a block is added or removed).
  151. * Record the height/width of the SVG image.
  152. * @param {!Blockly.WorkspaceSvg} workspace Any workspace in the SVG.
  153. */
  154. Blockly.svgResize = function(workspace) {
  155. var mainWorkspace = workspace;
  156. while (mainWorkspace.options.parentWorkspace) {
  157. mainWorkspace = mainWorkspace.options.parentWorkspace;
  158. }
  159. var svg = mainWorkspace.getParentSvg();
  160. var div = svg.parentNode;
  161. if (!div) {
  162. // Workspace deleted, or something.
  163. return;
  164. }
  165. var width = div.offsetWidth;
  166. var height = div.offsetHeight;
  167. if (svg.cachedWidth_ != width) {
  168. svg.setAttribute('width', width + 'px');
  169. svg.cachedWidth_ = width;
  170. }
  171. if (svg.cachedHeight_ != height) {
  172. svg.setAttribute('height', height + 'px');
  173. svg.cachedHeight_ = height;
  174. }
  175. mainWorkspace.resize();
  176. };
  177. /**
  178. * Handle a mouse-up anywhere on the page.
  179. * @param {!Event} e Mouse up event.
  180. * @private
  181. */
  182. Blockly.onMouseUp_ = function(e) {
  183. var workspace = Blockly.getMainWorkspace();
  184. Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN);
  185. workspace.dragMode_ = Blockly.DRAG_NONE;
  186. // Unbind the touch event if it exists.
  187. if (Blockly.onTouchUpWrapper_) {
  188. Blockly.unbindEvent_(Blockly.onTouchUpWrapper_);
  189. Blockly.onTouchUpWrapper_ = null;
  190. }
  191. if (Blockly.onMouseMoveWrapper_) {
  192. Blockly.unbindEvent_(Blockly.onMouseMoveWrapper_);
  193. Blockly.onMouseMoveWrapper_ = null;
  194. }
  195. };
  196. /**
  197. * Handle a mouse-move on SVG drawing surface.
  198. * @param {!Event} e Mouse move event.
  199. * @private
  200. */
  201. Blockly.onMouseMove_ = function(e) {
  202. if (e.touches && e.touches.length >= 2) {
  203. return; // Multi-touch gestures won't have e.clientX.
  204. }
  205. var workspace = Blockly.getMainWorkspace();
  206. if (workspace.dragMode_ != Blockly.DRAG_NONE) {
  207. var dx = e.clientX - workspace.startDragMouseX;
  208. var dy = e.clientY - workspace.startDragMouseY;
  209. var metrics = workspace.startDragMetrics;
  210. var x = workspace.startScrollX + dx;
  211. var y = workspace.startScrollY + dy;
  212. x = Math.min(x, -metrics.contentLeft);
  213. y = Math.min(y, -metrics.contentTop);
  214. x = Math.max(x, metrics.viewWidth - metrics.contentLeft -
  215. metrics.contentWidth);
  216. y = Math.max(y, metrics.viewHeight - metrics.contentTop -
  217. metrics.contentHeight);
  218. // Move the scrollbars and the page will scroll automatically.
  219. workspace.scrollbar.set(-x - metrics.contentLeft,
  220. -y - metrics.contentTop);
  221. // Cancel the long-press if the drag has moved too far.
  222. if (Math.sqrt(dx * dx + dy * dy) > Blockly.DRAG_RADIUS) {
  223. Blockly.longStop_();
  224. workspace.dragMode_ = Blockly.DRAG_FREE;
  225. }
  226. e.stopPropagation();
  227. e.preventDefault();
  228. }
  229. };
  230. /**
  231. * Handle a key-down on SVG drawing surface.
  232. * @param {!Event} e Key down event.
  233. * @private
  234. */
  235. Blockly.onKeyDown_ = function(e) {
  236. if (Blockly.mainWorkspace.options.readOnly || Blockly.isTargetInput_(e)) {
  237. // No key actions on readonly workspaces.
  238. // When focused on an HTML text input widget, don't trap any keys.
  239. return;
  240. }
  241. var deleteBlock = false;
  242. if (e.keyCode == 27) {
  243. // Pressing esc closes the context menu.
  244. Blockly.hideChaff();
  245. } else if (e.keyCode == 8 || e.keyCode == 46) {
  246. // Delete or backspace.
  247. // Stop the browser from going back to the previous page.
  248. // Do this first to prevent an error in the delete code from resulting in
  249. // data loss.
  250. e.preventDefault();
  251. if (Blockly.selected && Blockly.selected.isDeletable()) {
  252. deleteBlock = true;
  253. }
  254. } else if (e.altKey || e.ctrlKey || e.metaKey) {
  255. if (Blockly.selected &&
  256. Blockly.selected.isDeletable() && Blockly.selected.isMovable()) {
  257. if (e.keyCode == 67) {
  258. // 'c' for copy.
  259. Blockly.hideChaff();
  260. Blockly.copy_(Blockly.selected);
  261. } else if (e.keyCode == 88) {
  262. // 'x' for cut.
  263. Blockly.copy_(Blockly.selected);
  264. deleteBlock = true;
  265. }
  266. }
  267. if (e.keyCode == 86) {
  268. // 'v' for paste.
  269. if (Blockly.clipboardXml_) {
  270. Blockly.Events.setGroup(true);
  271. Blockly.clipboardSource_.paste(Blockly.clipboardXml_);
  272. Blockly.Events.setGroup(false);
  273. }
  274. } else if (e.keyCode == 90) {
  275. // 'z' for undo 'Z' is for redo.
  276. Blockly.hideChaff();
  277. Blockly.mainWorkspace.undo(e.shiftKey);
  278. }
  279. }
  280. if (deleteBlock) {
  281. // Common code for delete and cut.
  282. Blockly.Events.setGroup(true);
  283. Blockly.hideChaff();
  284. var heal = Blockly.dragMode_ != Blockly.DRAG_FREE;
  285. Blockly.selected.dispose(heal, true);
  286. if (Blockly.highlightedConnection_) {
  287. Blockly.highlightedConnection_.unhighlight();
  288. Blockly.highlightedConnection_ = null;
  289. }
  290. Blockly.Events.setGroup(false);
  291. }
  292. };
  293. /**
  294. * Stop binding to the global mouseup and mousemove events.
  295. * @private
  296. */
  297. Blockly.terminateDrag_ = function() {
  298. Blockly.BlockSvg.terminateDrag();
  299. Blockly.Flyout.terminateDrag_();
  300. };
  301. /**
  302. * PID of queued long-press task.
  303. * @private
  304. */
  305. Blockly.longPid_ = 0;
  306. /**
  307. * Context menus on touch devices are activated using a long-press.
  308. * Unfortunately the contextmenu touch event is currently (2015) only suported
  309. * by Chrome. This function is fired on any touchstart event, queues a task,
  310. * which after about a second opens the context menu. The tasks is killed
  311. * if the touch event terminates early.
  312. * @param {!Event} e Touch start event.
  313. * @param {!Blockly.Block|!Blockly.WorkspaceSvg} uiObject The block or workspace
  314. * under the touchstart event.
  315. * @private
  316. */
  317. Blockly.longStart_ = function(e, uiObject) {
  318. Blockly.longStop_();
  319. Blockly.longPid_ = setTimeout(function() {
  320. e.button = 2; // Simulate a right button click.
  321. uiObject.onMouseDown_(e);
  322. }, Blockly.LONGPRESS);
  323. };
  324. /**
  325. * Nope, that's not a long-press. Either touchend or touchcancel was fired,
  326. * or a drag hath begun. Kill the queued long-press task.
  327. * @private
  328. */
  329. Blockly.longStop_ = function() {
  330. if (Blockly.longPid_) {
  331. clearTimeout(Blockly.longPid_);
  332. Blockly.longPid_ = 0;
  333. }
  334. };
  335. /**
  336. * Copy a block onto the local clipboard.
  337. * @param {!Blockly.Block} block Block to be copied.
  338. * @private
  339. */
  340. Blockly.copy_ = function(block) {
  341. var xmlBlock = Blockly.Xml.blockToDom(block);
  342. if (Blockly.dragMode_ != Blockly.DRAG_FREE) {
  343. Blockly.Xml.deleteNext(xmlBlock);
  344. }
  345. // Encode start position in XML.
  346. var xy = block.getRelativeToSurfaceXY();
  347. xmlBlock.setAttribute('x', block.RTL ? -xy.x : xy.x);
  348. xmlBlock.setAttribute('y', xy.y);
  349. Blockly.clipboardXml_ = xmlBlock;
  350. Blockly.clipboardSource_ = block.workspace;
  351. };
  352. /**
  353. * Duplicate this block and its children.
  354. * @param {!Blockly.Block} block Block to be copied.
  355. * @private
  356. */
  357. Blockly.duplicate_ = function(block) {
  358. // Save the clipboard.
  359. var clipboardXml = Blockly.clipboardXml_;
  360. var clipboardSource = Blockly.clipboardSource_;
  361. // Create a duplicate via a copy/paste operation.
  362. Blockly.copy_(block);
  363. block.workspace.paste(Blockly.clipboardXml_);
  364. // Restore the clipboard.
  365. Blockly.clipboardXml_ = clipboardXml;
  366. Blockly.clipboardSource_ = clipboardSource;
  367. };
  368. /**
  369. * Cancel the native context menu, unless the focus is on an HTML input widget.
  370. * @param {!Event} e Mouse down event.
  371. * @private
  372. */
  373. Blockly.onContextMenu_ = function(e) {
  374. if (!Blockly.isTargetInput_(e)) {
  375. // When focused on an HTML text input widget, don't cancel the context menu.
  376. e.preventDefault();
  377. }
  378. };
  379. /**
  380. * Close tooltips, context menus, dropdown selections, etc.
  381. * @param {boolean=} opt_allowToolbox If true, don't close the toolbox.
  382. */
  383. Blockly.hideChaff = function(opt_allowToolbox) {
  384. Blockly.Tooltip.hide();
  385. Blockly.WidgetDiv.hide();
  386. if (!opt_allowToolbox) {
  387. var workspace = Blockly.getMainWorkspace();
  388. if (workspace.toolbox_ &&
  389. workspace.toolbox_.flyout_ &&
  390. workspace.toolbox_.flyout_.autoClose) {
  391. workspace.toolbox_.clearSelection();
  392. }
  393. }
  394. };
  395. /**
  396. * Return an object with all the metrics required to size scrollbars for the
  397. * main workspace. The following properties are computed:
  398. * .viewHeight: Height of the visible rectangle,
  399. * .viewWidth: Width of the visible rectangle,
  400. * .contentHeight: Height of the contents,
  401. * .contentWidth: Width of the content,
  402. * .viewTop: Offset of top edge of visible rectangle from parent,
  403. * .viewLeft: Offset of left edge of visible rectangle from parent,
  404. * .contentTop: Offset of the top-most content from the y=0 coordinate,
  405. * .contentLeft: Offset of the left-most content from the x=0 coordinate.
  406. * .absoluteTop: Top-edge of view.
  407. * .absoluteLeft: Left-edge of view.
  408. * .toolboxWidth: Width of toolbox, if it exists. Otherwise zero.
  409. * .toolboxHeight: Height of toolbox, if it exists. Otherwise zero.
  410. * .flyoutWidth: Width of the flyout if it is always open. Otherwise zero.
  411. * .flyoutHeight: Height of flyout if it is always open. Otherwise zero.
  412. * .toolboxPosition: Top, bottom, left or right.
  413. * @return {Object} Contains size and position metrics of main workspace.
  414. * @private
  415. * @this Blockly.WorkspaceSvg
  416. */
  417. Blockly.getMainWorkspaceMetrics_ = function() {
  418. var svgSize = Blockly.svgSize(this.getParentSvg());
  419. if (this.toolbox_) {
  420. if (this.toolboxPosition == Blockly.TOOLBOX_AT_TOP ||
  421. this.toolboxPosition == Blockly.TOOLBOX_AT_BOTTOM) {
  422. svgSize.height -= this.toolbox_.getHeight();
  423. } else if (this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT ||
  424. this.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) {
  425. svgSize.width -= this.toolbox_.getWidth();
  426. }
  427. }
  428. // Set the margin to match the flyout's margin so that the workspace does
  429. // not jump as blocks are added.
  430. var MARGIN = Blockly.Flyout.prototype.CORNER_RADIUS - 1;
  431. var viewWidth = svgSize.width - MARGIN;
  432. var viewHeight = svgSize.height - MARGIN;
  433. var blockBox = this.getBlocksBoundingBox();
  434. // Fix scale.
  435. var contentWidth = blockBox.width * this.scale;
  436. var contentHeight = blockBox.height * this.scale;
  437. var contentX = blockBox.x * this.scale;
  438. var contentY = blockBox.y * this.scale;
  439. if (this.scrollbar) {
  440. // Add a border around the content that is at least half a screenful wide.
  441. // Ensure border is wide enough that blocks can scroll over entire screen.
  442. var leftEdge = Math.min(contentX - viewWidth / 2,
  443. contentX + contentWidth - viewWidth);
  444. var rightEdge = Math.max(contentX + contentWidth + viewWidth / 2,
  445. contentX + viewWidth);
  446. var topEdge = Math.min(contentY - viewHeight / 2,
  447. contentY + contentHeight - viewHeight);
  448. var bottomEdge = Math.max(contentY + contentHeight + viewHeight / 2,
  449. contentY + viewHeight);
  450. } else {
  451. var leftEdge = blockBox.x;
  452. var rightEdge = leftEdge + blockBox.width;
  453. var topEdge = blockBox.y;
  454. var bottomEdge = topEdge + blockBox.height;
  455. }
  456. var absoluteLeft = 0;
  457. if (this.toolbox_ && this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT) {
  458. absoluteLeft = this.toolbox_.getWidth();
  459. }
  460. var absoluteTop = 0;
  461. if (this.toolbox_ && this.toolboxPosition == Blockly.TOOLBOX_AT_TOP) {
  462. absoluteTop = this.toolbox_.getHeight();
  463. }
  464. var metrics = {
  465. viewHeight: svgSize.height,
  466. viewWidth: svgSize.width,
  467. contentHeight: bottomEdge - topEdge,
  468. contentWidth: rightEdge - leftEdge,
  469. viewTop: -this.scrollY,
  470. viewLeft: -this.scrollX,
  471. contentTop: topEdge,
  472. contentLeft: leftEdge,
  473. absoluteTop: absoluteTop,
  474. absoluteLeft: absoluteLeft,
  475. toolboxWidth: this.toolbox_ ? this.toolbox_.getWidth() : 0,
  476. toolboxHeight: this.toolbox_ ? this.toolbox_.getHeight() : 0,
  477. flyoutWidth: this.flyout_ ? this.flyout_.getWidth() : 0,
  478. flyoutHeight: this.flyout_ ? this.flyout_.getHeight() : 0,
  479. toolboxPosition: this.toolboxPosition
  480. };
  481. return metrics;
  482. };
  483. /**
  484. * Sets the X/Y translations of the main workspace to match the scrollbars.
  485. * @param {!Object} xyRatio Contains an x and/or y property which is a float
  486. * between 0 and 1 specifying the degree of scrolling.
  487. * @private
  488. * @this Blockly.WorkspaceSvg
  489. */
  490. Blockly.setMainWorkspaceMetrics_ = function(xyRatio) {
  491. if (!this.scrollbar) {
  492. throw 'Attempt to set main workspace scroll without scrollbars.';
  493. }
  494. var metrics = this.getMetrics();
  495. if (goog.isNumber(xyRatio.x)) {
  496. this.scrollX = -metrics.contentWidth * xyRatio.x - metrics.contentLeft;
  497. }
  498. if (goog.isNumber(xyRatio.y)) {
  499. this.scrollY = -metrics.contentHeight * xyRatio.y - metrics.contentTop;
  500. }
  501. var x = this.scrollX + metrics.absoluteLeft;
  502. var y = this.scrollY + metrics.absoluteTop;
  503. this.translate(x, y);
  504. if (this.options.gridPattern) {
  505. this.options.gridPattern.setAttribute('x', x);
  506. this.options.gridPattern.setAttribute('y', y);
  507. if (goog.userAgent.IE) {
  508. // IE doesn't notice that the x/y offsets have changed. Force an update.
  509. this.updateGridPattern_();
  510. }
  511. }
  512. };
  513. /**
  514. * When something in Blockly's workspace changes, call a function.
  515. * @param {!Function} func Function to call.
  516. * @return {!Array.<!Array>} Opaque data that can be passed to
  517. * removeChangeListener.
  518. * @deprecated April 2015
  519. */
  520. Blockly.addChangeListener = function(func) {
  521. // Backwards compatability from before there could be multiple workspaces.
  522. console.warn('Deprecated call to Blockly.addChangeListener, ' +
  523. 'use workspace.addChangeListener instead.');
  524. return Blockly.getMainWorkspace().addChangeListener(func);
  525. };
  526. /**
  527. * Returns the main workspace. Returns the last used main workspace (based on
  528. * focus). Try not to use this function, particularly if there are multiple
  529. * Blockly instances on a page.
  530. * @return {!Blockly.Workspace} The main workspace.
  531. */
  532. Blockly.getMainWorkspace = function() {
  533. return Blockly.mainWorkspace;
  534. };
  535. // IE9 does not have a console. Create a stub to stop errors.
  536. if (!goog.global['console']) {
  537. goog.global['console'] = {
  538. 'log': function() {},
  539. 'warn': function() {}
  540. };
  541. }
  542. // Export symbols that would otherwise be renamed by Closure compiler.
  543. if (!goog.global['Blockly']) {
  544. goog.global['Blockly'] = {};
  545. }
  546. goog.global['Blockly']['getMainWorkspace'] = Blockly.getMainWorkspace;
  547. goog.global['Blockly']['addChangeListener'] = Blockly.addChangeListener;