mirror of
https://github.com/google/blockly.git
synced 2026-01-05 08:00:09 +01:00
Refactored field utilities.
This commit is contained in:
@@ -39,6 +39,7 @@ goog.require('Blockly.Input');
|
|||||||
goog.require('Blockly.Mutator');
|
goog.require('Blockly.Mutator');
|
||||||
goog.require('Blockly.utils');
|
goog.require('Blockly.utils');
|
||||||
goog.require('Blockly.utils.Coordinate');
|
goog.require('Blockly.utils.Coordinate');
|
||||||
|
goog.require('Blockly.utils.fields');
|
||||||
goog.require('Blockly.utils.string');
|
goog.require('Blockly.utils.string');
|
||||||
goog.require('Blockly.Warning');
|
goog.require('Blockly.Warning');
|
||||||
goog.require('Blockly.Workspace');
|
goog.require('Blockly.Workspace');
|
||||||
@@ -1636,18 +1637,13 @@ Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) {
|
|||||||
default:
|
default:
|
||||||
// This should handle all field JSON parsing, including
|
// This should handle all field JSON parsing, including
|
||||||
// options that can be applied to any field type.
|
// options that can be applied to any field type.
|
||||||
field = Blockly.Field.fromJson(element);
|
field = Blockly.utils.fields.fromJson(element);
|
||||||
|
|
||||||
// Unknown field.
|
// Unknown field.
|
||||||
if (!field) {
|
if (!field) {
|
||||||
if (element['alt']) {
|
if (element['alt']) {
|
||||||
element = element['alt'];
|
element = element['alt'];
|
||||||
altRepeat = true;
|
altRepeat = true;
|
||||||
} else {
|
|
||||||
console.warn('Blockly could not create a field of type ' +
|
|
||||||
element['type'] +
|
|
||||||
'. You may need to register your custom field. See ' +
|
|
||||||
'github.com/google/blockly/issues/1584');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ goog.require('Blockly.Events');
|
|||||||
goog.require('Blockly.Events.BlockMove');
|
goog.require('Blockly.Events.BlockMove');
|
||||||
goog.require('Blockly.InsertionMarkerManager');
|
goog.require('Blockly.InsertionMarkerManager');
|
||||||
goog.require('Blockly.utils.Coordinate');
|
goog.require('Blockly.utils.Coordinate');
|
||||||
|
goog.require('Blockly.utils.dom');
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -164,7 +165,7 @@ Blockly.BlockDragger.prototype.startBlockDrag = function(currentDragDeltaXY,
|
|||||||
|
|
||||||
// During a drag there may be a lot of rerenders, but not field changes.
|
// During a drag there may be a lot of rerenders, but not field changes.
|
||||||
// Turn the cache on so we don't do spurious remeasures during the drag.
|
// Turn the cache on so we don't do spurious remeasures during the drag.
|
||||||
Blockly.Field.startCache();
|
Blockly.utils.dom.startTextWidthCache();
|
||||||
this.workspace_.setResizesEnabled(false);
|
this.workspace_.setResizesEnabled(false);
|
||||||
Blockly.blockAnimations.disconnectUiStop();
|
Blockly.blockAnimations.disconnectUiStop();
|
||||||
|
|
||||||
@@ -225,7 +226,7 @@ Blockly.BlockDragger.prototype.endBlockDrag = function(e, currentDragDeltaXY) {
|
|||||||
this.dragBlock(e, currentDragDeltaXY);
|
this.dragBlock(e, currentDragDeltaXY);
|
||||||
this.dragIconData_ = [];
|
this.dragIconData_ = [];
|
||||||
|
|
||||||
Blockly.Field.stopCache();
|
Blockly.utils.dom.stopTextWidthCache();
|
||||||
|
|
||||||
Blockly.blockAnimations.disconnectUiStop();
|
Blockly.blockAnimations.disconnectUiStop();
|
||||||
|
|
||||||
|
|||||||
@@ -270,9 +270,9 @@ Blockly.BlockSvg.prototype.setParent = function(newParent) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Blockly.Field.startCache();
|
Blockly.utils.dom.startTextWidthCache();
|
||||||
Blockly.BlockSvg.superClass_.setParent.call(this, newParent);
|
Blockly.BlockSvg.superClass_.setParent.call(this, newParent);
|
||||||
Blockly.Field.stopCache();
|
Blockly.utils.dom.stopTextWidthCache();
|
||||||
|
|
||||||
var svgRoot = this.getSvgRoot();
|
var svgRoot = this.getSvgRoot();
|
||||||
|
|
||||||
@@ -893,7 +893,7 @@ Blockly.BlockSvg.prototype.dispose = function(healStack, animate) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Blockly.Tooltip.hide();
|
Blockly.Tooltip.hide();
|
||||||
Blockly.Field.startCache();
|
Blockly.utils.dom.startTextWidthCache();
|
||||||
// Save the block's workspace temporarily so we can resize the
|
// Save the block's workspace temporarily so we can resize the
|
||||||
// contents once the block is disposed.
|
// contents once the block is disposed.
|
||||||
var blockWorkspace = this.workspace;
|
var blockWorkspace = this.workspace;
|
||||||
@@ -952,7 +952,7 @@ Blockly.BlockSvg.prototype.dispose = function(healStack, animate) {
|
|||||||
this.svgPath_ = null;
|
this.svgPath_ = null;
|
||||||
this.svgPathLight_ = null;
|
this.svgPathLight_ = null;
|
||||||
this.svgPathDark_ = null;
|
this.svgPathDark_ = null;
|
||||||
Blockly.Field.stopCache();
|
Blockly.utils.dom.stopTextWidthCache();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1539,7 +1539,7 @@ Blockly.BlockSvg.prototype.positionNearConnection = function(sourceConnection,
|
|||||||
* If true, also render block's parent, grandparent, etc. Defaults to true.
|
* If true, also render block's parent, grandparent, etc. Defaults to true.
|
||||||
*/
|
*/
|
||||||
Blockly.BlockSvg.prototype.render = function(opt_bubble) {
|
Blockly.BlockSvg.prototype.render = function(opt_bubble) {
|
||||||
Blockly.Field.startCache();
|
Blockly.utils.dom.startTextWidthCache();
|
||||||
this.rendered = true;
|
this.rendered = true;
|
||||||
// TODO (#2702): Choose an API for picking the renderer.
|
// TODO (#2702): Choose an API for picking the renderer.
|
||||||
Blockly.blockRendering.render(this);
|
Blockly.blockRendering.render(this);
|
||||||
@@ -1555,7 +1555,7 @@ Blockly.BlockSvg.prototype.render = function(opt_bubble) {
|
|||||||
this.workspace.resizeContents();
|
this.workspace.resizeContents();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Blockly.Field.stopCache();
|
Blockly.utils.dom.stopTextWidthCache();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
136
core/field.js
136
core/field.js
@@ -57,71 +57,6 @@ Blockly.Field = function(value, opt_validator) {
|
|||||||
opt_validator && this.setValidator(opt_validator);
|
opt_validator && this.setValidator(opt_validator);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* The set of all registered fields, keyed by field type as used in the JSON
|
|
||||||
* definition of a block.
|
|
||||||
* @type {!Object<string, !{fromJson: Function}>}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
Blockly.Field.TYPE_MAP_ = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers a field type. May also override an existing field type.
|
|
||||||
* Blockly.Field.fromJson uses this registry to find the appropriate field.
|
|
||||||
* @param {string} type The field type name as used in the JSON definition.
|
|
||||||
* @param {!{fromJson: Function}} fieldClass The field class containing a
|
|
||||||
* fromJson function that can construct an instance of the field.
|
|
||||||
* @throws {Error} if the type name is empty, or the fieldClass is not an
|
|
||||||
* object containing a fromJson function.
|
|
||||||
*/
|
|
||||||
Blockly.Field.register = function(type, fieldClass) {
|
|
||||||
if ((typeof type != 'string') || (type.trim() == '')) {
|
|
||||||
throw Error('Invalid field type "' + type + '"');
|
|
||||||
}
|
|
||||||
if (!fieldClass || (typeof fieldClass.fromJson != 'function')) {
|
|
||||||
throw Error('Field "' + fieldClass + '" must have a fromJson function');
|
|
||||||
}
|
|
||||||
Blockly.Field.TYPE_MAP_[type] = fieldClass;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct a Field from a JSON arg object.
|
|
||||||
* Finds the appropriate registered field by the type name as registered using
|
|
||||||
* Blockly.Field.register.
|
|
||||||
* @param {!Object} options A JSON object with a type and options specific
|
|
||||||
* to the field type.
|
|
||||||
* @return {Blockly.Field} The new field instance or null if a field wasn't
|
|
||||||
* found with the given type name
|
|
||||||
* @package
|
|
||||||
*/
|
|
||||||
Blockly.Field.fromJson = function(options) {
|
|
||||||
var fieldClass = Blockly.Field.TYPE_MAP_[options['type']];
|
|
||||||
if (fieldClass) {
|
|
||||||
var field = fieldClass.fromJson(options);
|
|
||||||
if (options['tooltip'] !== undefined) {
|
|
||||||
var rawValue = options['tooltip'];
|
|
||||||
var localizedText = Blockly.utils.replaceMessageReferences(rawValue);
|
|
||||||
field.setTooltip(localizedText);
|
|
||||||
}
|
|
||||||
return field;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Temporary cache of text widths.
|
|
||||||
* @type {Object}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
Blockly.Field.cacheWidths_ = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Number of current references to cache.
|
|
||||||
* @type {number}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
Blockly.Field.cacheReference_ = 0;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default height of the border rect on any field.
|
* The default height of the border rect on any field.
|
||||||
* @type {number}
|
* @type {number}
|
||||||
@@ -588,14 +523,14 @@ Blockly.Field.prototype.render_ = function() {
|
|||||||
/**
|
/**
|
||||||
* Updates the width of the field. Redirects to updateSize_().
|
* Updates the width of the field. Redirects to updateSize_().
|
||||||
* @deprecated May 2019 Use Blockly.Field.updateSize_() to force an update
|
* @deprecated May 2019 Use Blockly.Field.updateSize_() to force an update
|
||||||
* to the size of the field, or Blockly.Field.getCachedWidth() to check the
|
* to the size of the field, or Blockly.utils.dom.getTextWidth() to
|
||||||
* size of the field..
|
* check the size of the field.
|
||||||
*/
|
*/
|
||||||
Blockly.Field.prototype.updateWidth = function() {
|
Blockly.Field.prototype.updateWidth = function() {
|
||||||
console.warn('Deprecated call to updateWidth, call' +
|
console.warn('Deprecated call to updateWidth, call' +
|
||||||
' Blockly.Field.updateSize_ to force an update to the size of the' +
|
' Blockly.Field.updateSize_ to force an update to the size of the' +
|
||||||
' field, or Blockly.Field.getCachedWidth() to check the size of the' +
|
' field, or Blockly.utils.dom.getTextWidth() to check the size' +
|
||||||
' field.');
|
' of the field.');
|
||||||
this.updateSize_();
|
this.updateSize_();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -604,7 +539,7 @@ Blockly.Field.prototype.updateWidth = function() {
|
|||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
Blockly.Field.prototype.updateSize_ = function() {
|
Blockly.Field.prototype.updateSize_ = function() {
|
||||||
var textWidth = Blockly.Field.getCachedWidth(this.textElement_);
|
var textWidth = Blockly.utils.dom.getTextWidth(this.textElement_);
|
||||||
var totalWidth = textWidth;
|
var totalWidth = textWidth;
|
||||||
if (this.borderRect_) {
|
if (this.borderRect_) {
|
||||||
totalWidth += Blockly.Field.X_PADDING;
|
totalWidth += Blockly.Field.X_PADDING;
|
||||||
@@ -613,67 +548,6 @@ Blockly.Field.prototype.updateSize_ = function() {
|
|||||||
this.size_.width = totalWidth;
|
this.size_.width = totalWidth;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the width of a text element, caching it in the process.
|
|
||||||
* @param {!Element} textElement An SVG 'text' element.
|
|
||||||
* @return {number} Width of element.
|
|
||||||
*/
|
|
||||||
Blockly.Field.getCachedWidth = function(textElement) {
|
|
||||||
var key = textElement.textContent + '\n' + textElement.className.baseVal;
|
|
||||||
var width;
|
|
||||||
|
|
||||||
// Return the cached width if it exists.
|
|
||||||
if (Blockly.Field.cacheWidths_) {
|
|
||||||
width = Blockly.Field.cacheWidths_[key];
|
|
||||||
if (width) {
|
|
||||||
return width;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt to compute fetch the width of the SVG text element.
|
|
||||||
try {
|
|
||||||
if (Blockly.utils.userAgent.IE || Blockly.utils.userAgent.EDGE) {
|
|
||||||
width = textElement.getBBox().width;
|
|
||||||
} else {
|
|
||||||
width = textElement.getComputedTextLength();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// In other cases where we fail to geth the computed text. Instead, use an
|
|
||||||
// approximation and do not cache the result. At some later point in time
|
|
||||||
// when the block is inserted into the visible DOM, this method will be
|
|
||||||
// called again and, at that point in time, will not throw an exception.
|
|
||||||
return textElement.textContent.length * 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cache the computed width and return.
|
|
||||||
if (Blockly.Field.cacheWidths_) {
|
|
||||||
Blockly.Field.cacheWidths_[key] = width;
|
|
||||||
}
|
|
||||||
return width;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start caching field widths. Every call to this function MUST also call
|
|
||||||
* stopCache. Caches must not survive between execution threads.
|
|
||||||
*/
|
|
||||||
Blockly.Field.startCache = function() {
|
|
||||||
Blockly.Field.cacheReference_++;
|
|
||||||
if (!Blockly.Field.cacheWidths_) {
|
|
||||||
Blockly.Field.cacheWidths_ = {};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stop caching field widths. Unless caching was already on when the
|
|
||||||
* corresponding call to startCache was made.
|
|
||||||
*/
|
|
||||||
Blockly.Field.stopCache = function() {
|
|
||||||
Blockly.Field.cacheReference_--;
|
|
||||||
if (!Blockly.Field.cacheReference_) {
|
|
||||||
Blockly.Field.cacheWidths_ = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the height and width of the field.
|
* Returns the height and width of the field.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ goog.provide('Blockly.FieldAngle');
|
|||||||
goog.require('Blockly.DropDownDiv');
|
goog.require('Blockly.DropDownDiv');
|
||||||
goog.require('Blockly.FieldTextInput');
|
goog.require('Blockly.FieldTextInput');
|
||||||
goog.require('Blockly.utils.dom');
|
goog.require('Blockly.utils.dom');
|
||||||
|
goog.require('Blockly.utils.fields');
|
||||||
goog.require('Blockly.utils.math');
|
goog.require('Blockly.utils.math');
|
||||||
goog.require('Blockly.utils.userAgent');
|
goog.require('Blockly.utils.userAgent');
|
||||||
|
|
||||||
@@ -353,4 +354,4 @@ Blockly.FieldAngle.prototype.doClassValidation_ = function(opt_newValue) {
|
|||||||
return n;
|
return n;
|
||||||
};
|
};
|
||||||
|
|
||||||
Blockly.Field.register('field_angle', Blockly.FieldAngle);
|
Blockly.utils.fields.register('field_angle', Blockly.FieldAngle);
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ goog.require('Blockly.Events');
|
|||||||
goog.require('Blockly.Events.BlockChange');
|
goog.require('Blockly.Events.BlockChange');
|
||||||
goog.require('Blockly.Field');
|
goog.require('Blockly.Field');
|
||||||
goog.require('Blockly.utils.dom');
|
goog.require('Blockly.utils.dom');
|
||||||
|
goog.require('Blockly.utils.fields');
|
||||||
goog.require('Blockly.utils.Size');
|
goog.require('Blockly.utils.Size');
|
||||||
|
|
||||||
|
|
||||||
@@ -210,4 +211,4 @@ Blockly.FieldCheckbox.prototype.convertValueToBool_ = function(value) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Blockly.Field.register('field_checkbox', Blockly.FieldCheckbox);
|
Blockly.utils.fields.register('field_checkbox', Blockly.FieldCheckbox);
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ goog.require('Blockly.Events');
|
|||||||
goog.require('Blockly.Events.BlockChange');
|
goog.require('Blockly.Events.BlockChange');
|
||||||
goog.require('Blockly.Field');
|
goog.require('Blockly.Field');
|
||||||
goog.require('Blockly.utils.colour');
|
goog.require('Blockly.utils.colour');
|
||||||
|
goog.require('Blockly.utils.fields');
|
||||||
goog.require('Blockly.utils.Size');
|
goog.require('Blockly.utils.Size');
|
||||||
|
|
||||||
|
|
||||||
@@ -346,4 +347,4 @@ Blockly.FieldColour.prototype.dropdownDispose_ = function() {
|
|||||||
Blockly.unbindEvent_(this.onUpWrapper_);
|
Blockly.unbindEvent_(this.onUpWrapper_);
|
||||||
};
|
};
|
||||||
|
|
||||||
Blockly.Field.register('field_colour', Blockly.FieldColour);
|
Blockly.utils.fields.register('field_colour', Blockly.FieldColour);
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ goog.provide('Blockly.FieldDate');
|
|||||||
goog.require('Blockly.Events');
|
goog.require('Blockly.Events');
|
||||||
goog.require('Blockly.Field');
|
goog.require('Blockly.Field');
|
||||||
goog.require('Blockly.utils.dom');
|
goog.require('Blockly.utils.dom');
|
||||||
|
goog.require('Blockly.utils.fields');
|
||||||
goog.require('Blockly.utils.string');
|
goog.require('Blockly.utils.string');
|
||||||
|
|
||||||
goog.require('goog.date');
|
goog.require('goog.date');
|
||||||
@@ -323,4 +324,4 @@ Blockly.FieldDate.CSS = [
|
|||||||
'}'
|
'}'
|
||||||
];
|
];
|
||||||
|
|
||||||
Blockly.Field.register('field_date', Blockly.FieldDate);
|
Blockly.utils.fields.register('field_date', Blockly.FieldDate);
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ goog.require('Blockly.Events.BlockChange');
|
|||||||
goog.require('Blockly.Field');
|
goog.require('Blockly.Field');
|
||||||
goog.require('Blockly.utils');
|
goog.require('Blockly.utils');
|
||||||
goog.require('Blockly.utils.dom');
|
goog.require('Blockly.utils.dom');
|
||||||
|
goog.require('Blockly.utils.fields');
|
||||||
goog.require('Blockly.utils.Size');
|
goog.require('Blockly.utils.Size');
|
||||||
goog.require('Blockly.utils.string');
|
goog.require('Blockly.utils.string');
|
||||||
goog.require('Blockly.utils.uiMenu');
|
goog.require('Blockly.utils.uiMenu');
|
||||||
@@ -492,7 +493,7 @@ Blockly.FieldDropdown.prototype.renderSelectedImage_ = function() {
|
|||||||
this.imageElement_.setAttribute('height', this.imageJson_.height);
|
this.imageElement_.setAttribute('height', this.imageJson_.height);
|
||||||
this.imageElement_.setAttribute('width', this.imageJson_.width);
|
this.imageElement_.setAttribute('width', this.imageJson_.width);
|
||||||
|
|
||||||
var arrowWidth = Blockly.Field.getCachedWidth(this.arrow_);
|
var arrowWidth = Blockly.utils.dom.getTextWidth(this.arrow_);
|
||||||
|
|
||||||
var imageHeight = Number(this.imageJson_.height);
|
var imageHeight = Number(this.imageJson_.height);
|
||||||
var imageWidth = Number(this.imageJson_.width);
|
var imageWidth = Number(this.imageJson_.width);
|
||||||
@@ -524,8 +525,8 @@ Blockly.FieldDropdown.prototype.renderSelectedText_ = function() {
|
|||||||
this.textElement_.setAttribute('x', Blockly.Field.DEFAULT_TEXT_OFFSET);
|
this.textElement_.setAttribute('x', Blockly.Field.DEFAULT_TEXT_OFFSET);
|
||||||
// Height and width include the border rect.
|
// Height and width include the border rect.
|
||||||
this.size_.height = Blockly.Field.BORDER_RECT_DEFAULT_HEIGHT;
|
this.size_.height = Blockly.Field.BORDER_RECT_DEFAULT_HEIGHT;
|
||||||
this.size_.width =
|
this.size_.width = Blockly.utils.dom.getTextWidth(this.textElement_)
|
||||||
Blockly.Field.getCachedWidth(this.textElement_) + Blockly.Field.X_PADDING;
|
+ Blockly.Field.X_PADDING;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -565,4 +566,4 @@ Blockly.FieldDropdown.validateOptions_ = function(options) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Blockly.Field.register('field_dropdown', Blockly.FieldDropdown);
|
Blockly.utils.fields.register('field_dropdown', Blockly.FieldDropdown);
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ goog.require('Blockly.Field');
|
|||||||
goog.require('Blockly.Tooltip');
|
goog.require('Blockly.Tooltip');
|
||||||
goog.require('Blockly.utils');
|
goog.require('Blockly.utils');
|
||||||
goog.require('Blockly.utils.dom');
|
goog.require('Blockly.utils.dom');
|
||||||
|
goog.require('Blockly.utils.fields');
|
||||||
goog.require('Blockly.utils.Size');
|
goog.require('Blockly.utils.Size');
|
||||||
|
|
||||||
|
|
||||||
@@ -222,4 +223,4 @@ Blockly.FieldImage.prototype.setOnClickHandler = function(func) {
|
|||||||
this.clickHandler_ = func;
|
this.clickHandler_ = func;
|
||||||
};
|
};
|
||||||
|
|
||||||
Blockly.Field.register('field_image', Blockly.FieldImage);
|
Blockly.utils.fields.register('field_image', Blockly.FieldImage);
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ goog.require('Blockly.Field');
|
|||||||
goog.require('Blockly.Tooltip');
|
goog.require('Blockly.Tooltip');
|
||||||
goog.require('Blockly.utils');
|
goog.require('Blockly.utils');
|
||||||
goog.require('Blockly.utils.dom');
|
goog.require('Blockly.utils.dom');
|
||||||
|
goog.require('Blockly.utils.fields');
|
||||||
goog.require('Blockly.utils.Size');
|
goog.require('Blockly.utils.Size');
|
||||||
|
|
||||||
|
|
||||||
@@ -100,4 +101,4 @@ Blockly.FieldLabel.prototype.doClassValidation_ = function(opt_newValue) {
|
|||||||
return String(opt_newValue);
|
return String(opt_newValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
Blockly.Field.register('field_label', Blockly.FieldLabel);
|
Blockly.utils.fields.register('field_label', Blockly.FieldLabel);
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ goog.provide('Blockly.FieldLabelSerializable');
|
|||||||
|
|
||||||
goog.require('Blockly.FieldLabel');
|
goog.require('Blockly.FieldLabel');
|
||||||
goog.require('Blockly.utils');
|
goog.require('Blockly.utils');
|
||||||
|
goog.require('Blockly.utils.fields');
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -75,5 +76,5 @@ Blockly.FieldLabelSerializable.prototype.EDITABLE = false;
|
|||||||
*/
|
*/
|
||||||
Blockly.FieldLabelSerializable.prototype.SERIALIZABLE = true;
|
Blockly.FieldLabelSerializable.prototype.SERIALIZABLE = true;
|
||||||
|
|
||||||
Blockly.Field.register(
|
Blockly.utils.fields.register(
|
||||||
'field_label_serializable', Blockly.FieldLabelSerializable);
|
'field_label_serializable', Blockly.FieldLabelSerializable);
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
goog.provide('Blockly.FieldNumber');
|
goog.provide('Blockly.FieldNumber');
|
||||||
|
|
||||||
goog.require('Blockly.FieldTextInput');
|
goog.require('Blockly.FieldTextInput');
|
||||||
|
goog.require('Blockly.utils.fields');
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -138,4 +139,4 @@ Blockly.FieldNumber.prototype.doClassValidation_ = function(opt_newValue) {
|
|||||||
return n;
|
return n;
|
||||||
};
|
};
|
||||||
|
|
||||||
Blockly.Field.register('field_number', Blockly.FieldNumber);
|
Blockly.utils.fields.register('field_number', Blockly.FieldNumber);
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ goog.require('Blockly.utils');
|
|||||||
goog.require('Blockly.utils.aria');
|
goog.require('Blockly.utils.aria');
|
||||||
goog.require('Blockly.utils.Coordinate');
|
goog.require('Blockly.utils.Coordinate');
|
||||||
goog.require('Blockly.utils.dom');
|
goog.require('Blockly.utils.dom');
|
||||||
|
goog.require('Blockly.utils.fields');
|
||||||
goog.require('Blockly.utils.Size');
|
goog.require('Blockly.utils.Size');
|
||||||
goog.require('Blockly.utils.userAgent');
|
goog.require('Blockly.utils.userAgent');
|
||||||
|
|
||||||
@@ -448,4 +449,4 @@ Blockly.FieldTextInput.nonnegativeIntegerValidator = function(text) {
|
|||||||
return n;
|
return n;
|
||||||
};
|
};
|
||||||
|
|
||||||
Blockly.Field.register('field_input', Blockly.FieldTextInput);
|
Blockly.utils.fields.register('field_input', Blockly.FieldTextInput);
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ goog.require('Blockly.Events.BlockChange');
|
|||||||
goog.require('Blockly.FieldDropdown');
|
goog.require('Blockly.FieldDropdown');
|
||||||
goog.require('Blockly.Msg');
|
goog.require('Blockly.Msg');
|
||||||
goog.require('Blockly.utils');
|
goog.require('Blockly.utils');
|
||||||
|
goog.require('Blockly.utils.fields');
|
||||||
goog.require('Blockly.utils.Size');
|
goog.require('Blockly.utils.Size');
|
||||||
goog.require('Blockly.VariableModel');
|
goog.require('Blockly.VariableModel');
|
||||||
goog.require('Blockly.Variables');
|
goog.require('Blockly.Variables');
|
||||||
@@ -430,4 +431,4 @@ Blockly.FieldVariable.prototype.referencesVariables = function() {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
Blockly.Field.register('field_variable', Blockly.FieldVariable);
|
Blockly.utils.fields.register('field_variable', Blockly.FieldVariable);
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ Blockly.FlyoutButton.prototype.createDom = function() {
|
|||||||
this.svgGroup_);
|
this.svgGroup_);
|
||||||
svgText.textContent = Blockly.utils.replaceMessageReferences(this.text_);
|
svgText.textContent = Blockly.utils.replaceMessageReferences(this.text_);
|
||||||
|
|
||||||
this.width = Blockly.Field.getCachedWidth(svgText);
|
this.width = Blockly.utils.dom.getTextWidth(svgText);
|
||||||
this.height = 20; // Can't compute it :(
|
this.height = 20; // Can't compute it :(
|
||||||
|
|
||||||
if (!this.isLabel_) {
|
if (!this.isLabel_) {
|
||||||
|
|||||||
@@ -63,6 +63,20 @@ Blockly.utils.dom.Node = {
|
|||||||
DOCUMENT_POSITION_CONTAINED_BY: 16
|
DOCUMENT_POSITION_CONTAINED_BY: 16
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporary cache of text widths.
|
||||||
|
* @type {Object}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
Blockly.utils.dom.cacheWidths_ = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of current references to cache.
|
||||||
|
* @type {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
Blockly.utils.dom.cacheReference_ = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper method for creating SVG elements.
|
* Helper method for creating SVG elements.
|
||||||
* @param {string} name Element's tag name.
|
* @param {string} name Element's tag name.
|
||||||
@@ -197,3 +211,64 @@ Blockly.utils.dom.setCssTransform = function(element, transform) {
|
|||||||
element.style['transform'] = transform;
|
element.style['transform'] = transform;
|
||||||
element.style['-webkit-transform'] = transform;
|
element.style['-webkit-transform'] = transform;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start caching text widths. Every call to this function MUST also call
|
||||||
|
* stopTextWidthCache. Caches must not survive between execution threads.
|
||||||
|
*/
|
||||||
|
Blockly.utils.dom.startTextWidthCache = function() {
|
||||||
|
Blockly.utils.dom.cacheReference_++;
|
||||||
|
if (!Blockly.utils.dom.cacheWidths_) {
|
||||||
|
Blockly.utils.dom.cacheWidths_ = {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop caching field widths. Unless caching was already on when the
|
||||||
|
* corresponding call to startTextWidthCache was made.
|
||||||
|
*/
|
||||||
|
Blockly.utils.dom.stopTextWidthCache = function() {
|
||||||
|
Blockly.utils.dom.cacheReference_--;
|
||||||
|
if (!Blockly.utils.dom.cacheReference_) {
|
||||||
|
Blockly.utils.dom.cacheWidths_ = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the width of a text element, caching it in the process.
|
||||||
|
* @param {!Element} textElement An SVG 'text' element.
|
||||||
|
* @return {number} Width of element.
|
||||||
|
*/
|
||||||
|
Blockly.utils.dom.getTextWidth = function(textElement) {
|
||||||
|
var key = textElement.textContent + '\n' + textElement.className.baseVal;
|
||||||
|
var width;
|
||||||
|
|
||||||
|
// Return the cached width if it exists.
|
||||||
|
if (Blockly.utils.dom.cacheWidths_) {
|
||||||
|
width = Blockly.utils.dom.cacheWidths_[key];
|
||||||
|
if (width) {
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to compute fetch the width of the SVG text element.
|
||||||
|
try {
|
||||||
|
if (Blockly.utils.userAgent.IE || Blockly.utils.userAgent.EDGE) {
|
||||||
|
width = textElement.getBBox().width;
|
||||||
|
} else {
|
||||||
|
width = textElement.getComputedTextLength();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// In other cases where we fail to get the computed text. Instead, use an
|
||||||
|
// approximation and do not cache the result. At some later point in time
|
||||||
|
// when the block is inserted into the visible DOM, this method will be
|
||||||
|
// called again and, at that point in time, will not throw an exception.
|
||||||
|
return textElement.textContent.length * 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache the computed width and return.
|
||||||
|
if (Blockly.utils.dom.cacheWidths_) {
|
||||||
|
Blockly.utils.dom.cacheWidths_[key] = width;
|
||||||
|
}
|
||||||
|
return width;
|
||||||
|
};
|
||||||
|
|||||||
92
core/utils/field.js
Normal file
92
core/utils/field.js
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Visual Blocks Editor
|
||||||
|
*
|
||||||
|
* Copyright 2019 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TODO: Files in this directory seem to not be specific to Blockly, yet
|
||||||
|
// this one is. Should it be moved to the core?
|
||||||
|
/**
|
||||||
|
* @fileoverview Utility methods for handling fields.
|
||||||
|
* @author bekawestberg@gmail.com (Beka Westberg)
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name Blockly.utils
|
||||||
|
* @namespace
|
||||||
|
*/
|
||||||
|
goog.provide('Blockly.utils.fields');
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The set of all registered fields, keyed by field type as used in the JSON
|
||||||
|
* definition of a block.
|
||||||
|
* @type {!Object<string, !{fromJson: Function}>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
Blockly.utils.fields.typeMap_ = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a field type. May also override an existing field type.
|
||||||
|
* Blockly.utils.fields.fromJson uses this registry to
|
||||||
|
* find the appropriate field type.
|
||||||
|
* @param {string} type The field type name as used in the JSON definition.
|
||||||
|
* @param {!{fromJson: Function}} fieldClass The field class containing a
|
||||||
|
* fromJson function that can construct an instance of the field.
|
||||||
|
* @throws {Error} if the type name is empty, or the fieldClass is not an
|
||||||
|
* object containing a fromJson function.
|
||||||
|
*/
|
||||||
|
Blockly.utils.fields.register = function(type, fieldClass) {
|
||||||
|
if ((typeof type != 'string') || (type.trim() == '')) {
|
||||||
|
throw Error('Invalid field type "' + type + '"');
|
||||||
|
}
|
||||||
|
if (!fieldClass || (typeof fieldClass.fromJson != 'function')) {
|
||||||
|
throw Error('Field "' + fieldClass + '" must have a fromJson function');
|
||||||
|
}
|
||||||
|
Blockly.utils.fields.typeMap_[type] = fieldClass;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a Field from a JSON arg object.
|
||||||
|
* Finds the appropriate registered field by the type name as registered using
|
||||||
|
* Blockly.utils.fields.register.
|
||||||
|
* @param {!Object} options A JSON object with a type and options specific
|
||||||
|
* to the field type.
|
||||||
|
* @return {Blockly.Field} The new field instance or null if a field wasn't
|
||||||
|
* found with the given type name
|
||||||
|
* @package
|
||||||
|
*/
|
||||||
|
Blockly.utils.fields.fromJson = function(options) {
|
||||||
|
var fieldClass = Blockly.utils.fields.typeMap_[options['type']];
|
||||||
|
if (!fieldClass) {
|
||||||
|
console.warn('Blockly could not create a field of type ' + options['type'] +
|
||||||
|
'. The field is probably not being registered. This may be because the' +
|
||||||
|
' file is not loaded, the field does not register itself (See:' +
|
||||||
|
' github.com/google/blockly/issues/1584), or the registration is not' +
|
||||||
|
' being reached.');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var field = fieldClass.fromJson(options);
|
||||||
|
if (options['tooltip'] !== undefined) {
|
||||||
|
var rawValue = options['tooltip'];
|
||||||
|
var localizedText = Blockly.utils.replaceMessageReferences(rawValue);
|
||||||
|
field.setTooltip(localizedText);
|
||||||
|
}
|
||||||
|
return field;
|
||||||
|
};
|
||||||
@@ -376,7 +376,7 @@ Blockly.Xml.domToWorkspace = function(xml, workspace) {
|
|||||||
width = workspace.getWidth();
|
width = workspace.getWidth();
|
||||||
}
|
}
|
||||||
var newBlockIds = []; // A list of block IDs added by this call.
|
var newBlockIds = []; // A list of block IDs added by this call.
|
||||||
Blockly.Field.startCache();
|
Blockly.utils.dom.startTextWidthCache();
|
||||||
// Safari 7.1.3 is known to provide node lists with extra references to
|
// Safari 7.1.3 is known to provide node lists with extra references to
|
||||||
// children beyond the lists' length. Trust the length, do not use the
|
// children beyond the lists' length. Trust the length, do not use the
|
||||||
// looping pattern of checking the index for an object.
|
// looping pattern of checking the index for an object.
|
||||||
@@ -433,7 +433,7 @@ Blockly.Xml.domToWorkspace = function(xml, workspace) {
|
|||||||
if (!existingGroup) {
|
if (!existingGroup) {
|
||||||
Blockly.Events.setGroup(false);
|
Blockly.Events.setGroup(false);
|
||||||
}
|
}
|
||||||
Blockly.Field.stopCache();
|
Blockly.utils.dom.stopTextWidthCache();
|
||||||
}
|
}
|
||||||
// Re-enable workspace resizing.
|
// Re-enable workspace resizing.
|
||||||
if (workspace.setResizesEnabled) {
|
if (workspace.setResizesEnabled) {
|
||||||
|
|||||||
@@ -533,7 +533,7 @@ CustomFields.FieldTurtle.prototype.fromXml = function(fieldElement) {
|
|||||||
|
|
||||||
// Blockly needs to know the JSON name of this field. Usually this is
|
// Blockly needs to know the JSON name of this field. Usually this is
|
||||||
// registered at the bottom of the field class.
|
// registered at the bottom of the field class.
|
||||||
Blockly.Field.register('field_turtle', CustomFields.FieldTurtle);
|
Blockly.utils.fields.register('field_turtle', CustomFields.FieldTurtle);
|
||||||
|
|
||||||
// Called by initView to create all of the SVGs. This is just used to keep
|
// Called by initView to create all of the SVGs. This is just used to keep
|
||||||
// the code more organized.
|
// the code more organized.
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
suite('Abstract Fields', function() {
|
suite('Abstract Fields', function() {
|
||||||
suite.skip('Is Serializable', function() {
|
suite('Is Serializable', function() {
|
||||||
// Both EDITABLE and SERIALIZABLE are default.
|
// Both EDITABLE and SERIALIZABLE are default.
|
||||||
function FieldDefault() {
|
function FieldDefault() {
|
||||||
this.name = 'NAME';
|
this.name = 'NAME';
|
||||||
@@ -72,7 +72,7 @@ suite('Abstract Fields', function() {
|
|||||||
assertEquals(true, field.isSerializable());
|
assertEquals(true, field.isSerializable());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
suite.skip('setValue', function() {
|
suite('setValue', function() {
|
||||||
function addSpies(field) {
|
function addSpies(field) {
|
||||||
if (!this.isSpying) {
|
if (!this.isSpying) {
|
||||||
sinon.spy(field, 'doValueInvalid_');
|
sinon.spy(field, 'doValueInvalid_');
|
||||||
|
|||||||
Reference in New Issue
Block a user