From 58f264f4ce38b46037d18675af79f4d96b9747b7 Mon Sep 17 00:00:00 2001 From: "ellen.spertus" Date: Sat, 22 Feb 2014 03:00:01 -0800 Subject: [PATCH] Automatic commit Sat Feb 22 03:00:01 PST 2014 --- blockly_compressed.js | 56 +++++----- blockly_uncompressed.js | 4 +- core/block.js | 236 +++++++++++++++++++++------------------ core/blockly.js | 18 ++- core/contextmenu.js | 6 +- core/realtime.js | 242 +++++++++++++++++++++++++++++++--------- tests/playground.html | 5 +- 7 files changed, 376 insertions(+), 191 deletions(-) diff --git a/blockly_compressed.js b/blockly_compressed.js index fe6e4a7fd..378a977be 100644 --- a/blockly_compressed.js +++ b/blockly_compressed.js @@ -797,10 +797,10 @@ Blockly.ConnectionDB.init=function(a){var b=[];b[Blockly.INPUT_VALUE]=new Blockl Blockly.ContextMenu={};Blockly.ContextMenu.X_PADDING=20;Blockly.ContextMenu.Y_HEIGHT=20;Blockly.ContextMenu.visible=!1; Blockly.ContextMenu.createDom=function(){var a=Blockly.createSvgElement("g",{"class":"blocklyHidden"},null);Blockly.ContextMenu.svgGroup=a;Blockly.ContextMenu.svgShadow=Blockly.createSvgElement("rect",{"class":"blocklyContextMenuShadow",x:2,y:-2,rx:4,ry:4},a);Blockly.ContextMenu.svgBackground=Blockly.createSvgElement("rect",{"class":"blocklyContextMenuBackground",y:-4,rx:4,ry:4},a);Blockly.ContextMenu.svgOptions=Blockly.createSvgElement("g",{"class":"blocklyContextMenuOptions"},a);return a}; Blockly.ContextMenu.show=function(a,b){if(b.length){goog.dom.removeChildren(Blockly.ContextMenu.svgOptions);Blockly.ContextMenu.svgGroup.style.display="block";for(var c=0,d=[Blockly.ContextMenu.svgBackground,Blockly.ContextMenu.svgShadow],e=0,f;f=b[e];e++){var g=Blockly.ContextMenu.optionToDom(f.text),h=g.firstChild,k=g.lastChild;Blockly.ContextMenu.svgOptions.appendChild(g);g.setAttribute("transform","translate(0, "+e*Blockly.ContextMenu.Y_HEIGHT+")");d.push(h);Blockly.bindEvent_(g,"mousedown",null, -Blockly.noEvent);f.enabled?(Blockly.bindEvent_(g,"mouseup",null,f.callback),Blockly.bindEvent_(g,"mouseup",null,Blockly.ContextMenu.hide)):g.setAttribute("class","blocklyMenuDivDisabled");c=Math.max(c,k.getComputedTextLength())}c+=2*Blockly.ContextMenu.X_PADDING;for(e=0;ef.height&&(d-=e.height-10);Blockly.RTL?0>=c-e.width?c++:c-=e.width:c+e.width>f.width?c-=e.width:c++;Blockly.ContextMenu.svgGroup.setAttribute("transform","translate("+c+", "+d+")");Blockly.ContextMenu.visible=!0}else Blockly.ContextMenu.hide()}; -Blockly.ContextMenu.optionToDom=function(a){var b=Blockly.createSvgElement("g",{"class":"blocklyMenuDiv"},null);Blockly.createSvgElement("rect",{height:Blockly.ContextMenu.Y_HEIGHT},b);var c=Blockly.createSvgElement("text",{"class":"blocklyMenuText",x:Blockly.ContextMenu.X_PADDING,y:15},b);a=document.createTextNode(a);c.appendChild(a);return b};Blockly.ContextMenu.hide=function(){Blockly.ContextMenu.visible&&(Blockly.ContextMenu.svgGroup.style.display="none",Blockly.ContextMenu.visible=!1)}; -Blockly.ContextMenu.callbackFactory=function(a,b){return function(){var c=Blockly.Xml.domToBlock(a.workspace,b),d=a.getRelativeToSurfaceXY();d.x=Blockly.RTL?d.x-Blockly.SNAP_RADIUS:d.x+Blockly.SNAP_RADIUS;d.y+=2*Blockly.SNAP_RADIUS;c.moveBy(d.x,d.y);c.select()}}; +Blockly.noEvent);f.enabled?(f=function(a){return function(){Blockly.doCommand(a)}}(f.callback),Blockly.bindEvent_(g,"mouseup",null,f),Blockly.bindEvent_(g,"mouseup",null,Blockly.ContextMenu.hide)):g.setAttribute("class","blocklyMenuDivDisabled");c=Math.max(c,k.getComputedTextLength())}c+=2*Blockly.ContextMenu.X_PADDING;for(e=0;eg.height&&(d-=e.height-10);Blockly.RTL?0>=c-e.width?c++:c-=e.width:c+e.width>g.width?c-=e.width:c++;Blockly.ContextMenu.svgGroup.setAttribute("transform","translate("+c+", "+d+")");Blockly.ContextMenu.visible= +!0}else Blockly.ContextMenu.hide()};Blockly.ContextMenu.optionToDom=function(a){var b=Blockly.createSvgElement("g",{"class":"blocklyMenuDiv"},null);Blockly.createSvgElement("rect",{height:Blockly.ContextMenu.Y_HEIGHT},b);var c=Blockly.createSvgElement("text",{"class":"blocklyMenuText",x:Blockly.ContextMenu.X_PADDING,y:15},b);a=document.createTextNode(a);c.appendChild(a);return b}; +Blockly.ContextMenu.hide=function(){Blockly.ContextMenu.visible&&(Blockly.ContextMenu.svgGroup.style.display="none",Blockly.ContextMenu.visible=!1)};Blockly.ContextMenu.callbackFactory=function(a,b){return function(){var c=Blockly.Xml.domToBlock(a.workspace,b),d=a.getRelativeToSurfaceXY();d.x=Blockly.RTL?d.x-Blockly.SNAP_RADIUS:d.x+Blockly.SNAP_RADIUS;d.y+=2*Blockly.SNAP_RADIUS;c.moveBy(d.x,d.y);c.select()}}; // Copyright 2012 Google Inc. Apache License 2.0 Blockly.Field=function(a){this.sourceBlock_=null;this.fieldGroup_=Blockly.createSvgElement("g",{},null);this.borderRect_=Blockly.createSvgElement("rect",{rx:4,ry:4,x:-Blockly.BlockSvg.SEP_SPACE_X/2,y:-12,height:16},this.fieldGroup_);this.textElement_=Blockly.createSvgElement("text",{"class":"blocklyText"},this.fieldGroup_);this.size_={height:25,width:0};this.setText(a);this.visible_=!0};Blockly.Field.prototype.clone=function(){goog.asserts.fail("There should never be an instance of Field, only its derived classes.")}; Blockly.Field.NBSP="\u00a0";Blockly.Field.prototype.EDITABLE=!0;Blockly.Field.prototype.init=function(a){if(this.sourceBlock_)throw"Field has already been initialized once.";this.sourceBlock_=a;this.updateEditable();a.getSvgRoot().appendChild(this.fieldGroup_);this.mouseUpWrapper_=Blockly.bindEvent_(this.fieldGroup_,"mouseup",this,this.onMouseUp_);this.setText(null)}; @@ -851,8 +851,8 @@ Blockly.Warning.prototype.textToDom_=function(a){var b=Blockly.createSvgElement( Blockly.Warning.prototype.setVisible=function(a){if(a!=this.isVisible())if(a){a=this.textToDom_(this.text_);this.bubble_=new Blockly.Bubble(this.block_.workspace,a,this.block_.svg_.svgGroup_,this.iconX_,this.iconY_,null,null);if(Blockly.RTL)for(var b=a.getBBox().width,c=0,d;d=a.childNodes[c];c++)d.setAttribute("text-anchor","end"),d.setAttribute("x",b+Blockly.Bubble.BORDER_WIDTH);this.updateColour();a=this.bubble_.getBubbleSize();this.bubble_.setBubbleSize(a.width,a.height)}else this.bubble_.dispose(), this.foreignObject_=this.body_=this.bubble_=null};Blockly.Warning.prototype.bodyFocus_=function(a){this.bubble_.promote_()};Blockly.Warning.prototype.setText=function(a){this.text_=a;this.isVisible()&&(this.setVisible(!1),this.setVisible(!0))};Blockly.Warning.prototype.dispose=function(){this.block_.warning=null;Blockly.Icon.prototype.dispose.call(this)}; // Copyright 2011 Google Inc. Apache License 2.0 -Blockly.uidCounter_=0;Blockly.getUidCounter=function(){return Blockly.uidCounter_};Blockly.setUidCounter=function(a){Blockly.uidCounter_=a};Blockly.Block=function(){goog.asserts.assert(0==arguments.length,"Please use Blockly.Block.obtain.")};Blockly.Block.obtain=function(a,b){if(Blockly.Realtime.isEnabled())return Blockly.Realtime.obtainBlock(a,b);var c=new Blockly.Block;c.initialize(a,b);return c}; -Blockly.Block.prototype.initialize=function(a,b){this.id=++Blockly.uidCounter_;a.addTopBlock(this);this.fill(a,b);goog.isFunction(this.onchange)&&Blockly.bindEvent_(a.getCanvas(),"blocklyWorkspaceChange",this,this.onchange)}; +Blockly.uidCounter_=0;Blockly.getUidCounter=function(){return Blockly.uidCounter_};Blockly.setUidCounter=function(a){Blockly.uidCounter_=a};Blockly.genUid=function(){var a=(++Blockly.uidCounter_).toString();return Blockly.Realtime.isEnabled()?Blockly.Realtime.genUid(a):a};Blockly.Block=function(){goog.asserts.assert(0==arguments.length,"Please use Blockly.Block.obtain.")}; +Blockly.Block.obtain=function(a,b){if(Blockly.Realtime.isEnabled())return Blockly.Realtime.obtainBlock(a,b);var c=new Blockly.Block;c.initialize(a,b);return c};Blockly.Block.prototype.initialize=function(a,b){this.id=Blockly.genUid();a.addTopBlock(this);this.fill(a,b);goog.isFunction(this.onchange)&&Blockly.bindEvent_(a.getCanvas(),"blocklyWorkspaceChange",this,this.onchange)}; Blockly.Block.prototype.fill=function(a,b){this.previousConnection=this.nextConnection=this.outputConnection=null;this.inputList=[];this.disabled=this.rendered=this.inputsInline=!1;this.tooltip="";this.contextMenu=!0;this.parentBlock_=null;this.childBlocks_=[];this.editable_=this.movable_=this.deletable_=!0;this.collapsed_=!1;this.workspace=a;this.isInFlyout=a.isFlyout;if(b){this.type=b;var c=Blockly.Blocks[b];goog.asserts.assertObject(c,'Error: "%s" is an unknown language block.',b);goog.mixin(this, c)}goog.isFunction(this.init)&&this.init()};Blockly.Block.getById=function(a,b){return Blockly.Realtime.isEnabled()?Blockly.Realtime.getBlockById(a):b.getBlockById(a)};Blockly.Block.prototype.svg_=null;Blockly.Block.prototype.mutator=null;Blockly.Block.prototype.comment=null;Blockly.Block.prototype.warning=null;Blockly.Block.prototype.getIcons=function(){var a=[];this.mutator&&a.push(this.mutator);this.comment&&a.push(this.comment);this.warning&&a.push(this.warning);return a}; Blockly.Block.prototype.initSvg=function(){this.svg_=new Blockly.BlockSvg(this);this.svg_.init();Blockly.readOnly||Blockly.bindEvent_(this.svg_.getRootElement(),"mousedown",this,this.onMouseDown_);this.workspace.getCanvas().appendChild(this.svg_.getRootElement())};Blockly.Block.prototype.getSvgRoot=function(){return this.svg_&&this.svg_.getRootElement()};Blockly.Block.dragMode_=0;Blockly.Block.onMouseUpWrapper_=null;Blockly.Block.onMouseMoveWrapper_=null; @@ -867,8 +867,8 @@ Blockly.Block.prototype.moveBy=function(a,b){var c=this.getRelativeToSurfaceXY() Blockly.Block.prototype.getHeightWidth=function(){try{var a=this.getSvgRoot().getBBox(),b=a.height}catch(c){return{height:0,width:0}}Blockly.BROKEN_CONTROL_POINTS&&(b-=10,this.nextConnection&&(b+=4));return{height:b-1,width:a.width}}; Blockly.Block.prototype.onMouseDown_=function(a){if(!this.isInFlyout){Blockly.svgResize();Blockly.terminateDrag_();this.select();Blockly.hideChaff();if(Blockly.isRightButton(a))Blockly.ContextMenu&&this.showContextMenu_(Blockly.mouseToSvg(a));else if(this.isMovable()){Blockly.removeAllRanges();Blockly.setCursorHand_(!0);var b=this.getRelativeToSurfaceXY();this.startDragX=b.x;this.startDragY=b.y;this.startDragMouseX=a.clientX;this.startDragMouseY=a.clientY;Blockly.Block.dragMode_=1;Blockly.Block.onMouseUpWrapper_= Blockly.bindEvent_(document,"mouseup",this,this.onMouseUp_);Blockly.Block.onMouseMoveWrapper_=Blockly.bindEvent_(document,"mousemove",this,this.onMouseMove_);this.draggedBubbles_=[];for(var b=this.getDescendants(),c=0,d;d=b[c];c++){d=d.getIcons();for(var e=0;ethis.workspace.remainingCapacity()&&(d.enabled=!1);c.push(d);this.isEditable()&&!this.collapsed_&&(d={enabled:!0},this.comment?(d.text=Blockly.Msg.REMOVE_COMMENT,d.callback=function(){b.setCommentText(null)}):(d.text=Blockly.Msg.ADD_COMMENT, d.callback=function(){b.setCommentText("")}),c.push(d));if(!this.collapsed_)for(d=0;d=a.clientX&&0==a.clientY&&0==a.button)){Blockly.removeAllRanges();var b=a.clientX-this.startDragMouseX,c=a.clientY-this.startDragMouseY;1==Blockly.Block.dragMode_&&Math.sqrt(Math.pow(b,2)+Math.pow(c,2))>Blockly.DRAG_RADIUS&&(Blockly.Block.dragMode_=2,this.setParent(null),this.setDragging_(!0));if(2==Blockly.Block.dragMode_){var d=this.startDragX+b,e=this.startDragY+c;this.svg_.getRootElement().setAttribute("transform","translate("+ -d+", "+e+")");for(d=0;d=a.clientX&&0==a.clientY&&0==a.button)){Blockly.removeAllRanges();var c=a.clientX-b.startDragMouseX,d=a.clientY-b.startDragMouseY;1==Blockly.Block.dragMode_&&Math.sqrt(Math.pow(c,2)+Math.pow(d,2))>Blockly.DRAG_RADIUS&&(Blockly.Block.dragMode_=2,b.setParent(null),b.setDragging_(!0));if(2==Blockly.Block.dragMode_){var e=b.startDragX+c,f=b.startDragY+d;b.svg_.getRootElement().setAttribute("transform", +"translate("+e+", "+f+")");for(e=0;eBlockly.getUidCounter()&&Blockly.setUidCounter(c+1)}a=Blockly.Realtime.topBlocks_;for(b=0;b>>/g,b);goog.cssom.addCssText(a)}; Blockly.Css.CONTENT=[".blocklySvg {"," background-color: #fff;"," border: 1px solid #ddd;","}",".blocklyWidgetDiv {"," position: absolute;"," display: none;"," z-index: 999;","}",".blocklyDraggable {"," /* Hotspot coordinates are baked into the CUR file, but they are still"," required in the CSS due to a Chrome bug."," http://code.google.com/p/chromium/issues/detail?id=1446 */"," cursor: url(<<>>/media/handopen.cur) 8 5, auto;","}",".blocklyResizeSE {"," fill: #aaa;"," cursor: se-resize;", @@ -1125,4 +1126,5 @@ Blockly.setCursorHand_=function(a){if(!Blockly.readOnly){var b="";a&&(b="url("+B Blockly.getMainWorkspaceMetrics_=function(){var a=Blockly.svgSize();a.width-=Blockly.Toolbox.width;var b=a.width-Blockly.Scrollbar.scrollbarThickness,c=a.height-Blockly.Scrollbar.scrollbarThickness;try{var d=Blockly.mainWorkspace.getCanvas().getBBox()}catch(e){return null}if(Blockly.mainWorkspace.scrollbar)var f=Math.min(d.x-b/2,d.x+d.width-b),b=Math.max(d.x+d.width+b/2,d.x+b),g=Math.min(d.y-c/2,d.y+d.height-c),c=Math.max(d.y+d.height+c/2,d.y+c);else f=d.x,b=f+d.width,g=d.y,c=g+d.height;return{viewHeight:a.height, viewWidth:a.width,contentHeight:c-g,contentWidth:b-f,viewTop:-Blockly.mainWorkspace.scrollY,viewLeft:-Blockly.mainWorkspace.scrollX,contentTop:g,contentLeft:f,absoluteTop:0,absoluteLeft:Blockly.RTL?0:Blockly.Toolbox.width}}; Blockly.setMainWorkspaceMetrics_=function(a){if(!Blockly.mainWorkspace.scrollbar)throw"Attempt to set main workspace scroll without scrollbars.";var b=Blockly.getMainWorkspaceMetrics_();goog.isNumber(a.x)&&(Blockly.mainWorkspace.scrollX=-b.contentWidth*a.x-b.contentLeft);goog.isNumber(a.y)&&(Blockly.mainWorkspace.scrollY=-b.contentHeight*a.y-b.contentTop);a="translate("+(Blockly.mainWorkspace.scrollX+b.absoluteLeft)+","+(Blockly.mainWorkspace.scrollY+b.absoluteTop)+")";Blockly.mainWorkspace.getCanvas().setAttribute("transform", -a);Blockly.mainWorkspace.getBubbleCanvas().setAttribute("transform",a)};Blockly.addChangeListener=function(a){return Blockly.bindEvent_(Blockly.mainWorkspace.getCanvas(),"blocklyWorkspaceChange",null,a)};Blockly.removeChangeListener=function(a){Blockly.unbindEvent_(a)};Blockly.getMainWorkspace=function(){return Blockly.mainWorkspace};window.Blockly=Blockly;Blockly.getMainWorkspace=Blockly.getMainWorkspace;Blockly.addChangeListener=Blockly.addChangeListener;Blockly.removeChangeListener=Blockly.removeChangeListener; \ No newline at end of file +a);Blockly.mainWorkspace.getBubbleCanvas().setAttribute("transform",a)};Blockly.doCommand=function(a){Blockly.Realtime.isEnabled?Blockly.Realtime.doCommand(a):a()};Blockly.addChangeListener=function(a){return Blockly.bindEvent_(Blockly.mainWorkspace.getCanvas(),"blocklyWorkspaceChange",null,a)};Blockly.removeChangeListener=function(a){Blockly.unbindEvent_(a)};Blockly.getMainWorkspace=function(){return Blockly.mainWorkspace};window.Blockly=Blockly;Blockly.getMainWorkspace=Blockly.getMainWorkspace; +Blockly.addChangeListener=Blockly.addChangeListener;Blockly.removeChangeListener=Blockly.removeChangeListener; \ No newline at end of file diff --git a/blockly_uncompressed.js b/blockly_uncompressed.js index cbfca09ac..405de8c77 100644 --- a/blockly_uncompressed.js +++ b/blockly_uncompressed.js @@ -24,9 +24,9 @@ if (!window.goog) { // Build map of all dependencies (used and unused). var dir = window.BLOCKLY_DIR.match(/[^\/]+$/)[0]; -goog.addDependency("../../../" + dir + "/core/block.js", ['Blockly.Block'], ['Blockly.BlockSvg', 'Blockly.Blocks', 'Blockly.Comment', 'Blockly.Connection', 'Blockly.ContextMenu', 'Blockly.Input', 'Blockly.Msg', 'Blockly.Mutator', 'Blockly.Warning', 'Blockly.Workspace', 'Blockly.Xml', 'goog.asserts', 'goog.string', 'goog.Timer', 'goog.array']); +goog.addDependency("../../../" + dir + "/core/block.js", ['Blockly.Block'], ['Blockly.BlockSvg', 'Blockly.Blocks', 'Blockly.Comment', 'Blockly.Connection', 'Blockly.ContextMenu', 'Blockly.Input', 'Blockly.Msg', 'Blockly.Mutator', 'Blockly.Warning', 'Blockly.Workspace', 'Blockly.Xml', 'goog.Timer', 'goog.array', 'goog.asserts', 'goog.string']); goog.addDependency("../../../" + dir + "/core/block_svg.js", ['Blockly.BlockSvg'], ['goog.userAgent']); -goog.addDependency("../../../" + dir + "/core/blockly.js", ['Blockly'], ['Blockly.Block', 'Blockly.Connection', 'Blockly.FieldAngle', 'Blockly.FieldCheckbox', 'Blockly.FieldColour', 'Blockly.FieldDropdown', 'Blockly.FieldImage', 'Blockly.FieldTextInput', 'Blockly.FieldVariable', 'Blockly.Generator', 'Blockly.Msg', 'Blockly.Procedures', 'Blockly.Realtime', 'Blockly.Toolbox', 'Blockly.WidgetDiv', 'Blockly.Workspace', 'Blockly.inject', 'Blockly.utils', 'goog.dom', 'goog.color', 'goog.events', 'goog.string', 'goog.ui.ColorPicker', 'goog.ui.tree.TreeControl', 'goog.userAgent']); +goog.addDependency("../../../" + dir + "/core/blockly.js", ['Blockly'], ['Blockly.Block', 'Blockly.Connection', 'Blockly.FieldAngle', 'Blockly.FieldCheckbox', 'Blockly.FieldColour', 'Blockly.FieldDropdown', 'Blockly.FieldImage', 'Blockly.FieldTextInput', 'Blockly.FieldVariable', 'Blockly.Generator', 'Blockly.Msg', 'Blockly.Procedures', 'Blockly.Realtime', 'Blockly.Toolbox', 'Blockly.WidgetDiv', 'Blockly.Workspace', 'Blockly.inject', 'Blockly.utils', 'goog.color', 'goog.dom', 'goog.events', 'goog.string', 'goog.ui.ColorPicker', 'goog.ui.tree.TreeControl', 'goog.userAgent']); goog.addDependency("../../../" + dir + "/core/blocks.js", ['Blockly.Blocks'], ['goog.asserts']); goog.addDependency("../../../" + dir + "/core/bubble.js", ['Blockly.Bubble'], ['Blockly.Workspace']); goog.addDependency("../../../" + dir + "/core/comment.js", ['Blockly.Comment'], ['Blockly.Bubble', 'Blockly.Icon']); diff --git a/core/block.js b/core/block.js index 98822122d..5ebb29637 100644 --- a/core/block.js +++ b/core/block.js @@ -37,10 +37,10 @@ goog.require('Blockly.Mutator'); goog.require('Blockly.Warning'); goog.require('Blockly.Workspace'); goog.require('Blockly.Xml'); -goog.require('goog.asserts'); -goog.require('goog.string'); goog.require('goog.Timer'); goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.string'); /** @@ -51,11 +51,11 @@ Blockly.uidCounter_ = 0; /** * Get the Blockly.uidCounter_ - * @returns {number} + * @return {number} */ Blockly.getUidCounter = function() { return Blockly.uidCounter_; -} +}; /** * Set the Blockly.uidCounter_ @@ -63,7 +63,21 @@ Blockly.getUidCounter = function() { */ Blockly.setUidCounter = function(val) { Blockly.uidCounter_ = val; -} +}; + +/** + * Generate a unique id. This will be locally or globally unique, depending on + * whether we are in single user or realtime collaborative mode. + * @return {string} + */ +Blockly.genUid = function() { + var uid = (++Blockly.uidCounter_).toString(); + if (Blockly.Realtime.isEnabled()) { + return Blockly.Realtime.genUid(uid); + } else { + return uid; + } +}; /** * Class for one block. @@ -100,7 +114,7 @@ Blockly.Block.obtain = function(workspace, prototypeName) { * type-specific functions for this block. */ Blockly.Block.prototype.initialize = function(workspace, prototypeName) { - this.id = ++Blockly.uidCounter_; + this.id = Blockly.genUid(); workspace.addTopBlock(this); this.fill(workspace, prototypeName); // Bind an onchange function, if it exists. @@ -552,38 +566,41 @@ Blockly.Block.prototype.onMouseDown_ = function(e) { * @private */ Blockly.Block.prototype.onMouseUp_ = function(e) { - Blockly.terminateDrag_(); - if (Blockly.selected && Blockly.highlightedConnection_) { - // Connect two blocks together. - Blockly.localConnection_.connect(Blockly.highlightedConnection_); - if (this.svg_) { - // Trigger a connection animation. - // Determine which connection is inferior (lower in the source stack). - var inferiorConnection; - if (Blockly.localConnection_.isSuperior()) { - inferiorConnection = Blockly.highlightedConnection_; - } else { - inferiorConnection = Blockly.localConnection_; + var this_ = this; + Blockly.doCommand(function() { + Blockly.terminateDrag_(); + if (Blockly.selected && Blockly.highlightedConnection_) { + // Connect two blocks together. + Blockly.localConnection_.connect(Blockly.highlightedConnection_); + if (this_.svg_) { + // Trigger a connection animation. + // Determine which connection is inferior (lower in the source stack). + var inferiorConnection; + if (Blockly.localConnection_.isSuperior()) { + inferiorConnection = Blockly.highlightedConnection_; + } else { + inferiorConnection = Blockly.localConnection_; + } + inferiorConnection.sourceBlock_.svg_.connectionUiEffect(); } - inferiorConnection.sourceBlock_.svg_.connectionUiEffect(); + if (this_.workspace.trashcan && this_.workspace.trashcan.isOpen) { + // Don't throw an object in the trash can if it just got connected. + this_.workspace.trashcan.close(); + } + } else if (this_.workspace.trashcan && this_.workspace.trashcan.isOpen) { + var trashcan = this_.workspace.trashcan; + goog.Timer.callOnce(trashcan.close, 100, trashcan); + Blockly.selected.dispose(false, true); + // Dropping a block on the trash can will usually cause the workspace to + // resize to contain the newly positioned block. Force a second resize + // now that the block has been deleted. + Blockly.fireUiEvent(window, 'resize'); } - if (this.workspace.trashcan && this.workspace.trashcan.isOpen) { - // Don't throw an object in the trash can if it just got connected. - this.workspace.trashcan.close(); + if (Blockly.highlightedConnection_) { + Blockly.highlightedConnection_.unhighlight(); + Blockly.highlightedConnection_ = null; } - } else if (this.workspace.trashcan && this.workspace.trashcan.isOpen) { - var trashcan = this.workspace.trashcan; - goog.Timer.callOnce(trashcan.close, 100, trashcan); - Blockly.selected.dispose(false, true); - // Dropping a block on the trash can will usually cause the workspace to - // resize to contain the newly positioned block. Force a second resize now - // that the block has been deleted. - Blockly.fireUiEvent(window, 'resize'); - } - if (Blockly.highlightedConnection_) { - Blockly.highlightedConnection_.unhighlight(); - Blockly.highlightedConnection_ = null; - } + }); }; /** @@ -828,80 +845,83 @@ Blockly.Block.prototype.setDragging_ = function(adding) { * @private */ Blockly.Block.prototype.onMouseMove_ = function(e) { - if (e.type == 'mousemove' && e.clientX <= 1 && e.clientY == 0 && - e.button == 0) { - /* HACK: - Safari Mobile 6.0 and Chrome for Android 18.0 fire rogue mousemove events - on certain touch actions. Ignore events with these signatures. - This may result in a one-pixel blind spot in other browsers, - but this shouldn't be noticable. */ - e.stopPropagation(); - return; - } - Blockly.removeAllRanges(); - var dx = e.clientX - this.startDragMouseX; - var dy = e.clientY - this.startDragMouseY; - if (Blockly.Block.dragMode_ == 1) { - // Still dragging within the sticky DRAG_RADIUS. - var dr = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)); - if (dr > Blockly.DRAG_RADIUS) { - // Switch to unrestricted dragging. - Blockly.Block.dragMode_ = 2; - // Push this block to the very top of the stack. - this.setParent(null); - this.setDragging_(true); + var this_ = this; + Blockly.doCommand(function() { + if (e.type == 'mousemove' && e.clientX <= 1 && e.clientY == 0 && + e.button == 0) { + /* HACK: + Safari Mobile 6.0 and Chrome for Android 18.0 fire rogue mousemove events + on certain touch actions. Ignore events with these signatures. + This may result in a one-pixel blind spot in other browsers, + but this shouldn't be noticable. */ + e.stopPropagation(); + return; } - } - if (Blockly.Block.dragMode_ == 2) { - // Unrestricted dragging. - var x = this.startDragX + dx; - var y = this.startDragY + dy; - this.svg_.getRootElement().setAttribute('transform', - 'translate(' + x + ', ' + y + ')'); - // Drag all the nested bubbles. - for (var i = 0; i < this.draggedBubbles_.length; i++) { - var commentData = this.draggedBubbles_[i]; - commentData.bubble.setIconLocation(commentData.x + dx, - commentData.y + dy); - } - - // Check to see if any of this block's connections are within range of - // another block's connection. - var myConnections = this.getConnections_(false); - var closestConnection = null; - var localConnection = null; - var radiusConnection = Blockly.SNAP_RADIUS; - for (var i = 0; i < myConnections.length; i++) { - var myConnection = myConnections[i]; - var neighbour = myConnection.closest(radiusConnection, dx, dy); - if (neighbour.connection) { - closestConnection = neighbour.connection; - localConnection = myConnection; - radiusConnection = neighbour.radius; + Blockly.removeAllRanges(); + var dx = e.clientX - this_.startDragMouseX; + var dy = e.clientY - this_.startDragMouseY; + if (Blockly.Block.dragMode_ == 1) { + // Still dragging within the sticky DRAG_RADIUS. + var dr = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)); + if (dr > Blockly.DRAG_RADIUS) { + // Switch to unrestricted dragging. + Blockly.Block.dragMode_ = 2; + // Push this block to the very top of the stack. + this_.setParent(null); + this_.setDragging_(true); } } + if (Blockly.Block.dragMode_ == 2) { + // Unrestricted dragging. + var x = this_.startDragX + dx; + var y = this_.startDragY + dy; + this_.svg_.getRootElement().setAttribute('transform', + 'translate(' + x + ', ' + y + ')'); + // Drag all the nested bubbles. + for (var i = 0; i < this_.draggedBubbles_.length; i++) { + var commentData = this_.draggedBubbles_[i]; + commentData.bubble.setIconLocation(commentData.x + dx, + commentData.y + dy); + } - // Remove connection highlighting if needed. - if (Blockly.highlightedConnection_ && - Blockly.highlightedConnection_ != closestConnection) { - Blockly.highlightedConnection_.unhighlight(); - Blockly.highlightedConnection_ = null; - Blockly.localConnection_ = null; + // Check to see if any of this block's connections are within range of + // another block's connection. + var myConnections = this_.getConnections_(false); + var closestConnection = null; + var localConnection = null; + var radiusConnection = Blockly.SNAP_RADIUS; + for (var i = 0; i < myConnections.length; i++) { + var myConnection = myConnections[i]; + var neighbour = myConnection.closest(radiusConnection, dx, dy); + if (neighbour.connection) { + closestConnection = neighbour.connection; + localConnection = myConnection; + radiusConnection = neighbour.radius; + } + } + + // Remove connection highlighting if needed. + if (Blockly.highlightedConnection_ && + Blockly.highlightedConnection_ != closestConnection) { + Blockly.highlightedConnection_.unhighlight(); + Blockly.highlightedConnection_ = null; + Blockly.localConnection_ = null; + } + // Add connection highlighting if needed. + if (closestConnection && + closestConnection != Blockly.highlightedConnection_) { + closestConnection.highlight(); + Blockly.highlightedConnection_ = closestConnection; + Blockly.localConnection_ = localConnection; + } + // Flip the trash can lid if needed. + if (this_.workspace.trashcan && this_.isDeletable()) { + this_.workspace.trashcan.onMouseMove(e); + } } - // Add connection highlighting if needed. - if (closestConnection && - closestConnection != Blockly.highlightedConnection_) { - closestConnection.highlight(); - Blockly.highlightedConnection_ = closestConnection; - Blockly.localConnection_ = localConnection; - } - // Flip the trash can lid if needed. - if (this.workspace.trashcan && this.isDeletable()) { - this.workspace.trashcan.onMouseMove(e); - } - } - // This event has been handled. No need to bubble up to the document. - e.stopPropagation(); + // This event has been handled. No need to bubble up to the document. + e.stopPropagation(); + }); }; /** @@ -1423,7 +1443,7 @@ Blockly.Block.prototype.setCollapsed = function(collapsed) { var text = this.toString(Blockly.COLLAPSE_CHARS); this.appendDummyInput(COLLAPSED_INPUT_NAME).appendField(text); } else { - this.removeInput(COLLAPSED_INPUT_NAME) + this.removeInput(COLLAPSED_INPUT_NAME); } if (!renderList.length) { @@ -1465,7 +1485,7 @@ Blockly.Block.prototype.toString = function(opt_maxLength) { text = goog.string.truncate(text, opt_maxLength); } return text; -} +}; /** * Shortcut for appending a value input row. @@ -1678,9 +1698,9 @@ Blockly.Block.prototype.moveNumberedInputBefore = function( // Validate arguments. goog.asserts.assert(inputIndex != refIndex, 'Can\'t move input to itself.'); goog.asserts.assert(inputIndex < this.inputList.length, - 'Input index ' + inputIndex + ' out of bounds.') + 'Input index ' + inputIndex + ' out of bounds.'); goog.asserts.assert(refIndex <= this.inputList.length, - 'Reference input ' + refIndex + ' out of bounds.') + 'Reference input ' + refIndex + ' out of bounds.'); // Remove input. var input = this.inputList[inputIndex]; this.inputList.splice(inputIndex, 1); diff --git a/core/blockly.js b/core/blockly.js index f80afb8ab..a7cfc939b 100644 --- a/core/blockly.js +++ b/core/blockly.js @@ -48,8 +48,8 @@ goog.require('Blockly.inject'); goog.require('Blockly.utils'); // Closure dependencies. -goog.require('goog.dom'); goog.require('goog.color'); +goog.require('goog.dom'); goog.require('goog.events'); goog.require('goog.string'); goog.require('goog.ui.ColorPicker'); @@ -669,6 +669,22 @@ Blockly.setMainWorkspaceMetrics_ = function(xyRatio) { translation); }; +/** + * Execute a command. Generally, a command is the result of a user action + * e.g., a click, drag or context menu selection. Calling the cmdThunk function + * through doCommand() allows us to capture information that can be used for + * capabilities like undo (which is supported by the realtime collaboration + * feature). + * @param {function()} cmdThunk A function representing the command execution. + */ +Blockly.doCommand = function(cmdThunk) { + if (Blockly.Realtime.isEnabled) { + Blockly.Realtime.doCommand(cmdThunk); + } else { + cmdThunk(); + } +}; + /** * When something in Blockly's workspace changes, call a function. * @param {!Function} func Function to call. diff --git a/core/contextmenu.js b/core/contextmenu.js index f3f70940f..5e46c56ad 100644 --- a/core/contextmenu.js +++ b/core/contextmenu.js @@ -109,7 +109,11 @@ Blockly.ContextMenu.show = function(xy, options) { resizeList.push(rectElement); Blockly.bindEvent_(gElement, 'mousedown', null, Blockly.noEvent); if (option.enabled) { - Blockly.bindEvent_(gElement, 'mouseup', null, option.callback); + var evtHandlerCapturer = function(callback) { + return function() { Blockly.doCommand(callback); }; + }; + var evtHandler = evtHandlerCapturer(option.callback); + Blockly.bindEvent_(gElement, 'mouseup', null, evtHandler); Blockly.bindEvent_(gElement, 'mouseup', null, Blockly.ContextMenu.hide); } else { gElement.setAttribute('class', 'blocklyMenuDivDisabled'); diff --git a/core/realtime.js b/core/realtime.js index b8226ccfd..71a42ef0c 100644 --- a/core/realtime.js +++ b/core/realtime.js @@ -21,10 +21,6 @@ /** * This file contains functions used by any Blockly app that wants to provide * realtime collaboration functionality. - * - * Note that it depends on the existence of particularly named UI elements. - * - * TODO: Inject the UI element names */ /** @@ -34,7 +30,7 @@ * Console. Instructions on how to do that can be found in the Blockly wiki page * at https://code.google.com/p/blockly/wiki/RealtimeCollaboration * Once you do that you can set the clientId in - * Blockly.Realtime.realtimeOptions_ + * Blockly.Realtime.rtclientOptions_ * @author markf@google.com (Mark Friedman) */ 'use strict'; @@ -52,6 +48,13 @@ goog.require('rtclient'); */ Blockly.Realtime.enabled_ = false; +/** + * The Realtime document being collaborated on. + * @type {gapi.drive.realtime.Document} + * @private + */ +Blockly.Realtime.document_ = null; + /** * The Realtime model of this doc. * @type {gapi.drive.realtime.Model} @@ -59,6 +62,13 @@ Blockly.Realtime.enabled_ = false; */ Blockly.Realtime.model_ = null; +/** + * The unique id associated with this editing session. + * @type {string} + * @private + */ +Blockly.Realtime.sessionId_ = null; + /** * The function used to initialize the UI after realtime is initialized. * @type {function()} @@ -100,6 +110,13 @@ Blockly.Realtime.chatBoxElementId_ = null; */ Blockly.Realtime.chatBoxInitialText_ = null; +/** + * Indicator of whether we are in the context of an undo or redo operation. + * @type {boolean} + * @private + */ +Blockly.Realtime.withinUndo_ = false; + /** * Returns whether realtime collaboration is enabled. * @return {boolean} @@ -108,6 +125,20 @@ Blockly.Realtime.isEnabled = function() { return Blockly.Realtime.enabled_; }; +/** + * The id of the button to use for undo. + * @type {string} + * @private + */ +Blockly.Realtime.undoElementId_ = null; + +/** + * The id of the button to use for redo. + * @type {string} + * @private + */ +Blockly.Realtime.redoElementId_ = null; + /** * This function is called the first time that the Realtime model is created * for a file. This function should be used to initialize any values of the @@ -175,37 +206,58 @@ Blockly.Realtime.getBlockById = function(id) { return Blockly.Realtime.blocksMap_.get(id); }; +/** + * Log the event for debugging purposses. + * @param {gapi.drive.realtime.BaseModelEvent} evt The event that occurred. + * @private + */ +Blockly.Realtime.logEvent_ = function (evt) { + console.log("Object event:"); + console.log(" id: " + evt.target.id); + console.log(" type: " + evt.type); + var events = evt.events; + if (events) { + var eventCount = events.length; + for (var i = 0; i < eventCount; i++) { + var event = events[i]; + console.log(" child event:"); + console.log(" id: " + event.target.id); + console.log(" type: " + event.type); + } + } +}; + /** * Event handler to call when a block is changed. * @param {gapi.drive.realtime.ObjectChangedEvent} evt The event that occurred. * @private */ Blockly.Realtime.onObjectChange_ = function(evt) { + Blockly.Realtime.logEvent_(evt); var events = evt.events; var eventCount = evt.events.length; for (var i = 0; i < eventCount; i++) { var event = events[i]; - if (!event.isLocal) { + if (!event.isLocal || Blockly.Realtime.withinUndo_) { + var block = event.target; if (event.type == 'value_changed') { if (event.property == 'xmlDom') { - var block = event.target; Blockly.Realtime.doWithinSync_(function() { Blockly.Realtime.placeBlockOnWorkspace_(block, false); Blockly.Realtime.moveBlock_(block); }); } else if (event.property == 'relativeX' || - event.property == 'relativeY') { - var block2 = event.target; + event.property == 'relativeY') { Blockly.Realtime.doWithinSync_(function() { - if (!block2.svg_) { - // If this is a move of a newly disconnected (i.e newly top level) - // block it will not have any svg (because it has been disposed of - // by it's parent), so we need to handle that here. - Blockly.Realtime.placeBlockOnWorkspace_(block2, false); + if (!block.svg_) { + // If this is a move of a newly disconnected (i.e newly top + // level) block it will not have any svg (because it has been + // disposed of by it's parent), so we need to handle that here. + Blockly.Realtime.placeBlockOnWorkspace_(block, false); } - Blockly.Realtime.moveBlock_(block2); + Blockly.Realtime.moveBlock_(block); }); - } + } } } } @@ -217,9 +269,10 @@ Blockly.Realtime.onObjectChange_ = function(evt) { * @private */ Blockly.Realtime.onBlocksMapChange_ = function(evt) { - console.log('Blocks Map event:'); - console.log(' id: ' + evt.property); - if (!evt.isLocal) { + Blockly.Realtime.logEvent_(evt); +// console.log('Blocks Map event:'); +// console.log(' id: ' + evt.property); + if (!evt.isLocal || Blockly.Realtime.withinUndo_) { var block = evt.newValue; if (block) { Blockly.Realtime.placeBlockOnWorkspace_(block, !(evt.oldValue)); @@ -259,6 +312,9 @@ Blockly.Realtime.doWithinSync_ = function(thunk) { */ Blockly.Realtime.placeBlockOnWorkspace_ = function(block, addToTop) { Blockly.Realtime.doWithinSync_(function() { +// if (!Blockly.Realtime.blocksMap_.has(block.id)) { +// Blockly.Realtime.blocksMap_.set(block.id, block); +// } var blockDom = Blockly.Xml.textToDom(block.xmlDom).firstChild; var newBlock = Blockly.Xml.domToBlock(Blockly.mainWorkspace, blockDom, true); @@ -309,16 +365,6 @@ Blockly.Realtime.deleteBlock = function(block) { * @private */ Blockly.Realtime.loadBlocks_ = function() { - var blocks = Blockly.Realtime.blocksMap_.values(); - for (var i = 0; i < blocks.length; i++) { - var block = blocks[i]; - // Since we now have blocks with already existing ids, we have to make sure - // that new blocks don't get any of the existing ids. - var blockIdNum = parseInt(block.id, 10); - if (blockIdNum > Blockly.getUidCounter()) { - Blockly.setUidCounter(blockIdNum + 1); - } - } var topBlocks = Blockly.Realtime.topBlocks_; for (var j = 0; j < topBlocks.length; j++) { var topBlock = topBlocks.get(j); @@ -366,6 +412,8 @@ Blockly.Realtime.blockChanged = function(block) { * @private */ Blockly.Realtime.onFileLoaded_ = function(doc) { + Blockly.Realtime.document_ = doc; + Blockly.Realtime.sessionId_ = Blockly.Realtime.getSessionId_(doc); Blockly.Realtime.model_ = doc.getModel(); Blockly.Realtime.blocksMap_ = Blockly.Realtime.model_.getRoot().get('blocks'); @@ -385,26 +433,57 @@ Blockly.Realtime.onFileLoaded_ = function(doc) { // Add logic for undo button. // TODO: Uncomment this when undo/redo are fixed. -/* - var undoButton = document.getElementById('undoButton'); - var redoButton = document.getElementById('redoButton'); +// +// var undoButton = document.getElementById(Blockly.Realtime.undoElementId_); +// var redoButton = document.getElementById(Blockly.Realtime.redoElementId_); +// +// if (undoButton) { +// undoButton.onclick = function (e) { +// try { +// Blockly.Realtime.withinUndo_ = true; +// Blockly.Realtime.model_.undo(); +// } finally { +// Blockly.Realtime.withinUndo_ = false; +// } +// }; +// } +// if (redoButton) { +// redoButton.onclick = function (e) { +// try { +// Blockly.Realtime.withinUndo_ = true; +// Blockly.Realtime.model_.redo(); +// } finally { +// Blockly.Realtime.withinUndo_ = false; +// } +// }; +// } +// +// // Add event handler for UndoRedoStateChanged events. +// var onUndoRedoStateChanged = function(e) { +// undoButton.disabled = !e.canUndo; +// redoButton.disabled = !e.canRedo; +// }; +// Blockly.Realtime.model_.addEventListener( +// gapi.drive.realtime.EventType.UNDO_REDO_STATE_CHANGED, +// onUndoRedoStateChanged); - undoButton.onclick = function(e) { - Blockly.Realtime.model_.undo(); - }; - redoButton.onclick = function(e) { - Blockly.Realtime.model_.redo(); - }; +}; - // Add event handler for UndoRedoStateChanged events. - var onUndoRedoStateChanged = function(e) { - undoButton.disabled = !e.canUndo; - redoButton.disabled = !e.canRedo; - }; - Blockly.Realtime.model_.addEventListener( - gapi.drive.realtime.EventType.UNDO_REDO_STATE_CHANGED, - onUndoRedoStateChanged); +/** + * Get the sessionId associated with this editing session. Note that it is + * unique to the current browser window/tab. + * @param {gapi.drive.realtime.Document} doc + * @return {*} + * @private */ +Blockly.Realtime.getSessionId_ = function(doc) { + var collaborators = doc.getCollaborators(); + for (var i = 0; i < collaborators.length; i++) { + var collaborator = collaborators[i]; + if (collaborator.isMe) { + return collaborator.sessionId; + } + } }; /** @@ -417,7 +496,6 @@ Blockly.Realtime.registerTypes_ = function() { custom.registerType(Blockly.Block, 'Block'); Blockly.Block.prototype.id = custom.collaborativeField('id'); - Blockly.Block.prototype.type = custom.collaborativeField('type'); Blockly.Block.prototype.xmlDom = custom.collaborativeField('xmlDom'); Blockly.Block.prototype.relativeX = custom.collaborativeField('relativeX'); Blockly.Block.prototype.relativeY = custom.collaborativeField('relativeY'); @@ -516,11 +594,12 @@ Blockly.Realtime.getUserDomain = function(fileId, callback) { * Options for the Realtime loader. * @private */ -Blockly.Realtime.realtimeOptions_ = { +Blockly.Realtime.rtclientOptions_ = { /** * Client ID from the console. + * This will be set from the options passed into Blockly.Realtime.start() */ - clientId: 'INSERT YOUR CLIENT ID HERE', + clientId: null, /** * The ID of the button to click to authorize. Must be a DOM element ID. @@ -588,6 +667,13 @@ Blockly.Realtime.parseOptions_ = function(options) { Blockly.Realtime.chatBoxInitialText_ = rtclient.getOption(chatBoxOptions, 'initText', Blockly.Msg.CHAT); } + Blockly.Realtime.rtclientOptions_.clientId = + rtclient.getOption(options, 'clientId'); + // TODO: Uncomment this when undo/redo are fixed. +// Blockly.Realtime.undoElementId_ = +// rtclient.getOption(options, 'undoElementId', 'undoButton'); +// Blockly.Realtime.redoElementId_ = +// rtclient.getOption(options, 'redoElementId', 'redoButton'); }; /** @@ -615,7 +701,7 @@ Blockly.Realtime.startRealtime = function(uiInitialize, uiContainer, options) { } }; Blockly.Realtime.realtimeLoader_ = - new rtclient.RealtimeLoader(Blockly.Realtime.realtimeOptions_); + new rtclient.RealtimeLoader(Blockly.Realtime.rtclientOptions_); Blockly.Realtime.realtimeLoader_.start(); }; @@ -632,7 +718,7 @@ Blockly.Realtime.addAuthUi_ = function(uiContainer) { var authText = goog.dom.createDom('p', null, Blockly.Msg.AUTH); authButtonDiv.appendChild(authText); var authButton = goog.dom.createDom('button', null, 'Authorize'); - authButton.id = Blockly.Realtime.realtimeOptions_.authButtonElementId; + authButton.id = Blockly.Realtime.rtclientOptions_.authButtonElementId; authButtonDiv.appendChild(authButton); uiContainer.appendChild(authButtonDiv); @@ -653,3 +739,57 @@ Blockly.Realtime.addAuthUi_ = function(uiContainer) { (blocklyDivBounds.height - authButtonDivBounds.height) / 4 + 'px'; return authButtonDiv; }; + +/** + * Execute a command. Generally, a command is the result of a user action + * e.g., a click, drag or context menu selection. + * @param {function()} cmdThunk A function representing the command execution. + */ +Blockly.Realtime.doCommand = function(cmdThunk) { + // TODO(): We'd like to use the realtime API compound operations as in the + // commented out code below. However, it appears that the realtime API is + // re-ordering events when they're within compound operations in a way which + // breaks us. We might need to implement our own compound operations as a + // workaround. Doing so might give us some other advantages since we could + // then allow compound operations that span synchronous blocks of code (e.g., + // span multiple Blockly events). It would also allow us to deal with the + // fact that the current realtime API puts some operations into the undo stack + // that we would prefer weren't there; namely local changes that occur as a + // result of remote realtime events. +// try { +// Blockly.Realtime.model_.beginCompoundOperation(); +// cmdThunk(); +// } finally { +// Blockly.Realtime.model_.endCompoundOperation(); +// } + cmdThunk(); +}; + +/** + * Generate an id that is unique among the all the sessions that ever + * collaborated on this document. + * @param {string} extra A string id which is unique within this particular + * session. + * @return {string} + */ +Blockly.Realtime.genUid = function(extra) { + /* The idea here is that we use the extra string to ensure uniqueness within + this session and the current sessionId to ensure uniqueness across + all the current sessions. There's still the (remote) chance that the + current sessionId is the same as some old (non-current) one, so we still + need to check that our uid hasn't been previously used. + + Note that you could potentially use a random number to generate the id but + there remains the small chance of regenerating the same number that's been + used before and I'm paranoid. It's not enough to just check that the + random uid hasn't been previously used because other concurrent sessions + might generate the same uid at the same time. Like I said, I'm paranoid. + + */ + var potentialUid = Blockly.Realtime.sessionId_ + '-' + extra; + if (!Blockly.Realtime.blocksMap_.has(potentialUid)) { + return potentialUid; + } else { + return (Blockly.Realtime.genUid('-' + extra)); + } +}; diff --git a/tests/playground.html b/tests/playground.html index 64c3aaa52..e4212368e 100644 --- a/tests/playground.html +++ b/tests/playground.html @@ -57,7 +57,9 @@ function start() { var toolbox = document.getElementById('toolbox'); Blockly.inject(document.getElementById('blocklyDiv'), {rtl: rtl, path: '../', toolbox: toolbox, realtime: false, - realtimeOptions: {chatbox: {elementId: 'chatbox'}}}); + realtimeOptions: + {clientId: 'YOUR CLIENT ID GOES HERE', + chatbox: {elementId: 'chatbox'}}}); if (Blockly.Realtime.isEnabled()) { enableRealtimeSpecificUi(); } @@ -339,6 +341,7 @@ h1 {
+