diff --git a/blockly_accessible_compressed.js b/blockly_accessible_compressed.js index 5f94f3d32..530b7e649 100644 --- a/blockly_accessible_compressed.js +++ b/blockly_accessible_compressed.js @@ -919,12 +919,13 @@ Blockly.Touch.shouldHandleEvent=function(a){return!Blockly.Touch.isMouseOrTouchE 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(goog.string.startsWith(a.type,"touch")){var b=a.changedTouches[0];a.clientX=b.clientX;a.clientY=b.clientY}}; Blockly.Touch.isMouseOrTouchEvent=function(a){return goog.string.startsWith(a.type,"touch")||goog.string.startsWith(a.type,"mouse")||goog.string.startsWith(a.type,"pointer")};Blockly.Touch.isTouchEvent=function(a){return goog.string.startsWith(a.type,"touch")||goog.string.startsWith(a.type,"pointer")}; Blockly.Touch.splitEventByTouches=function(a){var b=[];if(a.changedTouches)for(var c=0;c"+a.xml+"").firstChild};Blockly.Events.CommentCreate.prototype.run=function(a){var b=this.getEventWorkspace_();a?(a=goog.dom.createDom("xml"),a.appendChild(this.xml),Blockly.Xml.domToWorkspace(a,b)):(b=b.getCommentById(this.commentId))?b.dispose(!1,!1):console.warn("Can't uncreate non-existent comment: "+this.commentId)}; +Blockly.Events.CommentDelete=function(a){a&&(Blockly.Events.CommentDelete.superClass_.constructor.call(this,a),this.xml=a.toXmlWithXY())};goog.inherits(Blockly.Events.CommentDelete,Blockly.Events.CommentBase);Blockly.Events.CommentDelete.prototype.type=Blockly.Events.COMMENT_DELETE;Blockly.Events.CommentDelete.prototype.toJson=function(){return Blockly.Events.CommentDelete.superClass_.toJson.call(this)}; +Blockly.Events.CommentDelete.prototype.fromJson=function(a){Blockly.Events.CommentDelete.superClass_.fromJson.call(this,a)};Blockly.Events.CommentDelete.prototype.run=function(a){var b=this.getEventWorkspace_();a?(b=b.getCommentById(this.commentId))?b.dispose(!1,!1):console.warn("Can't uncreate non-existent comment: "+this.commentId):(a=goog.dom.createDom("xml"),a.appendChild(this.xml),Blockly.Xml.domToWorkspace(a,b))}; +Blockly.Events.CommentMove=function(a){a&&(Blockly.Events.CommentMove.superClass_.constructor.call(this,a),this.comment_=a,this.oldCoordinate_=a.getXY(),this.newCoordinate_=null)};goog.inherits(Blockly.Events.CommentMove,Blockly.Events.CommentBase);Blockly.Events.CommentMove.prototype.recordNew=function(){if(!this.comment_)throw Error("Tried to record the new position of a comment on the same event twice.");this.newCoordinate_=this.comment_.getXY();this.comment_=null}; +Blockly.Events.CommentMove.prototype.type=Blockly.Events.COMMENT_MOVE;Blockly.Events.CommentMove.prototype.setOldCoordinate=function(a){this.oldCoordinate_=a};Blockly.Events.CommentMove.prototype.toJson=function(){var a=Blockly.Events.CommentMove.superClass_.toJson.call(this);this.newCoordinate_&&(a.newCoordinate=Math.round(this.newCoordinate_.x)+","+Math.round(this.newCoordinate_.y));return a}; +Blockly.Events.CommentMove.prototype.fromJson=function(a){Blockly.Events.CommentMove.superClass_.fromJson.call(this,a);a.newCoordinate&&(a=a.newCoordinate.split(","),this.newCoordinate_=new goog.math.Coordinate(parseFloat(a[0]),parseFloat(a[1])))};Blockly.Events.CommentMove.prototype.isNull=function(){return goog.math.Coordinate.equals(this.oldCoordinate_,this.newCoordinate_)}; +Blockly.Events.CommentMove.prototype.run=function(a){var b=this.getEventWorkspace_().getCommentById(this.commentId);if(b){a=a?this.newCoordinate_:this.oldCoordinate_;var c=b.getXY();b.moveBy(a.x-c.x,a.y-c.y)}else console.warn("Can't move non-existent comment: "+this.commentId)};Blockly.WorkspaceComment=function(a,b,c,d,e){this.id=e&&!a.getCommentById(e)?e:Blockly.utils.genUid();a.addTopComment(this);this.xy_=new goog.math.Coordinate(0,0);this.height_=c;this.width_=d;this.workspace=a;this.RTL=a.RTL;this.movable_=this.deletable_=!0;this.content_=b;this.isComment=!0;Blockly.WorkspaceComment.fireCreateEvent(this)}; +Blockly.WorkspaceComment.prototype.dispose=function(){this.workspace&&(Blockly.Events.isEnabled()&&Blockly.Events.fire(new Blockly.Events.CommentDelete(this)),this.workspace.removeTopComment(this),this.workspace=null)};Blockly.WorkspaceComment.prototype.getHeight=function(){return this.height_};Blockly.WorkspaceComment.prototype.setHeight=function(a){this.height_=a};Blockly.WorkspaceComment.prototype.getWidth=function(){return this.width_}; +Blockly.WorkspaceComment.prototype.setWidth=function(a){this.width_=a};Blockly.WorkspaceComment.prototype.getXY=function(){return this.xy_.clone()};Blockly.WorkspaceComment.prototype.moveBy=function(a,b){var c=new Blockly.Events.CommentMove(this);this.xy_.translate(a,b);c.recordNew();Blockly.Events.fire(c)};Blockly.WorkspaceComment.prototype.isDeletable=function(){return this.deletable_&&!(this.workspace&&this.workspace.options.readOnly)}; +Blockly.WorkspaceComment.prototype.setDeletable=function(a){this.deletable_=a};Blockly.WorkspaceComment.prototype.isMovable=function(){return this.movable_&&!(this.workspace&&this.workspace.options.readOnly)};Blockly.WorkspaceComment.prototype.setMovable=function(a){this.movable_=a};Blockly.WorkspaceComment.prototype.getContent=function(){return this.content_}; +Blockly.WorkspaceComment.prototype.setContent=function(a){this.content_!=a&&(Blockly.Events.fire(new Blockly.Events.CommentChange(this,this.content_,a)),this.content_=a)};Blockly.WorkspaceComment.prototype.toXmlWithXY=function(a){a=this.toXml(a);a.setAttribute("x",Math.round(this.xy_.x));a.setAttribute("y",Math.round(this.xy_.y));a.setAttribute("h",this.height_);a.setAttribute("w",this.width_);return a}; +Blockly.WorkspaceComment.prototype.toXml=function(a){var b=goog.dom.createDom("comment");a||b.setAttribute("id",this.id);b.textContent=this.getContent();return b};Blockly.WorkspaceComment.fireCreateEvent=function(a){if(Blockly.Events.isEnabled()){var b=Blockly.Events.getGroup();b||Blockly.Events.setGroup(!0);try{Blockly.Events.fire(new Blockly.Events.CommentCreate(a))}finally{b||Blockly.Events.setGroup(!1)}}}; +Blockly.WorkspaceComment.fromXml=function(a,b){var c=Blockly.WorkspaceComment.parseAttributes(a);b=new Blockly.WorkspaceComment(b,c.content,c.h,c.w,c.id);c=parseInt(a.getAttribute("x"),10);a=parseInt(a.getAttribute("y"),10);isNaN(c)||isNaN(a)||b.moveBy(c,a);Blockly.WorkspaceComment.fireCreateEvent(b);return b}; +Blockly.WorkspaceComment.parseAttributes=function(a){var b=a.getAttribute("h"),c=a.getAttribute("w");return{id:a.getAttribute("id"),h:b?parseInt(b,10):100,w:c?parseInt(c,10):100,x:parseInt(a.getAttribute("x"),10),y:parseInt(a.getAttribute("y"),10),content:a.textContent}}; +Blockly.Workspace=function(a){this.id=Blockly.utils.genUid();Blockly.Workspace.WorkspaceDB_[this.id]=this;this.options=a||{};this.RTL=!!this.options.RTL;this.horizontalLayout=!!this.options.horizontalLayout;this.toolboxPosition=this.options.toolboxPosition;this.topBlocks_=[];this.topComments_=[];this.commentDB_=Object.create(null);this.listeners_=[];this.undoStack_=[];this.redoStack_=[];this.blockDB_=Object.create(null);this.variableMap_=new Blockly.VariableMap(this);this.potentialVariableMap_=null}; +Blockly.Workspace.prototype.rendered=!1;Blockly.Workspace.prototype.MAX_UNDO=1024;Blockly.Workspace.prototype.dispose=function(){this.listeners_.length=0;this.clear();delete Blockly.Workspace.WorkspaceDB_[this.id]};Blockly.Workspace.SCAN_ANGLE=3;Blockly.Workspace.prototype.addTopBlock=function(a){this.topBlocks_.push(a)};Blockly.Workspace.prototype.removeTopBlock=function(a){if(!goog.array.remove(this.topBlocks_,a))throw"Block not present in workspace's list of top-most blocks.";}; Blockly.Workspace.prototype.getTopBlocks=function(a){var b=[].concat(this.topBlocks_);if(a&&1this.MAX_UNDO&&this.undoStack_.unshift());for(var b=0,c;c=this.listeners_[b];b++)c(a)}; -Blockly.Workspace.prototype.getBlockById=function(a){return this.blockDB_[a]||null};Blockly.Workspace.prototype.allInputsFilled=function(a){for(var b=this.getTopBlocks(!1),c=0,d;d=b[c];c++)if(!d.allInputsFilled(a))return!1;return!0};Blockly.Workspace.prototype.getPotentialVariableMap=function(){return this.potentialVariableMap_};Blockly.Workspace.prototype.createPotentialVariableMap=function(){this.potentialVariableMap_=new Blockly.VariableMap(this)};Blockly.Workspace.prototype.getVariableMap=function(){return this.variableMap_}; -Blockly.Workspace.WorkspaceDB_=Object.create(null);Blockly.Workspace.getById=function(a){return Blockly.Workspace.WorkspaceDB_[a]||null};Blockly.Workspace.prototype.clear=Blockly.Workspace.prototype.clear;Blockly.Workspace.prototype.clearUndo=Blockly.Workspace.prototype.clearUndo;Blockly.Workspace.prototype.addChangeListener=Blockly.Workspace.prototype.addChangeListener;Blockly.Workspace.prototype.removeChangeListener=Blockly.Workspace.prototype.removeChangeListener;Blockly.Bubble=function(a,b,c,d,e,f){this.workspace_=a;this.content_=b;this.shape_=c;c=Blockly.Bubble.ARROW_ANGLE;this.workspace_.RTL&&(c=-c);this.arrow_radians_=goog.math.toRadians(c);a.getBubbleCanvas().appendChild(this.createDom_(b,!(!e||!f)));this.setAnchorLocation(d);e&&f||(b=this.content_.getBBox(),e=b.width+2*Blockly.Bubble.BORDER_WIDTH,f=b.height+2*Blockly.Bubble.BORDER_WIDTH);this.setBubbleSize(e,f);this.positionBubble_();this.renderArrow_();this.rendered_=!0;a.options.readOnly||(Blockly.bindEventWithChecks_(this.bubbleBack_, +Blockly.Workspace.prototype.getBlockById=function(a){return this.blockDB_[a]||null};Blockly.Workspace.prototype.getCommentById=function(a){return this.commentDB_[a]||null};Blockly.Workspace.prototype.allInputsFilled=function(a){for(var b=this.getTopBlocks(!1),c=0,d;d=b[c];c++)if(!d.allInputsFilled(a))return!1;return!0};Blockly.Workspace.prototype.getPotentialVariableMap=function(){return this.potentialVariableMap_}; +Blockly.Workspace.prototype.createPotentialVariableMap=function(){this.potentialVariableMap_=new Blockly.VariableMap(this)};Blockly.Workspace.prototype.getVariableMap=function(){return this.variableMap_};Blockly.Workspace.WorkspaceDB_=Object.create(null);Blockly.Workspace.getById=function(a){return Blockly.Workspace.WorkspaceDB_[a]||null};Blockly.Workspace.prototype.clear=Blockly.Workspace.prototype.clear;Blockly.Workspace.prototype.clearUndo=Blockly.Workspace.prototype.clearUndo; +Blockly.Workspace.prototype.addChangeListener=Blockly.Workspace.prototype.addChangeListener;Blockly.Workspace.prototype.removeChangeListener=Blockly.Workspace.prototype.removeChangeListener;Blockly.Bubble=function(a,b,c,d,e,f){this.workspace_=a;this.content_=b;this.shape_=c;c=Blockly.Bubble.ARROW_ANGLE;this.workspace_.RTL&&(c=-c);this.arrow_radians_=goog.math.toRadians(c);a.getBubbleCanvas().appendChild(this.createDom_(b,!(!e||!f)));this.setAnchorLocation(d);e&&f||(b=this.content_.getBBox(),e=b.width+2*Blockly.Bubble.BORDER_WIDTH,f=b.height+2*Blockly.Bubble.BORDER_WIDTH);this.setBubbleSize(e,f);this.positionBubble_();this.renderArrow_();this.rendered_=!0;a.options.readOnly||(Blockly.bindEventWithChecks_(this.bubbleBack_, "mousedown",this,this.bubbleMouseDown_),this.resizeGroup_&&Blockly.bindEventWithChecks_(this.resizeGroup_,"mousedown",this,this.resizeMouseDown_))};Blockly.Bubble.BORDER_WIDTH=6;Blockly.Bubble.ARROW_THICKNESS=5;Blockly.Bubble.ARROW_ANGLE=20;Blockly.Bubble.ARROW_BEND=4;Blockly.Bubble.ANCHOR_RADIUS=8;Blockly.Bubble.onMouseUpWrapper_=null;Blockly.Bubble.onMouseMoveWrapper_=null;Blockly.Bubble.prototype.resizeCallback_=null; Blockly.Bubble.unbindDragEvents_=function(){Blockly.Bubble.onMouseUpWrapper_&&(Blockly.unbindEvent_(Blockly.Bubble.onMouseUpWrapper_),Blockly.Bubble.onMouseUpWrapper_=null);Blockly.Bubble.onMouseMoveWrapper_&&(Blockly.unbindEvent_(Blockly.Bubble.onMouseMoveWrapper_),Blockly.Bubble.onMouseMoveWrapper_=null)};Blockly.Bubble.bubbleMouseUp_=function(){Blockly.Touch.clearTouchIdentifier();Blockly.Bubble.unbindDragEvents_()};Blockly.Bubble.prototype.rendered_=!1;Blockly.Bubble.prototype.anchorXY_=null; Blockly.Bubble.prototype.relativeLeft_=0;Blockly.Bubble.prototype.relativeTop_=0;Blockly.Bubble.prototype.width_=0;Blockly.Bubble.prototype.height_=0;Blockly.Bubble.prototype.autoLayout_=!0; Blockly.Bubble.prototype.createDom_=function(a,b){this.bubbleGroup_=Blockly.utils.createSvgElement("g",{},null);var c={filter:"url(#"+this.workspace_.options.embossFilterId+")"};-1!=goog.userAgent.getUserAgentString().indexOf("JavaFX")&&(c={});c=Blockly.utils.createSvgElement("g",c,this.bubbleGroup_);this.bubbleArrow_=Blockly.utils.createSvgElement("path",{},c);this.bubbleBack_=Blockly.utils.createSvgElement("rect",{"class":"blocklyDraggable",x:0,y:0,rx:Blockly.Bubble.BORDER_WIDTH,ry:Blockly.Bubble.BORDER_WIDTH}, c);b?(this.resizeGroup_=Blockly.utils.createSvgElement("g",{"class":this.workspace_.RTL?"blocklyResizeSW":"blocklyResizeSE"},this.bubbleGroup_),b=2*Blockly.Bubble.BORDER_WIDTH,Blockly.utils.createSvgElement("polygon",{points:"0,x x,x x,0".replace(/x/g,b.toString())},this.resizeGroup_),Blockly.utils.createSvgElement("line",{"class":"blocklyResizeLine",x1:b/3,y1:b-1,x2:b-1,y2:b/3},this.resizeGroup_),Blockly.utils.createSvgElement("line",{"class":"blocklyResizeLine",x1:2*b/3,y1:b-1,x2:b-1,y2:2*b/3}, -this.resizeGroup_)):this.resizeGroup_=null;this.bubbleGroup_.appendChild(a);return this.bubbleGroup_};Blockly.Bubble.prototype.getSvgRoot=function(){return this.bubbleGroup_};Blockly.Bubble.prototype.setSvgId=function(a){this.bubbleGroup_.dataset&&(this.bubbleGroup_.dataset.blockId=a)};Blockly.Bubble.prototype.bubbleMouseDown_=function(a){var b=this.workspace_.getGesture(a);b&&b.handleBubbleStart(a,this)}; +this.resizeGroup_)):this.resizeGroup_=null;this.bubbleGroup_.appendChild(a);return this.bubbleGroup_};Blockly.Bubble.prototype.getSvgRoot=function(){return this.bubbleGroup_};Blockly.Bubble.prototype.setSvgId=function(a){this.bubbleGroup_.dataset&&(this.bubbleGroup_.dataset.blockId=a)};Blockly.Bubble.prototype.bubbleMouseDown_=function(a){var b=this.workspace_.getGesture(a);b&&b.handleBubbleStart(a,this)};Blockly.Bubble.prototype.showContextMenu_=function(a){}; +Blockly.Bubble.prototype.isDeletable=function(){return!1}; Blockly.Bubble.prototype.resizeMouseDown_=function(a){this.promote_();Blockly.Bubble.unbindDragEvents_();Blockly.utils.isRightButton(a)||(this.workspace_.startDrag(a,new goog.math.Coordinate(this.workspace_.RTL?-this.width_:this.width_,this.height_)),Blockly.Bubble.onMouseUpWrapper_=Blockly.bindEventWithChecks_(document,"mouseup",this,Blockly.Bubble.bubbleMouseUp_),Blockly.Bubble.onMouseMoveWrapper_=Blockly.bindEventWithChecks_(document,"mousemove",this,this.resizeMouseMove_),Blockly.hideChaff()); a.stopPropagation()};Blockly.Bubble.prototype.resizeMouseMove_=function(a){this.autoLayout_=!1;a=this.workspace_.moveDrag(a);this.setBubbleSize(this.workspace_.RTL?-a.x:a.x,a.y);this.workspace_.RTL&&this.positionBubble_()};Blockly.Bubble.prototype.registerResizeEvent=function(a){this.resizeCallback_=a};Blockly.Bubble.prototype.promote_=function(){var a=this.bubbleGroup_.parentNode;return a.lastChild!==this.bubbleGroup_?(a.appendChild(this.bubbleGroup_),!0):!1}; Blockly.Bubble.prototype.setAnchorLocation=function(a){this.anchorXY_=a;this.rendered_&&this.positionBubble_()}; @@ -1060,9 +1083,30 @@ Blockly.BlockDragger.prototype.endBlockDrag=function(a,b){this.dragBlock(a,b);th this.workspace_.setResizesEnabled(!0);if(a=this.workspace_.getToolbox())b=this.draggingBlock_.isDeletable()?"blocklyToolboxDelete":"blocklyToolboxGrab",a.removeStyle(b);Blockly.Events.setGroup(!1)};Blockly.BlockDragger.prototype.fireMoveEvent_=function(){var a=new Blockly.Events.BlockMove(this.draggingBlock_);a.oldCoordinate=this.startXY_;a.recordNew();Blockly.Events.fire(a)}; Blockly.BlockDragger.prototype.maybeDeleteBlock_=function(){var a=this.workspace_.trashcan;this.wouldDeleteBlock_?(a&&goog.Timer.callOnce(a.close,100,a),this.fireMoveEvent_(),this.draggingBlock_.dispose(!1,!0)):a&&a.close();return this.wouldDeleteBlock_}; Blockly.BlockDragger.prototype.updateCursorDuringBlockDrag_=function(){this.wouldDeleteBlock_=this.draggedConnectionManager_.wouldDeleteBlock();var a=this.workspace_.trashcan;this.wouldDeleteBlock_?(this.draggingBlock_.setDeleteStyle(!0),this.deleteArea_==Blockly.DELETE_AREA_TRASH&&a&&a.setOpen_(!0)):(this.draggingBlock_.setDeleteStyle(!1),a&&a.setOpen_(!1))}; -Blockly.BlockDragger.prototype.pixelsToWorkspaceUnits_=function(a){a=new goog.math.Coordinate(a.x/this.workspace_.scale,a.y/this.workspace_.scale);this.workspace_.isMutator&&(a=a.scale(1/this.workspace_.options.parentWorkspace.scale));return a};Blockly.BlockDragger.prototype.dragIcons_=function(a){for(var b=0;b"!=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){(a=(new DOMParser).parseFromString(a,"text/xml"))&&a.firstChild&&"xml"==a.firstChild.nodeName.toLowerCase()&&a.firstChild===a.lastChild||goog.asserts.fail("Blockly.Xml.textToDom did not obtain a valid XML tree.");return a.firstChild}; 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;h=this.remainingCapacity())){this.currentGesture_&&this.currentGesture_.cancel();Blockly.Events.disable();try{var b=Blockly.Xml.domToBlock(a,this),c=parseInt(a.getAttribute("x"),10),d=parseInt(a.getAttribute("y"),10);if(!isNaN(c)&&!isNaN(d)){this.RTL&&(c=-c);do{a=!1;for(var e=this.getAllBlocks(),f=0,g;g=e[f];f++){var h=g.getRelativeToSurfaceXY();if(1>=Math.abs(c-h.x)&&1>=Math.abs(d-h.y)){a=!0; -break}}if(!a){var k=b.getConnections_(!1);f=0;for(var n;n=k[f];f++)if(n.closest(Blockly.SNAP_RADIUS,new goog.math.Coordinate(c,d)).connection){a=!0;break}}a&&(c=this.RTL?c-Blockly.SNAP_RADIUS:c+Blockly.SNAP_RADIUS,d+=2*Blockly.SNAP_RADIUS)}while(a);b.moveBy(c,d)}}finally{Blockly.Events.enable()}Blockly.Events.isEnabled()&&!b.isShadow()&&Blockly.Events.fire(new Blockly.Events.BlockCreate(b));b.select()}}; +Blockly.WorkspaceSvg.prototype.paste=function(a){!this.rendered||a.getElementsByTagName("block").length>=this.remainingCapacity()||(this.currentGesture_&&this.currentGesture_.cancel(),"comment"==a.tagName.toLowerCase()?this.pasteWorkspaceComment_(a):this.pasteBlock_(a))}; +Blockly.WorkspaceSvg.prototype.pasteBlock_=function(a){Blockly.Events.disable();try{var b=Blockly.Xml.domToBlock(a,this),c=parseInt(a.getAttribute("x"),10),d=parseInt(a.getAttribute("y"),10);if(!isNaN(c)&&!isNaN(d)){this.RTL&&(c=-c);do{a=!1;for(var e=this.getAllBlocks(),f=0,g;g=e[f];f++){var h=g.getRelativeToSurfaceXY();if(1>=Math.abs(c-h.x)&&1>=Math.abs(d-h.y)){a=!0;break}}if(!a){var k=b.getConnections_(!1);f=0;for(var n;n=k[f];f++)if(n.closest(Blockly.SNAP_RADIUS,new goog.math.Coordinate(c,d)).connection){a= +!0;break}}a&&(c=this.RTL?c-Blockly.SNAP_RADIUS:c+Blockly.SNAP_RADIUS,d+=2*Blockly.SNAP_RADIUS)}while(a);b.moveBy(c,d)}}finally{Blockly.Events.enable()}Blockly.Events.isEnabled()&&!b.isShadow()&&Blockly.Events.fire(new Blockly.Events.BlockCreate(b));b.select()}; +Blockly.WorkspaceSvg.prototype.pasteWorkspaceComment_=function(a){Blockly.Events.disable();try{var b=Blockly.WorkspaceCommentSvg.fromXml(a,this),c=parseInt(a.getAttribute("x"),10),d=parseInt(a.getAttribute("y"),10);isNaN(c)||isNaN(d)||(this.RTL&&(c=-c),b.moveBy(c+50,d+50))}finally{Blockly.Events.enable()}Blockly.Events.isEnabled();b.select()}; Blockly.WorkspaceSvg.prototype.refreshToolboxSelection=function(){var a=this.isFlyout?this.targetWorkspace:this;a&&!a.currentGesture_&&a.toolbox_&&a.toolbox_.flyout_&&a.toolbox_.refreshSelection()};Blockly.WorkspaceSvg.prototype.renameVariableById=function(a,b){Blockly.WorkspaceSvg.superClass_.renameVariableById.call(this,a,b);this.refreshToolboxSelection()};Blockly.WorkspaceSvg.prototype.deleteVariableById=function(a){Blockly.WorkspaceSvg.superClass_.deleteVariableById.call(this,a);this.refreshToolboxSelection()}; Blockly.WorkspaceSvg.prototype.createVariable=function(a,b,c){a=Blockly.WorkspaceSvg.superClass_.createVariable.call(this,a,b,c);this.refreshToolboxSelection();return a};Blockly.WorkspaceSvg.prototype.recordDeleteAreas=function(){this.deleteAreaTrash_=this.trashcan&&this.svgGroup_.parentNode?this.trashcan.getClientRect():null;this.deleteAreaToolbox_=this.flyout_?this.flyout_.getClientRect():this.toolbox_?this.toolbox_.getClientRect():null}; Blockly.WorkspaceSvg.prototype.isDeleteArea=function(a){a=new goog.math.Coordinate(a.clientX,a.clientY);return this.deleteAreaTrash_&&this.deleteAreaTrash_.contains(a)?Blockly.DELETE_AREA_TRASH:this.deleteAreaToolbox_&&this.deleteAreaToolbox_.contains(a)?Blockly.DELETE_AREA_TOOLBOX:Blockly.DELETE_AREA_NONE};Blockly.WorkspaceSvg.prototype.onMouseDown_=function(a){var b=this.getGesture(a);b&&b.handleWsStart(a,this)}; Blockly.WorkspaceSvg.prototype.startDrag=function(a,b){a=Blockly.utils.mouseToSvg(a,this.getParentSvg(),this.getInverseScreenCTM());a.x/=this.scale;a.y/=this.scale;this.dragDeltaXY_=goog.math.Coordinate.difference(b,a)};Blockly.WorkspaceSvg.prototype.moveDrag=function(a){a=Blockly.utils.mouseToSvg(a,this.getParentSvg(),this.getInverseScreenCTM());a.x/=this.scale;a.y/=this.scale;return goog.math.Coordinate.sum(this.dragDeltaXY_,a)}; Blockly.WorkspaceSvg.prototype.isDragging=function(){return null!=this.currentGesture_&&this.currentGesture_.isDragging()};Blockly.WorkspaceSvg.prototype.isDraggable=function(){return!!this.scrollbar};Blockly.WorkspaceSvg.prototype.onMouseWheel_=function(a){this.currentGesture_&&this.currentGesture_.cancel();var b=-a.deltaY/50,c=Blockly.utils.mouseToSvg(a,this.getParentSvg(),this.getInverseScreenCTM());this.zoom(c.x,c.y,b);a.preventDefault()}; -Blockly.WorkspaceSvg.prototype.getBlocksBoundingBox=function(){var a=this.getTopBlocks(!1);if(!a.length)return{x:0,y:0,width:0,height:0};for(var b=a[0].getBoundingRectangle(),c=1;cb.bottomRight.x&&(b.bottomRight.x=d.bottomRight.x);d.topLeft.yb.bottomRight.y&&(b.bottomRight.y=d.bottomRight.y)}return{x:b.topLeft.x,y:b.topLeft.y,width:b.bottomRight.x- -b.topLeft.x,height:b.bottomRight.y-b.topLeft.y}};Blockly.WorkspaceSvg.prototype.cleanUp=function(){this.setResizesEnabled(!1);Blockly.Events.setGroup(!0);for(var a=this.getTopBlocks(!0),b=0,c=0,d;d=a[c];c++){var e=d.getRelativeToSurfaceXY();d.moveBy(-e.x,b-e.y);d.snapToGrid();b=d.getRelativeToSurfaceXY().y+d.getHeightWidth().height+Blockly.BlockSvg.MIN_BLOCK_Y}Blockly.Events.setGroup(!1);this.setResizesEnabled(!0)}; +Blockly.WorkspaceSvg.prototype.getBlocksBoundingBox=function(){var a=this.getTopBlocks(!1),b=this.getTopComments(!1);a=a.concat(b);if(!a.length)return{x:0,y:0,width:0,height:0};b=a[0].getBoundingRectangle();for(var c=1;cb.bottomRight.x&&(b.bottomRight.x=d.bottomRight.x);d.topLeft.yb.bottomRight.y&&(b.bottomRight.y=d.bottomRight.y)}return{x:b.topLeft.x, +y:b.topLeft.y,width:b.bottomRight.x-b.topLeft.x,height:b.bottomRight.y-b.topLeft.y}};Blockly.WorkspaceSvg.prototype.cleanUp=function(){this.setResizesEnabled(!1);Blockly.Events.setGroup(!0);for(var a=this.getTopBlocks(!0),b=0,c=0,d;d=a[c];c++){var e=d.getRelativeToSurfaceXY();d.moveBy(-e.x,b-e.y);d.snapToGrid();b=d.getRelativeToSurfaceXY().y+d.getHeightWidth().height+Blockly.BlockSvg.MIN_BLOCK_Y}Blockly.Events.setGroup(!1);this.setResizesEnabled(!0)}; Blockly.WorkspaceSvg.prototype.showContextMenu_=function(a){function b(a){if(a.isDeletable())m=m.concat(a.getDescendants(!1));else{a=a.getChildren(!1);for(var c=0;cm.length?c():Blockly.confirm(Blockly.Msg.DELETE_ALL_BLOCKS.replace("%1",m.length),function(a){a&& @@ -1377,7 +1424,10 @@ Blockly.ContextMenu.position_=function(a,b,c){var d=Blockly.utils.getViewportBBo Blockly.ContextMenu.createWidget_=function(a){a.render(Blockly.WidgetDiv.DIV);var b=a.getElement();Blockly.utils.addClass(b,"blocklyContextMenu");Blockly.bindEventWithChecks_(b,"contextmenu",null,Blockly.utils.noEvent);a.setAllowAutoFocus(!0)};Blockly.ContextMenu.hide=function(){Blockly.WidgetDiv.hideIfOwner(Blockly.ContextMenu);Blockly.ContextMenu.currentBlock=null;Blockly.ContextMenu.eventWrapper_&&Blockly.unbindEvent_(Blockly.ContextMenu.eventWrapper_)}; Blockly.ContextMenu.callbackFactory=function(a,b){return function(){Blockly.Events.disable();try{var c=Blockly.Xml.domToBlock(b,a.workspace),d=a.getRelativeToSurfaceXY();d.x=a.RTL?d.x-Blockly.SNAP_RADIUS:d.x+Blockly.SNAP_RADIUS;d.y+=2*Blockly.SNAP_RADIUS;c.moveBy(d.x,d.y)}finally{Blockly.Events.enable()}Blockly.Events.isEnabled()&&!c.isShadow()&&Blockly.Events.fire(new Blockly.Events.BlockCreate(c));c.select()}}; Blockly.ContextMenu.blockDeleteOption=function(a){var b=a.getDescendants(!1).length,c=a.getNextBlock();c&&(b-=c.getDescendants(!1).length);return{text:1==b?Blockly.Msg.DELETE_BLOCK:Blockly.Msg.DELETE_X_BLOCKS.replace("%1",String(b)),enabled:!0,callback:function(){Blockly.Events.setGroup(!0);a.dispose(!0,!0);Blockly.Events.setGroup(!1)}}};Blockly.ContextMenu.blockHelpOption=function(a){return{enabled:!(goog.isFunction(a.helpUrl)?!a.helpUrl():!a.helpUrl),text:Blockly.Msg.HELP,callback:function(){a.showHelp_()}}}; -Blockly.ContextMenu.blockDuplicateOption=function(a){var b=!0;a.getDescendants(!1).length>a.workspace.remainingCapacity()&&(b=!1);return{text:Blockly.Msg.DUPLICATE_BLOCK,enabled:b,callback:function(){Blockly.duplicate_(a)}}};Blockly.ContextMenu.blockCommentOption=function(a){var b={enabled:!goog.userAgent.IE};a.comment?(b.text=Blockly.Msg.REMOVE_COMMENT,b.callback=function(){a.setCommentText(null)}):(b.text=Blockly.Msg.ADD_COMMENT,b.callback=function(){a.setCommentText("")});return b};Blockly.BlockSvg=function(a,b,c){this.svgGroup_=Blockly.utils.createSvgElement("g",{},null);this.svgGroup_.translate_="";this.svgPathDark_=Blockly.utils.createSvgElement("path",{"class":"blocklyPathDark",transform:"translate(1,1)"},this.svgGroup_);this.svgPath_=Blockly.utils.createSvgElement("path",{"class":"blocklyPath"},this.svgGroup_);this.svgPathLight_=Blockly.utils.createSvgElement("path",{"class":"blocklyPathLight"},this.svgGroup_);this.svgPath_.tooltip=this;this.rendered=!1;this.useDragSurface_= +Blockly.ContextMenu.blockDuplicateOption=function(a){var b=!0;a.getDescendants(!1).length>a.workspace.remainingCapacity()&&(b=!1);return{text:Blockly.Msg.DUPLICATE_BLOCK,enabled:b,callback:function(){Blockly.duplicate_(a)}}};Blockly.ContextMenu.blockCommentOption=function(a){var b={enabled:!goog.userAgent.IE};a.comment?(b.text=Blockly.Msg.REMOVE_COMMENT,b.callback=function(){a.setCommentText(null)}):(b.text=Blockly.Msg.ADD_COMMENT,b.callback=function(){a.setCommentText("")});return b}; +Blockly.ContextMenu.commentDeleteOption=function(a){return{text:Blockly.Msg.REMOVE_COMMENT,enabled:!0,callback:function(){Blockly.Events.setGroup(!0);a.dispose(!0,!0);Blockly.Events.setGroup(!1)}}};Blockly.ContextMenu.commentDuplicateOption=function(a){return{text:Blockly.Msg.DUPLICATE_COMMENT,enabled:!0,callback:function(){Blockly.duplicate_(a)}}}; +Blockly.ContextMenu.workspaceCommentOption=function(a,b){var c={enabled:!0};c.text=Blockly.Msg.ADD_COMMENT;c.callback=function(){var c=new Blockly.WorkspaceCommentSvg(a,Blockly.Msg.WORKSPACE_COMMENT_DEFAULT_TEXT,Blockly.WorkspaceCommentSvg.DEFAULT_SIZE,Blockly.WorkspaceCommentSvg.DEFAULT_SIZE),e=a.getInjectionDiv().getBoundingClientRect();e=new goog.math.Coordinate(b.clientX-e.left,b.clientY-e.top);var f=a.getOriginOffsetInPixels();e=goog.math.Coordinate.difference(e,f).scale(1/a.scale);c.moveBy(e.x, +e.y);a.rendered&&(c.initSvg(),c.render(!1),c.select())};return c};Blockly.BlockSvg=function(a,b,c){this.svgGroup_=Blockly.utils.createSvgElement("g",{},null);this.svgGroup_.translate_="";this.svgPathDark_=Blockly.utils.createSvgElement("path",{"class":"blocklyPathDark",transform:"translate(1,1)"},this.svgGroup_);this.svgPath_=Blockly.utils.createSvgElement("path",{"class":"blocklyPath"},this.svgGroup_);this.svgPathLight_=Blockly.utils.createSvgElement("path",{"class":"blocklyPathLight"},this.svgGroup_);this.svgPath_.tooltip=this;this.rendered=!1;this.useDragSurface_= Blockly.utils.is3dSupported()&&!!a.blockDragSurface_;Blockly.Tooltip.bindMouseEvents(this.svgPath_);Blockly.BlockSvg.superClass_.constructor.call(this,a,b,c);this.svgGroup_.dataset&&(this.svgGroup_.dataset.id=this.id)};goog.inherits(Blockly.BlockSvg,Blockly.Block);Blockly.BlockSvg.prototype.height=0;Blockly.BlockSvg.prototype.width=0;Blockly.BlockSvg.prototype.dragStartXY_=null;Blockly.BlockSvg.prototype.warningTextDb_=null;Blockly.BlockSvg.INLINE=-1; Blockly.BlockSvg.prototype.initSvg=function(){goog.asserts.assert(this.workspace.rendered,"Workspace is headless.");for(var a=0,b;b=this.inputList[a];a++)b.init();b=this.getIcons();for(a=0;a>>/g,Blockly.Css.mediaPath_);a=document.createElement("style");document.head.insertBefore(a,document.head.firstChild);c=document.createTextNode(c);a.appendChild(c);Blockly.Css.styleSheet_=a.sheet}};Blockly.Css.setCursor=function(a){console.warn("Deprecated call to Blockly.Css.setCursor.See https://github.com/google/blockly/issues/981 for context")}; Blockly.Css.CONTENT=[".blocklySvg {","background-color: #fff;","outline: none;","overflow: hidden;","position: absolute;","display: block;","}",".blocklyWidgetDiv {","display: none;","position: absolute;","z-index: 99999;","}",".injectionDiv {","height: 100%;","position: relative;","overflow: hidden;","touch-action: none","}",".blocklyNonSelectable {","user-select: none;","-moz-user-select: none;","-ms-user-select: none;","-webkit-user-select: none;","}",".blocklyWsDragSurface {","display: none;", "position: absolute;","top: 0;","left: 0;","}",".blocklyWsDragSurface.blocklyOverflowVisible {","overflow: visible;","}",".blocklyBlockDragSurface {","display: none;","position: absolute;","top: 0;","left: 0;","right: 0;","bottom: 0;","overflow: visible !important;","z-index: 50;","}",".blocklyTooltipDiv {","background-color: #ffffc7;","border: 1px solid #ddc;","box-shadow: 4px 4px 20px 1px rgba(0,0,0,.15);","color: #000;","display: none;","font-family: sans-serif;","font-size: 9pt;","opacity: .9;", -"padding: 2px;","position: absolute;","z-index: 100000;","}",".blocklyResizeSE {","cursor: se-resize;","fill: #aaa;","}",".blocklyResizeSW {","cursor: sw-resize;","fill: #aaa;","}",".blocklyResizeLine {","stroke: #888;","stroke-width: 1;","}",".blocklyHighlightedConnectionPath {","fill: none;","stroke: #fc3;","stroke-width: 4px;","}",".blocklyPathLight {","fill: none;","stroke-linecap: round;","stroke-width: 1;","}",".blocklySelected>.blocklyPath {","stroke: #fc3;","stroke-width: 3px;","}",".blocklySelected>.blocklyPathLight {", +"padding: 2px;","position: absolute;","z-index: 100000;","}",".blocklyResizeSE {","cursor: se-resize;","fill: #aaa;","}",".blocklyResizeSW {","cursor: sw-resize;","fill: #aaa;","}",".blocklyResizeLine {","stroke: #515A5A;","stroke-width: 1;","}",".blocklyHighlightedConnectionPath {","fill: none;","stroke: #fc3;","stroke-width: 4px;","}",".blocklyPathLight {","fill: none;","stroke-linecap: round;","stroke-width: 1;","}",".blocklySelected>.blocklyPath {","stroke: #fc3;","stroke-width: 3px;","}",".blocklySelected>.blocklyPathLight {", "display: none;","}",".blocklyDraggable {",'cursor: url("<<>>/handopen.cur"), auto;',"cursor: grab;","cursor: -webkit-grab;","}",".blocklyDragging {",'cursor: url("<<>>/handclosed.cur"), auto;',"cursor: grabbing;","cursor: -webkit-grabbing;","}",".blocklyDraggable:active {",'cursor: url("<<>>/handclosed.cur"), auto;',"cursor: grabbing;","cursor: -webkit-grabbing;","}",".blocklyBlockDragSurface .blocklyDraggable {",'cursor: url("<<>>/handclosed.cur"), auto;',"cursor: grabbing;", "cursor: -webkit-grabbing;","}",".blocklyDragging.blocklyDraggingDelete {",'cursor: url("<<>>/handdelete.cur"), auto;',"}",".blocklyToolboxDelete {",'cursor: url("<<>>/handdelete.cur"), auto;',"}",".blocklyToolboxGrab {",'cursor: url("<<>>/handclosed.cur"), auto;',"cursor: grabbing;","cursor: -webkit-grabbing;","}",".blocklyDragging>.blocklyPath,",".blocklyDragging>.blocklyPathLight {","fill-opacity: .8;","stroke-opacity: .8;","}",".blocklyDragging>.blocklyPathDark {","display: none;", "}",".blocklyDisabled>.blocklyPath {","fill-opacity: .5;","stroke-opacity: .5;","}",".blocklyDisabled>.blocklyPathLight,",".blocklyDisabled>.blocklyPathDark {","display: none;","}",".blocklyText {","cursor: default;","fill: #fff;","font-family: sans-serif;","font-size: 11pt;","}",".blocklyNonEditableText>text {","pointer-events: none;","}",".blocklyNonEditableText>rect,",".blocklyEditableText>rect {","fill: #fff;","fill-opacity: .6;","}",".blocklyNonEditableText>text,",".blocklyEditableText>text {", "fill: #000;","}",".blocklyEditableText:hover>rect {","stroke: #fff;","stroke-width: 2;","}",".blocklyBubbleText {","fill: #000;","}",".blocklyFlyout {","position: absolute;","z-index: 20;","}",".blocklyFlyoutButton {","fill: #888;","cursor: default;","}",".blocklyFlyoutButtonShadow {","fill: #666;","}",".blocklyFlyoutButton:hover {","fill: #aaa;","}",".blocklyFlyoutLabel {","cursor: default;","}",".blocklyFlyoutLabelBackground {","opacity: 0;","}",".blocklyFlyoutLabelText {","fill: #000;","}",".blocklySvg text, .blocklyBlockDragSurface text {", "user-select: none;","-moz-user-select: none;","-ms-user-select: none;","-webkit-user-select: none;","cursor: inherit;","}",".blocklyHidden {","display: none;","}",".blocklyFieldDropdown:not(.blocklyHidden) {","display: block;","}",".blocklyIconGroup {","cursor: default;","}",".blocklyIconGroup:not(:hover),",".blocklyIconGroupReadonly {","opacity: .6;","}",".blocklyIconShape {","fill: #00f;","stroke: #fff;","stroke-width: 1px;","}",".blocklyIconSymbol {","fill: #fff;","}",".blocklyMinimalBody {", -"margin: 0;","padding: 0;","}",".blocklyCommentTextarea {","background-color: #ffc;","border: 0;","outline: 0;","margin: 0;","padding: 2px;","resize: none;","display: block;","overflow: hidden;","}",".blocklyHtmlInput {","border: none;","border-radius: 4px;","font-family: sans-serif;","height: 100%;","margin: 0;","outline: none;","padding: 0 1px;","width: 100%","}",".blocklyMainBackground {","stroke-width: 1;","stroke: #c6c6c6;","}",".blocklyMutatorBackground {","fill: #fff;","stroke: #ddd;","stroke-width: 1;", -"}",".blocklyFlyoutBackground {","fill: #ddd;","fill-opacity: .8;","}",".blocklyTransparentBackground {","opacity: 0;","}",".blocklyMainWorkspaceScrollbar {","z-index: 20;","}",".blocklyFlyoutScrollbar {","z-index: 30;","}",".blocklyScrollbarHorizontal, .blocklyScrollbarVertical {","position: absolute;","outline: none;","}",".blocklyScrollbarBackground {","opacity: 0;","}",".blocklyScrollbarHandle {","fill: #ccc;","}",".blocklyScrollbarBackground:hover+.blocklyScrollbarHandle,",".blocklyScrollbarHandle:hover {", -"fill: #bbb;","}",".blocklyZoom>image {","opacity: .4;","}",".blocklyZoom>image:hover {","opacity: .6;","}",".blocklyZoom>image:active {","opacity: .8;","}",".blocklyFlyout .blocklyScrollbarHandle {","fill: #bbb;","}",".blocklyFlyout .blocklyScrollbarBackground:hover+.blocklyScrollbarHandle,",".blocklyFlyout .blocklyScrollbarHandle:hover {","fill: #aaa;","}",".blocklyInvalidInput {","background: #faa;","}",".blocklyAngleCircle {","stroke: #444;","stroke-width: 1;","fill: #ddd;","fill-opacity: .8;", -"}",".blocklyAngleMarks {","stroke: #444;","stroke-width: 1;","}",".blocklyAngleGauge {","fill: #f88;","fill-opacity: .8;","}",".blocklyAngleLine {","stroke: #f00;","stroke-width: 2;","stroke-linecap: round;","pointer-events: none;","}",".blocklyContextMenu {","border-radius: 4px;","}",".blocklyDropdownMenu {","padding: 0 !important;","max-height: 300px !important;","}",".blocklyWidgetDiv .goog-option-selected .goog-menuitem-checkbox,",".blocklyWidgetDiv .goog-option-selected .goog-menuitem-icon {", -"background: url(<<>>/sprites.png) no-repeat -48px -16px !important;","}",".blocklyToolboxDiv {","background-color: #ddd;","overflow-x: visible;","overflow-y: auto;","position: absolute;","user-select: none;","-moz-user-select: none;","-ms-user-select: none;","-webkit-user-select: none;","z-index: 70;","-webkit-tap-highlight-color: transparent;","}",".blocklyTreeRoot {","padding: 4px 0;","}",".blocklyTreeRoot:focus {","outline: none;","}",".blocklyTreeRow {","height: 22px;","line-height: 22px;", -"margin-bottom: 3px;","padding-right: 8px;","white-space: nowrap;","}",".blocklyHorizontalTree {","float: left;","margin: 1px 5px 8px 0;","}",".blocklyHorizontalTreeRtl {","float: right;","margin: 1px 0 8px 5px;","}",'.blocklyToolboxDiv[dir="RTL"] .blocklyTreeRow {',"margin-left: 8px;","}",".blocklyTreeRow:not(.blocklyTreeSelected):hover {","background-color: #e4e4e4;","}",".blocklyTreeSeparator {","border-bottom: solid #e5e5e5 1px;","height: 0;","margin: 5px 0;","}",".blocklyTreeSeparatorHorizontal {", -"border-right: solid #e5e5e5 1px;","width: 0;","padding: 5px 0;","margin: 0 5px;","}",".blocklyTreeIcon {","background-image: url(<<>>/sprites.png);","height: 16px;","vertical-align: middle;","width: 16px;","}",".blocklyTreeIconClosedLtr {","background-position: -32px -1px;","}",".blocklyTreeIconClosedRtl {","background-position: 0 -1px;","}",".blocklyTreeIconOpen {","background-position: -16px -1px;","}",".blocklyTreeSelected>.blocklyTreeIconClosedLtr {","background-position: -32px -17px;", -"}",".blocklyTreeSelected>.blocklyTreeIconClosedRtl {","background-position: 0 -17px;","}",".blocklyTreeSelected>.blocklyTreeIconOpen {","background-position: -16px -17px;","}",".blocklyTreeIconNone,",".blocklyTreeSelected>.blocklyTreeIconNone {","background-position: -48px -1px;","}",".blocklyTreeLabel {","cursor: default;","font-family: sans-serif;","font-size: 16px;","padding: 0 3px;","vertical-align: middle;","}",".blocklyToolboxDelete .blocklyTreeLabel {",'cursor: url("<<>>/handdelete.cur"), auto;', -"}",".blocklyTreeSelected .blocklyTreeLabel {","color: #fff;","}",".blocklyWidgetDiv .goog-palette {","outline: none;","cursor: default;","}",".blocklyWidgetDiv .goog-palette-table {","border: 1px solid #666;","border-collapse: collapse;","}",".blocklyWidgetDiv .goog-palette-cell {","height: 13px;","width: 15px;","margin: 0;","border: 0;","text-align: center;","vertical-align: middle;","border-right: 1px solid #666;","font-size: 1px;","}",".blocklyWidgetDiv .goog-palette-colorswatch {","position: relative;", -"height: 13px;","width: 15px;","border: 1px solid #666;","}",".blocklyWidgetDiv .goog-palette-cell-hover .goog-palette-colorswatch {","border: 1px solid #FFF;","}",".blocklyWidgetDiv .goog-palette-cell-selected .goog-palette-colorswatch {","border: 1px solid #000;","color: #fff;","}",".blocklyWidgetDiv .goog-menu {","background: #fff;","border-color: #ccc #666 #666 #ccc;","border-style: solid;","border-width: 1px;","cursor: default;","font: normal 13px Arial, sans-serif;","margin: 0;","outline: none;", -"padding: 4px 0;","position: absolute;","overflow-y: auto;","overflow-x: hidden;","max-height: 100%;","z-index: 20000;","}",".blocklyWidgetDiv .goog-menuitem {","color: #000;","font: normal 13px Arial, sans-serif;","list-style: none;","margin: 0;","padding: 4px 7em 4px 28px;","white-space: nowrap;","}",".blocklyWidgetDiv .goog-menuitem.goog-menuitem-rtl {","padding-left: 7em;","padding-right: 28px;","}",".blocklyWidgetDiv .goog-menu-nocheckbox .goog-menuitem,",".blocklyWidgetDiv .goog-menu-noicon .goog-menuitem {", -"padding-left: 12px;","}",".blocklyWidgetDiv .goog-menu-noaccel .goog-menuitem {","padding-right: 20px;","}",".blocklyWidgetDiv .goog-menuitem-content {","color: #000;","font: normal 13px Arial, sans-serif;","}",".blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-accel,",".blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-content {","color: #ccc !important;","}",".blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-icon {","opacity: 0.3;","filter: alpha(opacity=30);","}",".blocklyWidgetDiv .goog-menuitem-highlight,", -".blocklyWidgetDiv .goog-menuitem-hover {","background-color: #d6e9f8;","border-color: #d6e9f8;","border-style: dotted;","border-width: 1px 0;","padding-bottom: 3px;","padding-top: 3px;","}",".blocklyWidgetDiv .goog-menuitem-checkbox,",".blocklyWidgetDiv .goog-menuitem-icon {","background-repeat: no-repeat;","height: 16px;","left: 6px;","position: absolute;","right: auto;","vertical-align: middle;","width: 16px;","}",".blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-checkbox,",".blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-icon {", -"left: auto;","right: 6px;","}",".blocklyWidgetDiv .goog-option-selected .goog-menuitem-checkbox,",".blocklyWidgetDiv .goog-option-selected .goog-menuitem-icon {","background: url(//ssl.gstatic.com/editor/editortoolbar.png) no-repeat -512px 0;","}",".blocklyWidgetDiv .goog-menuitem-accel {","color: #999;","direction: ltr;","left: auto;","padding: 0 6px;","position: absolute;","right: 0;","text-align: right;","}",".blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-accel {","left: 0;","right: auto;", -"text-align: left;","}",".blocklyWidgetDiv .goog-menuitem-mnemonic-hint {","text-decoration: underline;","}",".blocklyWidgetDiv .goog-menuitem-mnemonic-separator {","color: #999;","font-size: 12px;","padding-left: 4px;","}",".blocklyWidgetDiv .goog-menuseparator {","border-top: 1px solid #ccc;","margin: 4px 0;","padding: 0;","}",""];Blockly.WidgetDiv={};Blockly.WidgetDiv.DIV=null;Blockly.WidgetDiv.owner_=null;Blockly.WidgetDiv.dispose_=null;Blockly.WidgetDiv.createDom=function(){Blockly.WidgetDiv.DIV||(Blockly.WidgetDiv.DIV=goog.dom.createDom("DIV","blocklyWidgetDiv"),document.body.appendChild(Blockly.WidgetDiv.DIV))}; +"margin: 0;","padding: 0;","}",".blocklyCommentForeignObject {","position: relative;","z-index: 0;","}",".blocklyCommentRect {","fill: #E7DE8E;","stroke: #bcA903;","stroke-width: 1px","}",".blocklyCommentTarget {","fill: transparent;","stroke: #bcA903;","}",".blocklyCommentTargetFocused {","fill: none;","}",".blocklyCommentHandleTarget {","fill: none;","}",".blocklyCommentHandleTargetFocused {","fill: transparent;","}",".blocklyFocused>.blocklyCommentRect {","fill: #B9B272;","stroke: #B9B272;","}", +".blocklySelected>.blocklyCommentTarget {","stroke: #fc3;","stroke-width: 3px;","}",".blocklyCommentTextarea {","background-color: #fef49c;","border: 0;","outline: 0;","margin: 0;","padding: 3px;","resize: none;","display: block;","overflow: hidden;","}",".blocklyCommentDeleteIcon {","cursor: pointer;","fill: #000;","display: none","}",".blocklySelected > .blocklyCommentDeleteIcon {","display: block","}",".blocklyDeleteIconShape {","fill: #000;","stroke: #000;","stroke-width: 1px;","}",".blocklyDeleteIconShape.blocklyDeleteIconHighlighted {", +"stroke: #fc3;","}",".blocklyHtmlInput {","border: none;","border-radius: 4px;","font-family: sans-serif;","height: 100%;","margin: 0;","outline: none;","padding: 0 1px;","width: 100%","}",".blocklyMainBackground {","stroke-width: 1;","stroke: #c6c6c6;","}",".blocklyMutatorBackground {","fill: #fff;","stroke: #ddd;","stroke-width: 1;","}",".blocklyFlyoutBackground {","fill: #ddd;","fill-opacity: .8;","}",".blocklyTransparentBackground {","opacity: 0;","}",".blocklyMainWorkspaceScrollbar {","z-index: 20;", +"}",".blocklyFlyoutScrollbar {","z-index: 30;","}",".blocklyScrollbarHorizontal, .blocklyScrollbarVertical {","position: absolute;","outline: none;","}",".blocklyScrollbarBackground {","opacity: 0;","}",".blocklyScrollbarHandle {","fill: #ccc;","}",".blocklyScrollbarBackground:hover+.blocklyScrollbarHandle,",".blocklyScrollbarHandle:hover {","fill: #bbb;","}",".blocklyZoom>image {","opacity: .4;","}",".blocklyZoom>image:hover {","opacity: .6;","}",".blocklyZoom>image:active {","opacity: .8;","}", +".blocklyFlyout .blocklyScrollbarHandle {","fill: #bbb;","}",".blocklyFlyout .blocklyScrollbarBackground:hover+.blocklyScrollbarHandle,",".blocklyFlyout .blocklyScrollbarHandle:hover {","fill: #aaa;","}",".blocklyInvalidInput {","background: #faa;","}",".blocklyAngleCircle {","stroke: #444;","stroke-width: 1;","fill: #ddd;","fill-opacity: .8;","}",".blocklyAngleMarks {","stroke: #444;","stroke-width: 1;","}",".blocklyAngleGauge {","fill: #f88;","fill-opacity: .8;","}",".blocklyAngleLine {","stroke: #f00;", +"stroke-width: 2;","stroke-linecap: round;","pointer-events: none;","}",".blocklyContextMenu {","border-radius: 4px;","}",".blocklyDropdownMenu {","padding: 0 !important;","max-height: 300px !important;","}",".blocklyWidgetDiv .goog-option-selected .goog-menuitem-checkbox,",".blocklyWidgetDiv .goog-option-selected .goog-menuitem-icon {","background: url(<<>>/sprites.png) no-repeat -48px -16px !important;","}",".blocklyToolboxDiv {","background-color: #ddd;","overflow-x: visible;","overflow-y: auto;", +"position: absolute;","user-select: none;","-moz-user-select: none;","-ms-user-select: none;","-webkit-user-select: none;","z-index: 70;","-webkit-tap-highlight-color: transparent;","}",".blocklyTreeRoot {","padding: 4px 0;","}",".blocklyTreeRoot:focus {","outline: none;","}",".blocklyTreeRow {","height: 22px;","line-height: 22px;","margin-bottom: 3px;","padding-right: 8px;","white-space: nowrap;","}",".blocklyHorizontalTree {","float: left;","margin: 1px 5px 8px 0;","}",".blocklyHorizontalTreeRtl {", +"float: right;","margin: 1px 0 8px 5px;","}",'.blocklyToolboxDiv[dir="RTL"] .blocklyTreeRow {',"margin-left: 8px;","}",".blocklyTreeRow:not(.blocklyTreeSelected):hover {","background-color: #e4e4e4;","}",".blocklyTreeSeparator {","border-bottom: solid #e5e5e5 1px;","height: 0;","margin: 5px 0;","}",".blocklyTreeSeparatorHorizontal {","border-right: solid #e5e5e5 1px;","width: 0;","padding: 5px 0;","margin: 0 5px;","}",".blocklyTreeIcon {","background-image: url(<<>>/sprites.png);","height: 16px;", +"vertical-align: middle;","width: 16px;","}",".blocklyTreeIconClosedLtr {","background-position: -32px -1px;","}",".blocklyTreeIconClosedRtl {","background-position: 0 -1px;","}",".blocklyTreeIconOpen {","background-position: -16px -1px;","}",".blocklyTreeSelected>.blocklyTreeIconClosedLtr {","background-position: -32px -17px;","}",".blocklyTreeSelected>.blocklyTreeIconClosedRtl {","background-position: 0 -17px;","}",".blocklyTreeSelected>.blocklyTreeIconOpen {","background-position: -16px -17px;", +"}",".blocklyTreeIconNone,",".blocklyTreeSelected>.blocklyTreeIconNone {","background-position: -48px -1px;","}",".blocklyTreeLabel {","cursor: default;","font-family: sans-serif;","font-size: 16px;","padding: 0 3px;","vertical-align: middle;","}",".blocklyToolboxDelete .blocklyTreeLabel {",'cursor: url("<<>>/handdelete.cur"), auto;',"}",".blocklyTreeSelected .blocklyTreeLabel {","color: #fff;","}",".blocklyWidgetDiv .goog-palette {","outline: none;","cursor: default;","}",".blocklyWidgetDiv .goog-palette-table {", +"border: 1px solid #666;","border-collapse: collapse;","}",".blocklyWidgetDiv .goog-palette-cell {","height: 13px;","width: 15px;","margin: 0;","border: 0;","text-align: center;","vertical-align: middle;","border-right: 1px solid #666;","font-size: 1px;","}",".blocklyWidgetDiv .goog-palette-colorswatch {","position: relative;","height: 13px;","width: 15px;","border: 1px solid #666;","}",".blocklyWidgetDiv .goog-palette-cell-hover .goog-palette-colorswatch {","border: 1px solid #FFF;","}",".blocklyWidgetDiv .goog-palette-cell-selected .goog-palette-colorswatch {", +"border: 1px solid #000;","color: #fff;","}",".blocklyWidgetDiv .goog-menu {","background: #fff;","border-color: #ccc #666 #666 #ccc;","border-style: solid;","border-width: 1px;","cursor: default;","font: normal 13px Arial, sans-serif;","margin: 0;","outline: none;","padding: 4px 0;","position: absolute;","overflow-y: auto;","overflow-x: hidden;","max-height: 100%;","z-index: 20000;","}",".blocklyWidgetDiv .goog-menuitem {","color: #000;","font: normal 13px Arial, sans-serif;","list-style: none;", +"margin: 0;","padding: 4px 7em 4px 28px;","white-space: nowrap;","}",".blocklyWidgetDiv .goog-menuitem.goog-menuitem-rtl {","padding-left: 7em;","padding-right: 28px;","}",".blocklyWidgetDiv .goog-menu-nocheckbox .goog-menuitem,",".blocklyWidgetDiv .goog-menu-noicon .goog-menuitem {","padding-left: 12px;","}",".blocklyWidgetDiv .goog-menu-noaccel .goog-menuitem {","padding-right: 20px;","}",".blocklyWidgetDiv .goog-menuitem-content {","color: #000;","font: normal 13px Arial, sans-serif;","}",".blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-accel,", +".blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-content {","color: #ccc !important;","}",".blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-icon {","opacity: 0.3;","filter: alpha(opacity=30);","}",".blocklyWidgetDiv .goog-menuitem-highlight,",".blocklyWidgetDiv .goog-menuitem-hover {","background-color: #d6e9f8;","border-color: #d6e9f8;","border-style: dotted;","border-width: 1px 0;","padding-bottom: 3px;","padding-top: 3px;","}",".blocklyWidgetDiv .goog-menuitem-checkbox,",".blocklyWidgetDiv .goog-menuitem-icon {", +"background-repeat: no-repeat;","height: 16px;","left: 6px;","position: absolute;","right: auto;","vertical-align: middle;","width: 16px;","}",".blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-checkbox,",".blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-icon {","left: auto;","right: 6px;","}",".blocklyWidgetDiv .goog-option-selected .goog-menuitem-checkbox,",".blocklyWidgetDiv .goog-option-selected .goog-menuitem-icon {","background: url(//ssl.gstatic.com/editor/editortoolbar.png) no-repeat -512px 0;", +"}",".blocklyWidgetDiv .goog-menuitem-accel {","color: #999;","direction: ltr;","left: auto;","padding: 0 6px;","position: absolute;","right: 0;","text-align: right;","}",".blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-accel {","left: 0;","right: auto;","text-align: left;","}",".blocklyWidgetDiv .goog-menuitem-mnemonic-hint {","text-decoration: underline;","}",".blocklyWidgetDiv .goog-menuitem-mnemonic-separator {","color: #999;","font-size: 12px;","padding-left: 4px;","}",".blocklyWidgetDiv .goog-menuseparator {", +"border-top: 1px solid #ccc;","margin: 4px 0;","padding: 0;","}",""];Blockly.WidgetDiv={};Blockly.WidgetDiv.DIV=null;Blockly.WidgetDiv.owner_=null;Blockly.WidgetDiv.dispose_=null;Blockly.WidgetDiv.createDom=function(){Blockly.WidgetDiv.DIV||(Blockly.WidgetDiv.DIV=goog.dom.createDom("DIV","blocklyWidgetDiv"),document.body.appendChild(Blockly.WidgetDiv.DIV))}; Blockly.WidgetDiv.show=function(a,b,c){Blockly.WidgetDiv.hide();Blockly.WidgetDiv.owner_=a;Blockly.WidgetDiv.dispose_=c;a=goog.style.getViewportPageOffset(document);Blockly.WidgetDiv.DIV.style.top=a.y+"px";Blockly.WidgetDiv.DIV.style.direction=b?"rtl":"ltr";Blockly.WidgetDiv.DIV.style.display="block"}; Blockly.WidgetDiv.hide=function(){Blockly.WidgetDiv.owner_&&(Blockly.WidgetDiv.owner_=null,Blockly.WidgetDiv.DIV.style.display="none",Blockly.WidgetDiv.DIV.style.left="",Blockly.WidgetDiv.DIV.style.top="",Blockly.WidgetDiv.dispose_&&Blockly.WidgetDiv.dispose_(),Blockly.WidgetDiv.dispose_=null,goog.dom.removeChildren(Blockly.WidgetDiv.DIV))};Blockly.WidgetDiv.isVisible=function(){return!!Blockly.WidgetDiv.owner_};Blockly.WidgetDiv.hideIfOwner=function(a){Blockly.WidgetDiv.owner_==a&&Blockly.WidgetDiv.hide()}; Blockly.WidgetDiv.position=function(a,b,c,d,e){bc.width+d.x&&(a=c.width+d.x):a"+a.xml+"").firstChild};Blockly.Events.CommentCreate.prototype.run=function(a){var b=this.getEventWorkspace_();a?(a=goog.dom.createDom("xml"),a.appendChild(this.xml),Blockly.Xml.domToWorkspace(a,b)):(b=b.getCommentById(this.commentId))?b.dispose(!1,!1):console.warn("Can't uncreate non-existent comment: "+this.commentId)}; +Blockly.Events.CommentDelete=function(a){a&&(Blockly.Events.CommentDelete.superClass_.constructor.call(this,a),this.xml=a.toXmlWithXY())};goog.inherits(Blockly.Events.CommentDelete,Blockly.Events.CommentBase);Blockly.Events.CommentDelete.prototype.type=Blockly.Events.COMMENT_DELETE;Blockly.Events.CommentDelete.prototype.toJson=function(){return Blockly.Events.CommentDelete.superClass_.toJson.call(this)}; +Blockly.Events.CommentDelete.prototype.fromJson=function(a){Blockly.Events.CommentDelete.superClass_.fromJson.call(this,a)};Blockly.Events.CommentDelete.prototype.run=function(a){var b=this.getEventWorkspace_();a?(b=b.getCommentById(this.commentId))?b.dispose(!1,!1):console.warn("Can't uncreate non-existent comment: "+this.commentId):(a=goog.dom.createDom("xml"),a.appendChild(this.xml),Blockly.Xml.domToWorkspace(a,b))}; +Blockly.Events.CommentMove=function(a){a&&(Blockly.Events.CommentMove.superClass_.constructor.call(this,a),this.comment_=a,this.oldCoordinate_=a.getXY(),this.newCoordinate_=null)};goog.inherits(Blockly.Events.CommentMove,Blockly.Events.CommentBase);Blockly.Events.CommentMove.prototype.recordNew=function(){if(!this.comment_)throw Error("Tried to record the new position of a comment on the same event twice.");this.newCoordinate_=this.comment_.getXY();this.comment_=null}; +Blockly.Events.CommentMove.prototype.type=Blockly.Events.COMMENT_MOVE;Blockly.Events.CommentMove.prototype.setOldCoordinate=function(a){this.oldCoordinate_=a};Blockly.Events.CommentMove.prototype.toJson=function(){var a=Blockly.Events.CommentMove.superClass_.toJson.call(this);this.newCoordinate_&&(a.newCoordinate=Math.round(this.newCoordinate_.x)+","+Math.round(this.newCoordinate_.y));return a}; +Blockly.Events.CommentMove.prototype.fromJson=function(a){Blockly.Events.CommentMove.superClass_.fromJson.call(this,a);a.newCoordinate&&(a=a.newCoordinate.split(","),this.newCoordinate_=new goog.math.Coordinate(parseFloat(a[0]),parseFloat(a[1])))};Blockly.Events.CommentMove.prototype.isNull=function(){return goog.math.Coordinate.equals(this.oldCoordinate_,this.newCoordinate_)}; +Blockly.Events.CommentMove.prototype.run=function(a){var b=this.getEventWorkspace_().getCommentById(this.commentId);if(b){a=a?this.newCoordinate_:this.oldCoordinate_;var c=b.getXY();b.moveBy(a.x-c.x,a.y-c.y)}else console.warn("Can't move non-existent comment: "+this.commentId)};Blockly.WorkspaceComment=function(a,b,c,d,e){this.id=e&&!a.getCommentById(e)?e:Blockly.utils.genUid();a.addTopComment(this);this.xy_=new goog.math.Coordinate(0,0);this.height_=c;this.width_=d;this.workspace=a;this.RTL=a.RTL;this.movable_=this.deletable_=!0;this.content_=b;this.isComment=!0;Blockly.WorkspaceComment.fireCreateEvent(this)}; +Blockly.WorkspaceComment.prototype.dispose=function(){this.workspace&&(Blockly.Events.isEnabled()&&Blockly.Events.fire(new Blockly.Events.CommentDelete(this)),this.workspace.removeTopComment(this),this.workspace=null)};Blockly.WorkspaceComment.prototype.getHeight=function(){return this.height_};Blockly.WorkspaceComment.prototype.setHeight=function(a){this.height_=a};Blockly.WorkspaceComment.prototype.getWidth=function(){return this.width_}; +Blockly.WorkspaceComment.prototype.setWidth=function(a){this.width_=a};Blockly.WorkspaceComment.prototype.getXY=function(){return this.xy_.clone()};Blockly.WorkspaceComment.prototype.moveBy=function(a,b){var c=new Blockly.Events.CommentMove(this);this.xy_.translate(a,b);c.recordNew();Blockly.Events.fire(c)};Blockly.WorkspaceComment.prototype.isDeletable=function(){return this.deletable_&&!(this.workspace&&this.workspace.options.readOnly)}; +Blockly.WorkspaceComment.prototype.setDeletable=function(a){this.deletable_=a};Blockly.WorkspaceComment.prototype.isMovable=function(){return this.movable_&&!(this.workspace&&this.workspace.options.readOnly)};Blockly.WorkspaceComment.prototype.setMovable=function(a){this.movable_=a};Blockly.WorkspaceComment.prototype.getContent=function(){return this.content_}; +Blockly.WorkspaceComment.prototype.setContent=function(a){this.content_!=a&&(Blockly.Events.fire(new Blockly.Events.CommentChange(this,this.content_,a)),this.content_=a)};Blockly.WorkspaceComment.prototype.toXmlWithXY=function(a){a=this.toXml(a);a.setAttribute("x",Math.round(this.xy_.x));a.setAttribute("y",Math.round(this.xy_.y));a.setAttribute("h",this.height_);a.setAttribute("w",this.width_);return a}; +Blockly.WorkspaceComment.prototype.toXml=function(a){var b=goog.dom.createDom("comment");a||b.setAttribute("id",this.id);b.textContent=this.getContent();return b};Blockly.WorkspaceComment.fireCreateEvent=function(a){if(Blockly.Events.isEnabled()){var b=Blockly.Events.getGroup();b||Blockly.Events.setGroup(!0);try{Blockly.Events.fire(new Blockly.Events.CommentCreate(a))}finally{b||Blockly.Events.setGroup(!1)}}}; +Blockly.WorkspaceComment.fromXml=function(a,b){var c=Blockly.WorkspaceComment.parseAttributes(a);c=new Blockly.WorkspaceComment(b,c.content,c.h,c.w,c.id);var d=parseInt(a.getAttribute("x"),10),e=parseInt(a.getAttribute("y"),10);isNaN(d)||isNaN(e)||c.moveBy(d,e);Blockly.WorkspaceComment.fireCreateEvent(c);return c}; +Blockly.WorkspaceComment.parseAttributes=function(a){var b=a.getAttribute("h"),c=a.getAttribute("w");return{id:a.getAttribute("id"),h:b?parseInt(b,10):100,w:c?parseInt(c,10):100,x:parseInt(a.getAttribute("x"),10),y:parseInt(a.getAttribute("y"),10),content:a.textContent}}; +Blockly.Workspace=function(a){this.id=Blockly.utils.genUid();Blockly.Workspace.WorkspaceDB_[this.id]=this;this.options=a||{};this.RTL=!!this.options.RTL;this.horizontalLayout=!!this.options.horizontalLayout;this.toolboxPosition=this.options.toolboxPosition;this.topBlocks_=[];this.topComments_=[];this.commentDB_=Object.create(null);this.listeners_=[];this.undoStack_=[];this.redoStack_=[];this.blockDB_=Object.create(null);this.variableMap_=new Blockly.VariableMap(this);this.potentialVariableMap_=null}; +Blockly.Workspace.prototype.rendered=!1;Blockly.Workspace.prototype.MAX_UNDO=1024;Blockly.Workspace.prototype.dispose=function(){this.listeners_.length=0;this.clear();delete Blockly.Workspace.WorkspaceDB_[this.id]};Blockly.Workspace.SCAN_ANGLE=3;Blockly.Workspace.prototype.addTopBlock=function(a){this.topBlocks_.push(a)};Blockly.Workspace.prototype.removeTopBlock=function(a){if(!goog.array.remove(this.topBlocks_,a))throw"Block not present in workspace's list of top-most blocks.";}; Blockly.Workspace.prototype.getTopBlocks=function(a){var b=[].concat(this.topBlocks_);if(a&&1this.MAX_UNDO&&this.undoStack_.unshift());for(var b=0,c;c=this.listeners_[b];b++)c(a)}; -Blockly.Workspace.prototype.getBlockById=function(a){return this.blockDB_[a]||null};Blockly.Workspace.prototype.allInputsFilled=function(a){for(var b=this.getTopBlocks(!1),c=0,d;d=b[c];c++)if(!d.allInputsFilled(a))return!1;return!0};Blockly.Workspace.prototype.getPotentialVariableMap=function(){return this.potentialVariableMap_};Blockly.Workspace.prototype.createPotentialVariableMap=function(){this.potentialVariableMap_=new Blockly.VariableMap(this)};Blockly.Workspace.prototype.getVariableMap=function(){return this.variableMap_}; -Blockly.Workspace.WorkspaceDB_=Object.create(null);Blockly.Workspace.getById=function(a){return Blockly.Workspace.WorkspaceDB_[a]||null};Blockly.Workspace.prototype.clear=Blockly.Workspace.prototype.clear;Blockly.Workspace.prototype.clearUndo=Blockly.Workspace.prototype.clearUndo;Blockly.Workspace.prototype.addChangeListener=Blockly.Workspace.prototype.addChangeListener;Blockly.Workspace.prototype.removeChangeListener=Blockly.Workspace.prototype.removeChangeListener;Blockly.Bubble=function(a,b,c,d,e,f){this.workspace_=a;this.content_=b;this.shape_=c;c=Blockly.Bubble.ARROW_ANGLE;this.workspace_.RTL&&(c=-c);this.arrow_radians_=goog.math.toRadians(c);a.getBubbleCanvas().appendChild(this.createDom_(b,!(!e||!f)));this.setAnchorLocation(d);e&&f||(b=this.content_.getBBox(),e=b.width+2*Blockly.Bubble.BORDER_WIDTH,f=b.height+2*Blockly.Bubble.BORDER_WIDTH);this.setBubbleSize(e,f);this.positionBubble_();this.renderArrow_();this.rendered_=!0;a.options.readOnly||(Blockly.bindEventWithChecks_(this.bubbleBack_, +Blockly.Workspace.prototype.getBlockById=function(a){return this.blockDB_[a]||null};Blockly.Workspace.prototype.getCommentById=function(a){return this.commentDB_[a]||null};Blockly.Workspace.prototype.allInputsFilled=function(a){for(var b=this.getTopBlocks(!1),c=0,d;d=b[c];c++)if(!d.allInputsFilled(a))return!1;return!0};Blockly.Workspace.prototype.getPotentialVariableMap=function(){return this.potentialVariableMap_}; +Blockly.Workspace.prototype.createPotentialVariableMap=function(){this.potentialVariableMap_=new Blockly.VariableMap(this)};Blockly.Workspace.prototype.getVariableMap=function(){return this.variableMap_};Blockly.Workspace.WorkspaceDB_=Object.create(null);Blockly.Workspace.getById=function(a){return Blockly.Workspace.WorkspaceDB_[a]||null};Blockly.Workspace.prototype.clear=Blockly.Workspace.prototype.clear;Blockly.Workspace.prototype.clearUndo=Blockly.Workspace.prototype.clearUndo; +Blockly.Workspace.prototype.addChangeListener=Blockly.Workspace.prototype.addChangeListener;Blockly.Workspace.prototype.removeChangeListener=Blockly.Workspace.prototype.removeChangeListener;Blockly.Bubble=function(a,b,c,d,e,f){this.workspace_=a;this.content_=b;this.shape_=c;c=Blockly.Bubble.ARROW_ANGLE;this.workspace_.RTL&&(c=-c);this.arrow_radians_=goog.math.toRadians(c);a.getBubbleCanvas().appendChild(this.createDom_(b,!(!e||!f)));this.setAnchorLocation(d);e&&f||(b=this.content_.getBBox(),e=b.width+2*Blockly.Bubble.BORDER_WIDTH,f=b.height+2*Blockly.Bubble.BORDER_WIDTH);this.setBubbleSize(e,f);this.positionBubble_();this.renderArrow_();this.rendered_=!0;a.options.readOnly||(Blockly.bindEventWithChecks_(this.bubbleBack_, "mousedown",this,this.bubbleMouseDown_),this.resizeGroup_&&Blockly.bindEventWithChecks_(this.resizeGroup_,"mousedown",this,this.resizeMouseDown_))};Blockly.Bubble.BORDER_WIDTH=6;Blockly.Bubble.ARROW_THICKNESS=5;Blockly.Bubble.ARROW_ANGLE=20;Blockly.Bubble.ARROW_BEND=4;Blockly.Bubble.ANCHOR_RADIUS=8;Blockly.Bubble.onMouseUpWrapper_=null;Blockly.Bubble.onMouseMoveWrapper_=null;Blockly.Bubble.prototype.resizeCallback_=null; Blockly.Bubble.unbindDragEvents_=function(){Blockly.Bubble.onMouseUpWrapper_&&(Blockly.unbindEvent_(Blockly.Bubble.onMouseUpWrapper_),Blockly.Bubble.onMouseUpWrapper_=null);Blockly.Bubble.onMouseMoveWrapper_&&(Blockly.unbindEvent_(Blockly.Bubble.onMouseMoveWrapper_),Blockly.Bubble.onMouseMoveWrapper_=null)};Blockly.Bubble.bubbleMouseUp_=function(){Blockly.Touch.clearTouchIdentifier();Blockly.Bubble.unbindDragEvents_()};Blockly.Bubble.prototype.rendered_=!1;Blockly.Bubble.prototype.anchorXY_=null; Blockly.Bubble.prototype.relativeLeft_=0;Blockly.Bubble.prototype.relativeTop_=0;Blockly.Bubble.prototype.width_=0;Blockly.Bubble.prototype.height_=0;Blockly.Bubble.prototype.autoLayout_=!0; Blockly.Bubble.prototype.createDom_=function(a,b){this.bubbleGroup_=Blockly.utils.createSvgElement("g",{},null);var c={filter:"url(#"+this.workspace_.options.embossFilterId+")"};-1!=goog.userAgent.getUserAgentString().indexOf("JavaFX")&&(c={});c=Blockly.utils.createSvgElement("g",c,this.bubbleGroup_);this.bubbleArrow_=Blockly.utils.createSvgElement("path",{},c);this.bubbleBack_=Blockly.utils.createSvgElement("rect",{"class":"blocklyDraggable",x:0,y:0,rx:Blockly.Bubble.BORDER_WIDTH,ry:Blockly.Bubble.BORDER_WIDTH}, c);b?(this.resizeGroup_=Blockly.utils.createSvgElement("g",{"class":this.workspace_.RTL?"blocklyResizeSW":"blocklyResizeSE"},this.bubbleGroup_),c=2*Blockly.Bubble.BORDER_WIDTH,Blockly.utils.createSvgElement("polygon",{points:"0,x x,x x,0".replace(/x/g,c.toString())},this.resizeGroup_),Blockly.utils.createSvgElement("line",{"class":"blocklyResizeLine",x1:c/3,y1:c-1,x2:c-1,y2:c/3},this.resizeGroup_),Blockly.utils.createSvgElement("line",{"class":"blocklyResizeLine",x1:2*c/3,y1:c-1,x2:c-1,y2:2*c/3}, -this.resizeGroup_)):this.resizeGroup_=null;this.bubbleGroup_.appendChild(a);return this.bubbleGroup_};Blockly.Bubble.prototype.getSvgRoot=function(){return this.bubbleGroup_};Blockly.Bubble.prototype.setSvgId=function(a){this.bubbleGroup_.dataset&&(this.bubbleGroup_.dataset.blockId=a)};Blockly.Bubble.prototype.bubbleMouseDown_=function(a){var b=this.workspace_.getGesture(a);b&&b.handleBubbleStart(a,this)}; +this.resizeGroup_)):this.resizeGroup_=null;this.bubbleGroup_.appendChild(a);return this.bubbleGroup_};Blockly.Bubble.prototype.getSvgRoot=function(){return this.bubbleGroup_};Blockly.Bubble.prototype.setSvgId=function(a){this.bubbleGroup_.dataset&&(this.bubbleGroup_.dataset.blockId=a)};Blockly.Bubble.prototype.bubbleMouseDown_=function(a){var b=this.workspace_.getGesture(a);b&&b.handleBubbleStart(a,this)};Blockly.Bubble.prototype.showContextMenu_=function(a){}; +Blockly.Bubble.prototype.isDeletable=function(){return!1}; Blockly.Bubble.prototype.resizeMouseDown_=function(a){this.promote_();Blockly.Bubble.unbindDragEvents_();Blockly.utils.isRightButton(a)||(this.workspace_.startDrag(a,new goog.math.Coordinate(this.workspace_.RTL?-this.width_:this.width_,this.height_)),Blockly.Bubble.onMouseUpWrapper_=Blockly.bindEventWithChecks_(document,"mouseup",this,Blockly.Bubble.bubbleMouseUp_),Blockly.Bubble.onMouseMoveWrapper_=Blockly.bindEventWithChecks_(document,"mousemove",this,this.resizeMouseMove_),Blockly.hideChaff()); a.stopPropagation()};Blockly.Bubble.prototype.resizeMouseMove_=function(a){this.autoLayout_=!1;a=this.workspace_.moveDrag(a);this.setBubbleSize(this.workspace_.RTL?-a.x:a.x,a.y);this.workspace_.RTL&&this.positionBubble_()};Blockly.Bubble.prototype.registerResizeEvent=function(a){this.resizeCallback_=a};Blockly.Bubble.prototype.promote_=function(){var a=this.bubbleGroup_.parentNode;return a.lastChild!==this.bubbleGroup_?(a.appendChild(this.bubbleGroup_),!0):!1}; Blockly.Bubble.prototype.setAnchorLocation=function(a){this.anchorXY_=a;this.rendered_&&this.positionBubble_()}; @@ -1063,9 +1085,30 @@ Blockly.BlockDragger.prototype.endBlockDrag=function(a,b){this.dragBlock(a,b);th this.workspace_.setResizesEnabled(!0);if(c=this.workspace_.getToolbox())d=this.draggingBlock_.isDeletable()?"blocklyToolboxDelete":"blocklyToolboxGrab",c.removeStyle(d);Blockly.Events.setGroup(!1)};Blockly.BlockDragger.prototype.fireMoveEvent_=function(){var a=new Blockly.Events.BlockMove(this.draggingBlock_);a.oldCoordinate=this.startXY_;a.recordNew();Blockly.Events.fire(a)}; Blockly.BlockDragger.prototype.maybeDeleteBlock_=function(){var a=this.workspace_.trashcan;this.wouldDeleteBlock_?(a&&goog.Timer.callOnce(a.close,100,a),this.fireMoveEvent_(),this.draggingBlock_.dispose(!1,!0)):a&&a.close();return this.wouldDeleteBlock_}; Blockly.BlockDragger.prototype.updateCursorDuringBlockDrag_=function(){this.wouldDeleteBlock_=this.draggedConnectionManager_.wouldDeleteBlock();var a=this.workspace_.trashcan;this.wouldDeleteBlock_?(this.draggingBlock_.setDeleteStyle(!0),this.deleteArea_==Blockly.DELETE_AREA_TRASH&&a&&a.setOpen_(!0)):(this.draggingBlock_.setDeleteStyle(!1),a&&a.setOpen_(!1))}; -Blockly.BlockDragger.prototype.pixelsToWorkspaceUnits_=function(a){a=new goog.math.Coordinate(a.x/this.workspace_.scale,a.y/this.workspace_.scale);this.workspace_.isMutator&&(a=a.scale(1/this.workspace_.options.parentWorkspace.scale));return a};Blockly.BlockDragger.prototype.dragIcons_=function(a){for(var b=0;b"!=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){(a=(new DOMParser).parseFromString(a,"text/xml"))&&a.firstChild&&"xml"==a.firstChild.nodeName.toLowerCase()&&a.firstChild===a.lastChild||goog.asserts.fail("Blockly.Xml.textToDom did not obtain a valid XML tree.");return a.firstChild}; 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;h=this.remainingCapacity())){this.currentGesture_&&this.currentGesture_.cancel();Blockly.Events.disable();try{var b=Blockly.Xml.domToBlock(a,this),c=parseInt(a.getAttribute("x"),10),d=parseInt(a.getAttribute("y"),10);if(!isNaN(c)&&!isNaN(d)){this.RTL&&(c=-c);do{a=!1;for(var e=this.getAllBlocks(),f=0,g;g=e[f];f++){var h=g.getRelativeToSurfaceXY();if(1>=Math.abs(c-h.x)&&1>=Math.abs(d-h.y)){a=!0; -break}}if(!a){var k=b.getConnections_(!1);f=0;for(var m;m=k[f];f++)if(m.closest(Blockly.SNAP_RADIUS,new goog.math.Coordinate(c,d)).connection){a=!0;break}}a&&(c=this.RTL?c-Blockly.SNAP_RADIUS:c+Blockly.SNAP_RADIUS,d+=2*Blockly.SNAP_RADIUS)}while(a);b.moveBy(c,d)}}finally{Blockly.Events.enable()}Blockly.Events.isEnabled()&&!b.isShadow()&&Blockly.Events.fire(new Blockly.Events.BlockCreate(b));b.select()}}; +Blockly.WorkspaceSvg.prototype.paste=function(a){!this.rendered||a.getElementsByTagName("block").length>=this.remainingCapacity()||(this.currentGesture_&&this.currentGesture_.cancel(),"comment"==a.tagName.toLowerCase()?this.pasteWorkspaceComment_(a):this.pasteBlock_(a))}; +Blockly.WorkspaceSvg.prototype.pasteBlock_=function(a){Blockly.Events.disable();try{var b=Blockly.Xml.domToBlock(a,this),c=parseInt(a.getAttribute("x"),10),d=parseInt(a.getAttribute("y"),10);if(!isNaN(c)&&!isNaN(d)){this.RTL&&(c=-c);do{a=!1;for(var e=this.getAllBlocks(),f=0,g;g=e[f];f++){var h=g.getRelativeToSurfaceXY();if(1>=Math.abs(c-h.x)&&1>=Math.abs(d-h.y)){a=!0;break}}if(!a){var k=b.getConnections_(!1);f=0;for(var m;m=k[f];f++)if(m.closest(Blockly.SNAP_RADIUS,new goog.math.Coordinate(c,d)).connection){a= +!0;break}}a&&(c=this.RTL?c-Blockly.SNAP_RADIUS:c+Blockly.SNAP_RADIUS,d+=2*Blockly.SNAP_RADIUS)}while(a);b.moveBy(c,d)}}finally{Blockly.Events.enable()}Blockly.Events.isEnabled()&&!b.isShadow()&&Blockly.Events.fire(new Blockly.Events.BlockCreate(b));b.select()}; +Blockly.WorkspaceSvg.prototype.pasteWorkspaceComment_=function(a){Blockly.Events.disable();try{var b=Blockly.WorkspaceCommentSvg.fromXml(a,this),c=parseInt(a.getAttribute("x"),10),d=parseInt(a.getAttribute("y"),10);isNaN(c)||isNaN(d)||(this.RTL&&(c=-c),b.moveBy(c+50,d+50))}finally{Blockly.Events.enable()}Blockly.Events.isEnabled();b.select()}; Blockly.WorkspaceSvg.prototype.refreshToolboxSelection=function(){var a=this.isFlyout?this.targetWorkspace:this;a&&!a.currentGesture_&&a.toolbox_&&a.toolbox_.flyout_&&a.toolbox_.refreshSelection()};Blockly.WorkspaceSvg.prototype.renameVariableById=function(a,b){Blockly.WorkspaceSvg.superClass_.renameVariableById.call(this,a,b);this.refreshToolboxSelection()};Blockly.WorkspaceSvg.prototype.deleteVariableById=function(a){Blockly.WorkspaceSvg.superClass_.deleteVariableById.call(this,a);this.refreshToolboxSelection()}; Blockly.WorkspaceSvg.prototype.createVariable=function(a,b,c){a=Blockly.WorkspaceSvg.superClass_.createVariable.call(this,a,b,c);this.refreshToolboxSelection();return a};Blockly.WorkspaceSvg.prototype.recordDeleteAreas=function(){this.deleteAreaTrash_=this.trashcan&&this.svgGroup_.parentNode?this.trashcan.getClientRect():null;this.deleteAreaToolbox_=this.flyout_?this.flyout_.getClientRect():this.toolbox_?this.toolbox_.getClientRect():null}; Blockly.WorkspaceSvg.prototype.isDeleteArea=function(a){a=new goog.math.Coordinate(a.clientX,a.clientY);return this.deleteAreaTrash_&&this.deleteAreaTrash_.contains(a)?Blockly.DELETE_AREA_TRASH:this.deleteAreaToolbox_&&this.deleteAreaToolbox_.contains(a)?Blockly.DELETE_AREA_TOOLBOX:Blockly.DELETE_AREA_NONE};Blockly.WorkspaceSvg.prototype.onMouseDown_=function(a){var b=this.getGesture(a);b&&b.handleWsStart(a,this)}; Blockly.WorkspaceSvg.prototype.startDrag=function(a,b){var c=Blockly.utils.mouseToSvg(a,this.getParentSvg(),this.getInverseScreenCTM());c.x/=this.scale;c.y/=this.scale;this.dragDeltaXY_=goog.math.Coordinate.difference(b,c)};Blockly.WorkspaceSvg.prototype.moveDrag=function(a){a=Blockly.utils.mouseToSvg(a,this.getParentSvg(),this.getInverseScreenCTM());a.x/=this.scale;a.y/=this.scale;return goog.math.Coordinate.sum(this.dragDeltaXY_,a)}; Blockly.WorkspaceSvg.prototype.isDragging=function(){return null!=this.currentGesture_&&this.currentGesture_.isDragging()};Blockly.WorkspaceSvg.prototype.isDraggable=function(){return!!this.scrollbar};Blockly.WorkspaceSvg.prototype.onMouseWheel_=function(a){this.currentGesture_&&this.currentGesture_.cancel();var b=-a.deltaY/50,c=Blockly.utils.mouseToSvg(a,this.getParentSvg(),this.getInverseScreenCTM());this.zoom(c.x,c.y,b);a.preventDefault()}; -Blockly.WorkspaceSvg.prototype.getBlocksBoundingBox=function(){var a=this.getTopBlocks(!1);if(!a.length)return{x:0,y:0,width:0,height:0};for(var b=a[0].getBoundingRectangle(),c=1;cb.bottomRight.x&&(b.bottomRight.x=d.bottomRight.x);d.topLeft.yb.bottomRight.y&&(b.bottomRight.y=d.bottomRight.y)}return{x:b.topLeft.x,y:b.topLeft.y,width:b.bottomRight.x- -b.topLeft.x,height:b.bottomRight.y-b.topLeft.y}};Blockly.WorkspaceSvg.prototype.cleanUp=function(){this.setResizesEnabled(!1);Blockly.Events.setGroup(!0);for(var a=this.getTopBlocks(!0),b=0,c=0,d;d=a[c];c++){var e=d.getRelativeToSurfaceXY();d.moveBy(-e.x,b-e.y);d.snapToGrid();b=d.getRelativeToSurfaceXY().y+d.getHeightWidth().height+Blockly.BlockSvg.MIN_BLOCK_Y}Blockly.Events.setGroup(!1);this.setResizesEnabled(!0)}; +Blockly.WorkspaceSvg.prototype.getBlocksBoundingBox=function(){var a=this.getTopBlocks(!1),b=this.getTopComments(!1);a=a.concat(b);if(!a.length)return{x:0,y:0,width:0,height:0};b=a[0].getBoundingRectangle();for(var c=1;cb.bottomRight.x&&(b.bottomRight.x=d.bottomRight.x);d.topLeft.yb.bottomRight.y&&(b.bottomRight.y=d.bottomRight.y)}return{x:b.topLeft.x, +y:b.topLeft.y,width:b.bottomRight.x-b.topLeft.x,height:b.bottomRight.y-b.topLeft.y}};Blockly.WorkspaceSvg.prototype.cleanUp=function(){this.setResizesEnabled(!1);Blockly.Events.setGroup(!0);for(var a=this.getTopBlocks(!0),b=0,c=0,d;d=a[c];c++){var e=d.getRelativeToSurfaceXY();d.moveBy(-e.x,b-e.y);d.snapToGrid();b=d.getRelativeToSurfaceXY().y+d.getHeightWidth().height+Blockly.BlockSvg.MIN_BLOCK_Y}Blockly.Events.setGroup(!1);this.setResizesEnabled(!0)}; Blockly.WorkspaceSvg.prototype.showContextMenu_=function(a){function b(a){if(a.isDeletable())n=n.concat(a.getDescendants(!1));else{a=a.getChildren(!1);for(var c=0;cn.length?c():Blockly.confirm(Blockly.Msg.DELETE_ALL_BLOCKS.replace("%1",n.length),function(a){a&& @@ -1385,7 +1431,10 @@ Blockly.ContextMenu.position_=function(a,b,c){var d=Blockly.utils.getViewportBBo Blockly.ContextMenu.createWidget_=function(a){a.render(Blockly.WidgetDiv.DIV);var b=a.getElement();Blockly.utils.addClass(b,"blocklyContextMenu");Blockly.bindEventWithChecks_(b,"contextmenu",null,Blockly.utils.noEvent);a.setAllowAutoFocus(!0)};Blockly.ContextMenu.hide=function(){Blockly.WidgetDiv.hideIfOwner(Blockly.ContextMenu);Blockly.ContextMenu.currentBlock=null;Blockly.ContextMenu.eventWrapper_&&Blockly.unbindEvent_(Blockly.ContextMenu.eventWrapper_)}; Blockly.ContextMenu.callbackFactory=function(a,b){return function(){Blockly.Events.disable();try{var c=Blockly.Xml.domToBlock(b,a.workspace),d=a.getRelativeToSurfaceXY();d.x=a.RTL?d.x-Blockly.SNAP_RADIUS:d.x+Blockly.SNAP_RADIUS;d.y+=2*Blockly.SNAP_RADIUS;c.moveBy(d.x,d.y)}finally{Blockly.Events.enable()}Blockly.Events.isEnabled()&&!c.isShadow()&&Blockly.Events.fire(new Blockly.Events.BlockCreate(c));c.select()}}; Blockly.ContextMenu.blockDeleteOption=function(a){var b=a.getDescendants(!1).length,c=a.getNextBlock();c&&(b-=c.getDescendants(!1).length);return{text:1==b?Blockly.Msg.DELETE_BLOCK:Blockly.Msg.DELETE_X_BLOCKS.replace("%1",String(b)),enabled:!0,callback:function(){Blockly.Events.setGroup(!0);a.dispose(!0,!0);Blockly.Events.setGroup(!1)}}};Blockly.ContextMenu.blockHelpOption=function(a){return{enabled:!(goog.isFunction(a.helpUrl)?!a.helpUrl():!a.helpUrl),text:Blockly.Msg.HELP,callback:function(){a.showHelp_()}}}; -Blockly.ContextMenu.blockDuplicateOption=function(a){var b=!0;a.getDescendants(!1).length>a.workspace.remainingCapacity()&&(b=!1);return{text:Blockly.Msg.DUPLICATE_BLOCK,enabled:b,callback:function(){Blockly.duplicate_(a)}}};Blockly.ContextMenu.blockCommentOption=function(a){var b={enabled:!goog.userAgent.IE};a.comment?(b.text=Blockly.Msg.REMOVE_COMMENT,b.callback=function(){a.setCommentText(null)}):(b.text=Blockly.Msg.ADD_COMMENT,b.callback=function(){a.setCommentText("")});return b};Blockly.BlockSvg=function(a,b,c){this.svgGroup_=Blockly.utils.createSvgElement("g",{},null);this.svgGroup_.translate_="";this.svgPathDark_=Blockly.utils.createSvgElement("path",{"class":"blocklyPathDark",transform:"translate(1,1)"},this.svgGroup_);this.svgPath_=Blockly.utils.createSvgElement("path",{"class":"blocklyPath"},this.svgGroup_);this.svgPathLight_=Blockly.utils.createSvgElement("path",{"class":"blocklyPathLight"},this.svgGroup_);this.svgPath_.tooltip=this;this.rendered=!1;this.useDragSurface_= +Blockly.ContextMenu.blockDuplicateOption=function(a){var b=!0;a.getDescendants(!1).length>a.workspace.remainingCapacity()&&(b=!1);return{text:Blockly.Msg.DUPLICATE_BLOCK,enabled:b,callback:function(){Blockly.duplicate_(a)}}};Blockly.ContextMenu.blockCommentOption=function(a){var b={enabled:!goog.userAgent.IE};a.comment?(b.text=Blockly.Msg.REMOVE_COMMENT,b.callback=function(){a.setCommentText(null)}):(b.text=Blockly.Msg.ADD_COMMENT,b.callback=function(){a.setCommentText("")});return b}; +Blockly.ContextMenu.commentDeleteOption=function(a){return{text:Blockly.Msg.REMOVE_COMMENT,enabled:!0,callback:function(){Blockly.Events.setGroup(!0);a.dispose(!0,!0);Blockly.Events.setGroup(!1)}}};Blockly.ContextMenu.commentDuplicateOption=function(a){return{text:Blockly.Msg.DUPLICATE_COMMENT,enabled:!0,callback:function(){Blockly.duplicate_(a)}}}; +Blockly.ContextMenu.workspaceCommentOption=function(a,b){var c={enabled:!0};c.text=Blockly.Msg.ADD_COMMENT;c.callback=function(){var c=new Blockly.WorkspaceCommentSvg(a,Blockly.Msg.WORKSPACE_COMMENT_DEFAULT_TEXT,Blockly.WorkspaceCommentSvg.DEFAULT_SIZE,Blockly.WorkspaceCommentSvg.DEFAULT_SIZE),e=a.getInjectionDiv().getBoundingClientRect();e=new goog.math.Coordinate(b.clientX-e.left,b.clientY-e.top);var f=a.getOriginOffsetInPixels();e=goog.math.Coordinate.difference(e,f).scale(1/a.scale);c.moveBy(e.x, +e.y);a.rendered&&(c.initSvg(),c.render(!1),c.select())};return c};Blockly.BlockSvg=function(a,b,c){this.svgGroup_=Blockly.utils.createSvgElement("g",{},null);this.svgGroup_.translate_="";this.svgPathDark_=Blockly.utils.createSvgElement("path",{"class":"blocklyPathDark",transform:"translate(1,1)"},this.svgGroup_);this.svgPath_=Blockly.utils.createSvgElement("path",{"class":"blocklyPath"},this.svgGroup_);this.svgPathLight_=Blockly.utils.createSvgElement("path",{"class":"blocklyPathLight"},this.svgGroup_);this.svgPath_.tooltip=this;this.rendered=!1;this.useDragSurface_= Blockly.utils.is3dSupported()&&!!a.blockDragSurface_;Blockly.Tooltip.bindMouseEvents(this.svgPath_);Blockly.BlockSvg.superClass_.constructor.call(this,a,b,c);this.svgGroup_.dataset&&(this.svgGroup_.dataset.id=this.id)};goog.inherits(Blockly.BlockSvg,Blockly.Block);Blockly.BlockSvg.prototype.height=0;Blockly.BlockSvg.prototype.width=0;Blockly.BlockSvg.prototype.dragStartXY_=null;Blockly.BlockSvg.prototype.warningTextDb_=null;Blockly.BlockSvg.INLINE=-1; Blockly.BlockSvg.prototype.initSvg=function(){goog.asserts.assert(this.workspace.rendered,"Workspace is headless.");for(var a=0,b;b=this.inputList[a];a++)b.init();b=this.getIcons();for(a=0;a>>/g,Blockly.Css.mediaPath_);var d=document.createElement("style");document.head.insertBefore(d,document.head.firstChild);c=document.createTextNode(c);d.appendChild(c);Blockly.Css.styleSheet_=d.sheet}};Blockly.Css.setCursor=function(a){console.warn("Deprecated call to Blockly.Css.setCursor.See https://github.com/google/blockly/issues/981 for context")}; Blockly.Css.CONTENT=[".blocklySvg {","background-color: #fff;","outline: none;","overflow: hidden;","position: absolute;","display: block;","}",".blocklyWidgetDiv {","display: none;","position: absolute;","z-index: 99999;","}",".injectionDiv {","height: 100%;","position: relative;","overflow: hidden;","touch-action: none","}",".blocklyNonSelectable {","user-select: none;","-moz-user-select: none;","-ms-user-select: none;","-webkit-user-select: none;","}",".blocklyWsDragSurface {","display: none;", "position: absolute;","top: 0;","left: 0;","}",".blocklyWsDragSurface.blocklyOverflowVisible {","overflow: visible;","}",".blocklyBlockDragSurface {","display: none;","position: absolute;","top: 0;","left: 0;","right: 0;","bottom: 0;","overflow: visible !important;","z-index: 50;","}",".blocklyTooltipDiv {","background-color: #ffffc7;","border: 1px solid #ddc;","box-shadow: 4px 4px 20px 1px rgba(0,0,0,.15);","color: #000;","display: none;","font-family: sans-serif;","font-size: 9pt;","opacity: .9;", -"padding: 2px;","position: absolute;","z-index: 100000;","}",".blocklyResizeSE {","cursor: se-resize;","fill: #aaa;","}",".blocklyResizeSW {","cursor: sw-resize;","fill: #aaa;","}",".blocklyResizeLine {","stroke: #888;","stroke-width: 1;","}",".blocklyHighlightedConnectionPath {","fill: none;","stroke: #fc3;","stroke-width: 4px;","}",".blocklyPathLight {","fill: none;","stroke-linecap: round;","stroke-width: 1;","}",".blocklySelected>.blocklyPath {","stroke: #fc3;","stroke-width: 3px;","}",".blocklySelected>.blocklyPathLight {", +"padding: 2px;","position: absolute;","z-index: 100000;","}",".blocklyResizeSE {","cursor: se-resize;","fill: #aaa;","}",".blocklyResizeSW {","cursor: sw-resize;","fill: #aaa;","}",".blocklyResizeLine {","stroke: #515A5A;","stroke-width: 1;","}",".blocklyHighlightedConnectionPath {","fill: none;","stroke: #fc3;","stroke-width: 4px;","}",".blocklyPathLight {","fill: none;","stroke-linecap: round;","stroke-width: 1;","}",".blocklySelected>.blocklyPath {","stroke: #fc3;","stroke-width: 3px;","}",".blocklySelected>.blocklyPathLight {", "display: none;","}",".blocklyDraggable {",'cursor: url("<<>>/handopen.cur"), auto;',"cursor: grab;","cursor: -webkit-grab;","}",".blocklyDragging {",'cursor: url("<<>>/handclosed.cur"), auto;',"cursor: grabbing;","cursor: -webkit-grabbing;","}",".blocklyDraggable:active {",'cursor: url("<<>>/handclosed.cur"), auto;',"cursor: grabbing;","cursor: -webkit-grabbing;","}",".blocklyBlockDragSurface .blocklyDraggable {",'cursor: url("<<>>/handclosed.cur"), auto;',"cursor: grabbing;", "cursor: -webkit-grabbing;","}",".blocklyDragging.blocklyDraggingDelete {",'cursor: url("<<>>/handdelete.cur"), auto;',"}",".blocklyToolboxDelete {",'cursor: url("<<>>/handdelete.cur"), auto;',"}",".blocklyToolboxGrab {",'cursor: url("<<>>/handclosed.cur"), auto;',"cursor: grabbing;","cursor: -webkit-grabbing;","}",".blocklyDragging>.blocklyPath,",".blocklyDragging>.blocklyPathLight {","fill-opacity: .8;","stroke-opacity: .8;","}",".blocklyDragging>.blocklyPathDark {","display: none;", "}",".blocklyDisabled>.blocklyPath {","fill-opacity: .5;","stroke-opacity: .5;","}",".blocklyDisabled>.blocklyPathLight,",".blocklyDisabled>.blocklyPathDark {","display: none;","}",".blocklyText {","cursor: default;","fill: #fff;","font-family: sans-serif;","font-size: 11pt;","}",".blocklyNonEditableText>text {","pointer-events: none;","}",".blocklyNonEditableText>rect,",".blocklyEditableText>rect {","fill: #fff;","fill-opacity: .6;","}",".blocklyNonEditableText>text,",".blocklyEditableText>text {", "fill: #000;","}",".blocklyEditableText:hover>rect {","stroke: #fff;","stroke-width: 2;","}",".blocklyBubbleText {","fill: #000;","}",".blocklyFlyout {","position: absolute;","z-index: 20;","}",".blocklyFlyoutButton {","fill: #888;","cursor: default;","}",".blocklyFlyoutButtonShadow {","fill: #666;","}",".blocklyFlyoutButton:hover {","fill: #aaa;","}",".blocklyFlyoutLabel {","cursor: default;","}",".blocklyFlyoutLabelBackground {","opacity: 0;","}",".blocklyFlyoutLabelText {","fill: #000;","}",".blocklySvg text, .blocklyBlockDragSurface text {", "user-select: none;","-moz-user-select: none;","-ms-user-select: none;","-webkit-user-select: none;","cursor: inherit;","}",".blocklyHidden {","display: none;","}",".blocklyFieldDropdown:not(.blocklyHidden) {","display: block;","}",".blocklyIconGroup {","cursor: default;","}",".blocklyIconGroup:not(:hover),",".blocklyIconGroupReadonly {","opacity: .6;","}",".blocklyIconShape {","fill: #00f;","stroke: #fff;","stroke-width: 1px;","}",".blocklyIconSymbol {","fill: #fff;","}",".blocklyMinimalBody {", -"margin: 0;","padding: 0;","}",".blocklyCommentTextarea {","background-color: #ffc;","border: 0;","outline: 0;","margin: 0;","padding: 2px;","resize: none;","display: block;","overflow: hidden;","}",".blocklyHtmlInput {","border: none;","border-radius: 4px;","font-family: sans-serif;","height: 100%;","margin: 0;","outline: none;","padding: 0 1px;","width: 100%","}",".blocklyMainBackground {","stroke-width: 1;","stroke: #c6c6c6;","}",".blocklyMutatorBackground {","fill: #fff;","stroke: #ddd;","stroke-width: 1;", -"}",".blocklyFlyoutBackground {","fill: #ddd;","fill-opacity: .8;","}",".blocklyTransparentBackground {","opacity: 0;","}",".blocklyMainWorkspaceScrollbar {","z-index: 20;","}",".blocklyFlyoutScrollbar {","z-index: 30;","}",".blocklyScrollbarHorizontal, .blocklyScrollbarVertical {","position: absolute;","outline: none;","}",".blocklyScrollbarBackground {","opacity: 0;","}",".blocklyScrollbarHandle {","fill: #ccc;","}",".blocklyScrollbarBackground:hover+.blocklyScrollbarHandle,",".blocklyScrollbarHandle:hover {", -"fill: #bbb;","}",".blocklyZoom>image {","opacity: .4;","}",".blocklyZoom>image:hover {","opacity: .6;","}",".blocklyZoom>image:active {","opacity: .8;","}",".blocklyFlyout .blocklyScrollbarHandle {","fill: #bbb;","}",".blocklyFlyout .blocklyScrollbarBackground:hover+.blocklyScrollbarHandle,",".blocklyFlyout .blocklyScrollbarHandle:hover {","fill: #aaa;","}",".blocklyInvalidInput {","background: #faa;","}",".blocklyAngleCircle {","stroke: #444;","stroke-width: 1;","fill: #ddd;","fill-opacity: .8;", -"}",".blocklyAngleMarks {","stroke: #444;","stroke-width: 1;","}",".blocklyAngleGauge {","fill: #f88;","fill-opacity: .8;","}",".blocklyAngleLine {","stroke: #f00;","stroke-width: 2;","stroke-linecap: round;","pointer-events: none;","}",".blocklyContextMenu {","border-radius: 4px;","}",".blocklyDropdownMenu {","padding: 0 !important;","max-height: 300px !important;","}",".blocklyWidgetDiv .goog-option-selected .goog-menuitem-checkbox,",".blocklyWidgetDiv .goog-option-selected .goog-menuitem-icon {", -"background: url(<<>>/sprites.png) no-repeat -48px -16px !important;","}",".blocklyToolboxDiv {","background-color: #ddd;","overflow-x: visible;","overflow-y: auto;","position: absolute;","user-select: none;","-moz-user-select: none;","-ms-user-select: none;","-webkit-user-select: none;","z-index: 70;","-webkit-tap-highlight-color: transparent;","}",".blocklyTreeRoot {","padding: 4px 0;","}",".blocklyTreeRoot:focus {","outline: none;","}",".blocklyTreeRow {","height: 22px;","line-height: 22px;", -"margin-bottom: 3px;","padding-right: 8px;","white-space: nowrap;","}",".blocklyHorizontalTree {","float: left;","margin: 1px 5px 8px 0;","}",".blocklyHorizontalTreeRtl {","float: right;","margin: 1px 0 8px 5px;","}",'.blocklyToolboxDiv[dir="RTL"] .blocklyTreeRow {',"margin-left: 8px;","}",".blocklyTreeRow:not(.blocklyTreeSelected):hover {","background-color: #e4e4e4;","}",".blocklyTreeSeparator {","border-bottom: solid #e5e5e5 1px;","height: 0;","margin: 5px 0;","}",".blocklyTreeSeparatorHorizontal {", -"border-right: solid #e5e5e5 1px;","width: 0;","padding: 5px 0;","margin: 0 5px;","}",".blocklyTreeIcon {","background-image: url(<<>>/sprites.png);","height: 16px;","vertical-align: middle;","width: 16px;","}",".blocklyTreeIconClosedLtr {","background-position: -32px -1px;","}",".blocklyTreeIconClosedRtl {","background-position: 0 -1px;","}",".blocklyTreeIconOpen {","background-position: -16px -1px;","}",".blocklyTreeSelected>.blocklyTreeIconClosedLtr {","background-position: -32px -17px;", -"}",".blocklyTreeSelected>.blocklyTreeIconClosedRtl {","background-position: 0 -17px;","}",".blocklyTreeSelected>.blocklyTreeIconOpen {","background-position: -16px -17px;","}",".blocklyTreeIconNone,",".blocklyTreeSelected>.blocklyTreeIconNone {","background-position: -48px -1px;","}",".blocklyTreeLabel {","cursor: default;","font-family: sans-serif;","font-size: 16px;","padding: 0 3px;","vertical-align: middle;","}",".blocklyToolboxDelete .blocklyTreeLabel {",'cursor: url("<<>>/handdelete.cur"), auto;', -"}",".blocklyTreeSelected .blocklyTreeLabel {","color: #fff;","}",".blocklyWidgetDiv .goog-palette {","outline: none;","cursor: default;","}",".blocklyWidgetDiv .goog-palette-table {","border: 1px solid #666;","border-collapse: collapse;","}",".blocklyWidgetDiv .goog-palette-cell {","height: 13px;","width: 15px;","margin: 0;","border: 0;","text-align: center;","vertical-align: middle;","border-right: 1px solid #666;","font-size: 1px;","}",".blocklyWidgetDiv .goog-palette-colorswatch {","position: relative;", -"height: 13px;","width: 15px;","border: 1px solid #666;","}",".blocklyWidgetDiv .goog-palette-cell-hover .goog-palette-colorswatch {","border: 1px solid #FFF;","}",".blocklyWidgetDiv .goog-palette-cell-selected .goog-palette-colorswatch {","border: 1px solid #000;","color: #fff;","}",".blocklyWidgetDiv .goog-menu {","background: #fff;","border-color: #ccc #666 #666 #ccc;","border-style: solid;","border-width: 1px;","cursor: default;","font: normal 13px Arial, sans-serif;","margin: 0;","outline: none;", -"padding: 4px 0;","position: absolute;","overflow-y: auto;","overflow-x: hidden;","max-height: 100%;","z-index: 20000;","}",".blocklyWidgetDiv .goog-menuitem {","color: #000;","font: normal 13px Arial, sans-serif;","list-style: none;","margin: 0;","padding: 4px 7em 4px 28px;","white-space: nowrap;","}",".blocklyWidgetDiv .goog-menuitem.goog-menuitem-rtl {","padding-left: 7em;","padding-right: 28px;","}",".blocklyWidgetDiv .goog-menu-nocheckbox .goog-menuitem,",".blocklyWidgetDiv .goog-menu-noicon .goog-menuitem {", -"padding-left: 12px;","}",".blocklyWidgetDiv .goog-menu-noaccel .goog-menuitem {","padding-right: 20px;","}",".blocklyWidgetDiv .goog-menuitem-content {","color: #000;","font: normal 13px Arial, sans-serif;","}",".blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-accel,",".blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-content {","color: #ccc !important;","}",".blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-icon {","opacity: 0.3;","filter: alpha(opacity=30);","}",".blocklyWidgetDiv .goog-menuitem-highlight,", -".blocklyWidgetDiv .goog-menuitem-hover {","background-color: #d6e9f8;","border-color: #d6e9f8;","border-style: dotted;","border-width: 1px 0;","padding-bottom: 3px;","padding-top: 3px;","}",".blocklyWidgetDiv .goog-menuitem-checkbox,",".blocklyWidgetDiv .goog-menuitem-icon {","background-repeat: no-repeat;","height: 16px;","left: 6px;","position: absolute;","right: auto;","vertical-align: middle;","width: 16px;","}",".blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-checkbox,",".blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-icon {", -"left: auto;","right: 6px;","}",".blocklyWidgetDiv .goog-option-selected .goog-menuitem-checkbox,",".blocklyWidgetDiv .goog-option-selected .goog-menuitem-icon {","background: url(//ssl.gstatic.com/editor/editortoolbar.png) no-repeat -512px 0;","}",".blocklyWidgetDiv .goog-menuitem-accel {","color: #999;","direction: ltr;","left: auto;","padding: 0 6px;","position: absolute;","right: 0;","text-align: right;","}",".blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-accel {","left: 0;","right: auto;", -"text-align: left;","}",".blocklyWidgetDiv .goog-menuitem-mnemonic-hint {","text-decoration: underline;","}",".blocklyWidgetDiv .goog-menuitem-mnemonic-separator {","color: #999;","font-size: 12px;","padding-left: 4px;","}",".blocklyWidgetDiv .goog-menuseparator {","border-top: 1px solid #ccc;","margin: 4px 0;","padding: 0;","}",""];Blockly.WidgetDiv={};Blockly.WidgetDiv.DIV=null;Blockly.WidgetDiv.owner_=null;Blockly.WidgetDiv.dispose_=null;Blockly.WidgetDiv.createDom=function(){Blockly.WidgetDiv.DIV||(Blockly.WidgetDiv.DIV=goog.dom.createDom("DIV","blocklyWidgetDiv"),document.body.appendChild(Blockly.WidgetDiv.DIV))}; +"margin: 0;","padding: 0;","}",".blocklyCommentForeignObject {","position: relative;","z-index: 0;","}",".blocklyCommentRect {","fill: #E7DE8E;","stroke: #bcA903;","stroke-width: 1px","}",".blocklyCommentTarget {","fill: transparent;","stroke: #bcA903;","}",".blocklyCommentTargetFocused {","fill: none;","}",".blocklyCommentHandleTarget {","fill: none;","}",".blocklyCommentHandleTargetFocused {","fill: transparent;","}",".blocklyFocused>.blocklyCommentRect {","fill: #B9B272;","stroke: #B9B272;","}", +".blocklySelected>.blocklyCommentTarget {","stroke: #fc3;","stroke-width: 3px;","}",".blocklyCommentTextarea {","background-color: #fef49c;","border: 0;","outline: 0;","margin: 0;","padding: 3px;","resize: none;","display: block;","overflow: hidden;","}",".blocklyCommentDeleteIcon {","cursor: pointer;","fill: #000;","display: none","}",".blocklySelected > .blocklyCommentDeleteIcon {","display: block","}",".blocklyDeleteIconShape {","fill: #000;","stroke: #000;","stroke-width: 1px;","}",".blocklyDeleteIconShape.blocklyDeleteIconHighlighted {", +"stroke: #fc3;","}",".blocklyHtmlInput {","border: none;","border-radius: 4px;","font-family: sans-serif;","height: 100%;","margin: 0;","outline: none;","padding: 0 1px;","width: 100%","}",".blocklyMainBackground {","stroke-width: 1;","stroke: #c6c6c6;","}",".blocklyMutatorBackground {","fill: #fff;","stroke: #ddd;","stroke-width: 1;","}",".blocklyFlyoutBackground {","fill: #ddd;","fill-opacity: .8;","}",".blocklyTransparentBackground {","opacity: 0;","}",".blocklyMainWorkspaceScrollbar {","z-index: 20;", +"}",".blocklyFlyoutScrollbar {","z-index: 30;","}",".blocklyScrollbarHorizontal, .blocklyScrollbarVertical {","position: absolute;","outline: none;","}",".blocklyScrollbarBackground {","opacity: 0;","}",".blocklyScrollbarHandle {","fill: #ccc;","}",".blocklyScrollbarBackground:hover+.blocklyScrollbarHandle,",".blocklyScrollbarHandle:hover {","fill: #bbb;","}",".blocklyZoom>image {","opacity: .4;","}",".blocklyZoom>image:hover {","opacity: .6;","}",".blocklyZoom>image:active {","opacity: .8;","}", +".blocklyFlyout .blocklyScrollbarHandle {","fill: #bbb;","}",".blocklyFlyout .blocklyScrollbarBackground:hover+.blocklyScrollbarHandle,",".blocklyFlyout .blocklyScrollbarHandle:hover {","fill: #aaa;","}",".blocklyInvalidInput {","background: #faa;","}",".blocklyAngleCircle {","stroke: #444;","stroke-width: 1;","fill: #ddd;","fill-opacity: .8;","}",".blocklyAngleMarks {","stroke: #444;","stroke-width: 1;","}",".blocklyAngleGauge {","fill: #f88;","fill-opacity: .8;","}",".blocklyAngleLine {","stroke: #f00;", +"stroke-width: 2;","stroke-linecap: round;","pointer-events: none;","}",".blocklyContextMenu {","border-radius: 4px;","}",".blocklyDropdownMenu {","padding: 0 !important;","max-height: 300px !important;","}",".blocklyWidgetDiv .goog-option-selected .goog-menuitem-checkbox,",".blocklyWidgetDiv .goog-option-selected .goog-menuitem-icon {","background: url(<<>>/sprites.png) no-repeat -48px -16px !important;","}",".blocklyToolboxDiv {","background-color: #ddd;","overflow-x: visible;","overflow-y: auto;", +"position: absolute;","user-select: none;","-moz-user-select: none;","-ms-user-select: none;","-webkit-user-select: none;","z-index: 70;","-webkit-tap-highlight-color: transparent;","}",".blocklyTreeRoot {","padding: 4px 0;","}",".blocklyTreeRoot:focus {","outline: none;","}",".blocklyTreeRow {","height: 22px;","line-height: 22px;","margin-bottom: 3px;","padding-right: 8px;","white-space: nowrap;","}",".blocklyHorizontalTree {","float: left;","margin: 1px 5px 8px 0;","}",".blocklyHorizontalTreeRtl {", +"float: right;","margin: 1px 0 8px 5px;","}",'.blocklyToolboxDiv[dir="RTL"] .blocklyTreeRow {',"margin-left: 8px;","}",".blocklyTreeRow:not(.blocklyTreeSelected):hover {","background-color: #e4e4e4;","}",".blocklyTreeSeparator {","border-bottom: solid #e5e5e5 1px;","height: 0;","margin: 5px 0;","}",".blocklyTreeSeparatorHorizontal {","border-right: solid #e5e5e5 1px;","width: 0;","padding: 5px 0;","margin: 0 5px;","}",".blocklyTreeIcon {","background-image: url(<<>>/sprites.png);","height: 16px;", +"vertical-align: middle;","width: 16px;","}",".blocklyTreeIconClosedLtr {","background-position: -32px -1px;","}",".blocklyTreeIconClosedRtl {","background-position: 0 -1px;","}",".blocklyTreeIconOpen {","background-position: -16px -1px;","}",".blocklyTreeSelected>.blocklyTreeIconClosedLtr {","background-position: -32px -17px;","}",".blocklyTreeSelected>.blocklyTreeIconClosedRtl {","background-position: 0 -17px;","}",".blocklyTreeSelected>.blocklyTreeIconOpen {","background-position: -16px -17px;", +"}",".blocklyTreeIconNone,",".blocklyTreeSelected>.blocklyTreeIconNone {","background-position: -48px -1px;","}",".blocklyTreeLabel {","cursor: default;","font-family: sans-serif;","font-size: 16px;","padding: 0 3px;","vertical-align: middle;","}",".blocklyToolboxDelete .blocklyTreeLabel {",'cursor: url("<<>>/handdelete.cur"), auto;',"}",".blocklyTreeSelected .blocklyTreeLabel {","color: #fff;","}",".blocklyWidgetDiv .goog-palette {","outline: none;","cursor: default;","}",".blocklyWidgetDiv .goog-palette-table {", +"border: 1px solid #666;","border-collapse: collapse;","}",".blocklyWidgetDiv .goog-palette-cell {","height: 13px;","width: 15px;","margin: 0;","border: 0;","text-align: center;","vertical-align: middle;","border-right: 1px solid #666;","font-size: 1px;","}",".blocklyWidgetDiv .goog-palette-colorswatch {","position: relative;","height: 13px;","width: 15px;","border: 1px solid #666;","}",".blocklyWidgetDiv .goog-palette-cell-hover .goog-palette-colorswatch {","border: 1px solid #FFF;","}",".blocklyWidgetDiv .goog-palette-cell-selected .goog-palette-colorswatch {", +"border: 1px solid #000;","color: #fff;","}",".blocklyWidgetDiv .goog-menu {","background: #fff;","border-color: #ccc #666 #666 #ccc;","border-style: solid;","border-width: 1px;","cursor: default;","font: normal 13px Arial, sans-serif;","margin: 0;","outline: none;","padding: 4px 0;","position: absolute;","overflow-y: auto;","overflow-x: hidden;","max-height: 100%;","z-index: 20000;","}",".blocklyWidgetDiv .goog-menuitem {","color: #000;","font: normal 13px Arial, sans-serif;","list-style: none;", +"margin: 0;","padding: 4px 7em 4px 28px;","white-space: nowrap;","}",".blocklyWidgetDiv .goog-menuitem.goog-menuitem-rtl {","padding-left: 7em;","padding-right: 28px;","}",".blocklyWidgetDiv .goog-menu-nocheckbox .goog-menuitem,",".blocklyWidgetDiv .goog-menu-noicon .goog-menuitem {","padding-left: 12px;","}",".blocklyWidgetDiv .goog-menu-noaccel .goog-menuitem {","padding-right: 20px;","}",".blocklyWidgetDiv .goog-menuitem-content {","color: #000;","font: normal 13px Arial, sans-serif;","}",".blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-accel,", +".blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-content {","color: #ccc !important;","}",".blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-icon {","opacity: 0.3;","filter: alpha(opacity=30);","}",".blocklyWidgetDiv .goog-menuitem-highlight,",".blocklyWidgetDiv .goog-menuitem-hover {","background-color: #d6e9f8;","border-color: #d6e9f8;","border-style: dotted;","border-width: 1px 0;","padding-bottom: 3px;","padding-top: 3px;","}",".blocklyWidgetDiv .goog-menuitem-checkbox,",".blocklyWidgetDiv .goog-menuitem-icon {", +"background-repeat: no-repeat;","height: 16px;","left: 6px;","position: absolute;","right: auto;","vertical-align: middle;","width: 16px;","}",".blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-checkbox,",".blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-icon {","left: auto;","right: 6px;","}",".blocklyWidgetDiv .goog-option-selected .goog-menuitem-checkbox,",".blocklyWidgetDiv .goog-option-selected .goog-menuitem-icon {","background: url(//ssl.gstatic.com/editor/editortoolbar.png) no-repeat -512px 0;", +"}",".blocklyWidgetDiv .goog-menuitem-accel {","color: #999;","direction: ltr;","left: auto;","padding: 0 6px;","position: absolute;","right: 0;","text-align: right;","}",".blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-accel {","left: 0;","right: auto;","text-align: left;","}",".blocklyWidgetDiv .goog-menuitem-mnemonic-hint {","text-decoration: underline;","}",".blocklyWidgetDiv .goog-menuitem-mnemonic-separator {","color: #999;","font-size: 12px;","padding-left: 4px;","}",".blocklyWidgetDiv .goog-menuseparator {", +"border-top: 1px solid #ccc;","margin: 4px 0;","padding: 0;","}",""];Blockly.WidgetDiv={};Blockly.WidgetDiv.DIV=null;Blockly.WidgetDiv.owner_=null;Blockly.WidgetDiv.dispose_=null;Blockly.WidgetDiv.createDom=function(){Blockly.WidgetDiv.DIV||(Blockly.WidgetDiv.DIV=goog.dom.createDom("DIV","blocklyWidgetDiv"),document.body.appendChild(Blockly.WidgetDiv.DIV))}; Blockly.WidgetDiv.show=function(a,b,c){Blockly.WidgetDiv.hide();Blockly.WidgetDiv.owner_=a;Blockly.WidgetDiv.dispose_=c;a=goog.style.getViewportPageOffset(document);Blockly.WidgetDiv.DIV.style.top=a.y+"px";Blockly.WidgetDiv.DIV.style.direction=b?"rtl":"ltr";Blockly.WidgetDiv.DIV.style.display="block"}; Blockly.WidgetDiv.hide=function(){Blockly.WidgetDiv.owner_&&(Blockly.WidgetDiv.owner_=null,Blockly.WidgetDiv.DIV.style.display="none",Blockly.WidgetDiv.DIV.style.left="",Blockly.WidgetDiv.DIV.style.top="",Blockly.WidgetDiv.dispose_&&Blockly.WidgetDiv.dispose_(),Blockly.WidgetDiv.dispose_=null,goog.dom.removeChildren(Blockly.WidgetDiv.DIV))};Blockly.WidgetDiv.isVisible=function(){return!!Blockly.WidgetDiv.owner_};Blockly.WidgetDiv.hideIfOwner=function(a){Blockly.WidgetDiv.owner_==a&&Blockly.WidgetDiv.hide()}; Blockly.WidgetDiv.position=function(a,b,c,d,e){bc.width+d.x&&(a=c.width+d.x):a.blocklyCommentRect {', + 'fill: #B9B272;', + 'stroke: #B9B272;', + '}', + + '.blocklySelected>.blocklyCommentTarget {', + 'stroke: #fc3;', + 'stroke-width: 3px;', + '}', + + '.blocklyCommentTextarea {', - 'background-color: #ffc;', + 'background-color: #fef49c;', 'border: 0;', 'outline: 0;', 'margin: 0;', - 'padding: 2px;', + 'padding: 3px;', 'resize: none;', 'display: block;', 'overflow: hidden;', '}', + '.blocklyCommentDeleteIcon {', + 'cursor: pointer;', + 'fill: #000;', + 'display: none', + '}', + + '.blocklySelected > .blocklyCommentDeleteIcon {', + 'display: block', + '}', + + '.blocklyDeleteIconShape {', + 'fill: #000;', + 'stroke: #000;', + 'stroke-width: 1px;', + '}', + + '.blocklyDeleteIconShape.blocklyDeleteIconHighlighted {', + 'stroke: #fc3;', + '}', + '.blocklyHtmlInput {', 'border: none;', 'border-radius: 4px;', diff --git a/core/events.js b/core/events.js index 446e49153..cb2827171 100644 --- a/core/events.js +++ b/core/events.js @@ -126,6 +126,30 @@ Blockly.Events.VAR_RENAME = 'var_rename'; */ Blockly.Events.UI = 'ui'; +/** + * Name of event that creates a comment. + * @const + */ +Blockly.Events.COMMENT_CREATE = 'comment_create'; + +/** + * Name of event that deletes a comment. + * @const + */ +Blockly.Events.COMMENT_DELETE = 'comment_delete'; + +/** + * Name of event that changes a comment. + * @const + */ +Blockly.Events.COMMENT_CHANGE = 'comment_change'; + +/** + * Name of event that moves a comment. + * @const + */ +Blockly.Events.COMMENT_MOVE = 'comment_move'; + /** * List of events queued for firing. * @private @@ -328,6 +352,18 @@ Blockly.Events.fromJson = function(json, workspace) { case Blockly.Events.UI: event = new Blockly.Events.Ui(null); break; + case Blockly.Events.COMMENT_CREATE: + event = new Blockly.Events.CommentCreate(null); + break; + case Blockly.Events.COMMENT_CHANGE: + event = new Blockly.Events.CommentChange(null); + break; + case Blockly.Events.COMMENT_MOVE: + event = new Blockly.Events.CommentMove(null); + break; + case Blockly.Events.COMMENT_DELETE: + event = new Blockly.Events.CommentDelete(null); + break; default: throw 'Unknown event type.'; } diff --git a/core/gesture.js b/core/gesture.js index deac26c06..127bbf36b 100644 --- a/core/gesture.js +++ b/core/gesture.js @@ -469,7 +469,6 @@ Blockly.Gesture.prototype.startDraggingBubble_ = function() { this.bubbleDragger_.dragBubble(this.mostRecentEvent_, this.currentDragDeltaXY_); }; - /** * Start a gesture: update the workspace to indicate that a gesture is in * progress and bind mousemove and mouseup handlers. @@ -628,6 +627,8 @@ Blockly.Gesture.prototype.handleRightClick = function(e) { this.bringBlockToFront_(); Blockly.hideChaff(this.flyout_); this.targetBlock_.showContextMenu_(e); + } else if (this.startBubble_) { + this.startBubble_.showContextMenu_(e); } else if (this.startWorkspace_ && !this.flyout_) { Blockly.hideChaff(); this.startWorkspace_.showContextMenu_(e); @@ -706,8 +707,9 @@ Blockly.Gesture.prototype.handleBubbleStart = function(e, bubble) { * @private */ Blockly.Gesture.prototype.doBubbleClick_ = function() { - // TODO: This isn't really enough, is it. - this.startBubble_.promote_(); + // TODO (#1673): Consistent handling of single clicks. + this.startBubble_.setFocus && this.startBubble_.setFocus(); + this.startBubble_.select && this.startBubble_.select(); }; /** @@ -752,6 +754,7 @@ Blockly.Gesture.prototype.doWorkspaceClick_ = function() { } }; + /* End functions defining what actions to take to execute clicks on each type * of target. */ @@ -802,7 +805,8 @@ Blockly.Gesture.prototype.setStartBubble = function(bubble) { * @package */ Blockly.Gesture.prototype.setStartBlock = function(block) { - if (!this.startBlock_) { + // If the gesture already went through a bubble, don't set the start block. + if (!this.startBlock_ && !this.startBubble_) { this.startBlock_ = block; if (block.isInFlyout && block != block.getRootBlock()) { this.setTargetBlock_(block.getRootBlock()); @@ -849,6 +853,7 @@ Blockly.Gesture.prototype.setStartFlyout_ = function(flyout) { } }; + /* End functions for populating a gesture at mouse down. */ /* Begin helper functions defining types of clicks. Any developer wanting @@ -899,7 +904,8 @@ Blockly.Gesture.prototype.isFieldClick_ = function() { * @private */ Blockly.Gesture.prototype.isWorkspaceClick_ = function() { - var onlyTouchedWorkspace = !this.startBlock_ && !this.startField_; + var onlyTouchedWorkspace = !this.startBlock_ && !this.startBubble_ && + !this.startField_; return onlyTouchedWorkspace && !this.hasExceededDragRadius_; }; diff --git a/core/workspace.js b/core/workspace.js index c9459fa69..13815f7d2 100644 --- a/core/workspace.js +++ b/core/workspace.js @@ -27,6 +27,7 @@ goog.provide('Blockly.Workspace'); goog.require('Blockly.VariableMap'); +goog.require('Blockly.WorkspaceComment'); goog.require('goog.array'); goog.require('goog.math'); @@ -55,6 +56,16 @@ Blockly.Workspace = function(opt_options) { * @private */ this.topBlocks_ = []; + /** + * @type {!Array.} + * @private + */ + this.topComments_ = []; + /** + * @type {!Object} + * @private + */ + this.commentDB_ = Object.create(null); /** * @type {!Array.} * @private @@ -170,6 +181,61 @@ Blockly.Workspace.prototype.getTopBlocks = function(ordered) { return blocks; }; +/** + * Add a comment to the list of top comments. + * @param {!Blockly.WorkspaceComment} comment comment to add. + * @package + */ +Blockly.Workspace.prototype.addTopComment = function(comment) { + this.topComments_.push(comment); + + // Note: If the comment database starts to hold block comments, this may need + // to move to a separate function. + if (this.commentDB_[comment.id]) { + console.warn('Overriding an existing comment on this workspace, with id "' + + comment.id + '"'); + } + this.commentDB_[comment.id] = comment; +}; + +/** + * Remove a comment from the list of top comments. + * @param {!Blockly.WorkspaceComment} comment comment to remove. + * @package + */ +Blockly.Workspace.prototype.removeTopComment = function(comment) { + if (!goog.array.remove(this.topComments_, comment)) { + throw 'Comment not present in workspace\'s list of top-most comments.'; + } + // Note: If the comment database starts to hold block comments, this may need + // to move to a separate function. + delete this.commentDB_[comment.id]; +}; + +/** + * Finds the top-level comments and returns them. Comments are optionally sorted + * by position; top to bottom (with slight LTR or RTL bias). + * @param {boolean} ordered Sort the list if true. + * @return {!Array.} The top-level comment objects. + * @package + */ +Blockly.Workspace.prototype.getTopComments = function(ordered) { + // Copy the topComments_ list. + var comments = [].concat(this.topComments_); + if (ordered && comments.length > 1) { + var offset = Math.sin(goog.math.toRadians(Blockly.Workspace.SCAN_ANGLE)); + if (this.RTL) { + offset *= -1; + } + comments.sort(function(a, b) { + var aXY = a.getRelativeToSurfaceXY(); + var bXY = b.getRelativeToSurfaceXY(); + return (aXY.y + offset * aXY.x) - (bXY.y + offset * bXY.x); + }); + } + return comments; +}; + /** * Find all blocks in workspace. Blocks are optionally sorted * by position; top to bottom (with slight LTR or RTL bias). @@ -195,7 +261,7 @@ Blockly.Workspace.prototype.getAllBlocks = function(ordered) { }; /** - * Dispose of all blocks in workspace. + * Dispose of all blocks and comments in workspace. */ Blockly.Workspace.prototype.clear = function() { var existingGroup = Blockly.Events.getGroup(); @@ -205,6 +271,9 @@ Blockly.Workspace.prototype.clear = function() { while (this.topBlocks_.length) { this.topBlocks_[0].dispose(); } + while (this.topComments_.length) { + this.topComments_[this.topComments_.length - 1].dispose(); + } if (!existingGroup) { Blockly.Events.setGroup(false); } @@ -458,6 +527,17 @@ Blockly.Workspace.prototype.getBlockById = function(id) { return this.blockDB_[id] || null; }; +/** + * Find the comment on this workspace with the specified ID. + * @param {string} id ID of comment to find. + * @return {Blockly.WorkspaceComment} The sought after comment or null if not + * found. + * @package + */ +Blockly.Workspace.prototype.getCommentById = function(id) { + return this.commentDB_[id] || null; +}; + /** * Checks whether all value and statement inputs in the workspace are filled * with blocks. diff --git a/core/workspace_comment.js b/core/workspace_comment.js new file mode 100644 index 000000000..4941a01d4 --- /dev/null +++ b/core/workspace_comment.js @@ -0,0 +1,370 @@ +/** + * @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 Object representing a code comment on the workspace. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.WorkspaceComment'); + +goog.require('Blockly.Events.CommentChange'); +goog.require('Blockly.Events.CommentCreate'); +goog.require('Blockly.Events.CommentDelete'); +goog.require('Blockly.Events.CommentMove'); + +goog.require('goog.math.Coordinate'); + + +/** + * Class for a workspace comment. + * @param {!Blockly.Workspace} workspace The block's workspace. + * @param {string} content The content of this workspace comment. + * @param {number} height Height of the comment. + * @param {number} width Width of the comment. + * @param {string=} opt_id Optional ID. Use this ID if provided, otherwise + * create a new ID. + * @constructor + */ +Blockly.WorkspaceComment = function(workspace, content, height, width, opt_id) { + /** @type {string} */ + this.id = (opt_id && !workspace.getCommentById(opt_id)) ? + opt_id : Blockly.utils.genUid(); + + workspace.addTopComment(this); + + /** + * The comment's position in workspace units. (0, 0) is at the workspace's + * origin; scale does not change this value. + * @type {!goog.math.Coordinate} + * @protected + */ + this.xy_ = new goog.math.Coordinate(0, 0); + + /** + * The comment's height in workspace units. Scale does not change this value. + * @type {number} + * @private + */ + this.height_ = height; + + /** + * The comment's width in workspace units. Scale does not change this value. + * @type {number} + * @private + */ + this.width_ = width; + + /** + * @type {!Blockly.Workspace} + */ + this.workspace = workspace; + + /** + * @protected + * @type {boolean} + */ + this.RTL = workspace.RTL; + + /** + * @type {boolean} + * @private + */ + this.deletable_ = true; + + /** + * @type {boolean} + * @private + */ + this.movable_ = true; + + /** + * @protected + * @type {!string} + */ + this.content_ = content; + + /** + * @package + * @type {boolean} + */ + this.isComment = true; + + Blockly.WorkspaceComment.fireCreateEvent(this); +}; + +/** + * Dispose of this comment. + * @package + */ +Blockly.WorkspaceComment.prototype.dispose = function() { + if (!this.workspace) { + // The comment has already been deleted. + return; + } + + if (Blockly.Events.isEnabled()) { + Blockly.Events.fire(new Blockly.Events.CommentDelete(this)); + } + + // Remove from the list of top comments and the comment database. + this.workspace.removeTopComment(this); + this.workspace = null; +}; + +// Height, width, x, and y are all stored on even non-rendered comments, to +// preserve state if you pass the contents through a headless workspace. + +/** + * Get comment height. + * @return {number} comment height. + * @package + */ +Blockly.WorkspaceComment.prototype.getHeight = function() { + return this.height_; +}; + +/** + * Set comment height. + * @param {number} height comment height. + * @package + */ +Blockly.WorkspaceComment.prototype.setHeight = function(height) { + this.height_ = height; +}; + +/** + * Get comment width. + * @return {number} comment width. + * @package + */ +Blockly.WorkspaceComment.prototype.getWidth = function() { + return this.width_; +}; + +/** + * Set comment width. + * @param {number} width comment width. + * @package + */ +Blockly.WorkspaceComment.prototype.setWidth = function(width) { + this.width_ = width; +}; + +/** + * Get stored location. + * @return {!goog.math.Coordinate} The comment's stored location. This is not + * valid if the comment is currently being dragged. + * @package + */ +Blockly.WorkspaceComment.prototype.getXY = function() { + return this.xy_.clone(); +}; + +/** + * Move a comment by a relative offset. + * @param {number} dx Horizontal offset, in workspace units. + * @param {number} dy Vertical offset, in workspace units. + * @package + */ +Blockly.WorkspaceComment.prototype.moveBy = function(dx, dy) { + var event = new Blockly.Events.CommentMove(this); + this.xy_.translate(dx, dy); + event.recordNew(); + Blockly.Events.fire(event); +}; + +/** + * Get whether this comment is deletable or not. + * @return {boolean} True if deletable. + * @package + */ +Blockly.WorkspaceComment.prototype.isDeletable = function() { + return this.deletable_ && + !(this.workspace && this.workspace.options.readOnly); +}; + +/** + * Set whether this comment is deletable or not. + * @param {boolean} deletable True if deletable. + * @package + */ +Blockly.WorkspaceComment.prototype.setDeletable = function(deletable) { + this.deletable_ = deletable; +}; + +/** + * Get whether this comment is movable or not. + * @return {boolean} True if movable. + * @package + */ +Blockly.WorkspaceComment.prototype.isMovable = function() { + return this.movable_ && + !(this.workspace && this.workspace.options.readOnly); +}; + +/** + * Set whether this comment is movable or not. + * @param {boolean} movable True if movable. + * @package + */ +Blockly.WorkspaceComment.prototype.setMovable = function(movable) { + this.movable_ = movable; +}; + +/** + * Returns this comment's text. + * @return {string} Comment text. + * @package + */ +Blockly.WorkspaceComment.prototype.getContent = function() { + return this.content_; +}; + +/** + * Set this comment's content. + * @param {string} content Comment content. + * @package + */ +Blockly.WorkspaceComment.prototype.setContent = function(content) { + if (this.content_ != content) { + Blockly.Events.fire( + new Blockly.Events.CommentChange(this, this.content_, content)); + this.content_ = content; + } +}; + +/** + * Encode a comment subtree as XML with XY coordinates. + * @param {boolean=} opt_noId True if the encoder should skip the comment id. + * @return {!Element} Tree of XML elements. + * @package + */ +Blockly.WorkspaceComment.prototype.toXmlWithXY = function(opt_noId) { + var element = this.toXml(opt_noId); + element.setAttribute('x', Math.round(this.xy_.x)); + element.setAttribute('y', Math.round(this.xy_.y)); + element.setAttribute('h', this.height_); + element.setAttribute('w', this.width_); + return element; +}; + +/** + * Encode a comment subtree as XML, but don't serialize the XY coordinates. + * This method avoids some expensive metrics-related calls that are made in + * toXmlWithXY(). + * @param {boolean=} opt_noId True if the encoder should skip the comment id. + * @return {!Element} Tree of XML elements. + * @package + */ +Blockly.WorkspaceComment.prototype.toXml = function(opt_noId) { + var commentElement = goog.dom.createDom('comment'); + if (!opt_noId) { + commentElement.setAttribute('id', this.id); + } + commentElement.textContent = this.getContent(); + return commentElement; +}; + +/** + * Fire a create event for the given workspace comment, if comments are enabled. + * @param {!Blockly.WorkspaceComment} comment The comment that was just created. + * @package + */ +Blockly.WorkspaceComment.fireCreateEvent = function(comment) { + if (Blockly.Events.isEnabled()) { + var existingGroup = Blockly.Events.getGroup(); + if (!existingGroup) { + Blockly.Events.setGroup(true); + } + try { + Blockly.Events.fire(new Blockly.Events.CommentCreate(comment)); + } finally { + if (!existingGroup) { + Blockly.Events.setGroup(false); + } + } + } +}; + +/** + * Decode an XML comment tag and create a comment on the workspace. + * @param {!Element} xmlComment XML comment element. + * @param {!Blockly.Workspace} workspace The workspace. + * @return {!Blockly.WorkspaceComment} The created workspace comment. + * @package + */ +Blockly.WorkspaceComment.fromXml = function(xmlComment, workspace) { + var info = Blockly.WorkspaceComment.parseAttributes(xmlComment); + + var comment = new Blockly.WorkspaceComment( + workspace, info.content, info.h, info.w, info.id); + + var commentX = parseInt(xmlComment.getAttribute('x'), 10); + var commentY = parseInt(xmlComment.getAttribute('y'), 10); + if (!isNaN(commentX) && !isNaN(commentY)) { + comment.moveBy(commentX, commentY); + } + + Blockly.WorkspaceComment.fireCreateEvent(comment); + return comment; +}; + +/** + * Decode an XML comment tag and return the results in an object. + * @param {!Element} xml XML comment element. + * @return {!Object} An object containing the information about the comment. + * @package + */ +Blockly.WorkspaceComment.parseAttributes = function(xml) { + var xmlH = xml.getAttribute('h'); + var xmlW = xml.getAttribute('w'); + + return { + /* @type {string} */ + id: xml.getAttribute('id'), + /** + * The height of the comment in workspace units, or 100 if not specified. + * @type {number} + */ + h: xmlH ? parseInt(xmlH, 10) : 100, + /** + * The width of the comment in workspace units, or 100 if not specified. + * @type {number} + */ + w: xmlW ? parseInt(xmlW, 10) : 100, + /** + * The x position of the comment in workspace coordinates, or NaN if not + * specified in the XML. + * @type {number} + */ + x: parseInt(xml.getAttribute('x'), 10), + /** + * The y position of the comment in workspace coordinates, or NaN if not + * specified in the XML. + * @type {number} + */ + y: parseInt(xml.getAttribute('y'), 10), + /* @type {string} */ + content: xml.textContent + }; +}; + diff --git a/core/workspace_comment_render_svg.js b/core/workspace_comment_render_svg.js new file mode 100644 index 000000000..1a8dcbbe9 --- /dev/null +++ b/core/workspace_comment_render_svg.js @@ -0,0 +1,463 @@ +/** + * @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 Methods for rendering a workspace comment as SVG + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.WorkspaceCommentSvg.render'); + +goog.require('Blockly.WorkspaceCommentSvg'); + + +/** + * Size of the resize icon. + * @type {number} + * @const + * @private + */ +Blockly.WorkspaceCommentSvg.RESIZE_SIZE = 8; + +/** + * Radius of the border around the comment. + * @type {number} + * @const + * @private + */ +Blockly.WorkspaceCommentSvg.BORDER_RADIUS = 3; + +/** + * Offset from the foreignobject edge to the textarea edge. + * @type {number} + * @const + * @private + */ +Blockly.WorkspaceCommentSvg.TEXTAREA_OFFSET = 2; + +/** + * Offset from the top to make room for a top bar. + * @type {number} + * @const + * @private + */ +Blockly.WorkspaceCommentSvg.TOP_OFFSET = 10; + +/** + * Returns a bounding box describing the dimensions of this comment. + * @return {!{height: number, width: number}} Object with height and width + * properties in workspace units. + * @package + */ +Blockly.WorkspaceCommentSvg.prototype.getHeightWidth = function() { + return { width: this.getWidth(), height: this.getHeight() }; +}; + +/** + * Renders the workspace comment. + * @package + */ +Blockly.WorkspaceCommentSvg.prototype.render = function() { + if (this.rendered_) { + return; + } + + var size = this.getHeightWidth(); + + // Add text area + this.createEditor_(); + this.svgGroup_.appendChild(this.foreignObject_); + + this.svgHandleTarget_ = Blockly.utils.createSvgElement('rect', + { + 'class': 'blocklyCommentHandleTarget', + 'x': 0, + 'y': 0 + }); + this.svgGroup_.appendChild(this.svgHandleTarget_); + this.svgRectTarget_ = Blockly.utils.createSvgElement('rect', + { + 'class': 'blocklyCommentTarget', + 'x': 0, + 'y': 0, + 'rx': Blockly.WorkspaceCommentSvg.BORDER_RADIUS, + 'ry': Blockly.WorkspaceCommentSvg.BORDER_RADIUS + }); + this.svgGroup_.appendChild(this.svgRectTarget_); + + // Add the resize icon + this.addResizeDom_(); + if (this.isDeletable()) { + // Add the delete icon + this.addDeleteDom_(); + } + + this.setSize_(size.width, size.height); + + // Set the content + this.textarea_.value = this.content_; + + this.rendered_ = true; + + if (this.resizeGroup_) { + Blockly.bindEventWithChecks_( + this.resizeGroup_, 'mousedown', this, this.resizeMouseDown_); + } + + if (this.isDeletable()) { + Blockly.bindEventWithChecks_( + this.deleteGroup_, 'mousedown', this, this.deleteMouseDown_); + Blockly.bindEventWithChecks_( + this.deleteGroup_, 'mouseout', this, this.deleteMouseOut_); + Blockly.bindEventWithChecks_( + this.deleteGroup_, 'mouseup', this, this.deleteMouseUp_); + } +}; + +/** + * Create the text area for the comment. + * @return {!Element} The top-level node of the editor. + * @private + */ +Blockly.WorkspaceCommentSvg.prototype.createEditor_ = function() { + /* Create the editor. Here's the markup that will be generated: + + + + + + */ + this.foreignObject_ = Blockly.utils.createSvgElement( + 'foreignObject', + { + 'x': 0, + 'y': Blockly.WorkspaceCommentSvg.TOP_OFFSET, + 'class': 'blocklyCommentForeignObject' + }, + null); + var body = document.createElementNS(Blockly.HTML_NS, 'body'); + body.setAttribute('xmlns', Blockly.HTML_NS); + body.className = 'blocklyMinimalBody'; + var textarea = document.createElementNS(Blockly.HTML_NS, 'textarea'); + textarea.className = 'blocklyCommentTextarea'; + textarea.setAttribute('dir', this.RTL ? 'RTL' : 'LTR'); + body.appendChild(textarea); + this.textarea_ = textarea; + this.foreignObject_.appendChild(body); + // Don't zoom with mousewheel. + Blockly.bindEventWithChecks_(textarea, 'wheel', this, function(e) { + e.stopPropagation(); + }); + Blockly.bindEventWithChecks_(textarea, 'change', this, function( + /* eslint-disable no-unused-vars */ e + /* eslint-enable no-unused-vars */) { + this.setContent(textarea.value); + }); + return this.foreignObject_; +}; + +/** + * Add the resize icon to the DOM + * @private + */ +Blockly.WorkspaceCommentSvg.prototype.addResizeDom_ = function() { + this.resizeGroup_ = Blockly.utils.createSvgElement( + 'g', + { + 'class': this.RTL ? 'blocklyResizeSW' : 'blocklyResizeSE' + }, + this.svgGroup_); + var resizeSize = Blockly.WorkspaceCommentSvg.RESIZE_SIZE; + Blockly.utils.createSvgElement( + 'polygon', + {'points': '0,x x,x x,0'.replace(/x/g, resizeSize.toString())}, + this.resizeGroup_); + Blockly.utils.createSvgElement( + 'line', + { + 'class': 'blocklyResizeLine', + 'x1': resizeSize / 3, 'y1': resizeSize - 1, + 'x2': resizeSize - 1, 'y2': resizeSize / 3 + }, this.resizeGroup_); + Blockly.utils.createSvgElement( + 'line', + { + 'class': 'blocklyResizeLine', + 'x1': resizeSize * 2 / 3, 'y1': resizeSize - 1, + 'x2': resizeSize - 1, 'y2': resizeSize * 2 / 3 + }, this.resizeGroup_); +}; + +/** + * Add the delete icon to the DOM + * @private + */ +Blockly.WorkspaceCommentSvg.prototype.addDeleteDom_ = function() { + this.deleteGroup_ = Blockly.utils.createSvgElement( + 'g', + { + 'class': 'blocklyCommentDeleteIcon' + }, + this.svgGroup_); + this.deleteIconBorder_ = Blockly.utils.createSvgElement('circle', + { + 'class': 'blocklyDeleteIconShape', + 'r': '7', + 'cx': '7.5', + 'cy': '7.5' + }, + this.deleteGroup_); + // x icon. + Blockly.utils.createSvgElement( + 'line', + { + 'x1': '5', 'y1': '10', + 'x2': '10', 'y2': '5', + 'stroke': '#fff', + 'stroke-width': '2' + }, + this.deleteGroup_); + Blockly.utils.createSvgElement( + 'line', + { + 'x1': '5', 'y1': '5', + 'x2': '10', 'y2': '10', + 'stroke': '#fff', + 'stroke-width': '2' + }, + this.deleteGroup_); +}; + +/** + * Handle a mouse-down on comment's resize corner. + * @param {!Event} e Mouse down event. + * @private + */ +Blockly.WorkspaceCommentSvg.prototype.resizeMouseDown_ = function(e) { + //this.promote_(); + this.unbindDragEvents_(); + if (Blockly.utils.isRightButton(e)) { + // No right-click. + e.stopPropagation(); + return; + } + // Left-click (or middle click) + this.workspace.startDrag(e, new goog.math.Coordinate( + this.workspace.RTL ? -this.width_ : this.width_, this.height_)); + + this.onMouseUpWrapper_ = Blockly.bindEventWithChecks_( + document, 'mouseup', this, this.resizeMouseUp_); + this.onMouseMoveWrapper_ = Blockly.bindEventWithChecks_( + document, 'mousemove', this, this.resizeMouseMove_); + Blockly.hideChaff(); + // This event has been handled. No need to bubble up to the document. + e.stopPropagation(); +}; + +/** + * Handle a mouse-down on comment's delete icon. + * @param {!Event} e Mouse down event. + * @private + */ +Blockly.WorkspaceCommentSvg.prototype.deleteMouseDown_ = function(e) { + // highlight the delete icon + Blockly.utils.addClass( + /** @type {!Element} */ (this.deleteIconBorder_), 'blocklyDeleteIconHighlighted'); + // This event has been handled. No need to bubble up to the document. + e.stopPropagation(); +}; + +/** + * Handle a mouse-out on comment's delete icon. + * @param {!Event} e Mouse out event. + * @private + */ +Blockly.WorkspaceCommentSvg.prototype.deleteMouseOut_ = function(/*e*/) { + // restore highlight on the delete icon + Blockly.utils.removeClass( + /** @type {!Element} */ (this.deleteIconBorder_), 'blocklyDeleteIconHighlighted'); +}; + +/** + * Handle a mouse-up on comment's delete icon. + * @param {!Event} e Mouse up event. + * @private + */ +Blockly.WorkspaceCommentSvg.prototype.deleteMouseUp_ = function(e) { + // Delete this comment + this.dispose(true, true); + // This event has been handled. No need to bubble up to the document. + e.stopPropagation(); +}; + +/** + * Stop binding to the global mouseup and mousemove events. + * @private + */ +Blockly.WorkspaceCommentSvg.prototype.unbindDragEvents_ = function() { + if (this.onMouseUpWrapper_) { + Blockly.unbindEvent_(this.onMouseUpWrapper_); + this.onMouseUpWrapper_ = null; + } + if (this.onMouseMoveWrapper_) { + Blockly.unbindEvent_(this.onMouseMoveWrapper_); + this.onMouseMoveWrapper_ = null; + } +}; + +/* + * Handle a mouse-up event while dragging a comment's border or resize handle. + * @param {!Event} e Mouse up event. + * @private + */ +Blockly.WorkspaceCommentSvg.prototype.resizeMouseUp_ = function(/*e*/) { + Blockly.Touch.clearTouchIdentifier(); + this.unbindDragEvents_(); +}; + +/** + * Resize this comment to follow the mouse. + * @param {!Event} e Mouse move event. + * @private + */ +Blockly.WorkspaceCommentSvg.prototype.resizeMouseMove_ = function(e) { + this.autoLayout_ = false; + var newXY = this.workspace.moveDrag(e); + this.setSize_(this.RTL ? -newXY.x : newXY.x, newXY.y); +}; + +/** + * Callback function triggered when the comment has resized. + * Resize the text area accordingly. + * @private + */ +Blockly.WorkspaceCommentSvg.prototype.resizeComment_ = function() { + var size = this.getHeightWidth(); + var topOffset = Blockly.WorkspaceCommentSvg.TOP_OFFSET; + var textOffset = Blockly.WorkspaceCommentSvg.TEXTAREA_OFFSET * 2; + + this.foreignObject_.setAttribute('width', + size.width); + this.foreignObject_.setAttribute('height', + size.height - topOffset); + if (this.RTL) { + this.foreignObject_.setAttribute('x', + -size.width); + } + this.textarea_.style.width = + (size.width - textOffset) + 'px'; + this.textarea_.style.height = + (size.height - textOffset - topOffset) + 'px'; +}; + +/** + * Set size + * @param {number} width width of the container + * @param {number} height height of the container + * @private + */ +Blockly.WorkspaceCommentSvg.prototype.setSize_ = function(width, height) { + // Minimum size of a comment. + width = Math.max(width, 45); + height = Math.max(height, 20 + Blockly.WorkspaceCommentSvg.TOP_OFFSET); + this.width_ = width; + this.height_ = height; + this.svgRect_.setAttribute('width', width); + this.svgRect_.setAttribute('height', height); + this.svgRectTarget_.setAttribute('width', width); + this.svgRectTarget_.setAttribute('height', height); + this.svgHandleTarget_.setAttribute('width', width); + this.svgHandleTarget_.setAttribute('height', Blockly.WorkspaceCommentSvg.TOP_OFFSET); + if (this.RTL) { + this.svgRect_.setAttribute('transform', 'scale(-1 1)'); + this.svgRectTarget_.setAttribute('transform', 'scale(-1 1)'); + } + + var resizeSize = Blockly.WorkspaceCommentSvg.RESIZE_SIZE; + if (this.resizeGroup_) { + if (this.RTL) { + // Mirror the resize group. + this.resizeGroup_.setAttribute('transform', 'translate(' + + (-width + resizeSize) + ',' + (height - resizeSize) + ') scale(-1 1)'); + this.deleteGroup_.setAttribute('transform', 'translate(' + + (-width + resizeSize) + ',' + (-resizeSize) + ') scale(-1 1)'); + } else { + this.resizeGroup_.setAttribute('transform', 'translate(' + + (width - resizeSize) + ',' + + (height - resizeSize) + ')'); + this.deleteGroup_.setAttribute('transform', 'translate(' + + (width - resizeSize) + ',' + + (-resizeSize) + ')'); + } + } + + // Allow the contents to resize. + this.resizeComment_(); +}; + +/** + * Dispose of any rendered comment components. + * @private + */ +Blockly.WorkspaceCommentSvg.prototype.disposeInternal_ = function() { + this.textarea_ = null; + this.foreignObject_ = null; + this.svgRectTarget_ = null; + this.svgHandleTarget_ = null; +}; + +/** + * Set the focus on the text area. + * @package + */ +Blockly.WorkspaceCommentSvg.prototype.setFocus = function() { + var comment = this; + this.focused_ = true; + // Defer CSS changes. + setTimeout(function() { + comment.textarea_.focus(); + comment.addFocus(); + Blockly.utils.addClass( + comment.svgRectTarget_, 'blocklyCommentTargetFocused'); + Blockly.utils.addClass( + comment.svgHandleTarget_, 'blocklyCommentHandleTargetFocused'); + }, 0); +}; + +/** + * Remove focus from the text area. + * @package + */ +Blockly.WorkspaceCommentSvg.prototype.blurFocus = function() { + var comment = this; + this.focused_ = false; + // Defer CSS changes. + setTimeout(function() { + comment.textarea_.blur(); + comment.removeFocus(); + Blockly.utils.removeClass( + comment.svgRectTarget_, 'blocklyCommentTargetFocused'); + Blockly.utils.removeClass( + comment.svgHandleTarget_, 'blocklyCommentHandleTargetFocused'); + }, 0); +}; diff --git a/core/workspace_comment_svg.js b/core/workspace_comment_svg.js new file mode 100644 index 000000000..64c92122e --- /dev/null +++ b/core/workspace_comment_svg.js @@ -0,0 +1,591 @@ +/** + * @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 Object representing a code comment on a rendered workspace. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.WorkspaceCommentSvg'); + +goog.require('Blockly.Events.CommentCreate'); +goog.require('Blockly.Events.CommentDelete'); +goog.require('Blockly.Events.CommentMove'); +goog.require('Blockly.WorkspaceComment'); + + +/** + * Class for a workspace comment's SVG representation. + * @param {!Blockly.Workspace} workspace The block's workspace. + * @param {string} content The content of this workspace comment. + * @param {number} height Height of the comment. + * @param {number} width Width of the comment. + * @param {string=} opt_id Optional ID. Use this ID if provided, otherwise + * create a new ID. + * @extends {Blockly.WorkspaceComment} + * @constructor + */ +Blockly.WorkspaceCommentSvg = function(workspace, content, height, width, + opt_id) { + // Create core elements for the block. + /** + * @type {SVGElement} + * @private + */ + this.svgGroup_ = Blockly.utils.createSvgElement( + 'g', {'class': 'blocklyComment'}, null); + this.svgGroup_.translate_ = ''; + + this.svgRect_ = Blockly.utils.createSvgElement( + 'rect', + { + 'class': 'blocklyCommentRect', + 'x': 0, + 'y': 0, + 'rx': Blockly.WorkspaceCommentSvg.BORDER_RADIUS, + 'ry': Blockly.WorkspaceCommentSvg.BORDER_RADIUS + }); + this.svgGroup_.appendChild(this.svgRect_); + + /** + * Whether the comment is rendered onscreen and is a part of the DOM. + * @type {boolean} + * @private + */ + this.rendered_ = false; + + /** + * Whether to move the comment to the drag surface when it is dragged. + * True if it should move, false if it should be translated directly. + * @type {boolean} + * @private + */ + this.useDragSurface_ = + Blockly.utils.is3dSupported() && !!workspace.blockDragSurface_; + + Blockly.WorkspaceCommentSvg.superClass_.constructor.call(this, + workspace, content, height, width, opt_id); + + this.render(); +}; goog.inherits(Blockly.WorkspaceCommentSvg, Blockly.WorkspaceComment); + +/** + * The width and height to use to size a workspace comment when it is first + * added, before it has been edited by the user. + * @type {number} + * @package + */ +Blockly.WorkspaceCommentSvg.DEFAULT_SIZE = 100; + +/** + * Dispose of this comment. + * @package + */ +Blockly.WorkspaceCommentSvg.prototype.dispose = function() { + if (!this.workspace) { + // The comment has already been deleted. + return; + } + // If this comment is being dragged, unlink the mouse events. + if (Blockly.selected == this) { + this.unselect(); + this.workspace.cancelCurrentGesture(); + } + + if (Blockly.Events.isEnabled()) { + Blockly.Events.fire(new Blockly.Events.CommentDelete(this)); + } + + goog.dom.removeNode(this.svgGroup_); + // Sever JavaScript to DOM connections. + this.svgGroup_ = null; + this.svgRect_ = null; + // Dispose of any rendered components + this.disposeInternal_(); + + Blockly.Events.disable(); + Blockly.WorkspaceCommentSvg.superClass_.dispose.call(this); + Blockly.Events.enable(); +}; + +/** + * Create and initialize the SVG representation of a workspace comment. + * May be called more than once. + * @package + */ +Blockly.WorkspaceCommentSvg.prototype.initSvg = function() { + goog.asserts.assert(this.workspace.rendered, 'Workspace is headless.'); + if (!this.workspace.options.readOnly && !this.eventsInit_) { + Blockly.bindEventWithChecks_( + this.svgRectTarget_, 'mousedown', this, this.pathMouseDown_); + Blockly.bindEventWithChecks_( + this.svgHandleTarget_, 'mousedown', this, this.pathMouseDown_); + } + this.eventsInit_ = true; + + this.updateMovable(); + if (!this.getSvgRoot().parentNode) { + this.workspace.getBubbleCanvas().appendChild(this.getSvgRoot()); + } +}; + +/** + * Handle a mouse-down on an SVG comment. + * @param {!Event} e Mouse down event or touch start event. + * @private + */ +Blockly.WorkspaceCommentSvg.prototype.pathMouseDown_ = function(e) { + var gesture = this.workspace.getGesture(e); + if (gesture) { + gesture.handleBubbleStart(e, this); + } +}; + +/** + * Show the context menu for this workspace comment. + * @param {!Event} e Mouse event. + * @private + */ +Blockly.WorkspaceCommentSvg.prototype.showContextMenu_ = function(e) { + if (this.workspace.options.readOnly) { + return; + } + // Save the current workspace comment in a variable for use in closures. + var comment = this; + var menuOptions = []; + + if (this.isDeletable() && this.isMovable()) { + menuOptions.push(Blockly.ContextMenu.commentDuplicateOption(comment)); + menuOptions.push(Blockly.ContextMenu.commentDeleteOption(comment)); + } + + Blockly.ContextMenu.show(e, menuOptions, this.RTL); +}; + +/** + * Select this comment. Highlight it visually. + * @package + */ +Blockly.WorkspaceCommentSvg.prototype.select = function() { + if (Blockly.selected == this) { + return; + } + var oldId = null; + if (Blockly.selected) { + oldId = Blockly.selected.id; + // Unselect any previously selected block. + Blockly.Events.disable(); + try { + Blockly.selected.unselect(); + } finally { + Blockly.Events.enable(); + } + } + var event = new Blockly.Events.Ui(null, 'selected', oldId, this.id); + event.workspaceId = this.workspace.id; + Blockly.Events.fire(event); + Blockly.selected = this; + this.addSelect(); +}; + +/** + * Unselect this comment. Remove its highlighting. + * @package + */ +Blockly.WorkspaceCommentSvg.prototype.unselect = function() { + if (Blockly.selected != this) { + return; + } + var event = new Blockly.Events.Ui(null, 'selected', this.id, null); + event.workspaceId = this.workspace.id; + Blockly.Events.fire(event); + Blockly.selected = null; + this.removeSelect(); + this.blurFocus(); +}; + +/** + * Select this comment. Highlight it visually. + * @package + */ +Blockly.WorkspaceCommentSvg.prototype.addSelect = function() { + Blockly.utils.addClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklySelected'); + this.setFocus(); +}; + +/** + * Unselect this comment. Remove its highlighting. + * @package + */ +Blockly.WorkspaceCommentSvg.prototype.removeSelect = function() { + Blockly.utils.removeClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklySelected'); + this.blurFocus(); +}; + +/** + * Focus this comment. Highlight it visually. + * @package + */ +Blockly.WorkspaceCommentSvg.prototype.addFocus = function() { + Blockly.utils.addClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklyFocused'); +}; + +/** + * Unfocus this comment. Remove its highlighting. + * @package + */ +Blockly.WorkspaceCommentSvg.prototype.removeFocus = function() { + Blockly.utils.removeClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklyFocused'); +}; + +/** + * Return the coordinates of the top-left corner of this comment relative to the + * drawing surface's origin (0,0), in workspace units. + * If the comment is on the workspace, (0, 0) is the origin of the workspace + * coordinate system. + * This does not change with workspace scale. + * @return {!goog.math.Coordinate} Object with .x and .y properties in + * workspace coordinates. + * @package + */ +Blockly.WorkspaceCommentSvg.prototype.getRelativeToSurfaceXY = function() { + var x = 0; + var y = 0; + + var dragSurfaceGroup = this.useDragSurface_ ? + this.workspace.blockDragSurface_.getGroup() : null; + + var element = this.getSvgRoot(); + if (element) { + do { + // Loop through this comment and every parent. + var xy = Blockly.utils.getRelativeXY(element); + x += xy.x; + y += xy.y; + // If this element is the current element on the drag surface, include + // the translation of the drag surface itself. + if (this.useDragSurface_ && + this.workspace.blockDragSurface_.getCurrentBlock() == element) { + var surfaceTranslation = + this.workspace.blockDragSurface_.getSurfaceTranslation(); + x += surfaceTranslation.x; + y += surfaceTranslation.y; + } + element = element.parentNode; + } while (element && element != this.workspace.getBubbleCanvas() && + element != dragSurfaceGroup); + } + this.xy_ = new goog.math.Coordinate(x, y); + return this.xy_; +}; + +/** + * Move a comment by a relative offset. + * @param {number} dx Horizontal offset, in workspace units. + * @param {number} dy Vertical offset, in workspace units. + * @package + */ +Blockly.WorkspaceCommentSvg.prototype.moveBy = function(dx, dy) { + var event = new Blockly.Events.CommentMove(this); + // TODO: Do I need to look up the relative to surface XY position here? + var xy = this.getRelativeToSurfaceXY(); + this.translate(xy.x + dx, xy.y + dy); + this.xy_ = new goog.math.Coordinate(xy.x + dx, xy.y + dy); + event.recordNew(); + Blockly.Events.fire(event); + this.workspace.resizeContents(); +}; + +/** + * Transforms a comment by setting the translation on the transform attribute + * of the block's SVG. + * @param {number} x The x coordinate of the translation in workspace units. + * @param {number} y The y coordinate of the translation in workspace units. + * @package + */ +Blockly.WorkspaceCommentSvg.prototype.translate = function(x, y) { + this.xy_ = new goog.math.Coordinate(x, y); + this.getSvgRoot().setAttribute('transform', + 'translate(' + x + ',' + y + ')'); +}; + +/** + * Move this comment to its workspace's drag surface, accounting for positioning. + * Generally should be called at the same time as setDragging(true). + * Does nothing if useDragSurface_ is false. + * @private + */ +Blockly.WorkspaceCommentSvg.prototype.moveToDragSurface_ = function() { + if (!this.useDragSurface_) { + return; + } + // The translation for drag surface blocks, + // is equal to the current relative-to-surface position, + // to keep the position in sync as it move on/off the surface. + // This is in workspace coordinates. + var xy = this.getRelativeToSurfaceXY(); + this.clearTransformAttributes_(); + this.workspace.blockDragSurface_.translateSurface(xy.x, xy.y); + // Execute the move on the top-level SVG component + this.workspace.blockDragSurface_.setBlocksAndShow(this.getSvgRoot()); +}; + +/** + * Move this comment back to the workspace block canvas. + * Generally should be called at the same time as setDragging(false). + * Does nothing if useDragSurface_ is false. + * @param {!goog.math.Coordinate} newXY The position the comment should take on + * on the workspace canvas, in workspace coordinates. + * @private + */ +Blockly.WorkspaceCommentSvg.prototype.moveOffDragSurface_ = function(newXY) { + if (!this.useDragSurface_) { + return; + } + // Translate to current position, turning off 3d. + this.translate(newXY.x, newXY.y); + this.workspace.blockDragSurface_.clearAndHide(this.workspace.getCanvas()); +}; + +/** + * Move this comment during a drag, taking into account whether we are using a + * drag surface to translate blocks. + * @param {?Blockly.BlockDragSurfaceSvg} dragSurface The surface that carries + * rendered items during a drag, or null if no drag surface is in use. + * @param {!goog.math.Coordinate} newLoc The location to translate to, in + * workspace coordinates. + * @package + */ +Blockly.WorkspaceCommentSvg.prototype.moveDuringDrag = function(dragSurface, newLoc) { + if (dragSurface) { + dragSurface.translateSurface(newLoc.x, newLoc.y); + } else { + this.svgGroup_.translate_ = 'translate(' + newLoc.x + ',' + newLoc.y + ')'; + this.svgGroup_.setAttribute('transform', + this.svgGroup_.translate_ + this.svgGroup_.skew_); + } +}; + +/** + * Move the bubble group to the specified location in workspace coordinates. + * @param {number} x The x position to move to. + * @param {number} y The y position to move to. + * @package + */ +Blockly.WorkspaceCommentSvg.prototype.moveTo = function(x, y) { + this.translate(x, y); +}; + +/** + * Clear the comment of transform="..." attributes. + * Used when the comment is switching from 3d to 2d transform or vice versa. + * @private + */ +Blockly.WorkspaceCommentSvg.prototype.clearTransformAttributes_ = function() { + Blockly.utils.removeAttribute(this.getSvgRoot(), 'transform'); +}; + +/** + * Returns the coordinates of a bounding box describing the dimensions of this + * comment. + * Coordinate system: workspace coordinates. + * @return {!{topLeft: goog.math.Coordinate, bottomRight: goog.math.Coordinate}} + * Object with top left and bottom right coordinates of the bounding box. + * @package + */ +Blockly.WorkspaceCommentSvg.prototype.getBoundingRectangle = function() { + var blockXY = this.getRelativeToSurfaceXY(); + var commentBounds = this.getHeightWidth(); + var topLeft; + var bottomRight; + if (this.RTL) { + topLeft = new goog.math.Coordinate(blockXY.x - (commentBounds.width), + blockXY.y); + // Add the width of the tab/puzzle piece knob to the x coordinate + // since X is the corner of the rectangle, not the whole puzzle piece. + bottomRight = new goog.math.Coordinate(blockXY.x, + blockXY.y + commentBounds.height); + } else { + // Subtract the width of the tab/puzzle piece knob to the x coordinate + // since X is the corner of the rectangle, not the whole puzzle piece. + topLeft = new goog.math.Coordinate(blockXY.x, blockXY.y); + bottomRight = new goog.math.Coordinate(blockXY.x + commentBounds.width, + blockXY.y + commentBounds.height); + } + return {topLeft: topLeft, bottomRight: bottomRight}; +}; + +/** + * Add or remove the UI indicating if this comment is movable or not. + * @package + */ +Blockly.WorkspaceCommentSvg.prototype.updateMovable = function() { + if (this.isMovable()) { + Blockly.utils.addClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklyDraggable'); + } else { + Blockly.utils.removeClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklyDraggable'); + } +}; + +/** + * Set whether this comment is movable or not. + * @param {boolean} movable True if movable. + * @package + */ +Blockly.WorkspaceCommentSvg.prototype.setMovable = function(movable) { + Blockly.WorkspaceCommentSvg.superClass_.setMovable.call(this, movable); + this.updateMovable(); +}; + +/** + * Recursively adds or removes the dragging class to this node and its children. + * @param {boolean} adding True if adding, false if removing. + * @package + */ +Blockly.WorkspaceCommentSvg.prototype.setDragging = function(adding) { + if (adding) { + var group = this.getSvgRoot(); + group.translate_ = ''; + group.skew_ = ''; + Blockly.utils.addClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklyDragging'); + } else { + Blockly.utils.removeClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklyDragging'); + } +}; + +/** + * Return the root node of the SVG or null if none exists. + * @return {Element} The root SVG node (probably a group). + * @package + */ +Blockly.WorkspaceCommentSvg.prototype.getSvgRoot = function() { + return this.svgGroup_; +}; + +/** + * Returns this comment's text. + * @return {string} Comment text. + * @package + */ +Blockly.WorkspaceCommentSvg.prototype.getContent = function() { + return this.textarea_ ? this.textarea_.value : this.content_; +}; + +/** + * Set this comment's content. + * @param {string} content Comment content. + * @package + */ +Blockly.WorkspaceCommentSvg.prototype.setContent = function(content) { + Blockly.WorkspaceCommentSvg.superClass_.setContent.call(this, content); + if (this.textarea_) { + this.textarea_.value = content; + } +}; + +/** + * Update the cursor over this comment by adding or removing a class. + * @param {boolean} enable True if the delete cursor should be shown, false + * otherwise. + * @package + */ +Blockly.WorkspaceCommentSvg.prototype.setDeleteStyle = function(enable) { + if (enable) { + Blockly.utils.addClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklyDraggingDelete'); + } else { + Blockly.utils.removeClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklyDraggingDelete'); + } +}; + +Blockly.WorkspaceCommentSvg.prototype.setAutoLayout = function() { + // NOP for compatibility with the bubble dragger. +}; + +/** + * Decode an XML comment tag and create a rendered comment on the workspace. + * @param {!Element} xmlComment XML comment element. + * @param {!Blockly.Workspace} workspace The workspace. + * @param {number=} opt_wsWidth The width of the workspace, which is used to + * position comments correctly in RTL. + * @return {!Blockly.WorkspaceCommentSvg} The created workspace comment. + * @package + */ +Blockly.WorkspaceCommentSvg.fromXml = function(xmlComment, workspace, + opt_wsWidth) { + Blockly.Events.disable(); + try { + var info = Blockly.WorkspaceComment.parseAttributes(xmlComment); + + var comment = new Blockly.WorkspaceCommentSvg(workspace, + info.content, info.h, info.w, info.id); + if (workspace.rendered) { + comment.initSvg(); + comment.render(false); + } + // Position the comment correctly, taking into account the width of a + // rendered RTL workspace. + if (!isNaN(info.x) && !isNaN(info.y)) { + if (workspace.RTL) { + var wsWidth = opt_wsWidth || workspace.getWidth(); + comment.moveBy(wsWidth - info.x, info.y); + } else { + comment.moveBy(info.x, info.y); + } + } + } finally { + Blockly.Events.enable(); + } + Blockly.WorkspaceComment.fireCreateEvent(comment); + + return comment; +}; + +/** + * Encode a comment subtree as XML with XY coordinates. + * @param {boolean=} opt_noId True if the encoder should skip the comment id. + * @return {!Element} Tree of XML elements. + * @package + */ +Blockly.WorkspaceCommentSvg.prototype.toXmlWithXY = function(opt_noId) { + var width; // Not used in LTR. + if (this.workspace.RTL) { + // Here be performance dragons: This calls getMetrics(). + width = this.workspace.getWidth(); + } + var element = this.toXml(opt_noId); + var xy = this.getRelativeToSurfaceXY(); + element.setAttribute('x', + Math.round(this.workspace.RTL ? width - xy.x : xy.x)); + element.setAttribute('y', Math.round(xy.y)); + element.setAttribute('h', this.getHeight()); + element.setAttribute('w', this.getWidth()); + return element; +}; diff --git a/core/workspace_svg.js b/core/workspace_svg.js index c5a73a27f..84b9fa539 100644 --- a/core/workspace_svg.js +++ b/core/workspace_svg.js @@ -41,6 +41,8 @@ goog.require('Blockly.Trashcan'); goog.require('Blockly.VariablesDynamic'); goog.require('Blockly.Workspace'); goog.require('Blockly.WorkspaceAudio'); +goog.require('Blockly.WorkspaceComment'); +goog.require('Blockly.WorkspaceCommentSvg'); goog.require('Blockly.WorkspaceDragSurfaceSvg'); goog.require('Blockly.Xml'); goog.require('Blockly.ZoomControls'); @@ -243,6 +245,14 @@ Blockly.WorkspaceSvg.prototype.useWorkspaceDragSurface_ = false; */ Blockly.WorkspaceSvg.prototype.isDragSurfaceActive_ = false; +/** + * The first parent div with 'injectionDiv' in the name, or null if not set. + * Access this with getInjectionDiv. + * @type {!Element} + * @private + */ +Blockly.WorkspaceSvg.prototype.injectionDiv_ = null; + /** * Last known position of the page scroll. * This is used to determine whether we have recalculated screen coordinate @@ -352,6 +362,29 @@ Blockly.WorkspaceSvg.prototype.getOriginOffsetInPixels = function() { return Blockly.utils.getInjectionDivXY_(this.svgBlockCanvas_); }; +/** + * Return the injection div that is a parent of this workspace. + * Walks the DOM the first time it's called, then returns a cached value. + * @return {!Element} The first parent div with 'injectionDiv' in the name. + * @package + */ +Blockly.WorkspaceSvg.prototype.getInjectionDiv = function() { + // NB: it would be better to pass this in at createDom, but is more likely to + // break existing uses of Blockly. + if (!this.injectionDiv_) { + var element = this.svgGroup_; + while (element) { + var classes = element.getAttribute('class') || ''; + if ((' ' + classes + ' ').indexOf(' injectionDiv ') != -1) { + this.injectionDiv_ = element; + break; + } + element = element.parentNode; + } + } + return this.injectionDiv_; +}; + /** * Save resize handler data so we can delete it later in dispose. * @param {!Array.} handler Data that can be passed to unbindEvent_. @@ -897,6 +930,18 @@ Blockly.WorkspaceSvg.prototype.paste = function(xmlBlock) { if (this.currentGesture_) { this.currentGesture_.cancel(); // Dragging while pasting? No. } + if (xmlBlock.tagName.toLowerCase() == 'comment') { + this.pasteWorkspaceComment_(xmlBlock); + } else { + this.pasteBlock_(xmlBlock); + } +}; + +/** + * Paste the provided block onto the workspace. + * @param {!Element} xmlBlock XML block element. + */ +Blockly.WorkspaceSvg.prototype.pasteBlock_ = function(xmlBlock) { Blockly.Events.disable(); try { var block = Blockly.Xml.domToBlock(xmlBlock, this); @@ -952,6 +997,37 @@ Blockly.WorkspaceSvg.prototype.paste = function(xmlBlock) { block.select(); }; +/** + * Paste the provided comment onto the workspace. + * @param {!Element} xmlComment XML workspace comment element. + * @private + */ +Blockly.WorkspaceSvg.prototype.pasteWorkspaceComment_ = function(xmlComment) { + Blockly.Events.disable(); + try { + var comment = Blockly.WorkspaceCommentSvg.fromXml(xmlComment, this); + // Move the duplicate to original position. + var commentX = parseInt(xmlComment.getAttribute('x'), 10); + var commentY = parseInt(xmlComment.getAttribute('y'), 10); + if (!isNaN(commentX) && !isNaN(commentY)) { + if (this.RTL) { + commentX = -commentX; + } + // Offset workspace comment. + // TODO: #1719 properly offset comment such that it's not interfereing with any blocks + commentX += 50; + commentY += 50; + comment.moveBy(commentX, commentY); + } + } finally { + Blockly.Events.enable(); + } + if (Blockly.Events.isEnabled()) { + // TODO: Fire a Workspace Comment Create event + } + comment.select(); +}; + /** * Refresh the toolbox unless there's a drag in progress. * @package @@ -1126,17 +1202,19 @@ Blockly.WorkspaceSvg.prototype.onMouseWheel_ = function(e) { */ Blockly.WorkspaceSvg.prototype.getBlocksBoundingBox = function() { var topBlocks = this.getTopBlocks(false); + var topComments = this.getTopComments(false); + var topElements = topBlocks.concat(topComments); // There are no blocks, return empty rectangle. - if (!topBlocks.length) { + if (!topElements.length) { return {x: 0, y: 0, width: 0, height: 0}; } // Initialize boundary using the first block. - var boundary = topBlocks[0].getBoundingRectangle(); + var boundary = topElements[0].getBoundingRectangle(); // Start at 1 since the 0th block was used for initialization - for (var i = 1; i < topBlocks.length; i++) { - var blockBoundary = topBlocks[i].getBoundingRectangle(); + for (var i = 1; i < topElements.length; i++) { + var blockBoundary = topElements[i].getBoundingRectangle(); if (blockBoundary.topLeft.x < boundary.topLeft.x) { boundary.topLeft.x = blockBoundary.topLeft.x; } diff --git a/core/ws_comment_events.js b/core/ws_comment_events.js new file mode 100644 index 000000000..dec55d8a4 --- /dev/null +++ b/core/ws_comment_events.js @@ -0,0 +1,410 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2018 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 Classes for all comment events. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.Events.CommentBase'); +goog.provide('Blockly.Events.CommentChange'); +goog.provide('Blockly.Events.CommentCreate'); +goog.provide('Blockly.Events.CommentDelete'); +goog.provide('Blockly.Events.CommentMove'); + +goog.require('Blockly.Events'); +goog.require('Blockly.Events.Abstract'); + +goog.require('goog.math.Coordinate'); + + +/** + * Abstract class for a comment event. + * @param {Blockly.WorkspaceComment} comment The comment this event corresponds + * to. + * @extends {Blockly.Events.Abstract} + * @constructor + */ +Blockly.Events.CommentBase = function(comment) { + /** + * The ID of the comment this event pertains to. + * @type {string} + */ + this.commentId = comment.id; + + /** + * The workspace identifier for this event. + * @type {string} + */ + this.workspaceId = comment.workspace.id; + + /** + * The event group id for the group this event belongs to. Groups define + * events that should be treated as an single action from the user's + * perspective, and should be undone together. + * @type {string} + */ + this.group = Blockly.Events.group_; + + /** + * Sets whether the event should be added to the undo stack. + * @type {boolean} + */ + this.recordUndo = Blockly.Events.recordUndo; +}; +goog.inherits(Blockly.Events.CommentBase, Blockly.Events.Abstract); + +/** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ +Blockly.Events.CommentBase.prototype.toJson = function() { + var json = { + 'type': this.type + }; + if (this.group) { + json['group'] = this.group; + } + if (this.commentId) { + json['commentId'] = this.commentId; + } + return json; +}; + +/** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ +Blockly.Events.CommentBase.prototype.fromJson = function(json) { + this.commentId = json['commentId']; + this.group = json['group']; +}; + +/** + * Class for a comment change event. + * @param {Blockly.WorkspaceComment} comment The comment that is being changed. + * Null for a blank event. + * @param {string} oldContents Previous contents of the comment. + * @param {string} newContents New contents of the comment. + * @extends {Blockly.Events.CommentBase} + * @constructor + */ +Blockly.Events.CommentChange = function(comment, oldContents, newContents) { + if (!comment) { + return; // Blank event to be populated by fromJson. + } + Blockly.Events.CommentChange.superClass_.constructor.call(this, comment); + this.oldContents_ = oldContents; + this.newContents_ = newContents; +}; +goog.inherits(Blockly.Events.CommentChange, Blockly.Events.CommentBase); + +/** + * Type of this event. + * @type {string} + */ +Blockly.Events.CommentChange.prototype.type = Blockly.Events.COMMENT_CHANGE; + +/** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ +Blockly.Events.CommentChange.prototype.toJson = function() { + var json = Blockly.Events.CommentChange.superClass_.toJson.call(this); + json['newContents'] = this.newContents_; + return json; +}; + +/** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ +Blockly.Events.CommentChange.prototype.fromJson = function(json) { + Blockly.Events.CommentChange.superClass_.fromJson.call(this, json); + this.newContents_ = json['newValue']; +}; + +/** + * Does this event record any change of state? + * @return {boolean} False if something changed. + */ +Blockly.Events.CommentChange.prototype.isNull = function() { + return this.oldContents_ == this.newContents_; +}; + +/** + * Run a change event. + * @param {boolean} forward True if run forward, false if run backward (undo). + */ +Blockly.Events.CommentChange.prototype.run = function(forward) { + var workspace = this.getEventWorkspace_(); + var comment = workspace.getCommentById(this.commentId); + if (!comment) { + console.warn('Can\'t change non-existent comment: ' + this.commentId); + return; + } + var contents = forward ? this.newContents_ : this.oldContents_; + + comment.setContent(contents); +}; + +/** + * Class for a comment creation event. + * @param {Blockly.WorkspaceComment} comment The created comment. + * Null for a blank event. + * @extends {Blockly.Events.CommentBase} + * @constructor + */ +Blockly.Events.CommentCreate = function(comment) { + if (!comment) { + return; // Blank event to be populated by fromJson. + } + Blockly.Events.CommentCreate.superClass_.constructor.call(this, comment); + + this.xml = comment.toXmlWithXY(); +}; +goog.inherits(Blockly.Events.CommentCreate, Blockly.Events.CommentBase); + +/** + * Type of this event. + * @type {string} + */ +Blockly.Events.CommentCreate.prototype.type = Blockly.Events.COMMENT_CREATE; + +/** + * Encode the event as JSON. + * TODO (#1266): "Full" and "minimal" serialization. + * @return {!Object} JSON representation. + */ +Blockly.Events.CommentCreate.prototype.toJson = function() { + var json = Blockly.Events.CommentCreate.superClass_.toJson.call(this); + json['xml'] = Blockly.Xml.domToText(this.xml); + return json; +}; + +/** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ +Blockly.Events.CommentCreate.prototype.fromJson = function(json) { + Blockly.Events.CommentCreate.superClass_.fromJson.call(this, json); + this.xml = Blockly.Xml.textToDom('' + json['xml'] + '').firstChild; +}; + +/** + * Run a creation event. + * @param {boolean} forward True if run forward, false if run backward (undo). + */ +Blockly.Events.CommentCreate.prototype.run = function(forward) { + var workspace = this.getEventWorkspace_(); + if (forward) { + var xml = goog.dom.createDom('xml'); + xml.appendChild(this.xml); + Blockly.Xml.domToWorkspace(xml, workspace); + } else { + var comment = workspace.getCommentById(this.commentId); + if (comment) { + comment.dispose(false, false); + } else { + // Only complain about root-level block. + console.warn("Can't uncreate non-existent comment: " + this.commentId); + } + } +}; + +/** + * Class for a comment deletion event. + * @param {Blockly.WorkspaceComment} comment The deleted comment. + * Null for a blank event. + * @extends {Blockly.Events.CommentBase} + * @constructor + */ +Blockly.Events.CommentDelete = function(comment) { + if (!comment) { + return; // Blank event to be populated by fromJson. + } + Blockly.Events.CommentDelete.superClass_.constructor.call(this, comment); + + this.xml = comment.toXmlWithXY(); +}; +goog.inherits(Blockly.Events.CommentDelete, Blockly.Events.CommentBase); + +/** + * Type of this event. + * @type {string} + */ +Blockly.Events.CommentDelete.prototype.type = Blockly.Events.COMMENT_DELETE; + +/** + * Encode the event as JSON. + * TODO (#1266): "Full" and "minimal" serialization. + * @return {!Object} JSON representation. + */ +Blockly.Events.CommentDelete.prototype.toJson = function() { + var json = Blockly.Events.CommentDelete.superClass_.toJson.call(this); + return json; +}; + +/** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ +Blockly.Events.CommentDelete.prototype.fromJson = function(json) { + Blockly.Events.CommentDelete.superClass_.fromJson.call(this, json); +}; + +/** + * Run a creation event. + * @param {boolean} forward True if run forward, false if run backward (undo). + */ +Blockly.Events.CommentDelete.prototype.run = function(forward) { + var workspace = this.getEventWorkspace_(); + if (forward) { + var comment = workspace.getCommentById(this.commentId); + if (comment) { + comment.dispose(false, false); + } else { + // Only complain about root-level block. + console.warn("Can't uncreate non-existent comment: " + this.commentId); + } + } else { + var xml = goog.dom.createDom('xml'); + xml.appendChild(this.xml); + Blockly.Xml.domToWorkspace(xml, workspace); + } +}; + +/** + * Class for a comment move event. Created before the move. + * @param {Blockly.WorkspaceComment} comment The comment that is being moved. + * Null for a blank event. + * @extends {Blockly.Events.CommentBase} + * @constructor + */ +Blockly.Events.CommentMove = function(comment) { + if (!comment) { + return; // Blank event to be populated by fromJson. + } + Blockly.Events.CommentMove.superClass_.constructor.call(this, comment); + + /** + * The comment that is being moved. Will be cleared after recording the new + * location. + * @type {?Blockly.WorkspaceComment} + */ + this.comment_ = comment; + + /** + * The location before the move, in workspace coordinates. + * @type {!goog.math.Coordinate} + */ + this.oldCoordinate_ = comment.getXY(); + + /** + * The location after the move, in workspace coordinates. + * @type {!goog.math.Coordinate} + */ + this.newCoordinate_ = null; +}; +goog.inherits(Blockly.Events.CommentMove, Blockly.Events.CommentBase); + +/** + * Record the comment's new location. Called after the move. Can only be + * called once. + */ +Blockly.Events.CommentMove.prototype.recordNew = function() { + if (!this.comment_) { + throw new Error('Tried to record the new position of a comment on the ' + + 'same event twice.'); + } + this.newCoordinate_ = this.comment_.getXY(); + this.comment_ = null; +}; + +/** + * Type of this event. + * @type {string} + */ +Blockly.Events.CommentMove.prototype.type = Blockly.Events.COMMENT_MOVE; + +/** + * Override the location before the move. Use this if you don't create the + * event until the end of the move, but you know the original location. + * @param {!goog.math.Coordinate} xy The location before the move, in workspace + * coordinates. + */ +Blockly.Events.CommentMove.prototype.setOldCoordinate = function(xy) { + this.oldCoordinate_ = xy; +}; + +/** + * Encode the event as JSON. + * TODO (#1266): "Full" and "minimal" serialization. + * @return {!Object} JSON representation. + */ +Blockly.Events.CommentMove.prototype.toJson = function() { + var json = Blockly.Events.CommentMove.superClass_.toJson.call(this); + if (this.newCoordinate_) { + json['newCoordinate'] = Math.round(this.newCoordinate_.x) + ',' + + Math.round(this.newCoordinate_.y); + } + return json; +}; + +/** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ +Blockly.Events.CommentMove.prototype.fromJson = function(json) { + Blockly.Events.CommentMove.superClass_.fromJson.call(this, json); + + if (json['newCoordinate']) { + var xy = json['newCoordinate'].split(','); + this.newCoordinate_ = + new goog.math.Coordinate(parseFloat(xy[0]), parseFloat(xy[1])); + } +}; + +/** + * Does this event record any change of state? + * @return {boolean} False if something changed. + */ +Blockly.Events.CommentMove.prototype.isNull = function() { + return goog.math.Coordinate.equals(this.oldCoordinate_, this.newCoordinate_); +}; + +/** + * Run a move event. + * @param {boolean} forward True if run forward, false if run backward (undo). + */ +Blockly.Events.CommentMove.prototype.run = function(forward) { + var workspace = this.getEventWorkspace_(); + var comment = workspace.getCommentById(this.commentId); + if (!comment) { + console.warn('Can\'t move non-existent comment: ' + this.commentId); + return; + } + + var target = forward ? this.newCoordinate_ : this.oldCoordinate_; + // TODO: Check if the comment is being dragged, and give up if so. + var current = comment.getXY(); + comment.moveBy(target.x - current.x, target.y - current.y); +}; diff --git a/core/xml.js b/core/xml.js index 9af982ca2..4f1507fff 100644 --- a/core/xml.js +++ b/core/xml.js @@ -45,9 +45,11 @@ goog.require('goog.dom'); */ Blockly.Xml.workspaceToDom = function(workspace, opt_noId) { var xml = goog.dom.createDom('xml'); - var variables = Blockly.Variables.allUsedVarModels(workspace); - if (variables.length) { - xml.appendChild(Blockly.Xml.variablesToDom(variables)); + xml.appendChild(Blockly.Xml.variablesToDom( + Blockly.Variables.allUsedVarModels(workspace))); + var comments = workspace.getTopComments(true); + for (var i = 0, comment; comment = comments[i]; i++) { + xml.appendChild(comment.toXmlWithXY(opt_noId)); } var blocks = workspace.getTopBlocks(true); for (var i = 0, block; block = blocks[i]; i++) { @@ -376,6 +378,7 @@ Blockly.Xml.domToWorkspace = function(xml, workspace) { console.warn('Deprecated call to Blockly.Xml.domToWorkspace, ' + 'swap the arguments.'); } + var width; // Not used in LTR. if (workspace.RTL) { width = workspace.getWidth(); @@ -418,6 +421,12 @@ Blockly.Xml.domToWorkspace = function(xml, workspace) { } else if (name == 'shadow') { goog.asserts.fail('Shadow block cannot be a top-level block.'); variablesFirst = false; + } else if (name == 'comment') { + if (workspace.rendered) { + Blockly.WorkspaceCommentSvg.fromXml(xmlChild, workspace, width); + } else { + Blockly.WorkspaceComment.fromXml(xmlChild, workspace); + } } else if (name == 'variables') { if (variablesFirst) { Blockly.Xml.domToVariables(xmlChild, workspace); @@ -562,6 +571,7 @@ Blockly.Xml.domToBlock = function(xmlBlock, workspace) { return topBlock; }; + /** * Decode an XML list of variables and add the variables to the workspace. * @param {!Element} xmlVariables List of XML variable elements. diff --git a/tests/jsunit/index.html b/tests/jsunit/index.html index 32d210285..06dde7718 100644 --- a/tests/jsunit/index.html +++ b/tests/jsunit/index.html @@ -28,6 +28,7 @@ + diff --git a/tests/jsunit/workspace_comment_test.js b/tests/jsunit/workspace_comment_test.js new file mode 100644 index 000000000..a36c2cdf5 --- /dev/null +++ b/tests/jsunit/workspace_comment_test.js @@ -0,0 +1,86 @@ +/** + * @license + * Blockly Tests + * + * 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. + */ +'use strict'; + +goog.require('goog.testing'); + +var workspace; + +function workspaceCommentTest_setUp() { + workspace = new Blockly.Workspace(); +} + +function workspaceCommentTest_tearDown() { + workspace.dispose(); +} + +function test_noWorkspaceComments() { + workspaceCommentTest_setUp(); + try { + assertEquals('Empty workspace: no comments (1).', 0, workspace.getTopComments(true).length); + assertEquals('Empty workspace: no comments (2).', 0, workspace.getTopComments(false).length); + workspace.clear(); + assertEquals('Empty workspace: no comments (3).', 0, workspace.getTopComments(true).length); + assertEquals('Empty workspace: no comments (4).', 0, workspace.getTopComments(false).length); + } finally { + workspaceCommentTest_tearDown(); + } +} + +function test_oneWorkspaceComment() { + workspaceCommentTest_setUp(); + try { + var comment = new Blockly.WorkspaceComment(workspace, 'comment text', 0, 0, 'comment id'); + assertEquals('One comment on workspace (1).', 1, workspace.getTopComments(true).length); + assertEquals('One comment on workspace (2).', 1, workspace.getTopComments(false).length); + assertEquals('Comment db contains this comment.', comment, workspace.commentDB_['comment id']); + workspace.clear(); + assertEquals('Cleared workspace: no comments (3).', 0, workspace.getTopComments(true).length); + assertEquals('Cleared workspace: no comments (4).', 0, workspace.getTopComments(false).length); + assertFalse('Comment DB does not contain this comment.', 'comment id' in workspace.commentDB_); + } finally { + workspaceCommentTest_tearDown(); + } +} + +function test_getWorkspaceCommentById() { + workspaceCommentTest_setUp(); + try { + var comment = new Blockly.WorkspaceComment(workspace, 'comment text', 0, 0, 'comment id'); + assertEquals('Getting a comment by id.', comment, workspace.getCommentById('comment id')); + assertEquals('No comment found.', null, workspace.getCommentById('not a comment')); + comment.dispose(); + assertEquals('Can\'t find the comment.', null, workspace.getCommentById('comment id')); + } finally { + workspaceCommentTest_tearDown(); + } +} + +function test_disposeWsCommentTwice() { + workspaceCommentTest_setUp(); + try { + var comment = new Blockly.WorkspaceComment(workspace, 'comment text', 0, 0, 'comment id'); + comment.dispose(); + // Nothing should go wrong the second time dispose is called. + comment.dispose(); + }finally { + workspaceCommentTest_tearDown(); + } +}