Initial commit of 001code-html Scratch frontend project.
Includes scratch-gui, scratch-vm, scratch-blocks, scratch-render, scratch-l10n, and deployment config. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
618
scratch-blocks/core/blockly.js
Normal file
618
scratch-blocks/core/blockly.js
Normal file
@@ -0,0 +1,618 @@
|
||||
/**
|
||||
* @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.DropDownDiv');
|
||||
goog.require('Blockly.Events');
|
||||
goog.require('Blockly.FieldAngle');
|
||||
goog.require('Blockly.FieldCheckbox');
|
||||
goog.require('Blockly.FieldColour');
|
||||
goog.require('Blockly.FieldColourSlider');
|
||||
// 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.FieldIconMenu');
|
||||
goog.require('Blockly.FieldImage');
|
||||
goog.require('Blockly.FieldNote');
|
||||
goog.require('Blockly.FieldTextInput');
|
||||
goog.require('Blockly.FieldTextInputRemovable');
|
||||
goog.require('Blockly.FieldTextDropdown');
|
||||
goog.require('Blockly.FieldNumber');
|
||||
goog.require('Blockly.FieldNumberDropdown');
|
||||
goog.require('Blockly.FieldMatrix');
|
||||
goog.require('Blockly.FieldVariable');
|
||||
goog.require('Blockly.FieldVerticalSeparator');
|
||||
goog.require('Blockly.Generator');
|
||||
goog.require('Blockly.Msg');
|
||||
goog.require('Blockly.Procedures');
|
||||
goog.require('Blockly.ScratchMsgs');
|
||||
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');
|
||||
|
||||
|
||||
// Turn off debugging when compiled.
|
||||
/* eslint-disable no-unused-vars */
|
||||
var CLOSURE_DEFINES = {'goog.DEBUG': false};
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* All of the connections on blocks that are currently being dragged.
|
||||
* @type {!Array.<!Blockly.Connection>}
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* Cached value for whether 3D is supported.
|
||||
* @type {!boolean}
|
||||
* @private
|
||||
*/
|
||||
Blockly.cache3dSupported_ = null;
|
||||
|
||||
/**
|
||||
* 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. Does nothing if the main workspace is not visible.
|
||||
* @param {!Event} e Key down event.
|
||||
* @private
|
||||
*/
|
||||
// TODO (https://github.com/google/blockly/issues/1998) handle cases where there are multiple workspaces
|
||||
// and non-main workspaces are able to accept input.
|
||||
Blockly.onKeyDown_ = function(e) {
|
||||
if (Blockly.mainWorkspace.options.readOnly || Blockly.utils.isTargetInput(e)
|
||||
|| (Blockly.mainWorkspace.rendered && !Blockly.mainWorkspace.isVisible())) {
|
||||
// No key actions on readonly workspaces.
|
||||
// When focused on an HTML text input widget, don't trap any keys.
|
||||
// Ignore keypresses on rendered workspaces that have been explicitly
|
||||
// hidden.
|
||||
return;
|
||||
}
|
||||
var deleteBlock = false;
|
||||
if (e.keyCode == 27) {
|
||||
// Pressing esc closes the context menu and any drop-down
|
||||
Blockly.hideChaff();
|
||||
Blockly.DropDownDiv.hide();
|
||||
} 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();
|
||||
// Don't delete while dragging. Jeez.
|
||||
if (Blockly.mainWorkspace.isDragging()) {
|
||||
return;
|
||||
}
|
||||
if (Blockly.selected && Blockly.selected.isDeletable()) {
|
||||
deleteBlock = true;
|
||||
}
|
||||
} else if (e.altKey || e.ctrlKey || e.metaKey) {
|
||||
// Don't use meta keys during drags.
|
||||
if (Blockly.mainWorkspace.isDragging()) {
|
||||
return;
|
||||
}
|
||||
if (Blockly.selected &&
|
||||
Blockly.selected.isDeletable() && Blockly.selected.isMovable()) {
|
||||
// Don't allow copying immovable or undeletable blocks. The next step
|
||||
// would be to paste, which would create additional undeletable/immovable
|
||||
// blocks on the workspace.
|
||||
if (e.keyCode == 67) {
|
||||
// 'c' for copy.
|
||||
Blockly.hideChaff();
|
||||
Blockly.copy_(Blockly.selected);
|
||||
} else if (e.keyCode == 88 && !Blockly.selected.workspace.isFlyout) {
|
||||
// 'x' for cut, but not in a flyout.
|
||||
// Don't even copy the selected item in the flyout.
|
||||
Blockly.copy_(Blockly.selected);
|
||||
deleteBlock = true;
|
||||
}
|
||||
}
|
||||
if (e.keyCode == 86) {
|
||||
// 'v' for paste.
|
||||
if (Blockly.clipboardXml_) {
|
||||
Blockly.Events.setGroup(true);
|
||||
// Pasting always pastes to the main workspace, even if the copy started
|
||||
// in a flyout workspace.
|
||||
var workspace = Blockly.clipboardSource_;
|
||||
if (workspace.isFlyout) {
|
||||
workspace = workspace.targetWorkspace;
|
||||
}
|
||||
workspace.paste(Blockly.clipboardXml_);
|
||||
Blockly.Events.setGroup(false);
|
||||
}
|
||||
} else if (e.keyCode == 90 || e.keyCode === 89) {
|
||||
// 'z' for undo 'Z' is for redo. 'y' is always redo.
|
||||
e.preventDefault();
|
||||
Blockly.hideChaff();
|
||||
Blockly.mainWorkspace.undo(e.shiftKey || e.keyCode === 89);
|
||||
}
|
||||
}
|
||||
// Common code for delete and cut.
|
||||
// Don't delete in the flyout.
|
||||
if (deleteBlock && !Blockly.selected.workspace.isFlyout) {
|
||||
Blockly.Events.setGroup(true);
|
||||
Blockly.hideChaff();
|
||||
Blockly.selected.dispose(/* heal */ true, true);
|
||||
Blockly.Events.setGroup(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Copy a block or workspace comment onto the local clipboard.
|
||||
* @param {!Blockly.Block | !Blockly.WorkspaceComment} toCopy Block or Workspace Comment
|
||||
* to be copied.
|
||||
* @private
|
||||
*/
|
||||
Blockly.copy_ = function(toCopy) {
|
||||
if (toCopy.isComment) {
|
||||
var xml = toCopy.toXmlWithXY();
|
||||
} else {
|
||||
var xml = Blockly.Xml.blockToDom(toCopy);
|
||||
// Encode start position in XML.
|
||||
var xy = toCopy.getRelativeToSurfaceXY();
|
||||
xml.setAttribute('x', toCopy.RTL ? -xy.x : xy.x);
|
||||
xml.setAttribute('y', xy.y);
|
||||
}
|
||||
Blockly.clipboardXml_ = xml;
|
||||
Blockly.clipboardSource_ = toCopy.workspace;
|
||||
};
|
||||
|
||||
/**
|
||||
* Duplicate this block and its children, or a workspace comment.
|
||||
* @param {!Blockly.Block | !Blockly.WorkspaceComment} toDuplicate Block or
|
||||
* Workspace Comment to be copied.
|
||||
* @private
|
||||
*/
|
||||
Blockly.duplicate_ = function(toDuplicate) {
|
||||
// Save the clipboard.
|
||||
var clipboardXml = Blockly.clipboardXml_;
|
||||
var clipboardSource = Blockly.clipboardSource_;
|
||||
|
||||
// Create a duplicate via a copy/paste operation.
|
||||
Blockly.copy_(toDuplicate);
|
||||
toDuplicate.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.utils.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.hideChaffInternal_(opt_allowToolbox);
|
||||
Blockly.WidgetDiv.hide(true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Close tooltips, context menus, dropdown selections, etc.
|
||||
* For some elements (e.g. field text inputs), rather than hiding, it will
|
||||
* move them.
|
||||
* @param {boolean=} opt_allowToolbox If true, don't close the toolbox.
|
||||
*/
|
||||
Blockly.hideChaffOnResize = function(opt_allowToolbox) {
|
||||
Blockly.hideChaffInternal_(opt_allowToolbox);
|
||||
Blockly.WidgetDiv.repositionForWindowResize();
|
||||
};
|
||||
|
||||
/**
|
||||
* Does a majority of the work for hideChaff including tooltips, dropdowns,
|
||||
* toolbox, etc. It does not deal with the WidgetDiv.
|
||||
* @param {boolean=} opt_allowToolbox If true, don't close the toolbox.
|
||||
* @private
|
||||
*/
|
||||
Blockly.hideChaffInternal_ = function(opt_allowToolbox) {
|
||||
Blockly.Tooltip.hide();
|
||||
Blockly.DropDownDiv.hideWithoutAnimation();
|
||||
if (!opt_allowToolbox) {
|
||||
var workspace = Blockly.getMainWorkspace();
|
||||
if (workspace.toolbox_ &&
|
||||
workspace.toolbox_.flyout_ &&
|
||||
workspace.toolbox_.flyout_.autoClose) {
|
||||
workspace.toolbox_.clearSelection();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 response.
|
||||
* @param {?string} _opt_title An optional title for the prompt.
|
||||
* @param {?string} _opt_varType An optional variable type for variable specific
|
||||
* prompt behavior.
|
||||
*/
|
||||
Blockly.prompt = function(message, defaultValue, callback, _opt_title,
|
||||
_opt_varType) {
|
||||
// opt_title and opt_varType are unused because we only need them to pass
|
||||
// information to the scratch-gui, which overwrites this function
|
||||
callback(window.prompt(message, defaultValue));
|
||||
};
|
||||
|
||||
/**
|
||||
* A callback for status buttons. The window.alert is here for testing and
|
||||
* should be overridden.
|
||||
* @param {string} id An identifier.
|
||||
*/
|
||||
Blockly.statusButtonCallback = function(id) {
|
||||
window.alert('status button was pressed for ' + id);
|
||||
};
|
||||
|
||||
/**
|
||||
* Refresh the visual state of a status button in all extension category headers.
|
||||
* @param {Blockly.Workspace} workspace A workspace.
|
||||
*/
|
||||
Blockly.refreshStatusButtons = function(workspace) {
|
||||
var buttons = workspace.getFlyout().buttons_;
|
||||
for (var i = 0; i < buttons.length; i++) {
|
||||
if (buttons[i] instanceof Blockly.FlyoutExtensionCategoryHeader) {
|
||||
buttons[i].refreshStatus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.<!Object>} jsonArray An array of JSON block definitions.
|
||||
*/
|
||||
Blockly.defineBlocksWithJsonArray = function(jsonArray) {
|
||||
for (var i = 0; i < jsonArray.length; i++) {
|
||||
var elem = jsonArray[i];
|
||||
if (!elem) {
|
||||
console.warn(
|
||||
'Block definition #' + i + ' in JSON array is ' + elem + '. ' +
|
||||
'Skipping.');
|
||||
} else {
|
||||
var typename = elem.type;
|
||||
if (typename == null || typename === '') {
|
||||
console.warn(
|
||||
'Block definition #' + i +
|
||||
' in JSON array is missing a type attribute. Skipping.');
|
||||
} else {
|
||||
Blockly.Blocks[typename] = {
|
||||
init: Blockly.jsonInitFactory_(elem)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Bind an event to a function call. When calling the function, verifies that
|
||||
* it belongs to the touch stream that is currently being processed, and splits
|
||||
* multitouch events into multiple events as needed.
|
||||
* @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.
|
||||
* @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 {!Array.<!Array>} Opaque data that can be passed to unbindEvent_.
|
||||
*/
|
||||
Blockly.bindEventWithChecks_ = function(node, name, thisObject, func,
|
||||
opt_noCaptureIdentifier, opt_noPreventDefault) {
|
||||
var handled = false;
|
||||
var wrapFunc = function(e) {
|
||||
var captureIdentifier = !opt_noCaptureIdentifier;
|
||||
// Handle each touch point separately. If the event was a mouse event, this
|
||||
// will hand back an array with one element, which we're fine handling.
|
||||
var events = Blockly.Touch.splitEventByTouches(e);
|
||||
for (var i = 0, event; event = events[i]; i++) {
|
||||
if (captureIdentifier && !Blockly.Touch.shouldHandleEvent(event)) {
|
||||
continue;
|
||||
}
|
||||
Blockly.Touch.setClientFromTouch(event);
|
||||
if (thisObject) {
|
||||
func.call(thisObject, event);
|
||||
} else {
|
||||
func(event);
|
||||
}
|
||||
handled = true;
|
||||
}
|
||||
};
|
||||
|
||||
node.addEventListener(name, wrapFunc, false);
|
||||
var bindData = [[node, name, wrapFunc]];
|
||||
|
||||
// Add equivalent touch event.
|
||||
if (name in Blockly.Touch.TOUCH_MAP) {
|
||||
var touchWrapFunc = function(e) {
|
||||
wrapFunc(e);
|
||||
// Calling preventDefault stops the browser from scrolling/zooming the
|
||||
// page.
|
||||
var preventDef = !opt_noPreventDefault;
|
||||
if (handled && preventDef) {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
for (var i = 0, type; type = Blockly.Touch.TOUCH_MAP[name][i]; i++) {
|
||||
node.addEventListener(type, touchWrapFunc, false);
|
||||
bindData.push([node, type, touchWrapFunc]);
|
||||
}
|
||||
}
|
||||
return bindData;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Bind an event to a function call. Handles multitouch events by using the
|
||||
* coordinates of the first changed touch, and doesn't do any safety checks for
|
||||
* simultaneous event processing.
|
||||
* @deprecated in favor of bindEventWithChecks_, but preserved for external
|
||||
* users.
|
||||
* @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 {!Array.<!Array>} Opaque data that can be passed to unbindEvent_.
|
||||
* @private
|
||||
*/
|
||||
Blockly.bindEvent_ = function(node, name, thisObject, func) {
|
||||
var wrapFunc = function(e) {
|
||||
if (thisObject) {
|
||||
func.call(thisObject, e);
|
||||
} else {
|
||||
func(e);
|
||||
}
|
||||
};
|
||||
|
||||
node.addEventListener(name, wrapFunc, false);
|
||||
var bindData = [[node, name, wrapFunc]];
|
||||
|
||||
// Add equivalent touch event.
|
||||
if (name in Blockly.Touch.TOUCH_MAP) {
|
||||
var touchWrapFunc = function(e) {
|
||||
// Punt on multitouch events.
|
||||
if (e.changedTouches.length == 1) {
|
||||
// Map the touch event's properties to the event.
|
||||
var touchPoint = e.changedTouches[0];
|
||||
e.clientX = touchPoint.clientX;
|
||||
e.clientY = touchPoint.clientY;
|
||||
}
|
||||
wrapFunc(e);
|
||||
|
||||
// Stop the browser from scrolling/zooming the page.
|
||||
e.preventDefault();
|
||||
};
|
||||
for (var i = 0, type; type = Blockly.Touch.TOUCH_MAP[name][i]; i++) {
|
||||
node.addEventListener(type, touchWrapFunc, false);
|
||||
bindData.push([node, type, touchWrapFunc]);
|
||||
}
|
||||
}
|
||||
return bindData;
|
||||
};
|
||||
|
||||
/**
|
||||
* Unbind one or more events event from a function call.
|
||||
* @param {!Array.<!Array>} bindData Opaque data from bindEvent_.
|
||||
* This list is emptied during the course of calling this function.
|
||||
* @return {!Function} The function call.
|
||||
* @private
|
||||
*/
|
||||
Blockly.unbindEvent_ = function(bindData) {
|
||||
while (bindData.length) {
|
||||
var bindDatum = bindData.pop();
|
||||
var node = bindDatum[0];
|
||||
var name = bindDatum[1];
|
||||
var func = bindDatum[2];
|
||||
node.removeEventListener(name, func, false);
|
||||
}
|
||||
return func;
|
||||
};
|
||||
|
||||
/**
|
||||
* Is the given string a number (includes negative and decimals).
|
||||
* @param {string} str Input string.
|
||||
* @return {boolean} True if number, false otherwise.
|
||||
*/
|
||||
Blockly.isNumber = function(str) {
|
||||
return !!str.match(/^\s*-?\d+(\.\d+)?\s*$/);
|
||||
};
|
||||
|
||||
// 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;
|
||||
Reference in New Issue
Block a user