123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392 |
- /**
- * @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 Functions for injecting Blockly into a web page.
- * @author fraser@google.com (Neil Fraser)
- */
- 'use strict';
- goog.provide('Blockly.inject');
- goog.require('Blockly.Css');
- goog.require('Blockly.Options');
- goog.require('Blockly.WorkspaceSvg');
- goog.require('goog.dom');
- goog.require('goog.ui.Component');
- goog.require('goog.userAgent');
- /**
- * Inject a Blockly editor into the specified container element (usually a div).
- * @param {!Element|string} container Containing element, or its ID,
- * or a CSS selector.
- * @param {Object=} opt_options Optional dictionary of options.
- * @return {!Blockly.Workspace} Newly created main workspace.
- */
- Blockly.inject = function(container, opt_options) {
- if (goog.isString(container)) {
- container = document.getElementById(container) ||
- document.querySelector(container);
- }
- // Verify that the container is in document.
- if (!goog.dom.contains(document, container)) {
- throw 'Error: container is not in current document.';
- }
- var options = new Blockly.Options(opt_options || {});
- var subContainer = goog.dom.createDom('div', 'injectionDiv');
- container.appendChild(subContainer);
- var svg = Blockly.createDom_(subContainer, options);
- var workspace = Blockly.createMainWorkspace_(svg, options);
- Blockly.init_(workspace);
- workspace.markFocused();
- Blockly.bindEventWithChecks_(svg, 'focus', workspace, workspace.markFocused);
- Blockly.svgResize(workspace);
- return workspace;
- };
- /**
- * Create the SVG image.
- * @param {!Element} container Containing element.
- * @param {!Blockly.Options} options Dictionary of options.
- * @return {!Element} Newly created SVG image.
- * @private
- */
- Blockly.createDom_ = function(container, options) {
- // Sadly browsers (Chrome vs Firefox) are currently inconsistent in laying
- // out content in RTL mode. Therefore Blockly forces the use of LTR,
- // then manually positions content in RTL as needed.
- container.setAttribute('dir', 'LTR');
- // Closure can be trusted to create HTML widgets with the proper direction.
- goog.ui.Component.setDefaultRightToLeft(options.RTL);
- // Load CSS.
- Blockly.Css.inject(options.hasCss, options.pathToMedia);
- // Build the SVG DOM.
- /*
- <svg
- xmlns="http://www.w3.org/2000/svg"
- xmlns:html="http://www.w3.org/1999/xhtml"
- xmlns:xlink="http://www.w3.org/1999/xlink"
- version="1.1"
- class="blocklySvg">
- ...
- </svg>
- */
- var svg = Blockly.createSvgElement('svg', {
- 'xmlns': 'http://www.w3.org/2000/svg',
- 'xmlns:html': 'http://www.w3.org/1999/xhtml',
- 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
- 'version': '1.1',
- 'class': 'blocklySvg'
- }, container);
- /*
- <defs>
- ... filters go here ...
- </defs>
- */
- var defs = Blockly.createSvgElement('defs', {}, svg);
- // Each filter/pattern needs a unique ID for the case of multiple Blockly
- // instances on a page. Browser behaviour becomes undefined otherwise.
- // https://neil.fraser.name/news/2015/11/01/
- var rnd = String(Math.random()).substring(2);
- /*
- <filter id="blocklyEmbossFilter837493">
- <feGaussianBlur in="SourceAlpha" stdDeviation="1" result="blur" />
- <feSpecularLighting in="blur" surfaceScale="1" specularConstant="0.5"
- specularExponent="10" lighting-color="white"
- result="specOut">
- <fePointLight x="-5000" y="-10000" z="20000" />
- </feSpecularLighting>
- <feComposite in="specOut" in2="SourceAlpha" operator="in"
- result="specOut" />
- <feComposite in="SourceGraphic" in2="specOut" operator="arithmetic"
- k1="0" k2="1" k3="1" k4="0" />
- </filter>
- */
- var embossFilter = Blockly.createSvgElement('filter',
- {'id': 'blocklyEmbossFilter' + rnd}, defs);
- Blockly.createSvgElement('feGaussianBlur',
- {'in': 'SourceAlpha', 'stdDeviation': 1, 'result': 'blur'}, embossFilter);
- var feSpecularLighting = Blockly.createSvgElement('feSpecularLighting',
- {'in': 'blur', 'surfaceScale': 1, 'specularConstant': 0.5,
- 'specularExponent': 10, 'lighting-color': 'white', 'result': 'specOut'},
- embossFilter);
- Blockly.createSvgElement('fePointLight',
- {'x': -5000, 'y': -10000, 'z': 20000}, feSpecularLighting);
- Blockly.createSvgElement('feComposite',
- {'in': 'specOut', 'in2': 'SourceAlpha', 'operator': 'in',
- 'result': 'specOut'}, embossFilter);
- Blockly.createSvgElement('feComposite',
- {'in': 'SourceGraphic', 'in2': 'specOut', 'operator': 'arithmetic',
- 'k1': 0, 'k2': 1, 'k3': 1, 'k4': 0}, embossFilter);
- options.embossFilterId = embossFilter.id;
- /*
- <pattern id="blocklyDisabledPattern837493" patternUnits="userSpaceOnUse"
- width="10" height="10">
- <rect width="10" height="10" fill="#aaa" />
- <path d="M 0 0 L 10 10 M 10 0 L 0 10" stroke="#cc0" />
- </pattern>
- */
- var disabledPattern = Blockly.createSvgElement('pattern',
- {'id': 'blocklyDisabledPattern' + rnd,
- 'patternUnits': 'userSpaceOnUse',
- 'width': 10, 'height': 10}, defs);
- Blockly.createSvgElement('rect',
- {'width': 10, 'height': 10, 'fill': '#aaa'}, disabledPattern);
- Blockly.createSvgElement('path',
- {'d': 'M 0 0 L 10 10 M 10 0 L 0 10', 'stroke': '#cc0'}, disabledPattern);
- options.disabledPatternId = disabledPattern.id;
- /*
- <pattern id="blocklyGridPattern837493" patternUnits="userSpaceOnUse">
- <rect stroke="#888" />
- <rect stroke="#888" />
- </pattern>
- */
- var gridPattern = Blockly.createSvgElement('pattern',
- {'id': 'blocklyGridPattern' + rnd,
- 'patternUnits': 'userSpaceOnUse'}, defs);
- if (options.gridOptions['length'] > 0 && options.gridOptions['spacing'] > 0) {
- Blockly.createSvgElement('line',
- {'stroke': options.gridOptions['colour']},
- gridPattern);
- if (options.gridOptions['length'] > 1) {
- Blockly.createSvgElement('line',
- {'stroke': options.gridOptions['colour']},
- gridPattern);
- }
- // x1, y1, x1, x2 properties will be set later in updateGridPattern_.
- }
- options.gridPattern = gridPattern;
- return svg;
- };
- /**
- * Create a main workspace and add it to the SVG.
- * @param {!Element} svg SVG element with pattern defined.
- * @param {!Blockly.Options} options Dictionary of options.
- * @return {!Blockly.Workspace} Newly created main workspace.
- * @private
- */
- Blockly.createMainWorkspace_ = function(svg, options) {
- options.parentWorkspace = null;
- var mainWorkspace = new Blockly.WorkspaceSvg(options);
- mainWorkspace.scale = options.zoomOptions.startScale;
- svg.appendChild(mainWorkspace.createDom('blocklyMainBackground'));
- // A null translation will also apply the correct initial scale.
- mainWorkspace.translate(0, 0);
- mainWorkspace.markFocused();
- if (!options.readOnly && !options.hasScrollbars) {
- var workspaceChanged = function() {
- if (Blockly.dragMode_ == Blockly.DRAG_NONE) {
- var metrics = mainWorkspace.getMetrics();
- var edgeLeft = metrics.viewLeft + metrics.absoluteLeft;
- var edgeTop = metrics.viewTop + metrics.absoluteTop;
- if (metrics.contentTop < edgeTop ||
- metrics.contentTop + metrics.contentHeight >
- metrics.viewHeight + edgeTop ||
- metrics.contentLeft <
- (options.RTL ? metrics.viewLeft : edgeLeft) ||
- metrics.contentLeft + metrics.contentWidth > (options.RTL ?
- metrics.viewWidth : metrics.viewWidth + edgeLeft)) {
- // One or more blocks may be out of bounds. Bump them back in.
- var MARGIN = 25;
- var blocks = mainWorkspace.getTopBlocks(false);
- for (var b = 0, block; block = blocks[b]; b++) {
- var blockXY = block.getRelativeToSurfaceXY();
- var blockHW = block.getHeightWidth();
- // Bump any block that's above the top back inside.
- var overflowTop = edgeTop + MARGIN - blockHW.height - blockXY.y;
- if (overflowTop > 0) {
- block.moveBy(0, overflowTop);
- }
- // Bump any block that's below the bottom back inside.
- var overflowBottom =
- edgeTop + metrics.viewHeight - MARGIN - blockXY.y;
- if (overflowBottom < 0) {
- block.moveBy(0, overflowBottom);
- }
- // Bump any block that's off the left back inside.
- var overflowLeft = MARGIN + edgeLeft -
- blockXY.x - (options.RTL ? 0 : blockHW.width);
- if (overflowLeft > 0) {
- block.moveBy(overflowLeft, 0);
- }
- // Bump any block that's off the right back inside.
- var overflowRight = edgeLeft + metrics.viewWidth - MARGIN -
- blockXY.x + (options.RTL ? blockHW.width : 0);
- if (overflowRight < 0) {
- block.moveBy(overflowRight, 0);
- }
- }
- }
- }
- };
- mainWorkspace.addChangeListener(workspaceChanged);
- }
- // The SVG is now fully assembled.
- Blockly.svgResize(mainWorkspace);
- Blockly.WidgetDiv.createDom();
- Blockly.Tooltip.createDom();
- return mainWorkspace;
- };
- /**
- * Initialize Blockly with various handlers.
- * @param {!Blockly.Workspace} mainWorkspace Newly created main workspace.
- * @private
- */
- Blockly.init_ = function(mainWorkspace) {
- var options = mainWorkspace.options;
- var svg = mainWorkspace.getParentSvg();
- // Supress the browser's context menu.
- Blockly.bindEventWithChecks_(svg, 'contextmenu', null,
- function(e) {
- if (!Blockly.isTargetInput_(e)) {
- e.preventDefault();
- }
- });
- var workspaceResizeHandler = Blockly.bindEventWithChecks_(window, 'resize',
- null,
- function() {
- Blockly.hideChaff(true);
- Blockly.svgResize(mainWorkspace);
- });
- mainWorkspace.setResizeHandlerWrapper(workspaceResizeHandler);
- Blockly.inject.bindDocumentEvents_();
- if (options.languageTree) {
- if (mainWorkspace.toolbox_) {
- mainWorkspace.toolbox_.init(mainWorkspace);
- } else if (mainWorkspace.flyout_) {
- // Build a fixed flyout with the root blocks.
- mainWorkspace.flyout_.init(mainWorkspace);
- mainWorkspace.flyout_.show(options.languageTree.childNodes);
- mainWorkspace.flyout_.scrollToStart();
- // Translate the workspace sideways to avoid the fixed flyout.
- mainWorkspace.scrollX = mainWorkspace.flyout_.width_;
- if (options.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) {
- mainWorkspace.scrollX *= -1;
- }
- mainWorkspace.translate(mainWorkspace.scrollX, 0);
- }
- }
- if (options.hasScrollbars) {
- mainWorkspace.scrollbar = new Blockly.ScrollbarPair(mainWorkspace);
- mainWorkspace.scrollbar.resize();
- }
- // Load the sounds.
- if (options.hasSounds) {
- Blockly.inject.loadSounds_(options.pathToMedia, mainWorkspace);
- }
- };
- /**
- * Bind document events, but only once. Destroying and reinjecting Blockly
- * should not bind again.
- * Bind events for scrolling the workspace.
- * Most of these events should be bound to the SVG's surface.
- * However, 'mouseup' has to be on the whole document so that a block dragged
- * out of bounds and released will know that it has been released.
- * Also, 'keydown' has to be on the whole document since the browser doesn't
- * understand a concept of focus on the SVG image.
- * @private
- */
- Blockly.inject.bindDocumentEvents_ = function() {
- if (!Blockly.documentEventsBound_) {
- Blockly.bindEventWithChecks_(document, 'keydown', null, Blockly.onKeyDown_);
- Blockly.bindEventWithChecks_(document, 'touchend', null, Blockly.longStop_);
- Blockly.bindEventWithChecks_(document, 'touchcancel', null,
- Blockly.longStop_);
- // Don't use bindEvent_ for document's mouseup since that would create a
- // corresponding touch handler that would squeltch the ability to interact
- // with non-Blockly elements.
- document.addEventListener('mouseup', Blockly.onMouseUp_, false);
- // Some iPad versions don't fire resize after portrait to landscape change.
- if (goog.userAgent.IPAD) {
- Blockly.bindEventWithChecks_(window, 'orientationchange', document,
- function() {
- // TODO(#397): Fix for multiple blockly workspaces.
- Blockly.svgResize(Blockly.getMainWorkspace());
- });
- }
- }
- Blockly.documentEventsBound_ = true;
- };
- /**
- * Load sounds for the given workspace.
- * @param {string} pathToMedia The path to the media directory.
- * @param {!Blockly.Workspace} workspace The workspace to load sounds for.
- * @private
- */
- Blockly.inject.loadSounds_ = function(pathToMedia, workspace) {
- workspace.loadAudio_(
- [pathToMedia + 'click.mp3',
- pathToMedia + 'click.wav',
- pathToMedia + 'click.ogg'], 'click');
- workspace.loadAudio_(
- [pathToMedia + 'disconnect.wav',
- pathToMedia + 'disconnect.mp3',
- pathToMedia + 'disconnect.ogg'], 'disconnect');
- workspace.loadAudio_(
- [pathToMedia + 'delete.mp3',
- pathToMedia + 'delete.ogg',
- pathToMedia + 'delete.wav'], 'delete');
- // Bind temporary hooks that preload the sounds.
- var soundBinds = [];
- var unbindSounds = function() {
- while (soundBinds.length) {
- Blockly.unbindEvent_(soundBinds.pop());
- }
- workspace.preloadAudio_();
- };
- // These are bound on mouse/touch events with Blockly.bindEventWithChecks_, so
- // they restrict the touch identifier that will be recognized. But this is
- // really something that happens on a click, not a drag, so that's not
- // necessary.
- // Android ignores any sound not loaded as a result of a user action.
- soundBinds.push(
- Blockly.bindEventWithChecks_(document, 'mousemove', null, unbindSounds,
- true));
- soundBinds.push(
- Blockly.bindEventWithChecks_(document, 'touchstart', null, unbindSounds,
- true));
- };
- /**
- * Modify the block tree on the existing toolbox.
- * @param {Node|string} tree DOM tree of blocks, or text representation of same.
- */
- Blockly.updateToolbox = function(tree) {
- console.warn('Deprecated call to Blockly.updateToolbox, ' +
- 'use workspace.updateToolbox instead.');
- Blockly.getMainWorkspace().updateToolbox(tree);
- };
|