diff --git a/blockly_compressed.js b/blockly_compressed.js index d5e08da8b..387bfe63a 100644 --- a/blockly_compressed.js +++ b/blockly_compressed.js @@ -772,10 +772,12 @@ Blockly.longStart_=function(a,b){Blockly.longStop_();a.changedTouches&&1!=a.chan Blockly.Touch.shouldHandleEvent=function(a){return!Blockly.Touch.isMouseOrTouchEvent(a)||Blockly.Touch.checkTouchIdentifier(a)};Blockly.Touch.getTouchIdentifierFromEvent=function(a){return void 0!=a.pointerId?a.pointerId:a.changedTouches&&a.changedTouches[0]&&void 0!==a.changedTouches[0].identifier&&null!==a.changedTouches[0].identifier?a.changedTouches[0].identifier:"mouse"}; Blockly.Touch.checkTouchIdentifier=function(a){var b=Blockly.Touch.getTouchIdentifierFromEvent(a);return void 0!==Blockly.Touch.touchIdentifier_&&null!==Blockly.Touch.touchIdentifier_?Blockly.Touch.touchIdentifier_==b:"mousedown"==a.type||"touchstart"==a.type||"pointerdown"==a.type?(Blockly.Touch.touchIdentifier_=b,!0):!1};Blockly.Touch.setClientFromTouch=function(a){if(Blockly.utils.string.startsWith(a.type,"touch")){var b=a.changedTouches[0];a.clientX=b.clientX;a.clientY=b.clientY}}; Blockly.Touch.isMouseOrTouchEvent=function(a){return Blockly.utils.string.startsWith(a.type,"touch")||Blockly.utils.string.startsWith(a.type,"mouse")||Blockly.utils.string.startsWith(a.type,"pointer")};Blockly.Touch.isTouchEvent=function(a){return Blockly.utils.string.startsWith(a.type,"touch")||Blockly.utils.string.startsWith(a.type,"pointer")}; -Blockly.Touch.splitEventByTouches=function(a){var b=[];if(a.changedTouches)for(var c=0;c"!=d.slice(-2)&&(b+=" ")}a=a.join("\n");a=a.replace(/(<(\w+)\b[^>]*>[^\n]*)\n *<\/\2>/g,"$1");return a.replace(/^\n/,"")}; Blockly.Xml.textToDom=function(a){var b=Blockly.utils.xml.textToDomDocument(a);if(!b||!b.documentElement||b.getElementsByTagName("parsererror").length)throw Error("textToDom was unable to parse: "+a);return b.documentElement};Blockly.Xml.clearWorkspaceAndLoadFromXml=function(a,b){b.setResizesEnabled(!1);b.clear();var c=Blockly.Xml.domToWorkspace(a,b);b.setResizesEnabled(!0);return c}; -Blockly.Xml.domToWorkspace=function(a,b){if(a instanceof Blockly.Workspace){var c=a;a=b;b=c;console.warn("Deprecated call to Blockly.Xml.domToWorkspace, swap the arguments.")}var d;b.RTL&&(d=b.getWidth());c=[];Blockly.Field.startCache();var e=a.childNodes.length,f=Blockly.Events.getGroup();f||Blockly.Events.setGroup(!0);b.setResizesEnabled&&b.setResizesEnabled(!1);var g=!0;try{for(var h=0;hthis.maxDisplayLength&&(a=a.substring(0,this.maxDisplayLength-2)+"\u2026");a=a.replace(/\s/g,Blockly.Field.NBSP);this.sourceBlock_.RTL&&(a+="\u200f");return a};Blockly.Field.prototype.getText=function(){return this.text_};Blockly.Field.prototype.setText=function(a){null!==a&&(a=String(a),a!==this.text_&&(this.text_=a,this.forceRerender()))}; @@ -1304,8 +1301,10 @@ Blockly.Field.prototype.forceRerender=function(){this.isDirty_=!0;this.sourceBlo Blockly.Field.prototype.setValue=function(a){if(null!==a){var b=this.doClassValidation_(a);a=this.processValidation_(a,b);if(!(a instanceof Error)){if(b=this.getValidator())if(b=b.call(this,a),a=this.processValidation_(a,b),a instanceof Error)return;b=this.getValue();b!==a&&(this.sourceBlock_&&Blockly.Events.isEnabled()&&Blockly.Events.fire(new Blockly.Events.BlockChange(this.sourceBlock_,"field",this.name,b,a)),this.doValueUpdate_(a),this.isDirty_&&this.forceRerender())}}}; Blockly.Field.prototype.processValidation_=function(a,b){if(null===b)return this.doValueInvalid_(a),this.isDirty_&&this.forceRerender(),Error();void 0!==b&&(a=b);return a};Blockly.Field.prototype.getValue=function(){return this.value_};Blockly.Field.prototype.doClassValidation_=function(a){return a=this.classValidator(a)};Blockly.Field.prototype.doValueUpdate_=function(a){this.value_=a;this.isDirty_=!0;this.text_=String(a)};Blockly.Field.prototype.doValueInvalid_=function(a){}; Blockly.Field.prototype.onMouseDown_=function(a){this.sourceBlock_&&this.sourceBlock_.workspace&&(a=this.sourceBlock_.workspace.getGesture(a))&&a.setStartField(this)};Blockly.Field.prototype.setTooltip=function(a){var b=this.getClickTarget_();b?b.tooltip=a||""===a?a:this.sourceBlock_:this.tooltip_=a};Blockly.Field.prototype.getClickTarget_=function(){return this.clickTarget_||this.getSvgRoot()};Blockly.Field.prototype.getAbsoluteXY_=function(){return Blockly.utils.style.getPageOffset(this.borderRect_)}; -Blockly.Field.prototype.referencesVariables=function(){return!1};Blockly.Field.prototype.getParentInput=function(){for(var a=null,b=this.sourceBlock_,c=b.inputList,d=0;da||a>this.fieldRow.length)throw Error("index "+a+" out of bounds.");if(!b&&!c)return a;"string"==typeof b&&(b=new Blockly.FieldLabel(b));b.setSourceBlock(this.sourceBlock_);this.sourceBlock_.rendered&&b.init();b.name=c;b.prefixField&&(a=this.insertFieldAt(a,b.prefixField));this.fieldRow.splice(a,0,b);++a;b.suffixField&&(a=this.insertFieldAt(a,b.suffixField));this.sourceBlock_.rendered&&(this.sourceBlock_.render(),this.sourceBlock_.bumpNeighbours_()); return a};Blockly.Input.prototype.removeField=function(a){for(var b=0,c;c=this.fieldRow[b];b++)if(c.name===a){c.dispose();this.fieldRow.splice(b,1);this.sourceBlock_.rendered&&(this.sourceBlock_.render(),this.sourceBlock_.bumpNeighbours_());return}throw Error('Field "%s" not found.',a);};Blockly.Input.prototype.isVisible=function(){return this.visible_}; Blockly.Input.prototype.setVisible=function(a){var b=[];if(this.visible_==a)return b;for(var c=(this.visible_=a)?"block":"none",d=0,e;e=this.fieldRow[d];d++)e.setVisible(a);this.connection&&(a?b=this.connection.unhideAll():this.connection.hideAll(),d=this.connection.targetBlock())&&(d.getSvgRoot().style.display=c,a||(d.rendered=!1));return b};Blockly.Input.prototype.setCheck=function(a){if(!this.connection)throw Error("This input does not have a connection.");this.connection.setCheck(a);return this}; @@ -1357,7 +1356,7 @@ Blockly.Block.prototype.jsonInitColour_=function(a,b){if("colour"in a)if(void 0= Blockly.Block.prototype.mixin=function(a,b){if(void 0!==b&&"boolean"!=typeof b)throw Error("opt_disableCheck must be a boolean if provided");if(!b){var c=[],d;for(d in a)void 0!==this[d]&&c.push(d);if(c.length)throw Error("Mixin will overwrite block members: "+JSON.stringify(c));}goog.mixin(this,a)}; Blockly.Block.prototype.interpolate_=function(a,b,c){var d=Blockly.utils.tokenizeInterpolation(a),e=[],f=0;a=[];for(var g=0;g=h||h>b.length)throw Error('Block "'+this.type+'": Message index %'+h+" out of range.");if(e[h])throw Error('Block "'+this.type+'": Message index %'+h+" duplicated.");e[h]=!0;f++;a.push(b[h-1])}else(h=h.trim())&&a.push(h)}if(f!=b.length)throw Error('Block "'+this.type+'": Message does not reference all '+b.length+" arg(s)."); a.length&&("string"==typeof a[a.length-1]||Blockly.utils.string.startsWith(a[a.length-1].type,"field_"))&&(g={type:"input_dummy"},c&&(g.align=c),a.push(g));c={LEFT:Blockly.ALIGN_LEFT,RIGHT:Blockly.ALIGN_RIGHT,CENTRE:Blockly.ALIGN_CENTRE};b=[];for(g=0;g=this.inputList.length)throw RangeError("Input index "+a+" out of bounds.");if(b>this.inputList.length)throw RangeError("Reference input "+b+" out of bounds.");var c=this.inputList[a];this.inputList.splice(a,1);athis.highlightOffset_&&this.steps_.push("V",a.yPos+a.height-this.highlightOffset_))}; Blockly.blockRendering.Highlighter.prototype.drawBottomRow=function(a){var b=a.yPos+a.height-a.overhangY;if(this.RTL_)this.steps_.push("V",b-this.highlightOffset_);else{var c=this.info_.bottomRow.elements[0];"square corner"===c.type?this.steps_.push(Blockly.utils.svgPaths.moveTo(a.xPos+this.highlightOffset_,b-this.highlightOffset_)):"round corner"===c.type&&(this.steps_.push(Blockly.utils.svgPaths.moveTo(a.xPos,b)),this.steps_.push(this.outsideCornerPaths_.bottomLeft()))}}; Blockly.blockRendering.Highlighter.prototype.drawLeft=function(){var a=this.info_.outputConnection;a&&(a=a.connectionOffsetY+a.height,this.RTL_?this.steps_.push(Blockly.utils.svgPaths.moveTo(this.info_.startX,a)):(this.steps_.push(Blockly.utils.svgPaths.moveTo(this.info_.startX+this.highlightOffset_,this.info_.height-this.highlightOffset_)),this.steps_.push("V",a)),this.steps_.push(this.puzzleTabPaths_.pathUp(this.RTL_)));this.RTL_||(a=this.info_.topRow,a.elements[0].isRoundedCorner()?this.steps_.push("V", this.outsideCornerPaths_.height):this.steps_.push("V",a.startY+this.highlightOffset_))}; Blockly.blockRendering.Highlighter.prototype.drawInlineInput=function(a){var b=this.highlightOffset_,c=a.xPos+a.connectionWidth,d=a.centerline-a.height/2,e=a.width-a.connectionWidth,f=d+b;this.RTL_?(d=a.connectionOffsetY-b,a=a.height-(a.connectionOffsetY+a.connectionHeight)+b,b=Blockly.utils.svgPaths.moveTo(c-b,f)+Blockly.utils.svgPaths.lineOnAxis("v",d)+this.puzzleTabPaths_.pathDown(this.RTL_)+Blockly.utils.svgPaths.lineOnAxis("v",a)+Blockly.utils.svgPaths.lineOnAxis("h",e)):b=Blockly.utils.svgPaths.moveTo(a.xPos+ -a.width+b,f)+Blockly.utils.svgPaths.lineOnAxis("v",a.height)+Blockly.utils.svgPaths.lineOnAxis("h",-e)+Blockly.utils.svgPaths.moveTo(c,d+a.connectionOffsetY)+this.puzzleTabPaths_.pathDown(this.RTL_);this.inlineSteps_.push(b)};Blockly.blockRendering.Debug=function(){this.debugElements_=[];this.svgRoot_=null};Blockly.blockRendering.Debug.prototype.clearElems=function(){for(var a=0,b;b=this.debugElements_[a];a++)Blockly.utils.dom.removeNode(b);this.debugElements_=[]};Blockly.blockRendering.Debug.prototype.drawSpacerRow=function(a,b,c){this.debugElements_.push(Blockly.utils.dom.createSvgElement("rect",{"class":"rowSpacerRect blockRenderDebug",x:c?-(a.xPos+a.width):a.xPos,y:b,width:a.width,height:a.height},this.svgRoot_))}; -Blockly.blockRendering.Debug.prototype.drawSpacerElem=function(a,b,c){var d=a.xPos;c&&(d=-(d+a.width));b=Math.min(a.height,b);this.debugElements_.push(Blockly.utils.dom.createSvgElement("rect",{"class":"elemSpacerRect blockRenderDebug",x:d,y:a.centerline-b/2,width:a.width,height:b},this.svgRoot_))}; -Blockly.blockRendering.Debug.prototype.drawRenderedElem=function(a,b){var c=a.xPos;b&&(c=-(c+a.width));this.debugElements_.push(Blockly.utils.dom.createSvgElement("rect",{"class":"rowRenderingRect blockRenderDebug",x:c,y:a.centerline-a.height/2,width:a.width,height:a.height},this.svgRoot_));a.isInput&&this.drawConnection(a.connection)}; -Blockly.blockRendering.Debug.prototype.drawConnection=function(a){if(a.type==Blockly.INPUT_VALUE){var b=4;var c="magenta";var d="none"}else a.type==Blockly.OUTPUT_VALUE?(b=2,d=c="magenta"):a.type==Blockly.NEXT_STATEMENT?(b=4,c="goldenrod",d="none"):a.type==Blockly.PREVIOUS_STATEMENT&&(b=2,d=c="goldenrod");this.debugElements_.push(Blockly.utils.dom.createSvgElement("circle",{"class":"blockRenderDebug",cx:a.offsetInBlock_.x,cy:a.offsetInBlock_.y,r:b,fill:d,stroke:c},this.svgRoot_))}; -Blockly.blockRendering.Debug.prototype.drawRenderedRow=function(a,b,c){this.debugElements_.push(Blockly.utils.dom.createSvgElement("rect",{"class":"elemRenderingRect blockRenderDebug",x:c?-(a.xPos+a.width):a.xPos,y:b,width:a.width,height:a.height},this.svgRoot_))};Blockly.blockRendering.Debug.prototype.drawRowWithElements=function(a,b,c){for(var d=0;da&&(a+=360);a>Blockly.FieldAngle.WRAP&&(a-=360);return a};Blockly.Field.register("field_angle",Blockly.FieldAngle);Blockly.FieldCheckbox=function(a,b){a=this.doClassValidation_(a);null===a&&(a="FALSE");Blockly.FieldCheckbox.superClass_.constructor.call(this,a,b);this.size_.width=Blockly.FieldCheckbox.WIDTH};goog.inherits(Blockly.FieldCheckbox,Blockly.Field);Blockly.FieldCheckbox.fromJson=function(a){return new Blockly.FieldCheckbox(a.checked)};Blockly.FieldCheckbox.WIDTH=15;Blockly.FieldCheckbox.CHECK_CHAR="\u2713";Blockly.FieldCheckbox.CHECK_X_OFFSET=Blockly.Field.DEFAULT_TEXT_OFFSET-3; +Blockly.FieldAngle.prototype.doClassValidation_=function(a){a=Number(a)%360;if(isNaN(a))return null;0>a&&(a+=360);a>Blockly.FieldAngle.WRAP&&(a-=360);return a};Blockly.fieldRegistry.register("field_angle",Blockly.FieldAngle);Blockly.FieldCheckbox=function(a,b){a=this.doClassValidation_(a);null===a&&(a="FALSE");Blockly.FieldCheckbox.superClass_.constructor.call(this,a,b);this.size_.width=Blockly.FieldCheckbox.WIDTH};goog.inherits(Blockly.FieldCheckbox,Blockly.Field);Blockly.FieldCheckbox.fromJson=function(a){return new Blockly.FieldCheckbox(a.checked)};Blockly.FieldCheckbox.WIDTH=15;Blockly.FieldCheckbox.CHECK_CHAR="\u2713";Blockly.FieldCheckbox.CHECK_X_OFFSET=Blockly.Field.DEFAULT_TEXT_OFFSET-3; Blockly.FieldCheckbox.CHECK_Y_OFFSET=14;Blockly.FieldCheckbox.prototype.SERIALIZABLE=!0;Blockly.FieldCheckbox.prototype.CURSOR="default";Blockly.FieldCheckbox.prototype.isDirty_=!1; Blockly.FieldCheckbox.prototype.initView=function(){Blockly.FieldCheckbox.superClass_.initView.call(this);this.textElement_.setAttribute("x",Blockly.FieldCheckbox.CHECK_X_OFFSET);this.textElement_.setAttribute("y",Blockly.FieldCheckbox.CHECK_Y_OFFSET);Blockly.utils.dom.addClass(this.textElement_,"blocklyCheckbox");var a=document.createTextNode(Blockly.FieldCheckbox.CHECK_CHAR);this.textElement_.appendChild(a);this.textElement_.style.display=this.value_?"block":"none"}; Blockly.FieldCheckbox.prototype.showEditor_=function(){this.setValue(!this.value_)};Blockly.FieldCheckbox.prototype.doClassValidation_=function(a){return!0===a||"TRUE"===a?"TRUE":!1===a||"FALSE"===a?"FALSE":null};Blockly.FieldCheckbox.prototype.doValueUpdate_=function(a){this.value_=this.convertValueToBool_(a);this.textElement_&&(this.textElement_.style.display=this.value_?"block":"none")};Blockly.FieldCheckbox.prototype.getValue=function(){return this.value_?"TRUE":"FALSE"}; -Blockly.FieldCheckbox.prototype.getValueBoolean=function(){return this.value_};Blockly.FieldCheckbox.prototype.getText=function(){return String(this.convertValueToBool_(this.value_))};Blockly.FieldCheckbox.prototype.convertValueToBool_=function(a){return"string"==typeof a?"TRUE"==a:!!a};Blockly.Field.register("field_checkbox",Blockly.FieldCheckbox);Blockly.utils.colour={};Blockly.utils.colour.parse=function(a){a=String(a).toLowerCase().trim();var b=Blockly.utils.colour.names[a];if(b)return b;b="#"==a[0]?a:"#"+a;if(/^#[0-9a-f]{6}$/.test(b))return b;if(/^#[0-9a-f]{3}$/.test(b))return["#",b[1],b[1],b[2],b[2],b[3],b[3]].join("");var c=a.match(/^(?:rgb)?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/);return c&&(a=Number(c[1]),b=Number(c[2]),c=Number(c[3]),0<=a&&256>a&&0<=b&&256>b&&0<=c&&256>c)?Blockly.utils.colour.rgbToHex(a,b,c):null}; +Blockly.FieldCheckbox.prototype.getValueBoolean=function(){return this.value_};Blockly.FieldCheckbox.prototype.getText=function(){return String(this.convertValueToBool_(this.value_))};Blockly.FieldCheckbox.prototype.convertValueToBool_=function(a){return"string"==typeof a?"TRUE"==a:!!a};Blockly.fieldRegistry.register("field_checkbox",Blockly.FieldCheckbox);Blockly.utils.colour={};Blockly.utils.colour.parse=function(a){a=String(a).toLowerCase().trim();var b=Blockly.utils.colour.names[a];if(b)return b;b="#"==a[0]?a:"#"+a;if(/^#[0-9a-f]{6}$/.test(b))return b;if(/^#[0-9a-f]{3}$/.test(b))return["#",b[1],b[1],b[2],b[2],b[3],b[3]].join("");var c=a.match(/^(?:rgb)?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/);return c&&(a=Number(c[1]),b=Number(c[2]),c=Number(c[3]),0<=a&&256>a&&0<=b&&256>b&&0<=c&&256>c)?Blockly.utils.colour.rgbToHex(a,b,c):null}; Blockly.utils.colour.rgbToHex=function(a,b,c){b=a<<16|b<<8|c;return 16>a?"#"+(16777216|b).toString(16).substr(1):"#"+b.toString(16)};Blockly.utils.colour.hexToRgb=function(a){a=parseInt(a.substr(1),16);return[a>>16,a>>8&255,a&255]}; Blockly.utils.colour.hsvToHex=function(a,b,c){var d=0,e=0,f=0;if(0==b)f=e=d=c;else{var g=Math.floor(a/60),h=a/60-g;a=c*(1-b);var k=c*(1-b*h);b=c*(1-b*(1-h));switch(g){case 1:d=k;e=c;f=a;break;case 2:d=a;e=c;f=b;break;case 3:d=a;e=k;f=c;break;case 4:d=b;e=a;f=c;break;case 5:d=c;e=a;f=k;break;case 6:case 0:d=c,e=b,f=a}}return Blockly.utils.colour.rgbToHex(Math.floor(d),Math.floor(e),Math.floor(f))}; Blockly.utils.colour.blend=function(a,b,c){a=Blockly.utils.colour.hexToRgb(Blockly.utils.colour.parse(a));b=Blockly.utils.colour.hexToRgb(Blockly.utils.colour.parse(b));return Blockly.utils.colour.rgbToHex(Math.round(b[0]+c*(a[0]-b[0])),Math.round(b[1]+c*(a[1]-b[1])),Math.round(b[2]+c*(a[2]-b[2])))}; @@ -1589,7 +1598,7 @@ Blockly.FieldColour.prototype.getText=function(){var a=this.value_;/^#(.)\1(.)\2 Blockly.FieldColour.TITLES=[];Blockly.FieldColour.COLUMNS=7;Blockly.FieldColour.prototype.setColours=function(a,b){this.colours_=a;b&&(this.titles_=b);return this};Blockly.FieldColour.prototype.setColumns=function(a){this.columns_=a;return this}; Blockly.FieldColour.prototype.showEditor_=function(){var a=this.dropdownCreate_();Blockly.DropDownDiv.getContentDiv().appendChild(a);Blockly.DropDownDiv.setColour(this.DROPDOWN_BACKGROUND_COLOUR,this.DROPDOWN_BORDER_COLOUR);Blockly.DropDownDiv.showPositionedByField(this,this.dropdownDispose_.bind(this))};Blockly.FieldColour.prototype.onClick_=function(a){(a=a.target)&&!a.label&&(a=a.parentNode);a=a&&a.label;null!==a&&(this.setValue(a),Blockly.DropDownDiv.hideIfOwner(this))}; Blockly.FieldColour.prototype.dropdownCreate_=function(){var a=this.columns_||Blockly.FieldColour.COLUMNS,b=this.colours_||Blockly.FieldColour.COLOURS,c=this.titles_||Blockly.FieldColour.TITLES,d=this.getValue(),e=document.createElement("table");e.className="blocklyColourTable";for(var f,g=0;g=c||0>=b)throw Error("Height and width values of an image field must be greater than 0.");this.imageHeight_=c;this.size_=new Blockly.utils.Size(b,c+Blockly.FieldImage.Y_PADDING);this.flipRtl_=f;this.text_=d||"";this.setValue(a||"");"function"==typeof e&& +d[0]+" in: ",d)):(b=!0,console.error("Invalid option["+c+"]: Each FieldDropdown option must be an array. Found: ",d))}if(b)throw TypeError("Found invalid FieldDropdown options.");};Blockly.fieldRegistry.register("field_dropdown",Blockly.FieldDropdown);Blockly.FieldLabelSerializable=function(a,b){Blockly.FieldLabelSerializable.superClass_.constructor.call(this,a,b)};goog.inherits(Blockly.FieldLabelSerializable,Blockly.FieldLabel);Blockly.FieldLabelSerializable.fromJson=function(a){var b=Blockly.utils.replaceMessageReferences(a.text);return new Blockly.FieldLabelSerializable(b,a["class"])};Blockly.FieldLabelSerializable.prototype.EDITABLE=!1;Blockly.FieldLabelSerializable.prototype.SERIALIZABLE=!0; +Blockly.fieldRegistry.register("field_label_serializable",Blockly.FieldLabelSerializable);Blockly.FieldImage=function(a,b,c,d,e,f){this.sourceBlock_=null;if(!a)throw Error("Src value of an image field is required");if(isNaN(c)||isNaN(b))throw Error("Height and width values of an image field must cast to numbers.");c=Number(c);b=Number(b);if(0>=c||0>=b)throw Error("Height and width values of an image field must be greater than 0.");this.imageHeight_=c;this.size_=new Blockly.utils.Size(b,c+Blockly.FieldImage.Y_PADDING);this.flipRtl_=f;this.text_=d||"";this.setValue(a||"");"function"==typeof e&& (this.clickHandler_=e)};goog.inherits(Blockly.FieldImage,Blockly.Field);Blockly.FieldImage.fromJson=function(a){var b=Blockly.utils.replaceMessageReferences(a.src),c=Number(Blockly.utils.replaceMessageReferences(a.width)),d=Number(Blockly.utils.replaceMessageReferences(a.height)),e=Blockly.utils.replaceMessageReferences(a.alt);return new Blockly.FieldImage(b,c,d,e,null,!!a.flipRtl)};Blockly.FieldImage.Y_PADDING=1;Blockly.FieldImage.prototype.EDITABLE=!1;Blockly.FieldImage.prototype.isDirty_=!1; Blockly.FieldImage.prototype.initView=function(){this.imageElement_=Blockly.utils.dom.createSvgElement("image",{height:this.imageHeight_+"px",width:this.size_.width+"px",alt:this.text_},this.fieldGroup_);this.imageElement_.setAttributeNS(Blockly.utils.dom.XLINK_NS,"xlink:href",this.value_)};Blockly.FieldImage.prototype.doClassValidation_=function(a){return"string"!=typeof a?null:a}; Blockly.FieldImage.prototype.doValueUpdate_=function(a){this.value_=a;this.imageElement_&&this.imageElement_.setAttributeNS(Blockly.utils.dom.XLINK_NS,"xlink:href",this.value_||"")};Blockly.FieldImage.prototype.getFlipRtl=function(){return this.flipRtl_};Blockly.FieldImage.prototype.setText=function(a){this.setAlt(a)};Blockly.FieldImage.prototype.setAlt=function(a){null!==a&&(this.text_=a,this.imageElement_&&this.imageElement_.setAttribute("alt",a||""))}; -Blockly.FieldImage.prototype.showEditor_=function(){this.clickHandler_&&this.clickHandler_(this)};Blockly.FieldImage.prototype.setOnClickHandler=function(a){this.clickHandler_=a};Blockly.Field.register("field_image",Blockly.FieldImage);Blockly.FieldNumber=function(a,b,c,d,e){this.setConstraints(b,c,d);a=this.doClassValidation_(a);null===a&&(a=0);Blockly.FieldNumber.superClass_.constructor.call(this,a,e)};goog.inherits(Blockly.FieldNumber,Blockly.FieldTextInput);Blockly.FieldNumber.fromJson=function(a){return new Blockly.FieldNumber(a.value,a.min,a.max,a.precision)};Blockly.FieldNumber.prototype.SERIALIZABLE=!0; +Blockly.FieldImage.prototype.showEditor_=function(){this.clickHandler_&&this.clickHandler_(this)};Blockly.FieldImage.prototype.setOnClickHandler=function(a){this.clickHandler_=a};Blockly.fieldRegistry.register("field_image",Blockly.FieldImage);Blockly.FieldNumber=function(a,b,c,d,e){this.setConstraints(b,c,d);a=this.doClassValidation_(a);null===a&&(a=0);Blockly.FieldNumber.superClass_.constructor.call(this,a,e)};goog.inherits(Blockly.FieldNumber,Blockly.FieldTextInput);Blockly.FieldNumber.fromJson=function(a){return new Blockly.FieldNumber(a.value,a.min,a.max,a.precision)};Blockly.FieldNumber.prototype.SERIALIZABLE=!0; Blockly.FieldNumber.prototype.setConstraints=function(a,b,c){c=Number(c);this.precision_=isNaN(c)?0:c;c=this.precision_.toString();var d=c.indexOf(".");this.fractionalDigits_=-1==d?-1:c.length-(d+1);a=Number(a);this.min_=isNaN(a)?-Infinity:a;b=Number(b);this.max_=isNaN(b)?Infinity:b;this.setValue(this.getValue())}; -Blockly.FieldNumber.prototype.doClassValidation_=function(a){if(null===a||void 0===a)return null;a=String(a);a=a.replace(/O/ig,"0");a=a.replace(/,/g,"");a=Number(a||0);if(isNaN(a))return null;a=Math.min(Math.max(a,this.min_),this.max_);this.precision_&&isFinite(a)&&(a=Math.round(a/this.precision_)*this.precision_);return a=-1==this.fractionalDigits_?a:Number(a.toFixed(this.fractionalDigits_))};Blockly.Field.register("field_number",Blockly.FieldNumber);Blockly.FieldVariable=function(a,b,c,d){this.menuGenerator_=Blockly.FieldVariable.dropdownCreate;this.size_=new Blockly.utils.Size(0,Blockly.BlockSvg.MIN_BLOCK_Y);b&&this.setValidator(b);this.defaultVariableName=a||"";this.setTypes_(c,d);this.value_=null};goog.inherits(Blockly.FieldVariable,Blockly.FieldDropdown);Blockly.FieldVariable.fromJson=function(a){var b=Blockly.utils.replaceMessageReferences(a.variable);return new Blockly.FieldVariable(b,null,a.variableTypes,a.defaultType)}; +Blockly.FieldNumber.prototype.doClassValidation_=function(a){if(null===a||void 0===a)return null;a=String(a);a=a.replace(/O/ig,"0");a=a.replace(/,/g,"");a=Number(a||0);if(isNaN(a))return null;a=Math.min(Math.max(a,this.min_),this.max_);this.precision_&&isFinite(a)&&(a=Math.round(a/this.precision_)*this.precision_);return a=-1==this.fractionalDigits_?a:Number(a.toFixed(this.fractionalDigits_))};Blockly.fieldRegistry.register("field_number",Blockly.FieldNumber);Blockly.FieldVariable=function(a,b,c,d){this.menuGenerator_=Blockly.FieldVariable.dropdownCreate;this.size_=new Blockly.utils.Size(0,Blockly.BlockSvg.MIN_BLOCK_Y);b&&this.setValidator(b);this.defaultVariableName=a||"";this.setTypes_(c,d);this.value_=null};goog.inherits(Blockly.FieldVariable,Blockly.FieldDropdown);Blockly.FieldVariable.fromJson=function(a){var b=Blockly.utils.replaceMessageReferences(a.variable);return new Blockly.FieldVariable(b,null,a.variableTypes,a.defaultType)}; Blockly.FieldVariable.prototype.workspace_=null;Blockly.FieldVariable.prototype.SERIALIZABLE=!0;Blockly.FieldVariable.prototype.initModel=function(){if(!this.variable_){var a=Blockly.Variables.getOrCreateVariablePackage(this.workspace_,null,this.defaultVariableName,this.defaultType_);Blockly.Events.disable();this.setValue(a.getId());Blockly.Events.enable()}}; Blockly.FieldVariable.prototype.fromXml=function(a){var b=a.getAttribute("id"),c=a.textContent,d=a.getAttribute("variabletype")||a.getAttribute("variableType")||"";b=Blockly.Variables.getOrCreateVariablePackage(this.workspace_,b,c,d);if(null!=d&&d!==b.type)throw Error("Serialized variable type with id '"+b.getId()+"' had type "+b.type+", and does not match variable field that references it: "+Blockly.Xml.domToText(a)+".");this.setValue(b.getId())}; Blockly.FieldVariable.prototype.toXml=function(a){this.initModel();a.id=this.variable_.getId();a.textContent=this.variable_.name;this.variable_.type&&a.setAttribute("variabletype",this.variable_.type);return a};Blockly.FieldVariable.prototype.setSourceBlock=function(a){if(a.isShadow())throw Error("Variable fields are not allowed to exist on shadow blocks.");Blockly.FieldVariable.superClass_.setSourceBlock.call(this,a);this.workspace_=a.workspace}; @@ -1625,7 +1634,7 @@ Blockly.FieldVariable.prototype.getVariableTypes_=function(){var a=this.variable Blockly.FieldVariable.prototype.setTypes_=function(a,b){var c=b||"";if(null==a||void 0==a)var d=null;else if(Array.isArray(a)){d=a;for(var e=!1,f=0;f} - * @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. * @type {number} @@ -588,14 +523,14 @@ Blockly.Field.prototype.render_ = function() { /** * Updates the width of the field. Redirects to updateSize_(). * @deprecated May 2019 Use Blockly.Field.updateSize_() to force an update - * to the size of the field, or Blockly.Field.getCachedWidth() to check the - * size of the field.. + * to the size of the field, or Blockly.utils.dom.getTextWidth() to + * check the size of the field. */ Blockly.Field.prototype.updateWidth = function() { console.warn('Deprecated call to updateWidth, call' + ' Blockly.Field.updateSize_ to force an update to the size of the' + - ' field, or Blockly.Field.getCachedWidth() to check the size of the' + - ' field.'); + ' field, or Blockly.utils.dom.getTextWidth() to check the size' + + ' of the field.'); this.updateSize_(); }; @@ -604,7 +539,7 @@ Blockly.Field.prototype.updateWidth = function() { * @protected */ Blockly.Field.prototype.updateSize_ = function() { - var textWidth = Blockly.Field.getCachedWidth(this.textElement_); + var textWidth = Blockly.utils.dom.getTextWidth(this.textElement_); var totalWidth = textWidth; if (this.borderRect_) { totalWidth += Blockly.Field.X_PADDING; @@ -613,67 +548,6 @@ Blockly.Field.prototype.updateSize_ = function() { 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. * diff --git a/core/field_angle.js b/core/field_angle.js index f4871ba7b..f1d54af76 100644 --- a/core/field_angle.js +++ b/core/field_angle.js @@ -27,6 +27,7 @@ goog.provide('Blockly.FieldAngle'); goog.require('Blockly.DropDownDiv'); +goog.require('Blockly.fieldRegistry'); goog.require('Blockly.FieldTextInput'); goog.require('Blockly.utils.dom'); goog.require('Blockly.utils.math'); @@ -353,4 +354,4 @@ Blockly.FieldAngle.prototype.doClassValidation_ = function(opt_newValue) { return n; }; -Blockly.Field.register('field_angle', Blockly.FieldAngle); +Blockly.fieldRegistry.register('field_angle', Blockly.FieldAngle); diff --git a/core/field_checkbox.js b/core/field_checkbox.js index f75f0a800..7f4ec4caf 100644 --- a/core/field_checkbox.js +++ b/core/field_checkbox.js @@ -29,6 +29,7 @@ goog.provide('Blockly.FieldCheckbox'); goog.require('Blockly.Events'); goog.require('Blockly.Events.BlockChange'); goog.require('Blockly.Field'); +goog.require('Blockly.fieldRegistry'); goog.require('Blockly.utils.dom'); goog.require('Blockly.utils.Size'); @@ -210,4 +211,4 @@ Blockly.FieldCheckbox.prototype.convertValueToBool_ = function(value) { } }; -Blockly.Field.register('field_checkbox', Blockly.FieldCheckbox); +Blockly.fieldRegistry.register('field_checkbox', Blockly.FieldCheckbox); diff --git a/core/field_colour.js b/core/field_colour.js index 2b5c529d2..059fcb756 100644 --- a/core/field_colour.js +++ b/core/field_colour.js @@ -30,6 +30,7 @@ goog.require('Blockly.DropDownDiv'); goog.require('Blockly.Events'); goog.require('Blockly.Events.BlockChange'); goog.require('Blockly.Field'); +goog.require('Blockly.fieldRegistry'); goog.require('Blockly.utils.colour'); goog.require('Blockly.utils.Size'); @@ -346,4 +347,4 @@ Blockly.FieldColour.prototype.dropdownDispose_ = function() { Blockly.unbindEvent_(this.onUpWrapper_); }; -Blockly.Field.register('field_colour', Blockly.FieldColour); +Blockly.fieldRegistry.register('field_colour', Blockly.FieldColour); diff --git a/core/field_date.js b/core/field_date.js index d742b30f4..3ab5d0a30 100644 --- a/core/field_date.js +++ b/core/field_date.js @@ -28,6 +28,7 @@ goog.provide('Blockly.FieldDate'); goog.require('Blockly.Events'); goog.require('Blockly.Field'); +goog.require('Blockly.fieldRegistry'); goog.require('Blockly.utils.dom'); goog.require('Blockly.utils.string'); @@ -323,4 +324,4 @@ Blockly.FieldDate.CSS = [ '}' ]; -Blockly.Field.register('field_date', Blockly.FieldDate); +Blockly.fieldRegistry.register('field_date', Blockly.FieldDate); diff --git a/core/field_dropdown.js b/core/field_dropdown.js index 210e424ca..70c00bd08 100644 --- a/core/field_dropdown.js +++ b/core/field_dropdown.js @@ -31,6 +31,7 @@ goog.provide('Blockly.FieldDropdown'); goog.require('Blockly.Events'); goog.require('Blockly.Events.BlockChange'); goog.require('Blockly.Field'); +goog.require('Blockly.fieldRegistry'); goog.require('Blockly.utils'); goog.require('Blockly.utils.dom'); goog.require('Blockly.utils.Size'); @@ -492,7 +493,7 @@ Blockly.FieldDropdown.prototype.renderSelectedImage_ = function() { this.imageElement_.setAttribute('height', this.imageJson_.height); 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 imageWidth = Number(this.imageJson_.width); @@ -524,8 +525,8 @@ Blockly.FieldDropdown.prototype.renderSelectedText_ = function() { this.textElement_.setAttribute('x', Blockly.Field.DEFAULT_TEXT_OFFSET); // Height and width include the border rect. this.size_.height = Blockly.Field.BORDER_RECT_DEFAULT_HEIGHT; - this.size_.width = - Blockly.Field.getCachedWidth(this.textElement_) + Blockly.Field.X_PADDING; + this.size_.width = Blockly.utils.dom.getTextWidth(this.textElement_) + + Blockly.Field.X_PADDING; }; /** @@ -565,4 +566,4 @@ Blockly.FieldDropdown.validateOptions_ = function(options) { } }; -Blockly.Field.register('field_dropdown', Blockly.FieldDropdown); +Blockly.fieldRegistry.register('field_dropdown', Blockly.FieldDropdown); diff --git a/core/field_image.js b/core/field_image.js index b0a13ec0c..22685f560 100644 --- a/core/field_image.js +++ b/core/field_image.js @@ -27,7 +27,7 @@ goog.provide('Blockly.FieldImage'); goog.require('Blockly.Field'); -goog.require('Blockly.Tooltip'); +goog.require('Blockly.fieldRegistry'); goog.require('Blockly.utils'); goog.require('Blockly.utils.dom'); goog.require('Blockly.utils.Size'); @@ -222,4 +222,4 @@ Blockly.FieldImage.prototype.setOnClickHandler = function(func) { this.clickHandler_ = func; }; -Blockly.Field.register('field_image', Blockly.FieldImage); +Blockly.fieldRegistry.register('field_image', Blockly.FieldImage); diff --git a/core/field_label.js b/core/field_label.js index abfe0b438..c89b574d0 100644 --- a/core/field_label.js +++ b/core/field_label.js @@ -28,7 +28,7 @@ goog.provide('Blockly.FieldLabel'); goog.require('Blockly.Field'); -goog.require('Blockly.Tooltip'); +goog.require('Blockly.fieldRegistry'); goog.require('Blockly.utils'); goog.require('Blockly.utils.dom'); goog.require('Blockly.utils.Size'); @@ -100,4 +100,4 @@ Blockly.FieldLabel.prototype.doClassValidation_ = function(opt_newValue) { return String(opt_newValue); }; -Blockly.Field.register('field_label', Blockly.FieldLabel); +Blockly.fieldRegistry.register('field_label', Blockly.FieldLabel); diff --git a/core/field_label_serializable.js b/core/field_label_serializable.js index 833a4c61d..f6fa0ee33 100644 --- a/core/field_label_serializable.js +++ b/core/field_label_serializable.js @@ -28,6 +28,7 @@ goog.provide('Blockly.FieldLabelSerializable'); goog.require('Blockly.FieldLabel'); +goog.require('Blockly.fieldRegistry'); goog.require('Blockly.utils'); @@ -75,5 +76,5 @@ Blockly.FieldLabelSerializable.prototype.EDITABLE = false; */ Blockly.FieldLabelSerializable.prototype.SERIALIZABLE = true; -Blockly.Field.register( +Blockly.fieldRegistry.register( 'field_label_serializable', Blockly.FieldLabelSerializable); diff --git a/core/field_number.js b/core/field_number.js index d430145c8..496ee66f6 100644 --- a/core/field_number.js +++ b/core/field_number.js @@ -26,6 +26,7 @@ goog.provide('Blockly.FieldNumber'); +goog.require('Blockly.fieldRegistry'); goog.require('Blockly.FieldTextInput'); @@ -138,4 +139,4 @@ Blockly.FieldNumber.prototype.doClassValidation_ = function(opt_newValue) { return n; }; -Blockly.Field.register('field_number', Blockly.FieldNumber); +Blockly.fieldRegistry.register('field_number', Blockly.FieldNumber); diff --git a/core/field_registry.js b/core/field_registry.js new file mode 100644 index 000000000..114b89f9f --- /dev/null +++ b/core/field_registry.js @@ -0,0 +1,93 @@ +/** + * @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. + */ + +/** + * @fileoverview Fields can be created based on a JSON definition. This file + * contains methods for registering those JSON definitions, and building the + * fields based on JSON. + * @author bekawestberg@gmail.com (Beka Westberg) + */ +'use strict'; + +goog.provide('Blockly.fieldRegistry'); + +goog.require('Blockly.utils'); + + +/** + * The set of all registered fields, keyed by field type as used in the JSON + * definition of a block. + * @type {!Object} + * @private + */ +Blockly.fieldRegistry.typeMap_ = {}; + +/** + * Registers a field type. May also override an existing field type. + * Blockly.fieldRegistry.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.fieldRegistry.register = function(type, fieldClass) { + if ((typeof type != 'string') || (type.trim() == '')) { + throw Error('Invalid field type "' + type + '". The type must be a' + + ' non-empty string.'); + } + if (!fieldClass || (typeof fieldClass.fromJson != 'function')) { + throw Error('Field "' + fieldClass + '" must have a fromJson function'); + } + type = type.toLowerCase(); + Blockly.fieldRegistry.typeMap_[type] = fieldClass; +}; + +/** + * Construct a Field from a JSON arg object. + * Finds the appropriate registered field by the type name as registered using + * Blockly.fieldRegistry.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.fieldRegistry.fromJson = function(options) { + var type = options['type'].toLowerCase(); + var fieldClass = Blockly.fieldRegistry.typeMap_[type]; + if (!fieldClass) { + console.warn('Blockly could not create a field of type ' + options['type'] + + '. The field is probably not being registered. This could 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; +}; diff --git a/core/field_textinput.js b/core/field_textinput.js index 92dcc31a0..a8f9b6f3b 100644 --- a/core/field_textinput.js +++ b/core/field_textinput.js @@ -29,6 +29,7 @@ goog.provide('Blockly.FieldTextInput'); goog.require('Blockly.Events'); goog.require('Blockly.Events.BlockChange'); goog.require('Blockly.Field'); +goog.require('Blockly.fieldRegistry'); goog.require('Blockly.Msg'); goog.require('Blockly.utils'); goog.require('Blockly.utils.aria'); @@ -448,4 +449,4 @@ Blockly.FieldTextInput.nonnegativeIntegerValidator = function(text) { return n; }; -Blockly.Field.register('field_input', Blockly.FieldTextInput); +Blockly.fieldRegistry.register('field_input', Blockly.FieldTextInput); diff --git a/core/field_variable.js b/core/field_variable.js index 209d9be57..853460e25 100644 --- a/core/field_variable.js +++ b/core/field_variable.js @@ -29,6 +29,7 @@ goog.provide('Blockly.FieldVariable'); goog.require('Blockly.Events'); goog.require('Blockly.Events.BlockChange'); goog.require('Blockly.FieldDropdown'); +goog.require('Blockly.fieldRegistry'); goog.require('Blockly.Msg'); goog.require('Blockly.utils'); goog.require('Blockly.utils.Size'); @@ -430,4 +431,4 @@ Blockly.FieldVariable.prototype.referencesVariables = function() { return true; }; -Blockly.Field.register('field_variable', Blockly.FieldVariable); +Blockly.fieldRegistry.register('field_variable', Blockly.FieldVariable); diff --git a/core/flyout_button.js b/core/flyout_button.js index bf4ae27ec..973e3a320 100644 --- a/core/flyout_button.js +++ b/core/flyout_button.js @@ -156,7 +156,7 @@ Blockly.FlyoutButton.prototype.createDom = function() { this.svgGroup_); 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 :( if (!this.isLabel_) { diff --git a/core/utils/dom.js b/core/utils/dom.js index 9897fb5da..2a52e331b 100644 --- a/core/utils/dom.js +++ b/core/utils/dom.js @@ -32,6 +32,8 @@ */ goog.provide('Blockly.utils.dom'); +goog.require('Blockly.utils.userAgent'); + /** * Required name space for SVG elements. @@ -63,6 +65,20 @@ Blockly.utils.dom.Node = { 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. * @param {string} name Element's tag name. @@ -197,3 +213,64 @@ Blockly.utils.dom.setCssTransform = function(element, transform) { element.style['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; +}; diff --git a/core/xml.js b/core/xml.js index 148ca3fd2..bc0e1fc0b 100644 --- a/core/xml.js +++ b/core/xml.js @@ -376,7 +376,7 @@ Blockly.Xml.domToWorkspace = function(xml, workspace) { width = workspace.getWidth(); } 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 // children beyond the lists' length. Trust the length, do not use the // looping pattern of checking the index for an object. @@ -433,7 +433,7 @@ Blockly.Xml.domToWorkspace = function(xml, workspace) { if (!existingGroup) { Blockly.Events.setGroup(false); } - Blockly.Field.stopCache(); + Blockly.utils.dom.stopTextWidthCache(); } // Re-enable workspace resizing. if (workspace.setResizesEnabled) { diff --git a/demos/custom-fields/field_turtle.js b/demos/custom-fields/field_turtle.js index da49d9a1a..f31989dff 100644 --- a/demos/custom-fields/field_turtle.js +++ b/demos/custom-fields/field_turtle.js @@ -29,6 +29,7 @@ goog.provide('CustomFields.FieldTurtle'); // You must require the abstract field class to inherit from. goog.require('Blockly.Field'); +goog.require('Blockly.fieldRegistry'); goog.require('Blockly.utils'); goog.require('Blockly.utils.dom'); goog.require('Blockly.utils.Size'); @@ -533,7 +534,7 @@ CustomFields.FieldTurtle.prototype.fromXml = function(fieldElement) { // Blockly needs to know the JSON name of this field. Usually this is // registered at the bottom of the field class. -Blockly.Field.register('field_turtle', CustomFields.FieldTurtle); +Blockly.fieldRegistry.register('field_turtle', CustomFields.FieldTurtle); // Called by initView to create all of the SVGs. This is just used to keep // the code more organized. @@ -742,4 +743,4 @@ CustomFields.FieldTurtle.prototype.createView_ = function() { 'stroke': '#000', 'stroke-opacity': .3 }, this.stripesPattern_); -} +}; diff --git a/tests/jsunit/field_test.js b/tests/jsunit/field_test.js deleted file mode 100644 index 2da04834d..000000000 --- a/tests/jsunit/field_test.js +++ /dev/null @@ -1,126 +0,0 @@ -/** - * @license - * Visual Blocks Editor - * - * Copyright 2017 Google Inc. - * https://developers.google.com/blockly/ - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - /** - * @fileoverview Tests for Blockly.Field - * @author fenichel@google.com (Rachel Fenichel) - */ -'use strict'; - -function test_field_isEditable_simple() { - var field = new Blockly.Field("Dummy text"); - // EDITABLE is true by default, but without a source block a field can't be - // edited. - assertFalse('Field without a block is not editable', - field.isCurrentlyEditable()); -} - -function test_field_isEditable_false() { - // Setting EDITABLE to false doesn't matter. - var field = new Blockly.Field("Dummy text"); - field.EDITABLE = false; - assertFalse('Field without a block is not editable', - field.isCurrentlyEditable()); -} - -function test_field_isEditable_editableBlock() { - var editableBlock = { - isEditable: function() { - return true; - } - }; - - var field = new Blockly.Field("Dummy text"); - field.sourceBlock_ = editableBlock; - - assertTrue('Editable field with editable block is editable', - field.isCurrentlyEditable()); -} - -function test_field_isEditable_editableBlock_false() { - var editableBlock = { - isEditable: function() { - return true; - } - }; - - var field = new Blockly.Field("Dummy text"); - field.sourceBlock_ = editableBlock; - field.EDITABLE = false; - - assertFalse('Non-editable field with editable block is not editable', - field.isCurrentlyEditable()); -} - -function test_field_isEditable_nonEditableBlock() { - var nonEditableBlock = { - isEditable: function() { - return false; - } - }; - - var field = new Blockly.Field("Dummy text"); - field.sourceBlock_ = nonEditableBlock; - - assertFalse('Editable field with non-editable block is not editable', - field.isCurrentlyEditable()); -} - -function test_field_isEditable_nonEditableBlock_false() { - var nonEditableBlock = { - isEditable: function() { - return false; - } - }; - - var field = new Blockly.Field("Dummy text"); - field.sourceBlock_ = nonEditableBlock; - field.EDITABLE = false; - - assertFalse('Non-editable field with non-editable block is not editable', - field.isCurrentlyEditable()); -} - -function test_field_register_with_custom_field() { - var CustomFieldType = function(value) { - CustomFieldType.superClass_.constructor.call(this, value); - }; - goog.inherits(CustomFieldType, Blockly.Field); - - CustomFieldType.fromJson = function(options) { - return new CustomFieldType(options['value']); - }; - - var json = { - type: 'field_custom_test', - value: 'ok' - }; - - // before registering - var field = Blockly.Field.fromJson(json); - assertNull(field); - - Blockly.Field.register('field_custom_test', CustomFieldType); - - // after registering - field = Blockly.Field.fromJson(json); - assertNotNull(field); - assertEquals(field.getValue(), 'ok'); -} diff --git a/tests/jsunit/index.html b/tests/jsunit/index.html index 038d20faf..3cf5307f9 100644 --- a/tests/jsunit/index.html +++ b/tests/jsunit/index.html @@ -18,7 +18,6 @@ - diff --git a/tests/mocha/field_date_test.js b/tests/mocha/field_date_test.js index 6945882c8..348a6d35e 100644 --- a/tests/mocha/field_date_test.js +++ b/tests/mocha/field_date_test.js @@ -18,7 +18,11 @@ * limitations under the License. */ -suite('Date Fields', function() { +/* If you want to run date tests add the date picker here: + * https://github.com/google/blockly/blob/master/core/blockly.js#L41 + * before unskipping. + */ +suite.skip('Date Fields', function() { function assertValue(dateField, expectedValue) { var actualValue = dateField.getValue(); var actualText = dateField.getText(); diff --git a/tests/mocha/field_registry_test.js b/tests/mocha/field_registry_test.js new file mode 100644 index 000000000..c04d135f4 --- /dev/null +++ b/tests/mocha/field_registry_test.js @@ -0,0 +1,126 @@ +/** + * @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. + */ + +/** + * @fileoverview Tests for Blockly.fieldRegistry + * @author bekawestberg@gmail.com (Beka Westberg) + */ +'use strict'; + +suite('Field Registry', function() { + function CustomFieldType(value) { + CustomFieldType.superClass_.constructor.call(this, value); + } + goog.inherits(CustomFieldType, Blockly.Field); + CustomFieldType.fromJson = function(options) { + return new CustomFieldType(options['value']); + }; + + teardown(function() { + if (Blockly.fieldRegistry.typeMap_['field_custom_test']) { + delete Blockly.fieldRegistry.typeMap_['field_custom_test']; + } + }); + suite('Registration', function() { + test('Simple', function() { + Blockly.fieldRegistry.register('field_custom_test', CustomFieldType); + }); + test('Empty String Key', function() { + chai.assert.throws(function() { + Blockly.fieldRegistry.register('', CustomFieldType); + }, 'Invalid field type'); + }); + test('Class as Key', function() { + chai.assert.throws(function() { + Blockly.fieldRegistry.register(CustomFieldType, ''); + }, 'Invalid field type'); + }); + test('fromJson as Key', function() { + chai.assert.throws(function() { + Blockly.fieldRegistry.register(CustomFieldType.fromJson, ''); + }, 'Invalid field type'); + }); + // TODO (#2788): What do you want it to do if you overwrite a key? + test('Overwrite a Key', function() { + Blockly.fieldRegistry.register('field_custom_test', CustomFieldType); + + Blockly.fieldRegistry.register('field_custom_test', CustomFieldType); + }); + test('Null Value', function() { + chai.assert.throws(function() { + Blockly.fieldRegistry.register('field_custom_test', null); + }, 'fromJson function'); + }); + test('No fromJson', function() { + var fromJson = CustomFieldType.fromJson; + delete CustomFieldType.fromJson; + chai.assert.throws(function() { + Blockly.fieldRegistry.register('field_custom_test', CustomFieldType); + }, 'fromJson function'); + CustomFieldType.fromJson = fromJson; + }); + test('fromJson not a function', function() { + var fromJson = CustomFieldType.fromJson; + CustomFieldType.fromJson = true; + chai.assert.throws(function() { + Blockly.fieldRegistry.register('field_custom_test', CustomFieldType); + }, 'fromJson function'); + CustomFieldType.fromJson = fromJson; + }); + }); + suite('Retrieval', function() { + test('Simple', function() { + Blockly.fieldRegistry.register('field_custom_test', CustomFieldType); + + var json = { + type: 'field_custom_test', + value: 'ok' + }; + + var field = Blockly.fieldRegistry.fromJson(json); + chai.assert.isNotNull(field); + chai.assert.equal('ok', field.getValue()); + }); + test('Not Registered', function() { + var json = { + type: 'field_custom_test', + value: 'ok' + }; + + var spy = sinon.stub(console, 'warn'); + var field = Blockly.fieldRegistry.fromJson(json); + chai.assert.isNull(field); + chai.assert.isTrue(spy.called); + spy.restore(); + }); + test('Case Different', function() { + Blockly.fieldRegistry.register('field_custom_test', CustomFieldType); + + var json = { + type: 'FIELD_CUSTOM_TEST', + value: 'ok' + }; + + var field = Blockly.fieldRegistry.fromJson(json); + chai.assert.isNotNull(field); + chai.assert.equal('ok', field.getValue()); + }); + }); +}); diff --git a/tests/mocha/field_test.js b/tests/mocha/field_test.js index 5493e93e2..6d5454434 100644 --- a/tests/mocha/field_test.js +++ b/tests/mocha/field_test.js @@ -19,7 +19,7 @@ */ suite('Abstract Fields', function() { - suite.skip('Is Serializable', function() { + suite('Is Serializable', function() { // Both EDITABLE and SERIALIZABLE are default. function FieldDefault() { this.name = 'NAME'; @@ -72,7 +72,7 @@ suite('Abstract Fields', function() { assertEquals(true, field.isSerializable()); }); }); - suite.skip('setValue', function() { + suite('setValue', function() { function addSpies(field) { if (!this.isSpying) { sinon.spy(field, 'doValueInvalid_'); diff --git a/tests/mocha/index.html b/tests/mocha/index.html index 736f577ae..7f27db666 100644 --- a/tests/mocha/index.html +++ b/tests/mocha/index.html @@ -40,6 +40,7 @@ + diff --git a/tests/mocha/xml_test.js b/tests/mocha/xml_test.js index 51dfa7f6d..dacf89250 100644 --- a/tests/mocha/xml_test.js +++ b/tests/mocha/xml_test.js @@ -94,7 +94,11 @@ suite('XML', function() { assertSimpleField(resultFieldDom, 'COLOUR', '#000099'); delete Blockly.Blocks['field_colour_test_block']; }); - test('Date', function() { + /* If you want to run date tests add the date picker here: + * https://github.com/google/blockly/blob/master/core/blockly.js#L41 + * before unskipping. + */ + test.skip('Date', function() { Blockly.defineBlocksWithJsonArray([{ "type": "field_date_test_block", "message0": "%1",