blockly.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  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. /**
  26. * The top level namespace used to access the Blockly library.
  27. * @namespace Blockly
  28. */
  29. goog.provide('Blockly');
  30. goog.require('Blockly.BlockSvg.render');
  31. goog.require('Blockly.Events');
  32. goog.require('Blockly.FieldAngle');
  33. goog.require('Blockly.FieldCheckbox');
  34. goog.require('Blockly.FieldColour');
  35. // Date picker commented out since it increases footprint by 60%.
  36. // Add it only if you need it.
  37. //goog.require('Blockly.FieldDate');
  38. goog.require('Blockly.FieldDropdown');
  39. goog.require('Blockly.FieldImage');
  40. goog.require('Blockly.FieldClickImage');
  41. goog.require('Blockly.FieldTextInput');
  42. goog.require('Blockly.FieldTextArea');
  43. goog.require('Blockly.FieldTable');
  44. goog.require('Blockly.FieldNumber');
  45. goog.require('Blockly.FieldVariable');
  46. goog.require('Blockly.Generator');
  47. goog.require('Blockly.Msg');
  48. goog.require('Blockly.Procedures');
  49. goog.require('Blockly.Toolbox');
  50. goog.require('Blockly.Touch');
  51. goog.require('Blockly.WidgetDiv');
  52. goog.require('Blockly.WorkspaceSvg');
  53. goog.require('Blockly.constants');
  54. goog.require('Blockly.inject');
  55. goog.require('Blockly.utils');
  56. goog.require('goog.color');
  57. goog.require('goog.userAgent');
  58. // Turn off debugging when compiled.
  59. var CLOSURE_DEFINES = {'goog.DEBUG': false};
  60. /**
  61. * The main workspace most recently used.
  62. * Set by Blockly.WorkspaceSvg.prototype.markFocused
  63. * @type {Blockly.Workspace}
  64. */
  65. Blockly.mainWorkspace = null;
  66. /**
  67. * Currently selected block.
  68. * @type {Blockly.Block}
  69. */
  70. Blockly.selected = null;
  71. /**
  72. * Currently highlighted connection (during a drag).
  73. * @type {Blockly.Connection}
  74. * @private
  75. */
  76. Blockly.highlightedConnection_ = null;
  77. /**
  78. * Connection on dragged block that matches the highlighted connection.
  79. * @type {Blockly.Connection}
  80. * @private
  81. */
  82. Blockly.localConnection_ = null;
  83. /**
  84. * All of the connections on blocks that are currently being dragged.
  85. * @type {!Array.<!Blockly.Connection>}
  86. * @private
  87. */
  88. Blockly.draggingConnections_ = [];
  89. /**
  90. * Contents of the local clipboard.
  91. * @type {Element}
  92. * @private
  93. */
  94. Blockly.clipboardXml_ = null;
  95. /**
  96. * Source of the local clipboard.
  97. * @type {Blockly.WorkspaceSvg}
  98. * @private
  99. */
  100. Blockly.clipboardSource_ = null;
  101. /**
  102. * Is the mouse dragging a block?
  103. * DRAG_NONE - No drag operation.
  104. * DRAG_STICKY - Still inside the sticky DRAG_RADIUS.
  105. * DRAG_FREE - Freely draggable.
  106. * @private
  107. */
  108. Blockly.dragMode_ = Blockly.DRAG_NONE;
  109. /**
  110. * Map from function names to callbacks, for deciding what to do when a button
  111. * is clicked.
  112. * @type {!Object<string, function(!Blockly.FlyoutButton)>}
  113. */
  114. Blockly.flyoutButtonCallbacks_ = {};
  115. /**
  116. * Register a callback function associated with a given key, for clicks on
  117. * buttons and labels in the flyout.
  118. * For instance, a button specified by the XML
  119. * <button text="create variable" callbackKey="CREATE_VARIABLE"></button>
  120. * should be matched by a call to
  121. * registerButtonCallback("CREATE_VARIABLE", yourCallbackFunction).
  122. * @param {string} key The name to use to look up this function.
  123. * @param {function(!Blockly.FlyoutButton)} func The function to call when the
  124. * given button is clicked.
  125. */
  126. Blockly.registerButtonCallback = function(key, func) {
  127. Blockly.flyoutButtonCallbacks_[key] = func;
  128. };
  129. /**
  130. * Convert a hue (HSV model) into an RGB hex triplet.
  131. * @param {number} hue Hue on a colour wheel (0-360).
  132. * @return {string} RGB code, e.g. '#5ba65b'.
  133. */
  134. Blockly.hueToRgb = function(hue) {
  135. return goog.color.hsvToHex(hue, Blockly.HSV_SATURATION,
  136. Blockly.HSV_VALUE * 255);
  137. };
  138. /**
  139. * Returns the dimensions of the specified SVG image.
  140. * @param {!Element} svg SVG image.
  141. * @return {!Object} Contains width and height properties.
  142. */
  143. Blockly.svgSize = function(svg) {
  144. return {width: svg.cachedWidth_,
  145. height: svg.cachedHeight_};
  146. };
  147. /**
  148. * Size the workspace when the contents change. This also updates
  149. * scrollbars accordingly.
  150. * @param {!Blockly.WorkspaceSvg} workspace The workspace to resize.
  151. */
  152. Blockly.resizeSvgContents = function(workspace) {
  153. workspace.resizeContents();
  154. };
  155. /**
  156. * Size the SVG image to completely fill its container. Call this when the view
  157. * actually changes sizes (e.g. on a window resize/device orientation change).
  158. * See Blockly.resizeSvgContents to resize the workspace when the contents
  159. * change (e.g. when a block is added or removed).
  160. * Record the height/width of the SVG image.
  161. * @param {!Blockly.WorkspaceSvg} workspace Any workspace in the SVG.
  162. */
  163. Blockly.svgResize = function(workspace) {
  164. var mainWorkspace = workspace;
  165. while (mainWorkspace.options.parentWorkspace) {
  166. mainWorkspace = mainWorkspace.options.parentWorkspace;
  167. }
  168. var svg = mainWorkspace.getParentSvg();
  169. var div = svg.parentNode;
  170. if (!div) {
  171. // Workspace deleted, or something.
  172. return;
  173. }
  174. var width = div.offsetWidth;
  175. var height = div.offsetHeight;
  176. if (svg.cachedWidth_ != width) {
  177. svg.setAttribute('width', width + 'px');
  178. svg.cachedWidth_ = width;
  179. }
  180. if (svg.cachedHeight_ != height) {
  181. svg.setAttribute('height', height + 'px');
  182. svg.cachedHeight_ = height;
  183. }
  184. mainWorkspace.resize();
  185. };
  186. /**
  187. * Handle a key-down on SVG drawing surface.
  188. * @param {!Event} e Key down event.
  189. * @private
  190. */
  191. Blockly.onKeyDown_ = function(e) {
  192. if (Blockly.mainWorkspace.options.readOnly || Blockly.isTargetInput_(e)) {
  193. // No key actions on readonly workspaces.
  194. // When focused on an HTML text input widget, don't trap any keys.
  195. return;
  196. }
  197. var deleteBlock = false;
  198. if (e.keyCode == 27) {
  199. // Pressing esc closes the context menu.
  200. Blockly.hideChaff();
  201. } else if (e.keyCode == 8 || e.keyCode == 46) {
  202. // Delete or backspace.
  203. // Stop the browser from going back to the previous page.
  204. // Do this first to prevent an error in the delete code from resulting in
  205. // data loss.
  206. e.preventDefault();
  207. if (Blockly.selected && Blockly.selected.isDeletable()) {
  208. deleteBlock = true;
  209. }
  210. } else if (e.altKey || e.ctrlKey || e.metaKey) {
  211. if (Blockly.selected &&
  212. Blockly.selected.isDeletable() && Blockly.selected.isMovable()) {
  213. if (e.keyCode == 67) {
  214. // 'c' for copy.
  215. Blockly.hideChaff();
  216. Blockly.copy_(Blockly.selected);
  217. } else if (e.keyCode == 88) {
  218. // 'x' for cut.
  219. Blockly.copy_(Blockly.selected);
  220. deleteBlock = true;
  221. }
  222. }
  223. if (e.keyCode == 86) {
  224. // 'v' for paste.
  225. Blockly.mainWorkspace.pasteFromClipboard();
  226. } else if (e.keyCode == 90) {
  227. // 'z' for undo
  228. //Blockly.getMainWorkspace().undo();
  229. Blockly.hideChaff();
  230. Blockly.mainWorkspace.undo(false);
  231. } else if (e.keyCode == 89) {
  232. // 'y' for undo
  233. //Blockly.getMainWorkspace().redo();
  234. Blockly.hideChaff();
  235. Blockly.mainWorkspace.undo(true);
  236. }
  237. }
  238. if (deleteBlock) {
  239. // Common code for delete and cut.
  240. Blockly.Events.setGroup(true);
  241. Blockly.hideChaff();
  242. var heal = Blockly.dragMode_ != Blockly.DRAG_FREE;
  243. Blockly.selected.dispose(heal, true);
  244. if (Blockly.highlightedConnection_) {
  245. Blockly.highlightedConnection_.unhighlight();
  246. Blockly.highlightedConnection_ = null;
  247. }
  248. Blockly.Events.setGroup(false);
  249. }
  250. };
  251. /**
  252. * Stop binding to the global mouseup and mousemove events.
  253. * @private
  254. */
  255. Blockly.terminateDrag_ = function() {
  256. Blockly.BlockSvg.terminateDrag();
  257. Blockly.Flyout.terminateDrag_();
  258. };
  259. /**
  260. * Copy a block onto the local clipboard.
  261. * @param {!Blockly.Block} block Block to be copied.
  262. * @private
  263. */
  264. Blockly.copy_ = function(block) {
  265. var xmlBlock = Blockly.Xml.blockToDom(block);
  266. if (Blockly.dragMode_ != Blockly.DRAG_FREE) {
  267. Blockly.Xml.deleteNext(xmlBlock);
  268. }
  269. // Encode start position in XML.
  270. var xy = block.getRelativeToSurfaceXY();
  271. xmlBlock.setAttribute('x', block.RTL ? -xy.x : xy.x);
  272. xmlBlock.setAttribute('y', xy.y);
  273. Blockly.clipboardXml_ = xmlBlock;
  274. Blockly.clipboardSource_ = block.workspace;
  275. localStorage.setItem('_blockly_clipboardXml_', Blockly.Xml.domToText(xmlBlock));
  276. };
  277. /**
  278. * Duplicate this block and its children.
  279. * @param {!Blockly.Block} block Block to be copied.
  280. * @private
  281. */
  282. Blockly.duplicate_ = function(block) {
  283. // Save the clipboard.
  284. var clipboardXml = Blockly.clipboardXml_;
  285. var clipboardSource = Blockly.clipboardSource_;
  286. // Create a duplicate via a copy/paste operation.
  287. Blockly.copy_(block);
  288. block.workspace.paste(Blockly.clipboardXml_);
  289. // Restore the clipboard.
  290. Blockly.clipboardXml_ = clipboardXml;
  291. Blockly.clipboardSource_ = clipboardSource;
  292. };
  293. /**
  294. * Cancel the native context menu, unless the focus is on an HTML input widget.
  295. * @param {!Event} e Mouse down event.
  296. * @private
  297. */
  298. Blockly.onContextMenu_ = function(e) {
  299. if (!Blockly.isTargetInput_(e)) {
  300. // When focused on an HTML text input widget, don't cancel the context menu.
  301. e.preventDefault();
  302. }
  303. };
  304. /**
  305. * Close tooltips, context menus, dropdown selections, etc.
  306. * @param {boolean=} opt_allowToolbox If true, don't close the toolbox.
  307. */
  308. Blockly.hideChaff = function(opt_allowToolbox) {
  309. Blockly.Tooltip.hide();
  310. Blockly.WidgetDiv.hide();
  311. if (!opt_allowToolbox) {
  312. var workspace = Blockly.getMainWorkspace();
  313. if (workspace.toolbox_ &&
  314. workspace.toolbox_.flyout_ &&
  315. workspace.toolbox_.flyout_.autoClose) {
  316. workspace.toolbox_.clearSelection();
  317. }
  318. }
  319. };
  320. /**
  321. * When something in Blockly's workspace changes, call a function.
  322. * @param {!Function} func Function to call.
  323. * @return {!Array.<!Array>} Opaque data that can be passed to
  324. * removeChangeListener.
  325. * @deprecated April 2015
  326. */
  327. Blockly.addChangeListener = function(func) {
  328. // Backwards compatability from before there could be multiple workspaces.
  329. console.warn('Deprecated call to Blockly.addChangeListener, ' +
  330. 'use workspace.addChangeListener instead.');
  331. return Blockly.getMainWorkspace().addChangeListener(func);
  332. };
  333. /**
  334. * Returns the main workspace. Returns the last used main workspace (based on
  335. * focus). Try not to use this function, particularly if there are multiple
  336. * Blockly instances on a page.
  337. * @return {!Blockly.Workspace} The main workspace.
  338. */
  339. Blockly.getMainWorkspace = function() {
  340. return Blockly.mainWorkspace;
  341. };
  342. /**
  343. * Wrapper to window.alert() that app developers may override to
  344. * provide alternatives to the modal browser window.
  345. * @param {string} message The message to display to the user.
  346. * @param {function()=} opt_callback The callback when the alert is dismissed.
  347. */
  348. Blockly.alert = function(message, opt_callback) {
  349. window.alert(message);
  350. if (opt_callback) {
  351. opt_callback();
  352. }
  353. };
  354. /**
  355. * Wrapper to window.confirm() that app developers may override to
  356. * provide alternatives to the modal browser window.
  357. * @param {string} message The message to display to the user.
  358. * @param {!function(boolean)} callback The callback for handling user response.
  359. */
  360. Blockly.confirm = function(message, callback) {
  361. callback(window.confirm(message));
  362. };
  363. /**
  364. * Wrapper to window.prompt() that app developers may override to provide
  365. * alternatives to the modal browser window. Built-in browser prompts are
  366. * often used for better text input experience on mobile device. We strongly
  367. * recommend testing mobile when overriding this.
  368. * @param {string} message The message to display to the user.
  369. * @param {string} defaultValue The value to initialize the prompt with.
  370. * @param {!function(string)} callback The callback for handling user reponse.
  371. */
  372. Blockly.prompt = function(message, defaultValue, callback) {
  373. callback(window.prompt(message, defaultValue));
  374. };
  375. /**
  376. * Helper function for defining a block from JSON. The resulting function has
  377. * the correct value of jsonDef at the point in code where jsonInit is called.
  378. * @param {!Object} jsonDef The JSON definition of a block.
  379. * @return {function} A function that calls jsonInit with the correct value
  380. * of jsonDef.
  381. * @private
  382. */
  383. Blockly.jsonInitFactory_ = function(jsonDef) {
  384. return function() {
  385. this.jsonInit(jsonDef);
  386. };
  387. };
  388. /**
  389. * Define blocks from an array of JSON block definitions, as might be generated
  390. * by the Blockly Developer Tools.
  391. * @param {!Array.<!Object>} jsonArray An array of JSON block definitions.
  392. */
  393. Blockly.defineBlocksWithJsonArray = function(jsonArray) {
  394. for (var i = 0, elem; elem = jsonArray[i]; i++) {
  395. Blockly.Blocks[elem.type] = {
  396. init: Blockly.jsonInitFactory_(elem)
  397. };
  398. }
  399. };
  400. // IE9 does not have a console. Create a stub to stop errors.
  401. if (!goog.global['console']) {
  402. goog.global['console'] = {
  403. 'log': function() {},
  404. 'warn': function() {}
  405. };
  406. }
  407. // Export symbols that would otherwise be renamed by Closure compiler.
  408. if (!goog.global['Blockly']) {
  409. goog.global['Blockly'] = {};
  410. }
  411. goog.global['Blockly']['getMainWorkspace'] = Blockly.getMainWorkspace;
  412. goog.global['Blockly']['addChangeListener'] = Blockly.addChangeListener;