/** * @license * Visual Blocks Editor * * Copyright 2011 Google Inc. * https://developers.google.com/blockly/ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @fileoverview Core JavaScript library for Blockly. * @author fraser@google.com (Neil Fraser) */ 'use strict'; /** * The top level namespace used to access the Blockly library. * @namespace Blockly */ goog.provide('Blockly'); goog.require('Blockly.BlockSvg.render'); goog.require('Blockly.Events'); goog.require('Blockly.FieldAngle'); goog.require('Blockly.FieldCheckbox'); goog.require('Blockly.FieldColour'); // Date picker commented out since it increases footprint by 60%. // Add it only if you need it. //goog.require('Blockly.FieldDate'); goog.require('Blockly.FieldDropdown'); goog.require('Blockly.FieldImage'); goog.require('Blockly.FieldClickImage'); goog.require('Blockly.FieldTextInput'); goog.require('Blockly.FieldTextArea'); goog.require('Blockly.FieldTable'); goog.require('Blockly.FieldNumber'); goog.require('Blockly.FieldVariable'); goog.require('Blockly.Generator'); goog.require('Blockly.Msg'); goog.require('Blockly.Procedures'); goog.require('Blockly.Toolbox'); goog.require('Blockly.Touch'); goog.require('Blockly.WidgetDiv'); goog.require('Blockly.WorkspaceSvg'); goog.require('Blockly.constants'); goog.require('Blockly.inject'); goog.require('Blockly.utils'); goog.require('goog.color'); goog.require('goog.userAgent'); // Turn off debugging when compiled. var CLOSURE_DEFINES = {'goog.DEBUG': false}; /** * The main workspace most recently used. * Set by Blockly.WorkspaceSvg.prototype.markFocused * @type {Blockly.Workspace} */ Blockly.mainWorkspace = null; /** * Currently selected block. * @type {Blockly.Block} */ Blockly.selected = null; /** * Currently highlighted connection (during a drag). * @type {Blockly.Connection} * @private */ Blockly.highlightedConnection_ = null; /** * Connection on dragged block that matches the highlighted connection. * @type {Blockly.Connection} * @private */ Blockly.localConnection_ = null; /** * All of the connections on blocks that are currently being dragged. * @type {!Array.} * @private */ Blockly.draggingConnections_ = []; /** * Contents of the local clipboard. * @type {Element} * @private */ Blockly.clipboardXml_ = null; /** * Source of the local clipboard. * @type {Blockly.WorkspaceSvg} * @private */ Blockly.clipboardSource_ = null; /** * Is the mouse dragging a block? * DRAG_NONE - No drag operation. * DRAG_STICKY - Still inside the sticky DRAG_RADIUS. * DRAG_FREE - Freely draggable. * @private */ Blockly.dragMode_ = Blockly.DRAG_NONE; /** * Map from function names to callbacks, for deciding what to do when a button * is clicked. * @type {!Object} */ Blockly.flyoutButtonCallbacks_ = {}; /** * Register a callback function associated with a given key, for clicks on * buttons and labels in the flyout. * For instance, a button specified by the XML * * should be matched by a call to * registerButtonCallback("CREATE_VARIABLE", yourCallbackFunction). * @param {string} key The name to use to look up this function. * @param {function(!Blockly.FlyoutButton)} func The function to call when the * given button is clicked. */ Blockly.registerButtonCallback = function(key, func) { Blockly.flyoutButtonCallbacks_[key] = func; }; /** * 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'. */ Blockly.hueToRgb = function(hue) { return goog.color.hsvToHex(hue, Blockly.HSV_SATURATION, Blockly.HSV_VALUE * 255); }; /** * Returns the dimensions of the specified SVG image. * @param {!Element} svg SVG image. * @return {!Object} Contains width and height properties. */ Blockly.svgSize = function(svg) { return {width: svg.cachedWidth_, height: svg.cachedHeight_}; }; /** * Size the workspace when the contents change. This also updates * scrollbars accordingly. * @param {!Blockly.WorkspaceSvg} workspace The workspace to resize. */ Blockly.resizeSvgContents = function(workspace) { workspace.resizeContents(); }; /** * 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 {!Blockly.WorkspaceSvg} workspace Any workspace in the SVG. */ Blockly.svgResize = function(workspace) { var mainWorkspace = workspace; while (mainWorkspace.options.parentWorkspace) { mainWorkspace = mainWorkspace.options.parentWorkspace; } var svg = mainWorkspace.getParentSvg(); var div = svg.parentNode; if (!div) { // Workspace deleted, or something. return; } var width = div.offsetWidth; var height = div.offsetHeight; if (svg.cachedWidth_ != width) { svg.setAttribute('width', width + 'px'); svg.cachedWidth_ = width; } if (svg.cachedHeight_ != height) { svg.setAttribute('height', height + 'px'); svg.cachedHeight_ = height; } mainWorkspace.resize(); }; /** * Handle a key-down on SVG drawing surface. * @param {!Event} e Key down event. * @private */ Blockly.onKeyDown_ = function(e) { if (Blockly.mainWorkspace.options.readOnly || Blockly.isTargetInput_(e)) { // No key actions on readonly workspaces. // When focused on an HTML text input widget, don't trap any keys. return; } var deleteBlock = false; if (e.keyCode == 27) { // Pressing esc closes the context menu. Blockly.hideChaff(); } else if (e.keyCode == 8 || e.keyCode == 46) { // Delete or backspace. // Stop the browser from going back to the previous page. // Do this first to prevent an error in the delete code from resulting in // data loss. e.preventDefault(); if (Blockly.selected && Blockly.selected.isDeletable()) { deleteBlock = true; } } else if (e.altKey || e.ctrlKey || e.metaKey) { if (Blockly.selected && Blockly.selected.isDeletable() && Blockly.selected.isMovable()) { if (e.keyCode == 67) { // 'c' for copy. Blockly.hideChaff(); Blockly.copy_(Blockly.selected); } else if (e.keyCode == 88) { // 'x' for cut. Blockly.copy_(Blockly.selected); deleteBlock = true; } } if (e.keyCode == 86) { // 'v' for paste. Blockly.mainWorkspace.pasteFromClipboard(); } else if (e.keyCode == 90) { // 'z' for undo //Blockly.getMainWorkspace().undo(); Blockly.hideChaff(); Blockly.mainWorkspace.undo(false); } else if (e.keyCode == 89) { // 'y' for undo //Blockly.getMainWorkspace().redo(); Blockly.hideChaff(); Blockly.mainWorkspace.undo(true); } } if (deleteBlock) { // Common code for delete and cut. Blockly.Events.setGroup(true); Blockly.hideChaff(); var heal = Blockly.dragMode_ != Blockly.DRAG_FREE; Blockly.selected.dispose(heal, true); if (Blockly.highlightedConnection_) { Blockly.highlightedConnection_.unhighlight(); Blockly.highlightedConnection_ = null; } Blockly.Events.setGroup(false); } }; /** * Stop binding to the global mouseup and mousemove events. * @private */ Blockly.terminateDrag_ = function() { Blockly.BlockSvg.terminateDrag(); Blockly.Flyout.terminateDrag_(); }; /** * Copy a block onto the local clipboard. * @param {!Blockly.Block} block Block to be copied. * @private */ Blockly.copy_ = function(block) { var xmlBlock = Blockly.Xml.blockToDom(block); if (Blockly.dragMode_ != Blockly.DRAG_FREE) { Blockly.Xml.deleteNext(xmlBlock); } // Encode start position in XML. var xy = block.getRelativeToSurfaceXY(); xmlBlock.setAttribute('x', block.RTL ? -xy.x : xy.x); xmlBlock.setAttribute('y', xy.y); Blockly.clipboardXml_ = xmlBlock; Blockly.clipboardSource_ = block.workspace; localStorage.setItem('_blockly_clipboardXml_', Blockly.Xml.domToText(xmlBlock)); }; /** * Duplicate this block and its children. * @param {!Blockly.Block} block Block to be copied. * @private */ Blockly.duplicate_ = function(block) { // Save the clipboard. var clipboardXml = Blockly.clipboardXml_; var clipboardSource = Blockly.clipboardSource_; // Create a duplicate via a copy/paste operation. Blockly.copy_(block); block.workspace.paste(Blockly.clipboardXml_); // Restore the clipboard. Blockly.clipboardXml_ = clipboardXml; Blockly.clipboardSource_ = clipboardSource; }; /** * Cancel the native context menu, unless the focus is on an HTML input widget. * @param {!Event} e Mouse down event. * @private */ Blockly.onContextMenu_ = function(e) { if (!Blockly.isTargetInput_(e)) { // When focused on an HTML text input widget, don't cancel the context menu. e.preventDefault(); } }; /** * Close tooltips, context menus, dropdown selections, etc. * @param {boolean=} opt_allowToolbox If true, don't close the toolbox. */ Blockly.hideChaff = function(opt_allowToolbox) { Blockly.Tooltip.hide(); Blockly.WidgetDiv.hide(); if (!opt_allowToolbox) { var workspace = Blockly.getMainWorkspace(); if (workspace.toolbox_ && workspace.toolbox_.flyout_ && workspace.toolbox_.flyout_.autoClose) { workspace.toolbox_.clearSelection(); } } }; /** * When something in Blockly's workspace changes, call a function. * @param {!Function} func Function to call. * @return {!Array.} Opaque data that can be passed to * removeChangeListener. * @deprecated April 2015 */ Blockly.addChangeListener = function(func) { // Backwards compatability from before there could be multiple workspaces. console.warn('Deprecated call to Blockly.addChangeListener, ' + 'use workspace.addChangeListener instead.'); return Blockly.getMainWorkspace().addChangeListener(func); }; /** * 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 {!Blockly.Workspace} The main workspace. */ Blockly.getMainWorkspace = function() { return Blockly.mainWorkspace; }; /** * Wrapper to window.alert() that app developers may override 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. */ Blockly.alert = function(message, opt_callback) { window.alert(message); if (opt_callback) { opt_callback(); } }; /** * Wrapper to window.confirm() that app developers may override 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. */ Blockly.confirm = function(message, callback) { callback(window.confirm(message)); }; /** * Wrapper to window.prompt() that app developers may override 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 reponse. */ Blockly.prompt = function(message, defaultValue, callback) { callback(window.prompt(message, defaultValue)); }; /** * Helper function for defining a block from JSON. The resulting function has * the correct value of jsonDef at the point in code where jsonInit is called. * @param {!Object} jsonDef The JSON definition of a block. * @return {function} A function that calls jsonInit with the correct value * of jsonDef. * @private */ Blockly.jsonInitFactory_ = function(jsonDef) { return function() { this.jsonInit(jsonDef); }; }; /** * 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. */ Blockly.defineBlocksWithJsonArray = function(jsonArray) { for (var i = 0, elem; elem = jsonArray[i]; i++) { Blockly.Blocks[elem.type] = { init: Blockly.jsonInitFactory_(elem) }; } }; // IE9 does not have a console. Create a stub to stop errors. if (!goog.global['console']) { goog.global['console'] = { 'log': function() {}, 'warn': function() {} }; } // Export symbols that would otherwise be renamed by Closure compiler. if (!goog.global['Blockly']) { goog.global['Blockly'] = {}; } goog.global['Blockly']['getMainWorkspace'] = Blockly.getMainWorkspace; goog.global['Blockly']['addChangeListener'] = Blockly.addChangeListener;