Migrate core/inject.js to goog_module syntax (#5456)

* Move bump objects function into a separate module

* Fix types

* Migrate core/inject.js to ES6 const/let

* Migrate core/inject.js to goog.module and move more functions to bump_objects.js

* Migrate core/inject.js named requires

* clang-format core/inject.js

* Rename private functions
This commit is contained in:
Rachel Fenichel
2021-09-15 08:15:25 -07:00
committed by GitHub
parent 2ba51d4d75
commit 9b8cd7ce5b
3 changed files with 242 additions and 235 deletions

View File

@@ -10,81 +10,79 @@
*/
'use strict';
goog.provide('Blockly.inject');
goog.module('Blockly.inject');
goog.module.declareLegacyNamespace();
goog.require('Blockly.BlockDragSurfaceSvg');
goog.require('Blockly.browserEvents');
goog.require('Blockly.bumpObjects');
goog.require('Blockly.common');
goog.require('Blockly.Css');
goog.require('Blockly.DropDownDiv');
goog.require('Blockly.Events');
goog.require('Blockly.Grid');
goog.require('Blockly.Msg');
goog.require('Blockly.Options');
goog.require('Blockly.ScrollbarPair');
goog.require('Blockly.Touch');
goog.require('Blockly.Tooltip');
goog.require('Blockly.utils');
goog.require('Blockly.utils.aria');
goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.Svg');
goog.require('Blockly.utils.userAgent');
goog.require('Blockly.Workspace');
goog.require('Blockly.WorkspaceDragSurfaceSvg');
goog.require('Blockly.WorkspaceSvg');
goog.require('Blockly.WidgetDiv');
goog.requireType('Blockly.BlocklyOptions');
goog.requireType('Blockly.BlockSvg');
goog.requireType('Blockly.WorkspaceCommentSvg');
const BlockDragSurfaceSvg = goog.require('Blockly.BlockDragSurfaceSvg');
/* eslint-disable-next-line no-unused-vars */
const BlocklyOptions = goog.requireType('Blockly.BlocklyOptions');
const Css = goog.require('Blockly.Css');
const DropDownDiv = goog.require('Blockly.DropDownDiv');
const Grid = goog.require('Blockly.Grid');
const Msg = goog.require('Blockly.Msg');
const Options = goog.require('Blockly.Options');
const ScrollbarPair = goog.require('Blockly.ScrollbarPair');
const Touch = goog.require('Blockly.Touch');
const Tooltip = goog.require('Blockly.Tooltip');
const Svg = goog.require('Blockly.utils.Svg');
const Workspace = goog.require('Blockly.Workspace');
const WorkspaceDragSurfaceSvg = goog.require('Blockly.WorkspaceDragSurfaceSvg');
const WorkspaceSvg = goog.require('Blockly.WorkspaceSvg');
const WidgetDiv = goog.require('Blockly.WidgetDiv');
const aria = goog.require('Blockly.utils.aria');
const browserEvents = goog.require('Blockly.browserEvents');
const bumpObjects = goog.require('Blockly.bumpObjects');
const common = goog.require('Blockly.common');
const dom = goog.require('Blockly.utils.dom');
const userAgent = goog.require('Blockly.utils.userAgent');
const utils = goog.require('Blockly.utils');
/**
* 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 {Blockly.BlocklyOptions=} opt_options Optional dictionary of options.
* @return {!Blockly.WorkspaceSvg} Newly created main workspace.
* @param {BlocklyOptions=} opt_options Optional dictionary of options.
* @return {!WorkspaceSvg} Newly created main workspace.
*/
Blockly.inject = function(container, opt_options) {
const inject = function(container, opt_options) {
if (typeof container == 'string') {
container = document.getElementById(container) ||
document.querySelector(container);
container =
document.getElementById(container) || document.querySelector(container);
}
// Verify that the container is in document.
if (!container || !Blockly.utils.dom.containsNode(document, container)) {
if (!container || !dom.containsNode(document, container)) {
throw Error('Error: container is not in current document.');
}
var options = new Blockly.Options(opt_options ||
(/** @type {!Blockly.BlocklyOptions} */ ({})));
var subContainer = document.createElement('div');
const options =
new Options(opt_options || (/** @type {!BlocklyOptions} */ ({})));
const subContainer = document.createElement('div');
subContainer.className = 'injectionDiv';
subContainer.tabIndex = 0;
Blockly.utils.aria.setState(subContainer,
Blockly.utils.aria.State.LABEL, Blockly.Msg['WORKSPACE_ARIA_LABEL']);
aria.setState(subContainer, aria.State.LABEL, Msg['WORKSPACE_ARIA_LABEL']);
container.appendChild(subContainer);
var svg = Blockly.createDom_(subContainer, options);
const svg = createDom(subContainer, options);
// Create surfaces for dragging things. These are optimizations
// so that the browser does not repaint during the drag.
var blockDragSurface = new Blockly.BlockDragSurfaceSvg(subContainer);
const blockDragSurface = new BlockDragSurfaceSvg(subContainer);
var workspaceDragSurface = new Blockly.WorkspaceDragSurfaceSvg(subContainer);
const workspaceDragSurface = new WorkspaceDragSurfaceSvg(subContainer);
var workspace = Blockly.createMainWorkspace_(svg, options, blockDragSurface,
workspaceDragSurface);
const workspace =
createMainWorkspace(svg, options, blockDragSurface, workspaceDragSurface);
Blockly.init_(workspace);
init(workspace);
// Keep focus on the first workspace so entering keyboard navigation looks correct.
Blockly.common.setMainWorkspace(workspace);
// Keep focus on the first workspace so entering keyboard navigation looks
// correct.
common.setMainWorkspace(workspace);
Blockly.svgResize(workspace);
subContainer.addEventListener('focusin', function() {
Blockly.common.setMainWorkspace(workspace);
common.setMainWorkspace(workspace);
});
return workspace;
@@ -93,18 +91,17 @@ Blockly.inject = function(container, opt_options) {
/**
* Create the SVG image.
* @param {!Element} container Containing element.
* @param {!Blockly.Options} options Dictionary of options.
* @param {!Options} options Dictionary of options.
* @return {!Element} Newly created SVG image.
* @private
*/
Blockly.createDom_ = function(container, options) {
const 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');
// Load CSS.
Blockly.Css.inject(options.hasCss, options.pathToMedia);
Css.inject(options.hasCss, options.pathToMedia);
// Build the SVG DOM.
/*
@@ -117,61 +114,61 @@ Blockly.createDom_ = function(container, options) {
...
</svg>
*/
var svg = Blockly.utils.dom.createSvgElement(
Blockly.utils.Svg.SVG, {
'xmlns': Blockly.utils.dom.SVG_NS,
'xmlns:html': Blockly.utils.dom.HTML_NS,
'xmlns:xlink': Blockly.utils.dom.XLINK_NS,
const svg = dom.createSvgElement(
Svg.SVG, {
'xmlns': dom.SVG_NS,
'xmlns:html': dom.HTML_NS,
'xmlns:xlink': dom.XLINK_NS,
'version': '1.1',
'class': 'blocklySvg',
'tabindex': '0'
}, container);
},
container);
/*
<defs>
... filters go here ...
</defs>
*/
var defs = Blockly.utils.dom.createSvgElement(
Blockly.utils.Svg.DEFS, {}, svg);
const defs = dom.createSvgElement(Svg.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);
const rnd = String(Math.random()).substring(2);
options.gridPattern = Blockly.Grid.createDom(rnd, options.gridOptions, defs);
options.gridPattern = Grid.createDom(rnd, options.gridOptions, defs);
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.
* @param {!Blockly.BlockDragSurfaceSvg} blockDragSurface Drag surface SVG
* @param {!Options} options Dictionary of options.
* @param {!BlockDragSurfaceSvg} blockDragSurface Drag surface SVG
* for the blocks.
* @param {!Blockly.WorkspaceDragSurfaceSvg} workspaceDragSurface Drag surface
* @param {!WorkspaceDragSurfaceSvg} workspaceDragSurface Drag surface
* SVG for the workspace.
* @return {!Blockly.WorkspaceSvg} Newly created main workspace.
* @private
* @return {!WorkspaceSvg} Newly created main workspace.
*/
Blockly.createMainWorkspace_ = function(svg, options, blockDragSurface,
workspaceDragSurface) {
const createMainWorkspace = function(
svg, options, blockDragSurface, workspaceDragSurface) {
options.parentWorkspace = null;
var mainWorkspace =
new Blockly.WorkspaceSvg(options, blockDragSurface, workspaceDragSurface);
var wsOptions = mainWorkspace.options;
const mainWorkspace =
new WorkspaceSvg(options, blockDragSurface, workspaceDragSurface);
const wsOptions = mainWorkspace.options;
mainWorkspace.scale = wsOptions.zoomOptions.startScale;
svg.appendChild(mainWorkspace.createDom('blocklyMainBackground'));
// Set the theme name and renderer name onto the injection div.
Blockly.utils.dom.addClass(mainWorkspace.getInjectionDiv(),
dom.addClass(
mainWorkspace.getInjectionDiv(),
mainWorkspace.getRenderer().getClassName());
Blockly.utils.dom.addClass(mainWorkspace.getInjectionDiv(),
mainWorkspace.getTheme().getClassName());
dom.addClass(
mainWorkspace.getInjectionDiv(), mainWorkspace.getTheme().getClassName());
if (!wsOptions.hasCategories && wsOptions.languageTree) {
// Add flyout as an <svg> that is a sibling of the workspace SVG.
var flyout = mainWorkspace.addFlyout(Blockly.utils.Svg.SVG);
Blockly.utils.dom.insertAfter(flyout, svg);
const flyout = mainWorkspace.addFlyout(Svg.SVG);
dom.insertAfter(flyout, svg);
}
if (wsOptions.hasTrashcan) {
mainWorkspace.addTrashcan();
@@ -180,146 +177,54 @@ Blockly.createMainWorkspace_ = function(svg, options, blockDragSurface,
mainWorkspace.addZoomControls();
}
// Register the workspace svg as a UI component.
mainWorkspace.getThemeManager().subscribe(svg, 'workspaceBackgroundColour',
'background-color');
mainWorkspace.getThemeManager().subscribe(
svg, 'workspaceBackgroundColour', 'background-color');
// A null translation will also apply the correct initial scale.
mainWorkspace.translate(0, 0);
mainWorkspace.addChangeListener(Blockly.bumpIntoBoundsHandler_(mainWorkspace));
mainWorkspace.addChangeListener(
bumpObjects.bumpIntoBoundsHandler(mainWorkspace));
// The SVG is now fully assembled.
Blockly.svgResize(mainWorkspace);
Blockly.WidgetDiv.createDom();
Blockly.DropDownDiv.createDom();
Blockly.Tooltip.createDom();
WidgetDiv.createDom();
DropDownDiv.createDom();
Tooltip.createDom();
return mainWorkspace;
};
/**
* Extracts the object from the given event.
* @param {!Blockly.WorkspaceSvg} workspace The workspace the event originated
* from.
* @param {!Blockly.Events.BumpEvent} e An event containing an object.
* @return {?Blockly.BlockSvg|?Blockly.WorkspaceCommentSvg} The extracted
* object.
* @private
*/
Blockly.extractObjectFromEvent_ = function(workspace, e) {
var object = null;
switch (e.type) {
case Blockly.Events.BLOCK_CREATE:
case Blockly.Events.BLOCK_MOVE:
object = workspace.getBlockById(e.blockId);
if (object) {
object = object.getRootBlock();
}
break;
case Blockly.Events.COMMENT_CREATE:
case Blockly.Events.COMMENT_MOVE:
object = (
/** @type {?Blockly.WorkspaceCommentSvg} */
(workspace.getCommentById(e.commentId)));
break;
}
return object;
};
/**
* Bumps the top objects in the given workspace into bounds.
* @param {!Blockly.WorkspaceSvg} workspace The workspace.
* @private
*/
Blockly.bumpTopObjectsIntoBounds_ = function(workspace) {
var metricsManager = workspace.getMetricsManager();
if (!metricsManager.hasFixedEdges() || workspace.isDragging()) {
return;
}
var scrollMetricsInWsCoords = metricsManager.getScrollMetrics(true);
var topBlocks = workspace.getTopBoundedElements();
for (var i = 0, block; (block = topBlocks[i]); i++) {
goog.module.get('Blockly.bumpObjects').bumpIntoBounds(workspace, scrollMetricsInWsCoords, block);
}
};
/**
* Creates a handler for bumping objects when they cross fixed bounds.
* @param {!Blockly.WorkspaceSvg} workspace The workspace to handle.
* @return {function(Blockly.Events.Abstract)} The event handler.
* @private
*/
Blockly.bumpIntoBoundsHandler_ = function(workspace) {
return function(e) {
var metricsManager = workspace.getMetricsManager();
if (!metricsManager.hasFixedEdges() || workspace.isDragging()) {
return;
}
if (Blockly.Events.BUMP_EVENTS.indexOf(e.type) !== -1) {
var scrollMetricsInWsCoords = metricsManager.getScrollMetrics(true);
// Triggered by move/create event
var object = Blockly.extractObjectFromEvent_(workspace, e);
if (!object) {
return;
}
// Handle undo.
var oldGroup = Blockly.Events.getGroup();
Blockly.Events.setGroup(e.group);
var wasBumped = goog.module.get('Blockly.bumpObjects')
.bumpIntoBounds(
workspace, scrollMetricsInWsCoords,
/** @type {!Blockly.IBoundedElement} */ (object));
if (wasBumped && !e.group) {
console.warn('Moved object in bounds but there was no' +
' event group. This may break undo.');
}
if (oldGroup !== null) {
Blockly.Events.setGroup(oldGroup);
}
} else if (e.type === Blockly.Events.VIEWPORT_CHANGE) {
var viewportEvent = /** @type {!Blockly.Events.ViewportChange} */ (e);
if (viewportEvent.scale > viewportEvent.oldScale) {
Blockly.bumpTopObjectsIntoBounds_(workspace);
}
}
};
};
/**
* Initialize Blockly with various handlers.
* @param {!Blockly.WorkspaceSvg} mainWorkspace Newly created main workspace.
* @private
* @param {!WorkspaceSvg} mainWorkspace Newly created main workspace.
*/
Blockly.init_ = function(mainWorkspace) {
var options = mainWorkspace.options;
var svg = mainWorkspace.getParentSvg();
const init = function(mainWorkspace) {
const options = mainWorkspace.options;
const svg = mainWorkspace.getParentSvg();
// Suppress the browser's context menu.
Blockly.browserEvents.conditionalBind(
browserEvents.conditionalBind(
/** @type {!Element} */ (svg.parentNode), 'contextmenu', null,
function(e) {
if (!Blockly.utils.isTargetInput(e)) {
if (!utils.isTargetInput(e)) {
e.preventDefault();
}
});
var workspaceResizeHandler =
Blockly.browserEvents.conditionalBind(window, 'resize', null, function() {
const workspaceResizeHandler =
browserEvents.conditionalBind(window, 'resize', null, function() {
Blockly.hideChaff(true);
Blockly.svgResize(mainWorkspace);
Blockly.bumpTopObjectsIntoBounds_(mainWorkspace);
goog.module.get('Blockly.bumpObjects')
.bumpTopObjectsIntoBounds(mainWorkspace);
});
mainWorkspace.setResizeHandlerWrapper(workspaceResizeHandler);
Blockly.inject.bindDocumentEvents_();
bindDocumentEvents();
if (options.languageTree) {
var toolbox = mainWorkspace.getToolbox();
var flyout = mainWorkspace.getFlyout(true);
const toolbox = mainWorkspace.getToolbox();
const flyout = mainWorkspace.getFlyout(true);
if (toolbox) {
toolbox.init();
} else if (flyout) {
@@ -340,14 +245,13 @@ Blockly.init_ = function(mainWorkspace) {
}
if (options.moveOptions && options.moveOptions.scrollbars) {
var horizontalScroll = options.moveOptions.scrollbars === true ||
const horizontalScroll = options.moveOptions.scrollbars === true ||
!!options.moveOptions.scrollbars.horizontal;
var verticalScroll = options.moveOptions.scrollbars === true ||
const verticalScroll = options.moveOptions.scrollbars === true ||
!!options.moveOptions.scrollbars.vertical;
mainWorkspace.scrollbar =
new Blockly.ScrollbarPair(
mainWorkspace, horizontalScroll, verticalScroll,
'blocklyMainWorkspaceScrollbar');
mainWorkspace.scrollbar = new ScrollbarPair(
mainWorkspace, horizontalScroll, verticalScroll,
'blocklyMainWorkspaceScrollbar');
mainWorkspace.scrollbar.resize();
} else {
mainWorkspace.setMetrics({x: 0.5, y: 0.5});
@@ -355,10 +259,17 @@ Blockly.init_ = function(mainWorkspace) {
// Load the sounds.
if (options.hasSounds) {
Blockly.inject.loadSounds_(options.pathToMedia, mainWorkspace);
loadSounds(options.pathToMedia, mainWorkspace);
}
};
/**
* Whether event handlers have been bound. Document event handlers will only
* be bound once, even if Blockly is destroyed and reinjected.
* @type {boolean}
*/
let documentEventsBound = false;
/**
* Bind document events, but only once. Destroying and reinjecting Blockly
* should not bind again.
@@ -368,82 +279,80 @@ Blockly.init_ = function(mainWorkspace) {
* 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.browserEvents.conditionalBind(document, 'scroll', null, function() {
var workspaces = Blockly.Workspace.getAll();
for (var i = 0, workspace; (workspace = workspaces[i]); i++) {
const bindDocumentEvents = function() {
if (!documentEventsBound) {
browserEvents.conditionalBind(document, 'scroll', null, function() {
const workspaces = Workspace.getAll();
for (let i = 0, workspace; (workspace = workspaces[i]); i++) {
if (workspace.updateInverseScreenCTM) {
workspace.updateInverseScreenCTM();
}
}
});
Blockly.browserEvents.conditionalBind(
document, 'keydown', null, Blockly.onKeyDown);
browserEvents.conditionalBind(document, 'keydown', null, Blockly.onKeyDown);
// longStop needs to run to stop the context menu from showing up. It
// should run regardless of what other touch event handlers have run.
Blockly.browserEvents.bind(document, 'touchend', null, Blockly.Touch.longStop);
Blockly.browserEvents.bind(
document, 'touchcancel', null, Blockly.Touch.longStop);
browserEvents.bind(document, 'touchend', null, Touch.longStop);
browserEvents.bind(document, 'touchcancel', null, Touch.longStop);
// Some iPad versions don't fire resize after portrait to landscape change.
if (Blockly.utils.userAgent.IPAD) {
Blockly.browserEvents.conditionalBind(
if (userAgent.IPAD) {
browserEvents.conditionalBind(
window, 'orientationchange', document, function() {
// TODO (#397): Fix for multiple Blockly workspaces.
Blockly.svgResize(/** @type {!Blockly.WorkspaceSvg} */
(Blockly.common.getMainWorkspace()));
Blockly.svgResize(/** @type {!WorkspaceSvg} */
(common.getMainWorkspace()));
});
}
}
Blockly.documentEventsBound_ = true;
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
* @param {!Workspace} workspace The workspace to load sounds for.
*/
Blockly.inject.loadSounds_ = function(pathToMedia, workspace) {
var audioMgr = workspace.getAudioManager();
const loadSounds = function(pathToMedia, workspace) {
const audioMgr = workspace.getAudioManager();
audioMgr.load(
[
pathToMedia + 'click.mp3',
pathToMedia + 'click.wav',
pathToMedia + 'click.mp3', pathToMedia + 'click.wav',
pathToMedia + 'click.ogg'
], 'click');
],
'click');
audioMgr.load(
[
pathToMedia + 'disconnect.wav',
pathToMedia + 'disconnect.mp3',
pathToMedia + 'disconnect.wav', pathToMedia + 'disconnect.mp3',
pathToMedia + 'disconnect.ogg'
], 'disconnect');
],
'disconnect');
audioMgr.load(
[
pathToMedia + 'delete.mp3',
pathToMedia + 'delete.ogg',
pathToMedia + 'delete.mp3', pathToMedia + 'delete.ogg',
pathToMedia + 'delete.wav'
], 'delete');
],
'delete');
// Bind temporary hooks that preload the sounds.
var soundBinds = [];
var unbindSounds = function() {
const soundBinds = [];
const unbindSounds = function() {
while (soundBinds.length) {
Blockly.browserEvents.unbind(soundBinds.pop());
browserEvents.unbind(soundBinds.pop());
}
audioMgr.preload();
};
// 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.
// These are bound on mouse/touch events with
// Blockly.browserEvents.conditionalBind, 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.browserEvents.conditionalBind(
soundBinds.push(browserEvents.conditionalBind(
document, 'mousemove', null, unbindSounds, true));
soundBinds.push(Blockly.browserEvents.conditionalBind(
soundBinds.push(browserEvents.conditionalBind(
document, 'touchstart', null, unbindSounds, true));
};
exports = inject;