chore: Move functions from utils (#5706)

* chore: move functions from utils to more specific files

* chore: use new names for utils functions

* chore: run clang-format

* chore: add deprecation warnings back to utils.js
This commit is contained in:
Rachel Fenichel
2021-11-15 15:59:27 -08:00
committed by GitHub
parent 6c10b6031c
commit 969fcac455
30 changed files with 749 additions and 495 deletions

View File

@@ -25,6 +25,7 @@ const eventUtils = goog.require('Blockly.Events.utils');
const fieldRegistry = goog.require('Blockly.fieldRegistry');
const idGenerator = goog.require('Blockly.utils.idGenerator');
const object = goog.require('Blockly.utils.object');
const parsing = goog.require('Blockly.utils.parsing');
const utils = goog.require('Blockly.utils');
const {ASTNode} = goog.require('Blockly.ASTNode');
const {Blocks} = goog.require('Blockly.blocks');
@@ -1015,7 +1016,7 @@ Block.prototype.getHue = function() {
* or a message reference string pointing to one of those two values.
*/
Block.prototype.setColour = function(colour) {
const parsed = utils.parseBlockColour(colour);
const parsed = parsing.parseBlockColour(colour);
this.hue_ = parsed.hue;
this.colour_ = parsed.hex;
};
@@ -1582,7 +1583,7 @@ Block.prototype.jsonInit = function(json) {
}
if (json['tooltip'] !== undefined) {
const rawValue = json['tooltip'];
const localizedText = utils.replaceMessageReferences(rawValue);
const localizedText = parsing.replaceMessageReferences(rawValue);
this.setTooltip(localizedText);
}
if (json['enableContextMenu'] !== undefined) {
@@ -1593,7 +1594,7 @@ Block.prototype.jsonInit = function(json) {
}
if (json['helpUrl'] !== undefined) {
const rawValue = json['helpUrl'];
const localizedValue = utils.replaceMessageReferences(rawValue);
const localizedValue = parsing.replaceMessageReferences(rawValue);
this.setHelpUrl(localizedValue);
}
if (typeof json['extensions'] === 'string') {
@@ -1693,7 +1694,7 @@ Block.prototype.mixin = function(mixinObj, opt_disableCheck) {
*/
Block.prototype.interpolate_ = function(
message, args, lastDummyAlign, warningPrefix) {
const tokens = utils.tokenizeInterpolation(message);
const tokens = parsing.tokenizeInterpolation(message);
this.validateTokens_(tokens, args.length);
const elements = this.interpolateArguments_(tokens, args, lastDummyAlign);

View File

@@ -27,7 +27,7 @@
goog.module('Blockly.BlockDragSurfaceSvg');
const dom = goog.require('Blockly.utils.dom');
const utils = goog.require('Blockly.utils');
const svgMath = goog.require('Blockly.utils.svgMath');
const {Coordinate} = goog.require('Blockly.utils.Coordinate');
const {Svg} = goog.require('Blockly.utils.Svg');
@@ -200,7 +200,7 @@ BlockDragSurfaceSvg.prototype.translateSurface = function(x, y) {
* @return {!Coordinate} Current translation of the surface.
*/
BlockDragSurfaceSvg.prototype.getSurfaceTranslation = function() {
const xy = utils.getRelativeXY(/** @type {!SVGElement} */ (this.SVG_));
const xy = svgMath.getRelativeXY(/** @type {!SVGElement} */ (this.SVG_));
return new Coordinate(xy.x / this.scale_, xy.y / this.scale_);
};

View File

@@ -27,6 +27,7 @@ const dom = goog.require('Blockly.utils.dom');
const eventUtils = goog.require('Blockly.Events.utils');
const internalConstants = goog.require('Blockly.internalConstants');
const object = goog.require('Blockly.utils.object');
const svgMath = goog.require('Blockly.utils.svgMath');
const userAgent = goog.require('Blockly.utils.userAgent');
const utils = goog.require('Blockly.utils');
const {ASTNode} = goog.require('Blockly.ASTNode');
@@ -145,7 +146,7 @@ const BlockSvg = function(workspace, prototypeName, opt_id) {
* @private
*/
this.useDragSurface_ =
utils.is3dSupported() && !!workspace.getBlockDragSurface();
svgMath.is3dSupported() && !!workspace.getBlockDragSurface();
const svgPath = this.pathObject.svgPath;
svgPath.tooltip = this;
@@ -428,7 +429,7 @@ BlockSvg.prototype.getRelativeToSurfaceXY = function() {
if (element) {
do {
// Loop through this block and every parent.
const xy = utils.getRelativeXY(element);
const xy = svgMath.getRelativeXY(element);
x += xy.x;
y += xy.y;
// If this element is the current element on the drag surface, include

View File

@@ -16,7 +16,7 @@
goog.module('Blockly.BubbleDragger');
const eventUtils = goog.require('Blockly.Events.utils');
const utils = goog.require('Blockly.utils');
const svgMath = goog.require('Blockly.utils.svgMath');
/* eslint-disable-next-line no-unused-vars */
const {BlockDragSurfaceSvg} = goog.requireType('Blockly.BlockDragSurfaceSvg');
const {ComponentManager} = goog.require('Blockly.ComponentManager');
@@ -92,7 +92,7 @@ const BubbleDragger = function(bubble, workspace) {
* @private
*/
this.dragSurface_ =
utils.is3dSupported() && !!workspace.getBlockDragSurface() ?
svgMath.is3dSupported() && !!workspace.getBlockDragSurface() ?
workspace.getBlockDragSurface() :
null;
};

View File

@@ -26,7 +26,7 @@ const dom = goog.require('Blockly.utils.dom');
const eventUtils = goog.require('Blockly.Events.utils');
const internalConstants = goog.require('Blockly.internalConstants');
const userAgent = goog.require('Blockly.utils.userAgent');
const utils = goog.require('Blockly.utils');
const svgMath = goog.require('Blockly.utils.svgMath');
/* eslint-disable-next-line no-unused-vars */
const {Block} = goog.requireType('Blockly.Block');
const {Coordinate} = goog.require('Blockly.utils.Coordinate');
@@ -169,7 +169,7 @@ const populate_ = function(options, rtl) {
*/
const position_ = function(menu, e, rtl) {
// Record windowSize and scrollOffset before adding menu.
const viewportBBox = utils.getViewportBBox();
const viewportBBox = svgMath.getViewportBBox();
// This one is just a point, but we'll pretend that it's a rect so we can use
// some helper functions.
const anchorBBox = new Rect(

View File

@@ -21,6 +21,7 @@
*/
goog.module('Blockly.Extensions');
const parsing = goog.require('Blockly.utils.parsing');
const utils = goog.require('Blockly.utils');
/* eslint-disable-next-line no-unused-vars */
const {Block} = goog.requireType('Blockly.Block');
@@ -376,7 +377,7 @@ const buildTooltipForDropdown = function(dropdownName, lookupTable) {
utils.runAfterPageLoad(function() {
for (const key in lookupTable) {
// Will print warnings if reference is missing.
utils.checkMessageReferences(lookupTable[key]);
parsing.checkMessageReferences(lookupTable[key]);
}
});
}
@@ -405,7 +406,7 @@ const buildTooltipForDropdown = function(dropdownName, lookupTable) {
console.warn(warning + '.');
}
} else {
tooltip = utils.replaceMessageReferences(tooltip);
tooltip = parsing.replaceMessageReferences(tooltip);
}
return tooltip;
}.bind(this));
@@ -455,7 +456,7 @@ const buildTooltipWithFieldText = function(msgTemplate, fieldName) {
if (typeof document === 'object') { // Relies on document.readyState
utils.runAfterPageLoad(function() {
// Will print warnings if reference is missing.
utils.checkMessageReferences(msgTemplate);
parsing.checkMessageReferences(msgTemplate);
});
}
@@ -466,7 +467,7 @@ const buildTooltipWithFieldText = function(msgTemplate, fieldName) {
const extensionFn = function() {
this.setTooltip(function() {
const field = this.getField(fieldName);
return utils.replaceMessageReferences(msgTemplate)
return parsing.replaceMessageReferences(msgTemplate)
.replace('%1', field ? field.getText() : '');
}.bind(this));
};

View File

@@ -25,9 +25,9 @@ const Xml = goog.require('Blockly.Xml');
const browserEvents = goog.require('Blockly.browserEvents');
const dom = goog.require('Blockly.utils.dom');
const eventUtils = goog.require('Blockly.Events.utils');
const parsing = goog.require('Blockly.utils.parsing');
const style = goog.require('Blockly.utils.style');
const userAgent = goog.require('Blockly.utils.userAgent');
const utils = goog.require('Blockly.utils');
const utilsXml = goog.require('Blockly.utils.xml');
/* eslint-disable-next-line no-unused-vars */
const {BlockSvg} = goog.requireType('Blockly.BlockSvg');
@@ -287,7 +287,7 @@ Field.prototype.SERIALIZABLE = false;
Field.prototype.configure_ = function(config) {
let tooltip = config['tooltip'];
if (typeof tooltip === 'string') {
tooltip = utils.replaceMessageReferences(config['tooltip']);
tooltip = parsing.replaceMessageReferences(config['tooltip']);
}
tooltip && this.setTooltip(tooltip);

View File

@@ -23,8 +23,8 @@ const aria = goog.require('Blockly.utils.aria');
const dom = goog.require('Blockly.utils.dom');
const fieldRegistry = goog.require('Blockly.fieldRegistry');
const object = goog.require('Blockly.utils.object');
const parsing = goog.require('Blockly.utils.parsing');
const userAgent = goog.require('Blockly.utils.userAgent');
const utils = goog.require('Blockly.utils');
const utilsString = goog.require('Blockly.utils.string');
const {Coordinate} = goog.require('Blockly.utils.Coordinate');
const {DropDownDiv} = goog.require('Blockly.DropDownDiv');
@@ -429,10 +429,10 @@ FieldDropdown.prototype.trimOptions_ = function() {
for (let i = 0; i < options.length; i++) {
const label = options[i][0];
if (typeof label === 'string') {
options[i][0] = utils.replaceMessageReferences(label);
options[i][0] = parsing.replaceMessageReferences(label);
} else {
if (label.alt !== null) {
options[i][0].alt = utils.replaceMessageReferences(label.alt);
options[i][0].alt = parsing.replaceMessageReferences(label.alt);
}
hasImages = true;
}

View File

@@ -18,7 +18,7 @@ goog.module('Blockly.FieldImage');
const dom = goog.require('Blockly.utils.dom');
const fieldRegistry = goog.require('Blockly.fieldRegistry');
const object = goog.require('Blockly.utils.object');
const utils = goog.require('Blockly.utils');
const parsing = goog.require('Blockly.utils.parsing');
const {Field} = goog.require('Blockly.Field');
const {Size} = goog.require('Blockly.utils.Size');
const {Svg} = goog.require('Blockly.utils.Svg');
@@ -48,9 +48,9 @@ const FieldImage = function(
if (!src) {
throw Error('Src value of an image field is required');
}
src = utils.replaceMessageReferences(src);
const imageHeight = Number(utils.replaceMessageReferences(height));
const imageWidth = Number(utils.replaceMessageReferences(width));
src = parsing.replaceMessageReferences(src);
const imageHeight = Number(parsing.replaceMessageReferences(height));
const imageWidth = Number(parsing.replaceMessageReferences(width));
if (isNaN(imageHeight) || isNaN(imageWidth)) {
throw Error(
'Height and width values of an image field must cast to' +
@@ -81,7 +81,7 @@ const FieldImage = function(
if (!opt_config) { // If the config wasn't passed, do old configuration.
this.flipRtl_ = !!opt_flipRtl;
this.altText_ = utils.replaceMessageReferences(opt_alt) || '';
this.altText_ = parsing.replaceMessageReferences(opt_alt) || '';
}
// Initialize other properties.
@@ -177,7 +177,7 @@ FieldImage.prototype.isDirty_ = false;
FieldImage.prototype.configure_ = function(config) {
FieldImage.superClass_.configure_.call(this, config);
this.flipRtl_ = !!config['flipRtl'];
this.altText_ = utils.replaceMessageReferences(config['alt']) || '';
this.altText_ = parsing.replaceMessageReferences(config['alt']) || '';
};
/**

View File

@@ -20,7 +20,7 @@ goog.module('Blockly.FieldLabel');
const dom = goog.require('Blockly.utils.dom');
const fieldRegistry = goog.require('Blockly.fieldRegistry');
const object = goog.require('Blockly.utils.object');
const utils = goog.require('Blockly.utils');
const parsing = goog.require('Blockly.utils.parsing');
const {Field} = goog.require('Blockly.Field');
@@ -69,7 +69,7 @@ FieldLabel.prototype.DEFAULT_VALUE = '';
* @nocollapse
*/
FieldLabel.fromJson = function(options) {
const text = utils.replaceMessageReferences(options['text']);
const text = parsing.replaceMessageReferences(options['text']);
// `this` might be a subclass of FieldLabel if that class doesn't override
// the static fromJson method.
return new this(text, undefined, options);

View File

@@ -21,7 +21,7 @@ goog.module('Blockly.FieldLabelSerializable');
const fieldRegistry = goog.require('Blockly.fieldRegistry');
const object = goog.require('Blockly.utils.object');
const utils = goog.require('Blockly.utils');
const parsing = goog.require('Blockly.utils.parsing');
const {FieldLabel} = goog.require('Blockly.FieldLabel');
@@ -54,7 +54,7 @@ object.inherits(FieldLabelSerializable, FieldLabel);
* @nocollapse
*/
FieldLabelSerializable.fromJson = function(options) {
const text = utils.replaceMessageReferences(options['text']);
const text = parsing.replaceMessageReferences(options['text']);
// `this` might be a subclass of FieldLabelSerializable if that class doesn't
// override the static fromJson method.
return new this(text, undefined, options);

View File

@@ -21,8 +21,8 @@ const aria = goog.require('Blockly.utils.aria');
const dom = goog.require('Blockly.utils.dom');
const fieldRegistry = goog.require('Blockly.fieldRegistry');
const object = goog.require('Blockly.utils.object');
const parsing = goog.require('Blockly.utils.parsing');
const userAgent = goog.require('Blockly.utils.userAgent');
const utils = goog.require('Blockly.utils');
const {FieldTextInput} = goog.require('Blockly.FieldTextInput');
const {Field} = goog.require('Blockly.Field');
const {KeyCodes} = goog.require('Blockly.utils.KeyCodes');
@@ -90,7 +90,7 @@ FieldMultilineInput.prototype.configure_ = function(config) {
* @nocollapse
*/
FieldMultilineInput.fromJson = function(options) {
const text = utils.replaceMessageReferences(options['text']);
const text = parsing.replaceMessageReferences(options['text']);
// `this` might be a subclass of FieldMultilineInput if that class doesn't
// override the static fromJson method.
return new this(text, undefined, options);

View File

@@ -24,8 +24,8 @@ const dom = goog.require('Blockly.utils.dom');
const eventUtils = goog.require('Blockly.Events.utils');
const fieldRegistry = goog.require('Blockly.fieldRegistry');
const object = goog.require('Blockly.utils.object');
const parsing = goog.require('Blockly.utils.parsing');
const userAgent = goog.require('Blockly.utils.userAgent');
const utils = goog.require('Blockly.utils');
/* eslint-disable-next-line no-unused-vars */
const {BlockSvg} = goog.requireType('Blockly.BlockSvg');
const {Coordinate} = goog.require('Blockly.utils.Coordinate');
@@ -116,7 +116,7 @@ FieldTextInput.prototype.DEFAULT_VALUE = '';
* @nocollapse
*/
FieldTextInput.fromJson = function(options) {
const text = utils.replaceMessageReferences(options['text']);
const text = parsing.replaceMessageReferences(options['text']);
// `this` might be a subclass of FieldTextInput if that class doesn't override
// the static fromJson method.
return new this(text, undefined, options);

View File

@@ -21,7 +21,7 @@ const Xml = goog.require('Blockly.Xml');
const fieldRegistry = goog.require('Blockly.fieldRegistry');
const internalConstants = goog.require('Blockly.internalConstants');
const object = goog.require('Blockly.utils.object');
const utils = goog.require('Blockly.utils');
const parsing = goog.require('Blockly.utils.parsing');
/* eslint-disable-next-line no-unused-vars */
const {Block} = goog.requireType('Blockly.Block');
const {FieldDropdown} = goog.require('Blockly.FieldDropdown');
@@ -104,7 +104,7 @@ object.inherits(FieldVariable, FieldDropdown);
* @nocollapse
*/
FieldVariable.fromJson = function(options) {
const varName = utils.replaceMessageReferences(options['variable']);
const varName = parsing.replaceMessageReferences(options['variable']);
// `this` might be a subclass of FieldVariable if that class doesn't override
// the static fromJson method.
return new this(varName, undefined, undefined, undefined, options);

View File

@@ -21,7 +21,7 @@ const dom = goog.require('Blockly.utils.dom');
const style = goog.require('Blockly.utils.style');
/* eslint-disable-next-line no-unused-vars */
const toolbox = goog.requireType('Blockly.utils.toolbox');
const utils = goog.require('Blockly.utils');
const parsing = goog.require('Blockly.utils.parsing');
const {Coordinate} = goog.require('Blockly.utils.Coordinate');
const {Svg} = goog.require('Blockly.utils.Svg');
/* eslint-disable-next-line no-unused-vars */
@@ -170,7 +170,7 @@ FlyoutButton.prototype.createDom = function() {
'text-anchor': 'middle',
},
this.svgGroup_);
let text = utils.replaceMessageReferences(this.text_);
let text = parsing.replaceMessageReferences(this.text_);
if (this.workspace_.RTL) {
// Force text to be RTL by adding an RLM.
text += '\u200F';

View File

@@ -17,7 +17,7 @@ goog.module('Blockly.Icon');
const browserEvents = goog.require('Blockly.browserEvents');
const dom = goog.require('Blockly.utils.dom');
const utils = goog.require('Blockly.utils');
const svgMath = goog.require('Blockly.utils.svgMath');
/* eslint-disable-next-line no-unused-vars */
const {BlockSvg} = goog.requireType('Blockly.BlockSvg');
/* eslint-disable-next-line no-unused-vars */
@@ -170,7 +170,7 @@ Icon.prototype.setIconLocation = function(xy) {
Icon.prototype.computeIconLocation = function() {
// Find coordinates for the centre of the icon and update the arrow.
const blockXY = this.block_.getRelativeToSurfaceXY();
const iconXY = utils.getRelativeXY(
const iconXY = svgMath.getRelativeXY(
/** @type {!SVGElement} */ (this.iconGroup_));
const newXY = new Coordinate(
blockXY.x + iconXY.x + this.SIZE / 2,

View File

@@ -22,7 +22,7 @@ const eventUtils = goog.require('Blockly.Events.utils');
const internalConstants = goog.require('Blockly.internalConstants');
const object = goog.require('Blockly.utils.object');
const svgPaths = goog.require('Blockly.utils.svgPaths');
const utils = goog.require('Blockly.utils');
const svgMath = goog.require('Blockly.utils.svgMath');
/* eslint-disable-next-line no-unused-vars */
const {BlockSvg} = goog.requireType('Blockly.BlockSvg');
/* eslint-disable-next-line no-unused-vars */
@@ -270,7 +270,7 @@ RenderedConnection.prototype.tighten = function() {
throw Error('block is not rendered.');
}
// Workspace coordinates.
const xy = utils.getRelativeXY(svgRoot);
const xy = svgMath.getRelativeXY(svgRoot);
block.getSvgRoot().setAttribute(
'transform', 'translate(' + (xy.x - dx) + ',' + (xy.y - dy) + ')');
block.moveConnections(-dx, -dy);

View File

@@ -20,7 +20,7 @@ const dom = goog.require('Blockly.utils.dom');
const object = goog.require('Blockly.utils.object');
const svgPaths = goog.require('Blockly.utils.svgPaths');
const userAgent = goog.require('Blockly.utils.userAgent');
const utils = goog.require('Blockly.utils');
const parsing = goog.require('Blockly.utils.parsing');
const {ConnectionType} = goog.require('Blockly.ConnectionType');
/* eslint-disable-next-line no-unused-vars */
const {RenderedConnection} = goog.requireType('Blockly.RenderedConnection');
@@ -722,13 +722,14 @@ ConstantProvider.prototype.validatedBlockStyle_ = function(blockStyle) {
object.mixin(valid, blockStyle);
}
// Validate required properties.
const parsedColour = utils.parseBlockColour(valid['colourPrimary'] || '#000');
const parsedColour =
parsing.parseBlockColour(valid['colourPrimary'] || '#000');
valid.colourPrimary = parsedColour.hex;
valid.colourSecondary = valid['colourSecondary'] ?
utils.parseBlockColour(valid['colourSecondary']).hex :
parsing.parseBlockColour(valid['colourSecondary']).hex :
this.generateSecondaryColour_(valid.colourPrimary);
valid.colourTertiary = valid['colourTertiary'] ?
utils.parseBlockColour(valid['colourTertiary']).hex :
parsing.parseBlockColour(valid['colourTertiary']).hex :
this.generateTertiaryColour_(valid.colourPrimary);
valid.hat = valid['hat'] || '';

View File

@@ -18,7 +18,7 @@ goog.module('Blockly.Scrollbar');
const Touch = goog.require('Blockly.Touch');
const browserEvents = goog.require('Blockly.browserEvents');
const dom = goog.require('Blockly.utils.dom');
const utils = goog.require('Blockly.utils');
const svgMath = goog.require('Blockly.utils.svgMath');
const {Coordinate} = goog.require('Blockly.utils.Coordinate');
/* eslint-disable-next-line no-unused-vars */
const {Metrics} = goog.requireType('Blockly.utils.Metrics');
@@ -691,7 +691,7 @@ Scrollbar.prototype.onMouseDownBar_ = function(e) {
e, this.workspace_.getParentSvg(), this.workspace_.getInverseScreenCTM());
const mouseLocation = this.horizontal_ ? mouseXY.x : mouseXY.y;
const handleXY = utils.getInjectionDivXY_(this.svgHandle_);
const handleXY = svgMath.getInjectionDivXY(this.svgHandle_);
const handleStart = this.horizontal_ ? handleXY.x : handleXY.y;
let handlePosition = this.handlePosition_;

View File

@@ -20,9 +20,9 @@ const aria = goog.require('Blockly.utils.aria');
const colourUtils = goog.require('Blockly.utils.colour');
const dom = goog.require('Blockly.utils.dom');
const object = goog.require('Blockly.utils.object');
const parsing = goog.require('Blockly.utils.parsing');
const registry = goog.require('Blockly.registry');
const toolbox = goog.require('Blockly.utils.toolbox');
const utils = goog.require('Blockly.utils');
/* eslint-disable-next-line no-unused-vars */
const {ICollapsibleToolboxItem} = goog.requireType('Blockly.ICollapsibleToolboxItem');
/* eslint-disable-next-line no-unused-vars */
@@ -53,7 +53,7 @@ const ToolboxCategory = function(categoryDef, toolbox, opt_parent) {
* @type {string}
* @protected
*/
this.name_ = utils.replaceMessageReferences(categoryDef['name']);
this.name_ = parsing.replaceMessageReferences(categoryDef['name']);
/**
* The colour of the category.
@@ -423,7 +423,7 @@ ToolboxCategory.prototype.getClickTarget = function() {
ToolboxCategory.prototype.parseColour_ = function(colourValue) {
// Decode the colour for any potential message references
// (eg. `%{BKY_MATH_HUE}`).
const colour = utils.replaceMessageReferences(colourValue);
const colour = parsing.replaceMessageReferences(colourValue);
if (colour === null || colour === '') {
// No attribute. No colour.
return '';

View File

@@ -6,20 +6,15 @@
/**
* @fileoverview Utility methods.
* These methods are not specific to Blockly, and could be factored out into
* a JavaScript framework such as Closure.
*/
'use strict';
/**
* Utility methods.
* These methods are not specific to Blockly, and could be factored out into
* a JavaScript framework such as Closure.
* @namespace Blockly.utils
*/
goog.module('Blockly.utils');
const Msg = goog.require('Blockly.Msg');
const aria = goog.require('Blockly.utils.aria');
const browserEvents = goog.require('Blockly.browserEvents');
const colourUtils = goog.require('Blockly.utils.colour');
@@ -27,11 +22,12 @@ const deprecation = goog.require('Blockly.utils.deprecation');
const dom = goog.require('Blockly.utils.dom');
const global = goog.require('Blockly.utils.global');
const idGenerator = goog.require('Blockly.utils.idGenerator');
const internalConstants = goog.require('Blockly.internalConstants');
const math = goog.require('Blockly.utils.math');
const object = goog.require('Blockly.utils.object');
const parsing = goog.require('Blockly.utils.parsing');
const stringUtils = goog.require('Blockly.utils.string');
const style = goog.require('Blockly.utils.style');
const svgMath = goog.require('Blockly.utils.svgMath');
const svgPaths = goog.require('Blockly.utils.svgPaths');
const toolbox = goog.require('Blockly.utils.toolbox');
const userAgent = goog.require('Blockly.utils.userAgent');
@@ -59,12 +55,14 @@ exports.KeyCodes = KeyCodes;
exports.math = math;
exports.Metrics = Metrics;
exports.object = object;
exports.parsing = parsing;
exports.Rect = Rect;
exports.Size = Size;
exports.string = stringUtils;
exports.style = style;
exports.Svg = Svg;
exports.svgPaths = svgPaths;
exports.svgMath = svgMath;
exports.toolbox = toolbox;
exports.userAgent = userAgent;
exports.xml = xmlUtils;
@@ -103,41 +101,14 @@ exports.isTargetInput = isTargetInput;
* its parent. Only for SVG elements and children (e.g. rect, g, path).
* @param {!Element} element SVG element to find the coordinates of.
* @return {!Coordinate} Object with .x and .y properties.
* @deprecated
* @alias Blockly.utils.getRelativeXY
*/
const getRelativeXY = function(element) {
const xy = new Coordinate(0, 0);
// First, check for x and y attributes.
const x = element.getAttribute('x');
if (x) {
xy.x = parseInt(x, 10);
}
const y = element.getAttribute('y');
if (y) {
xy.y = parseInt(y, 10);
}
// Second, check for transform="translate(...)" attribute.
const transform = element.getAttribute('transform');
const r = transform && transform.match(getRelativeXY.XY_REGEX_);
if (r) {
xy.x += Number(r[1]);
if (r[3]) {
xy.y += Number(r[3]);
}
}
// Then check for style = transform: translate(...) or translate3d(...)
const style = element.getAttribute('style');
if (style && style.indexOf('translate') > -1) {
const styleComponents = style.match(getRelativeXY.XY_STYLE_REGEX_);
if (styleComponents) {
xy.x += Number(styleComponents[1]);
if (styleComponents[3]) {
xy.y += Number(styleComponents[3]);
}
}
}
return xy;
deprecation.warn(
'Blockly.utils.getRelativeXY', 'December 2021', 'December 2022',
'Blockly.svgMath.getRelativeXY');
return svgMath.getRelativeXY(element);
};
exports.getRelativeXY = getRelativeXY;
@@ -148,46 +119,17 @@ exports.getRelativeXY = getRelativeXY;
* not a child of the div Blockly was injected into, the behaviour is
* undefined.
* @return {!Coordinate} Object with .x and .y properties.
* @deprecated
* @alias Blockly.utils.getInjectionDivXY_
*/
const getInjectionDivXY = function(element) {
let x = 0;
let y = 0;
while (element) {
const xy = getRelativeXY(element);
x = x + xy.x;
y = y + xy.y;
const classes = element.getAttribute('class') || '';
if ((' ' + classes + ' ').indexOf(' injectionDiv ') !== -1) {
break;
}
element = /** @type {!Element} */ (element.parentNode);
}
return new Coordinate(x, y);
deprecation.warn(
'Blockly.utils.getInjectionDivXY_', 'December 2021', 'December 2022',
'Blockly.svgMath.getInjectionDivXY');
return svgMath.getInjectionDivXY(element);
};
exports.getInjectionDivXY_ = getInjectionDivXY;
/**
* Static regex to pull the x,y values out of an SVG translate() directive.
* Note that Firefox and IE (9,10) return 'translate(12)' instead of
* 'translate(12, 0)'.
* Note that IE (9,10) returns 'translate(16 8)' instead of 'translate(16, 8)'.
* Note that IE has been reported to return scientific notation (0.123456e-42).
* @type {!RegExp}
* @private
*/
getRelativeXY.XY_REGEX_ = /translate\(\s*([-+\d.e]+)([ ,]\s*([-+\d.e]+)\s*)?/;
/**
* Static regex to pull the x,y values out of a translate() or translate3d()
* style property.
* Accounts for same exceptions as XY_REGEX_.
* @type {!RegExp}
* @private
*/
getRelativeXY.XY_STYLE_REGEX_ =
/transform:\s*translate(?:3d)?\(\s*([-+\d.e]+)\s*px([ ,]\s*([-+\d.e]+)\s*px)?/;
/**
* Returns true this event is a right-click.
* @param {!Event} e Mouse event.
@@ -246,10 +188,14 @@ exports.getScrollDeltaPixels = getScrollDeltaPixels;
* @param {string} message Text which might contain string table references and
* interpolation tokens.
* @return {!Array<string|number>} Array of strings and numbers.
* @deprecated
* @alias Blockly.utils.tokenizeInterpolation
*/
const tokenizeInterpolation = function(message) {
return tokenizeInterpolation_(message, true);
deprecation.warn(
'Blockly.utils.tokenizeInterpolation', 'December 2021', 'December 2022',
'Blockly.parsing.tokenizeInterpolation');
return parsing.tokenizeInterpolation(message);
};
exports.tokenizeInterpolation = tokenizeInterpolation;
@@ -260,16 +206,14 @@ exports.tokenizeInterpolation = tokenizeInterpolation;
* @param {string|?} message Message, which may be a string that contains
* string table references.
* @return {string} String with message references replaced.
* @deprecated
* @alias Blockly.utils.replaceMessageReferences
*/
const replaceMessageReferences = function(message) {
if (typeof message !== 'string') {
return message;
}
const interpolatedResult = tokenizeInterpolation_(message, false);
// When parseInterpolationTokens === false, interpolatedResult should be at
// most length 1.
return interpolatedResult.length ? String(interpolatedResult[0]) : '';
deprecation.warn(
'Blockly.utils.replaceMessageReferences', 'December 2021',
'December 2022', 'Blockly.parsing.replaceMessageReferences');
return parsing.replaceMessageReferences(message);
};
exports.replaceMessageReferences = replaceMessageReferences;
@@ -279,166 +223,17 @@ exports.replaceMessageReferences = replaceMessageReferences;
* @param {string} message Text which might contain string table references.
* @return {boolean} True if all message references have matching values.
* Otherwise, false.
* @deprecated
* @alias Blockly.utils.checkMessageReferences
*/
const checkMessageReferences = function(message) {
let validSoFar = true;
const msgTable = Msg;
// TODO (#1169): Implement support for other string tables,
// prefixes other than BKY_.
const m = message.match(/%{BKY_[A-Z]\w*}/ig);
for (let i = 0; i < m.length; i++) {
const msgKey = m[i].toUpperCase();
if (msgTable[msgKey.slice(6, -1)] === undefined) {
console.warn('No message string for ' + m[i] + ' in ' + message);
validSoFar = false; // Continue to report other errors.
}
}
return validSoFar;
deprecation.warn(
'Blockly.utils.checkMessageReferences', 'December 2021', 'December 2022',
'Blockly.parsing.checkMessageReferences');
return parsing.checkMessageReferences(message);
};
exports.checkMessageReferences = checkMessageReferences;
/**
* Internal implementation of the message reference and interpolation token
* parsing used by tokenizeInterpolation() and replaceMessageReferences().
* @param {string} message Text which might contain string table references and
* interpolation tokens.
* @param {boolean} parseInterpolationTokens Option to parse numeric
* interpolation tokens (%1, %2, ...) when true.
* @return {!Array<string|number>} Array of strings and numbers.
*/
const tokenizeInterpolation_ = function(message, parseInterpolationTokens) {
const tokens = [];
const chars = message.split('');
chars.push(''); // End marker.
// Parse the message with a finite state machine.
// 0 - Base case.
// 1 - % found.
// 2 - Digit found.
// 3 - Message ref found.
let state = 0;
const buffer = [];
let number = null;
for (let i = 0; i < chars.length; i++) {
const c = chars[i];
if (state === 0) {
if (c === '%') {
const text = buffer.join('');
if (text) {
tokens.push(text);
}
buffer.length = 0;
state = 1; // Start escape.
} else {
buffer.push(c); // Regular char.
}
} else if (state === 1) {
if (c === '%') {
buffer.push(c); // Escaped %: %%
state = 0;
} else if (parseInterpolationTokens && '0' <= c && c <= '9') {
state = 2;
number = c;
const text = buffer.join('');
if (text) {
tokens.push(text);
}
buffer.length = 0;
} else if (c === '{') {
state = 3;
} else {
buffer.push('%', c); // Not recognized. Return as literal.
state = 0;
}
} else if (state === 2) {
if ('0' <= c && c <= '9') {
number += c; // Multi-digit number.
} else {
tokens.push(parseInt(number, 10));
i--; // Parse this char again.
state = 0;
}
} else if (state === 3) { // String table reference
if (c === '') {
// Premature end before closing '}'
buffer.splice(0, 0, '%{'); // Re-insert leading delimiter
i--; // Parse this char again.
state = 0; // and parse as string literal.
} else if (c !== '}') {
buffer.push(c);
} else {
const rawKey = buffer.join('');
if (/[A-Z]\w*/i.test(rawKey)) { // Strict matching
// Found a valid string key. Attempt case insensitive match.
const keyUpper = rawKey.toUpperCase();
// BKY_ is the prefix used to namespace the strings used in Blockly
// core files and the predefined blocks in ../blocks/.
// These strings are defined in ../msgs/ files.
const bklyKey = stringUtils.startsWith(keyUpper, 'BKY_') ?
keyUpper.substring(4) :
null;
if (bklyKey && bklyKey in Msg) {
const rawValue = Msg[bklyKey];
if (typeof rawValue === 'string') {
// Attempt to dereference substrings, too, appending to the end.
Array.prototype.push.apply(
tokens,
tokenizeInterpolation_(rawValue, parseInterpolationTokens));
} else if (parseInterpolationTokens) {
// When parsing interpolation tokens, numbers are special
// placeholders (%1, %2, etc). Make sure all other values are
// strings.
tokens.push(String(rawValue));
} else {
tokens.push(rawValue);
}
} else {
// No entry found in the string table. Pass reference as string.
tokens.push('%{' + rawKey + '}');
}
buffer.length = 0; // Clear the array
state = 0;
} else {
tokens.push('%{' + rawKey + '}');
buffer.length = 0;
state = 0; // and parse as string literal.
}
}
}
}
let text = buffer.join('');
if (text) {
tokens.push(text);
}
// Merge adjacent text tokens into a single string.
const mergedTokens = [];
buffer.length = 0;
for (let i = 0; i < tokens.length; i++) {
if (typeof tokens[i] === 'string') {
buffer.push(tokens[i]);
} else {
text = buffer.join('');
if (text) {
mergedTokens.push(text);
}
buffer.length = 0;
mergedTokens.push(tokens[i]);
}
}
text = buffer.join('');
if (text) {
mergedTokens.push(text);
}
buffer.length = 0;
return mergedTokens;
};
/**
* Generate a unique ID.
* @return {string} A globally unique ID string.
@@ -457,51 +252,14 @@ exports.genUid = genUid;
* Check if 3D transforms are supported by adding an element
* and attempting to set the property.
* @return {boolean} True if 3D transforms are supported.
* @deprecated
* @alias Blockly.utils.is3dSupported
*/
const is3dSupported = function() {
if (is3dSupported.cached_ !== undefined) {
return is3dSupported.cached_;
}
// CC-BY-SA Lorenzo Polidori
// stackoverflow.com/questions/5661671/detecting-transform-translate3d-support
if (!global.globalThis['getComputedStyle']) {
return false;
}
const el = document.createElement('p');
let has3d = 'none';
const transforms = {
'webkitTransform': '-webkit-transform',
'OTransform': '-o-transform',
'msTransform': '-ms-transform',
'MozTransform': '-moz-transform',
'transform': 'transform',
};
// Add it to the body to get the computed style.
document.body.insertBefore(el, null);
for (const t in transforms) {
if (el.style[t] !== undefined) {
el.style[t] = 'translate3d(1px,1px,1px)';
const computedStyle = global.globalThis['getComputedStyle'](el);
if (!computedStyle) {
// getComputedStyle in Firefox returns null when Blockly is loaded
// inside an iframe with display: none. Returning false and not
// caching is3dSupported means we try again later. This is most likely
// when users are interacting with blocks which should mean Blockly is
// visible again.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=548397
document.body.removeChild(el);
return false;
}
has3d = computedStyle.getPropertyValue(transforms[t]);
}
}
document.body.removeChild(el);
is3dSupported.cached_ = has3d !== 'none';
return is3dSupported.cached_;
deprecation.warn(
'Blockly.utils.is3dSupported', 'December 2021', 'December 2022',
'Blockly.svgMath.is3dSupported');
return svgMath.is3dSupported();
};
exports.is3dSupported = is3dSupported;
@@ -535,14 +293,14 @@ exports.runAfterPageLoad = runAfterPageLoad;
* @return {!Rect} An object containing window width, height, and
* scroll position in window coordinates.
* @alias Blockly.utils.getViewportBBox
* @deprecated
* @package
*/
const getViewportBBox = function() {
// Pixels, in window coordinates.
const scrollOffset = style.getViewportPageOffset();
return new Rect(
scrollOffset.y, document.documentElement.clientHeight + scrollOffset.y,
scrollOffset.x, document.documentElement.clientWidth + scrollOffset.x);
deprecation.warn(
'Blockly.utils.getViewportBBox', 'December 2021', 'December 2022',
'Blockly.svgMath.getViewportBBox');
return svgMath.getViewportBBox();
};
exports.getViewportBBox = getViewportBBox;
@@ -568,19 +326,14 @@ exports.arrayRemove = arrayRemove;
* Gets the document scroll distance as a coordinate object.
* Copied from Closure's goog.dom.getDocumentScroll.
* @return {!Coordinate} Object with values 'x' and 'y'.
* @deprecated
* @alias Blockly.utils.getDocumentScroll
*/
const getDocumentScroll = function() {
const el = document.documentElement;
const win = window;
if (userAgent.IE && win.pageYOffset !== el.scrollTop) {
// The keyboard on IE10 touch devices shifts the page using the pageYOffset
// without modifying scrollTop. For this case, we want the body scroll
// offsets.
return new Coordinate(el.scrollLeft, el.scrollTop);
}
return new Coordinate(
win.pageXOffset || el.scrollLeft, win.pageYOffset || el.scrollTop);
deprecation.warn(
'Blockly.utils.getDocumentScroll', 'December 2021', 'December 2022',
'Blockly.svgMath.getDocumentScroll');
return svgMath.getDocumentScroll();
};
exports.getDocumentScroll = getDocumentScroll;
@@ -620,36 +373,14 @@ exports.getBlockTypeCounts = getBlockTypeCounts;
* @param {!WorkspaceSvg} ws The workspace to find the coordinates on.
* @param {!Coordinate} screenCoordinates The screen coordinates to
* be converted to workspace coordinates
* @deprecated
* @return {!Coordinate} The workspace coordinates.
* @alias Blockly.utils.screenToWsCoordinates
* @package
*/
const screenToWsCoordinates = function(ws, screenCoordinates) {
const screenX = screenCoordinates.x;
const screenY = screenCoordinates.y;
const injectionDiv = ws.getInjectionDiv();
// Bounding rect coordinates are in client coordinates, meaning that they
// are in pixels relative to the upper left corner of the visible browser
// window. These coordinates change when you scroll the browser window.
const boundingRect = injectionDiv.getBoundingClientRect();
// The client coordinates offset by the injection div's upper left corner.
const clientOffsetPixels =
new Coordinate(screenX - boundingRect.left, screenY - boundingRect.top);
// The offset in pixels between the main workspace's origin and the upper
// left corner of the injection div.
const mainOffsetPixels = ws.getOriginOffsetInPixels();
// The position of the new comment in pixels relative to the origin of the
// main workspace.
const finalOffsetPixels =
Coordinate.difference(clientOffsetPixels, mainOffsetPixels);
// The position in main workspace coordinates.
const finalOffsetMainWs = finalOffsetPixels.scale(1 / ws.scale);
return finalOffsetMainWs;
deprecation.warn(
'Blockly.utils.screenToWsCoordinates', 'December 2021', 'December 2022',
'Blockly.svgMath.screenToWsCoordinates');
return svgMath.screenToWsCoordinates(ws, screenCoordinates);
};
exports.screenToWsCoordinates = screenToWsCoordinates;
@@ -661,32 +392,13 @@ exports.screenToWsCoordinates = screenToWsCoordinates;
* @return {{hue: ?number, hex: string}} An object containing the colour as
* a #RRGGBB string, and the hue if the input was an HSV hue value.
* @throws {Error} If the colour cannot be parsed.
* @deprecated
* @alias Blockly.utils.parseBlockColour
*/
const parseBlockColour = function(colour) {
const dereferenced =
(typeof colour === 'string') ? replaceMessageReferences(colour) : colour;
const hue = Number(dereferenced);
if (!isNaN(hue) && 0 <= hue && hue <= 360) {
return {
hue: hue,
hex: colourUtils.hsvToHex(
hue, internalConstants.HSV_SATURATION,
internalConstants.HSV_VALUE * 255),
};
} else {
const hex = colourUtils.parse(dereferenced);
if (hex) {
// Only store hue if colour is set as a hue.
return {hue: null, hex: hex};
} else {
let errorMsg = 'Invalid colour: "' + dereferenced + '"';
if (colour !== dereferenced) {
errorMsg += ' (from "' + colour + '")';
}
throw Error(errorMsg);
}
}
deprecation.warn(
'Blockly.utils.parseBlockColour', 'December 2021', 'December 2022',
'Blockly.parsing.parseBlockColour');
return parsing.parseBlockColour(colour);
};
exports.parseBlockColour = parseBlockColour;

263
core/utils/parsing.js Normal file
View File

@@ -0,0 +1,263 @@
/**
* @license
* Copyright 2021 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Utility methods related to block and message parsing.
*/
'use strict';
/**
* @namespace Blockly.utils.parsing
*/
goog.module('Blockly.utils.parsing');
const Msg = goog.require('Blockly.Msg');
const colourUtils = goog.require('Blockly.utils.colour');
const internalConstants = goog.require('Blockly.internalConstants');
const stringUtils = goog.require('Blockly.utils.string');
/**
* Internal implementation of the message reference and interpolation token
* parsing used by tokenizeInterpolation() and replaceMessageReferences().
* @param {string} message Text which might contain string table references and
* interpolation tokens.
* @param {boolean} parseInterpolationTokens Option to parse numeric
* interpolation tokens (%1, %2, ...) when true.
* @return {!Array<string|number>} Array of strings and numbers.
*/
const tokenizeInterpolationInternal = function(
message, parseInterpolationTokens) {
const tokens = [];
const chars = message.split('');
chars.push(''); // End marker.
// Parse the message with a finite state machine.
// 0 - Base case.
// 1 - % found.
// 2 - Digit found.
// 3 - Message ref found.
let state = 0;
const buffer = [];
let number = null;
for (let i = 0; i < chars.length; i++) {
const c = chars[i];
if (state === 0) {
if (c === '%') {
const text = buffer.join('');
if (text) {
tokens.push(text);
}
buffer.length = 0;
state = 1; // Start escape.
} else {
buffer.push(c); // Regular char.
}
} else if (state === 1) {
if (c === '%') {
buffer.push(c); // Escaped %: %%
state = 0;
} else if (parseInterpolationTokens && '0' <= c && c <= '9') {
state = 2;
number = c;
const text = buffer.join('');
if (text) {
tokens.push(text);
}
buffer.length = 0;
} else if (c === '{') {
state = 3;
} else {
buffer.push('%', c); // Not recognized. Return as literal.
state = 0;
}
} else if (state === 2) {
if ('0' <= c && c <= '9') {
number += c; // Multi-digit number.
} else {
tokens.push(parseInt(number, 10));
i--; // Parse this char again.
state = 0;
}
} else if (state === 3) { // String table reference
if (c === '') {
// Premature end before closing '}'
buffer.splice(0, 0, '%{'); // Re-insert leading delimiter
i--; // Parse this char again.
state = 0; // and parse as string literal.
} else if (c !== '}') {
buffer.push(c);
} else {
const rawKey = buffer.join('');
if (/[A-Z]\w*/i.test(rawKey)) { // Strict matching
// Found a valid string key. Attempt case insensitive match.
const keyUpper = rawKey.toUpperCase();
// BKY_ is the prefix used to namespace the strings used in Blockly
// core files and the predefined blocks in ../blocks/.
// These strings are defined in ../msgs/ files.
const bklyKey = stringUtils.startsWith(keyUpper, 'BKY_') ?
keyUpper.substring(4) :
null;
if (bklyKey && bklyKey in Msg) {
const rawValue = Msg[bklyKey];
if (typeof rawValue === 'string') {
// Attempt to dereference substrings, too, appending to the end.
Array.prototype.push.apply(
tokens,
tokenizeInterpolationInternal(
rawValue, parseInterpolationTokens));
} else if (parseInterpolationTokens) {
// When parsing interpolation tokens, numbers are special
// placeholders (%1, %2, etc). Make sure all other values are
// strings.
tokens.push(String(rawValue));
} else {
tokens.push(rawValue);
}
} else {
// No entry found in the string table. Pass reference as string.
tokens.push('%{' + rawKey + '}');
}
buffer.length = 0; // Clear the array
state = 0;
} else {
tokens.push('%{' + rawKey + '}');
buffer.length = 0;
state = 0; // and parse as string literal.
}
}
}
}
let text = buffer.join('');
if (text) {
tokens.push(text);
}
// Merge adjacent text tokens into a single string.
const mergedTokens = [];
buffer.length = 0;
for (let i = 0; i < tokens.length; i++) {
if (typeof tokens[i] === 'string') {
buffer.push(tokens[i]);
} else {
text = buffer.join('');
if (text) {
mergedTokens.push(text);
}
buffer.length = 0;
mergedTokens.push(tokens[i]);
}
}
text = buffer.join('');
if (text) {
mergedTokens.push(text);
}
buffer.length = 0;
return mergedTokens;
};
/**
* Parse a string with any number of interpolation tokens (%1, %2, ...).
* It will also replace string table references (e.g., %{bky_my_msg} and
* %{BKY_MY_MSG} will both be replaced with the value in
* Msg['MY_MSG']). Percentage sign characters '%' may be self-escaped
* (e.g., '%%').
* @param {string} message Text which might contain string table references and
* interpolation tokens.
* @return {!Array<string|number>} Array of strings and numbers.
* @alias Blockly.parsing.tokenizeInterpolation
*/
const tokenizeInterpolation = function(message) {
return tokenizeInterpolationInternal(message, true);
};
exports.tokenizeInterpolation = tokenizeInterpolation;
/**
* Replaces string table references in a message, if the message is a string.
* For example, "%{bky_my_msg}" and "%{BKY_MY_MSG}" will both be replaced with
* the value in Msg['MY_MSG'].
* @param {string|?} message Message, which may be a string that contains
* string table references.
* @return {string} String with message references replaced.
* @alias Blockly.parsing.replaceMessageReferences
*/
const replaceMessageReferences = function(message) {
if (typeof message !== 'string') {
return message;
}
const interpolatedResult = tokenizeInterpolationInternal(message, false);
// When parseInterpolationTokens === false, interpolatedResult should be at
// most length 1.
return interpolatedResult.length ? String(interpolatedResult[0]) : '';
};
exports.replaceMessageReferences = replaceMessageReferences;
/**
* Validates that any %{MSG_KEY} references in the message refer to keys of
* the Msg string table.
* @param {string} message Text which might contain string table references.
* @return {boolean} True if all message references have matching values.
* Otherwise, false.
* @alias Blockly.parsing.checkMessageReferences
*/
const checkMessageReferences = function(message) {
let validSoFar = true;
const msgTable = Msg;
// TODO (#1169): Implement support for other string tables,
// prefixes other than BKY_.
const m = message.match(/%{BKY_[A-Z]\w*}/ig);
for (let i = 0; i < m.length; i++) {
const msgKey = m[i].toUpperCase();
if (msgTable[msgKey.slice(6, -1)] === undefined) {
console.warn('No message string for ' + m[i] + ' in ' + message);
validSoFar = false; // Continue to report other errors.
}
}
return validSoFar;
};
exports.checkMessageReferences = checkMessageReferences;
/**
* Parse a block colour from a number or string, as provided in a block
* definition.
* @param {number|string} colour HSV hue value (0 to 360), #RRGGBB string,
* or a message reference string pointing to one of those two values.
* @return {{hue: ?number, hex: string}} An object containing the colour as
* a #RRGGBB string, and the hue if the input was an HSV hue value.
* @throws {Error} If the colour cannot be parsed.
* @alias Blockly.parsing.parseBlockColour
*/
const parseBlockColour = function(colour) {
const dereferenced =
(typeof colour === 'string') ? replaceMessageReferences(colour) : colour;
const hue = Number(dereferenced);
if (!isNaN(hue) && 0 <= hue && hue <= 360) {
return {
hue: hue,
hex: colourUtils.hsvToHex(
hue, internalConstants.HSV_SATURATION,
internalConstants.HSV_VALUE * 255),
};
} else {
const hex = colourUtils.parse(dereferenced);
if (hex) {
// Only store hue if colour is set as a hue.
return {hue: null, hex: hex};
} else {
let errorMsg = 'Invalid colour: "' + dereferenced + '"';
if (colour !== dereferenced) {
errorMsg += ' (from "' + colour + '")';
}
throw Error(errorMsg);
}
}
};
exports.parseBlockColour = parseBlockColour;

245
core/utils/svg_math.js Normal file
View File

@@ -0,0 +1,245 @@
/**
* @license
* Copyright 2021 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Utility methods for SVG math.
*/
'use strict';
/**
* Utility methods realted to figuring out positions of SVG elements.
* @namespace Blockly.utils.svgMath
*/
goog.module('Blockly.utils.svgMath');
const global = goog.require('Blockly.utils.global');
const style = goog.require('Blockly.utils.style');
const userAgent = goog.require('Blockly.utils.userAgent');
const {Coordinate} = goog.require('Blockly.utils.Coordinate');
const {Rect} = goog.require('Blockly.utils.Rect');
/* eslint-disable-next-line no-unused-vars */
const {WorkspaceSvg} = goog.requireType('Blockly.WorkspaceSvg');
/**
* Static regex to pull the x,y values out of an SVG translate() directive.
* Note that Firefox and IE (9,10) return 'translate(12)' instead of
* 'translate(12, 0)'.
* Note that IE (9,10) returns 'translate(16 8)' instead of 'translate(16, 8)'.
* Note that IE has been reported to return scientific notation (0.123456e-42).
* @type {!RegExp}
*/
const XY_REGEX = /translate\(\s*([-+\d.e]+)([ ,]\s*([-+\d.e]+)\s*)?/;
/**
* Static regex to pull the x,y values out of a translate() or translate3d()
* style property.
* Accounts for same exceptions as XY_REGEX.
* @type {!RegExp}
*/
const XY_STYLE_REGEX =
/transform:\s*translate(?:3d)?\(\s*([-+\d.e]+)\s*px([ ,]\s*([-+\d.e]+)\s*px)?/;
/**
* Return the coordinates of the top-left corner of this element relative to
* its parent. Only for SVG elements and children (e.g. rect, g, path).
* @param {!Element} element SVG element to find the coordinates of.
* @return {!Coordinate} Object with .x and .y properties.
* @alias Blockly.svgMath.getRelativeXY
*/
const getRelativeXY = function(element) {
const xy = new Coordinate(0, 0);
// First, check for x and y attributes.
const x = element.getAttribute('x');
if (x) {
xy.x = parseInt(x, 10);
}
const y = element.getAttribute('y');
if (y) {
xy.y = parseInt(y, 10);
}
// Second, check for transform="translate(...)" attribute.
const transform = element.getAttribute('transform');
const r = transform && transform.match(XY_REGEX);
if (r) {
xy.x += Number(r[1]);
if (r[3]) {
xy.y += Number(r[3]);
}
}
// Then check for style = transform: translate(...) or translate3d(...)
const style = element.getAttribute('style');
if (style && style.indexOf('translate') > -1) {
const styleComponents = style.match(XY_STYLE_REGEX);
if (styleComponents) {
xy.x += Number(styleComponents[1]);
if (styleComponents[3]) {
xy.y += Number(styleComponents[3]);
}
}
}
return xy;
};
exports.getRelativeXY = getRelativeXY;
/**
* Return the coordinates of the top-left corner of this element relative to
* the div Blockly was injected into.
* @param {!Element} element SVG element to find the coordinates of. If this is
* not a child of the div Blockly was injected into, the behaviour is
* undefined.
* @return {!Coordinate} Object with .x and .y properties.
* @alias Blockly.svgMath.getInjectionDivXY
*/
const getInjectionDivXY = function(element) {
let x = 0;
let y = 0;
while (element) {
const xy = getRelativeXY(element);
x = x + xy.x;
y = y + xy.y;
const classes = element.getAttribute('class') || '';
if ((' ' + classes + ' ').indexOf(' injectionDiv ') !== -1) {
break;
}
element = /** @type {!Element} */ (element.parentNode);
}
return new Coordinate(x, y);
};
exports.getInjectionDivXY = getInjectionDivXY;
/**
* Check if 3D transforms are supported by adding an element
* and attempting to set the property.
* @return {boolean} True if 3D transforms are supported.
* @alias Blockly.svgMath.is3dSupported
*/
const is3dSupported = function() {
if (is3dSupported.cached_ !== undefined) {
return is3dSupported.cached_;
}
// CC-BY-SA Lorenzo Polidori
// stackoverflow.com/questions/5661671/detecting-transform-translate3d-support
if (!global.globalThis['getComputedStyle']) {
return false;
}
const el = document.createElement('p');
let has3d = 'none';
const transforms = {
'webkitTransform': '-webkit-transform',
'OTransform': '-o-transform',
'msTransform': '-ms-transform',
'MozTransform': '-moz-transform',
'transform': 'transform',
};
// Add it to the body to get the computed style.
document.body.insertBefore(el, null);
for (const t in transforms) {
if (el.style[t] !== undefined) {
el.style[t] = 'translate3d(1px,1px,1px)';
const computedStyle = global.globalThis['getComputedStyle'](el);
if (!computedStyle) {
// getComputedStyle in Firefox returns null when Blockly is loaded
// inside an iframe with display: none. Returning false and not
// caching is3dSupported means we try again later. This is most likely
// when users are interacting with blocks which should mean Blockly is
// visible again.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=548397
document.body.removeChild(el);
return false;
}
has3d = computedStyle.getPropertyValue(transforms[t]);
}
}
document.body.removeChild(el);
is3dSupported.cached_ = has3d !== 'none';
return is3dSupported.cached_;
};
exports.is3dSupported = is3dSupported;
/**
* Get the position of the current viewport in window coordinates. This takes
* scroll into account.
* @return {!Rect} An object containing window width, height, and
* scroll position in window coordinates.
* @alias Blockly.svgMath.getViewportBBox
* @package
*/
const getViewportBBox = function() {
// Pixels, in window coordinates.
const scrollOffset = style.getViewportPageOffset();
return new Rect(
scrollOffset.y, document.documentElement.clientHeight + scrollOffset.y,
scrollOffset.x, document.documentElement.clientWidth + scrollOffset.x);
};
exports.getViewportBBox = getViewportBBox;
/**
* Gets the document scroll distance as a coordinate object.
* Copied from Closure's goog.dom.getDocumentScroll.
* @return {!Coordinate} Object with values 'x' and 'y'.
* @alias Blockly.svgMath.getDocumentScroll
*/
const getDocumentScroll = function() {
const el = document.documentElement;
const win = window;
if (userAgent.IE && win.pageYOffset !== el.scrollTop) {
// The keyboard on IE10 touch devices shifts the page using the pageYOffset
// without modifying scrollTop. For this case, we want the body scroll
// offsets.
return new Coordinate(el.scrollLeft, el.scrollTop);
}
return new Coordinate(
win.pageXOffset || el.scrollLeft, win.pageYOffset || el.scrollTop);
};
exports.getDocumentScroll = getDocumentScroll;
/**
* Converts screen coordinates to workspace coordinates.
* @param {!WorkspaceSvg} ws The workspace to find the coordinates on.
* @param {!Coordinate} screenCoordinates The screen coordinates to
* be converted to workspace coordinates
* @return {!Coordinate} The workspace coordinates.
* @alias Blockly.svgMath.screenToWsCoordinates
*/
const screenToWsCoordinates = function(ws, screenCoordinates) {
const screenX = screenCoordinates.x;
const screenY = screenCoordinates.y;
const injectionDiv = ws.getInjectionDiv();
// Bounding rect coordinates are in client coordinates, meaning that they
// are in pixels relative to the upper left corner of the visible browser
// window. These coordinates change when you scroll the browser window.
const boundingRect = injectionDiv.getBoundingClientRect();
// The client coordinates offset by the injection div's upper left corner.
const clientOffsetPixels =
new Coordinate(screenX - boundingRect.left, screenY - boundingRect.top);
// The offset in pixels between the main workspace's origin and the upper
// left corner of the injection div.
const mainOffsetPixels = ws.getOriginOffsetInPixels();
// The position of the new comment in pixels relative to the origin of the
// main workspace.
const finalOffsetPixels =
Coordinate.difference(clientOffsetPixels, mainOffsetPixels);
// The position in main workspace coordinates.
const finalOffsetMainWs = finalOffsetPixels.scale(1 / ws.scale);
return finalOffsetMainWs;
};
exports.screenToWsCoordinates = screenToWsCoordinates;
exports.TEST_ONLY = {
XY_REGEX,
XY_STYLE_REGEX,
};

View File

@@ -23,7 +23,7 @@ const common = goog.require('Blockly.common');
const dom = goog.require('Blockly.utils.dom');
const eventUtils = goog.require('Blockly.Events.utils');
const object = goog.require('Blockly.utils.object');
const utils = goog.require('Blockly.utils');
const svgMath = goog.require('Blockly.utils.svgMath');
/* eslint-disable-next-line no-unused-vars */
const {BlockDragSurfaceSvg} = goog.requireType('Blockly.BlockDragSurfaceSvg');
const {Coordinate} = goog.require('Blockly.utils.Coordinate');
@@ -137,7 +137,7 @@ const WorkspaceCommentSvg = function(
* @private
*/
this.useDragSurface_ =
utils.is3dSupported() && !!workspace.getBlockDragSurface();
svgMath.is3dSupported() && !!workspace.getBlockDragSurface();
WorkspaceCommentSvg.superClass_.constructor.call(
this, workspace, content, height, width, opt_id);
@@ -355,7 +355,7 @@ WorkspaceCommentSvg.prototype.getRelativeToSurfaceXY = function() {
if (element) {
do {
// Loop through this comment and every parent.
const xy = utils.getRelativeXY(/** @type {!Element} */ (element));
const xy = svgMath.getRelativeXY(/** @type {!Element} */ (element));
x += xy.x;
y += xy.y;
// If this element is the current element on the drag surface, include

View File

@@ -23,7 +23,7 @@
goog.module('Blockly.WorkspaceDragSurfaceSvg');
const dom = goog.require('Blockly.utils.dom');
const utils = goog.require('Blockly.utils');
const svgMath = goog.require('Blockly.utils.svgMath');
/* eslint-disable-next-line no-unused-vars */
const {Coordinate} = goog.requireType('Blockly.utils.Coordinate');
const {Svg} = goog.require('Blockly.utils.Svg');
@@ -111,7 +111,7 @@ WorkspaceDragSurfaceSvg.prototype.translateSurface = function(x, y) {
* @package
*/
WorkspaceDragSurfaceSvg.prototype.getSurfaceTranslation = function() {
return utils.getRelativeXY(/** @type {!SVGElement} */ (this.SVG_));
return svgMath.getRelativeXY(/** @type {!SVGElement} */ (this.SVG_));
};
/**

View File

@@ -34,6 +34,7 @@ const eventUtils = goog.require('Blockly.Events.utils');
const internalConstants = goog.require('Blockly.internalConstants');
const object = goog.require('Blockly.utils.object');
const registry = goog.require('Blockly.registry');
const svgMath = goog.require('Blockly.utils.svgMath');
const toolbox = goog.require('Blockly.utils.toolbox');
const userAgent = goog.require('Blockly.utils.userAgent');
const utils = goog.require('Blockly.utils');
@@ -170,7 +171,7 @@ const WorkspaceSvg = function(
}
this.useWorkspaceDragSurface_ =
!!this.workspaceDragSurface_ && utils.is3dSupported();
!!this.workspaceDragSurface_ && svgMath.is3dSupported();
/**
* List of currently highlighted blocks. Block highlighting is often used to
@@ -793,7 +794,7 @@ WorkspaceSvg.prototype.getSvgXY = function(element) {
}
do {
// Loop through this block and every parent.
const xy = utils.getRelativeXY(element);
const xy = svgMath.getRelativeXY(element);
if (element === this.getCanvas() || element === this.getBubbleCanvas()) {
// After the SVG canvas, don't scale the coordinates.
scale = 1;
@@ -825,7 +826,7 @@ WorkspaceSvg.prototype.getCachedParentSvgSize = function() {
* @package
*/
WorkspaceSvg.prototype.getOriginOffsetInPixels = function() {
return utils.getInjectionDivXY_(this.getCanvas());
return svgMath.getInjectionDivXY(this.getCanvas());
};
/**
@@ -1214,7 +1215,7 @@ WorkspaceSvg.prototype.resize = function() {
*/
WorkspaceSvg.prototype.updateScreenCalculationsIfScrolled = function() {
/* eslint-disable indent */
const currScroll = utils.getDocumentScroll();
const currScroll = svgMath.getDocumentScroll();
if (!Coordinate.equals(this.lastRecordedPageScroll_, currScroll)) {
this.lastRecordedPageScroll_ = currScroll;
this.updateScreenCalculations_();
@@ -1385,7 +1386,7 @@ WorkspaceSvg.prototype.setupDragSurface = function() {
/** @type {Element} */ (this.svgBlockCanvas_.previousSibling);
const width = parseInt(this.getParentSvg().getAttribute('width'), 10);
const height = parseInt(this.getParentSvg().getAttribute('height'), 10);
const coord = utils.getRelativeXY(this.getCanvas());
const coord = svgMath.getRelativeXY(this.getCanvas());
this.workspaceDragSurface_.setContentsAndShow(
this.getCanvas(), this.getBubbleCanvas(), previousElement, width, height,
this.scale);