diff --git a/apps/puzzle/puzzle.js b/apps/puzzle/puzzle.js index e2a527027..53653ea43 100644 --- a/apps/puzzle/puzzle.js +++ b/apps/puzzle/puzzle.js @@ -76,15 +76,15 @@ Puzzle.init = function() { var blocksCities = []; var i = 1; while (BlocklyApps.getMsgOrNull('Puzzle_country' + i)) { - var block = new Blockly.Block(Blockly.mainWorkspace, 'country'); + var block = Blockly.Block.obtain(Blockly.mainWorkspace, 'country'); block.populate(i); blocksCountries.push(block); - var block = new Blockly.Block(Blockly.mainWorkspace, 'flag'); + var block = Blockly.Block.obtain(Blockly.mainWorkspace, 'flag'); block.populate(i); blocksFlags.push(block); var j = 1; while (BlocklyApps.getMsgOrNull('Puzzle_country' + i + 'City' + j)) { - var block = new Blockly.Block(Blockly.mainWorkspace, 'city'); + var block = Blockly.Block.obtain(Blockly.mainWorkspace, 'city'); block.populate(i, j); blocksCities.push(block); j++; diff --git a/blockly_compressed.js b/blockly_compressed.js index 8c96cfd3c..dcc801f78 100644 --- a/blockly_compressed.js +++ b/blockly_compressed.js @@ -94,7 +94,7 @@ goog.array.zip=function(a){if(!arguments.length)return[];for(var b=[],c=0;;c++){ goog.debug.entryPointRegistry.monitorAll=function(a){goog.debug.entryPointRegistry.monitorsMayExist_=!0;for(var b=goog.bind(a.wrap,a),c=0;c=a.keyCode)a.keyCode=-1}catch(b){}};goog.events.BrowserEvent.prototype.getBrowserEvent=function(){return this.event_};goog.events.BrowserEvent.prototype.disposeInternal=function(){};goog.events.EventId=function(a){this.id=a};goog.events.EventId.prototype.toString=function(){return this.id};goog.events.Listenable=function(){};goog.events.Listenable.IMPLEMENTED_BY_PROP="closure_listenable_"+(1E6*Math.random()|0);goog.events.Listenable.addImplementation=function(a){a.prototype[goog.events.Listenable.IMPLEMENTED_BY_PROP]=!0};goog.events.Listenable.isImplementedBy=function(a){try{return!(!a||!a[goog.events.Listenable.IMPLEMENTED_BY_PROP])}catch(b){return!1}};goog.events.ListenableKey=function(){};goog.events.ListenableKey.counter_=0;goog.events.ListenableKey.reserveKey=function(){return++goog.events.ListenableKey.counter_};goog.events.Listener=function(a,b,c,d,e,f){goog.events.Listener.ENABLE_MONITORING&&(this.creationStack=Error().stack);this.listener=a;this.proxy=b;this.src=c;this.type=d;this.capture=!!e;this.handler=f;this.key=goog.events.ListenableKey.reserveKey();this.removed=this.callOnce=!1};goog.events.Listener.ENABLE_MONITORING=!1;goog.events.Listener.prototype.markAsRemoved=function(){this.removed=!0;this.handler=this.src=this.proxy=this.listener=null};goog.object={};goog.object.forEach=function(a,b,c){for(var d in a)b.call(c,a[d],d,a)};goog.object.filter=function(a,b,c){var d={},e;for(e in a)b.call(c,a[e],e,a)&&(d[e]=a[e]);return d};goog.object.map=function(a,b,c){var d={},e;for(e in a)d[e]=b.call(c,a[e],e,a);return d};goog.object.some=function(a,b,c){for(var d in a)if(b.call(c,a[d],d,a))return!0;return!1};goog.object.every=function(a,b,c){for(var d in a)if(!b.call(c,a[d],d,a))return!1;return!0}; +goog.events.BrowserEvent.prototype.preventDefault=function(){goog.events.BrowserEvent.superClass_.preventDefault.call(this);var a=this.event_;if(a.preventDefault)a.preventDefault();else if(a.returnValue=!1,goog.events.BrowserFeature.SET_KEY_CODE_TO_PREVENT_DEFAULT)try{if(a.ctrlKey||112<=a.keyCode&&123>=a.keyCode)a.keyCode=-1}catch(b){}};goog.events.BrowserEvent.prototype.getBrowserEvent=function(){return this.event_};goog.events.BrowserEvent.prototype.disposeInternal=function(){};goog.events.Listenable=function(){};goog.events.Listenable.IMPLEMENTED_BY_PROP="closure_listenable_"+(1E6*Math.random()|0);goog.events.Listenable.addImplementation=function(a){a.prototype[goog.events.Listenable.IMPLEMENTED_BY_PROP]=!0};goog.events.Listenable.isImplementedBy=function(a){try{return!(!a||!a[goog.events.Listenable.IMPLEMENTED_BY_PROP])}catch(b){return!1}};goog.events.ListenableKey=function(){};goog.events.ListenableKey.counter_=0;goog.events.ListenableKey.reserveKey=function(){return++goog.events.ListenableKey.counter_};goog.events.Listener=function(a,b,c,d,e,f){goog.events.Listener.ENABLE_MONITORING&&(this.creationStack=Error().stack);this.listener=a;this.proxy=b;this.src=c;this.type=d;this.capture=!!e;this.handler=f;this.key=goog.events.ListenableKey.reserveKey();this.removed=this.callOnce=!1};goog.events.Listener.ENABLE_MONITORING=!1;goog.events.Listener.prototype.markAsRemoved=function(){this.removed=!0;this.handler=this.src=this.proxy=this.listener=null};goog.object={};goog.object.forEach=function(a,b,c){for(var d in a)b.call(c,a[d],d,a)};goog.object.filter=function(a,b,c){var d={},e;for(e in a)b.call(c,a[e],e,a)&&(d[e]=a[e]);return d};goog.object.map=function(a,b,c){var d={},e;for(e in a)d[e]=b.call(c,a[e],e,a);return d};goog.object.some=function(a,b,c){for(var d in a)if(b.call(c,a[d],d,a))return!0;return!1};goog.object.every=function(a,b,c){for(var d in a)if(!b.call(c,a[d],d,a))return!1;return!0}; goog.object.getCount=function(a){var b=0,c;for(c in a)b++;return b};goog.object.getAnyKey=function(a){for(var b in a)return b};goog.object.getAnyValue=function(a){for(var b in a)return a[b]};goog.object.contains=function(a,b){return goog.object.containsValue(a,b)};goog.object.getValues=function(a){var b=[],c=0,d;for(d in a)b[c++]=a[d];return b};goog.object.getKeys=function(a){var b=[],c=0,d;for(d in a)b[c++]=d;return b}; goog.object.getValueByKeys=function(a,b){for(var c=goog.isArrayLike(b),d=c?b:arguments,c=c?0:1;ca.aspectRatio()?a.width/this.width:a.height/this.height;return this.scale(a)};goog.dom.ASSUME_QUIRKS_MODE=!1;goog.dom.ASSUME_STANDARDS_MODE=!1;goog.dom.COMPAT_MODE_KNOWN_=goog.dom.ASSUME_QUIRKS_MODE||goog.dom.ASSUME_STANDARDS_MODE;goog.dom.getDomHelper=function(a){return a?new goog.dom.DomHelper(goog.dom.getOwnerDocument(a)):goog.dom.defaultDomHelper_||(goog.dom.defaultDomHelper_=new goog.dom.DomHelper)};goog.dom.getDocument=function(){return document};goog.dom.getElement=function(a){return goog.dom.getElementHelper_(document,a)}; goog.dom.getElementHelper_=function(a,b){return goog.isString(b)?a.getElementById(b):b};goog.dom.getRequiredElement=function(a){return goog.dom.getRequiredElementHelper_(document,a)};goog.dom.getRequiredElementHelper_=function(a,b){goog.asserts.assertString(b);var c=goog.dom.getElementHelper_(a,b);return c=goog.asserts.assertElement(c,"No element found with id: "+b)};goog.dom.$=goog.dom.getElement; goog.dom.getElementsByTagNameAndClass=function(a,b,c){return goog.dom.getElementsByTagNameAndClass_(document,a,b,c)};goog.dom.getElementsByClass=function(a,b){var c=b||document;return goog.dom.canUseQuerySelector_(c)?c.querySelectorAll("."+a):c.getElementsByClassName?c.getElementsByClassName(a):goog.dom.getElementsByTagNameAndClass_(document,"*",a,b)}; -goog.dom.getElementByClass=function(a,b){var c=b||document,d=null;return(d=goog.dom.canUseQuerySelector_(c)?c.querySelector("."+a):goog.dom.getElementsByClass(a,b)[0])||null};goog.dom.canUseQuerySelector_=function(a){return!(!a.querySelectorAll||!a.querySelector)}; +goog.dom.getElementByClass=function(a,b){var c=b||document,d=null;return(d=goog.dom.canUseQuerySelector_(c)?c.querySelector("."+a):goog.dom.getElementsByClass(a,b)[0])||null};goog.dom.getRequiredElementByClass=function(a,b){var c=goog.dom.getElementByClass(a,b);return goog.asserts.assert(c,"No element found with className: "+a)};goog.dom.canUseQuerySelector_=function(a){return!(!a.querySelectorAll||!a.querySelector)}; goog.dom.getElementsByTagNameAndClass_=function(a,b,c,d){a=d||a;b=b&&"*"!=b?b.toUpperCase():"";if(goog.dom.canUseQuerySelector_(a)&&(b||c))return a.querySelectorAll(b+(c?"."+c:""));if(c&&a.getElementsByClassName){a=a.getElementsByClassName(c);if(b){d={};for(var e=0,f=0,g;g=a[f];f++)b==g.nodeName&&(d[e++]=g);d.length=e;return d}return a}a=a.getElementsByTagName(b||"*");if(c){d={};for(f=e=0;g=a[f];f++)b=g.className,"function"==typeof b.split&&goog.array.contains(b.split(/\s+/),c)&&(d[e++]=g);d.length= e;return d}return a};goog.dom.$$=goog.dom.getElementsByTagNameAndClass;goog.dom.setProperties=function(a,b){goog.object.forEach(b,function(b,d){"style"==d?a.style.cssText=b:"class"==d?a.className=b:"for"==d?a.htmlFor=b:d in goog.dom.DIRECT_ATTRIBUTE_MAP_?a.setAttribute(goog.dom.DIRECT_ATTRIBUTE_MAP_[d],b):goog.string.startsWith(d,"aria-")||goog.string.startsWith(d,"data-")?a.setAttribute(d,b):a[d]=b})}; goog.dom.DIRECT_ATTRIBUTE_MAP_={cellpadding:"cellPadding",cellspacing:"cellSpacing",colspan:"colSpan",frameborder:"frameBorder",height:"height",maxlength:"maxLength",role:"role",rowspan:"rowSpan",type:"type",usemap:"useMap",valign:"vAlign",width:"width"};goog.dom.getViewportSize=function(a){return goog.dom.getViewportSize_(a||window)};goog.dom.getViewportSize_=function(a){a=a.document;a=goog.dom.isCss1CompatMode_(a)?a.documentElement:a.body;return new goog.math.Size(a.clientWidth,a.clientHeight)}; @@ -228,16 +228,16 @@ goog.dom.getAncestorByClass=function(a,b){return goog.dom.getAncestorByTagNameAn goog.dom.getPixelRatio=goog.functions.cacheReturnValue(function(){var a=goog.dom.getWindow(),b=goog.userAgent.GECKO&&goog.userAgent.MOBILE;return goog.isDef(a.devicePixelRatio)&&!b?a.devicePixelRatio:a.matchMedia?goog.dom.matchesPixelRatio_(0.75)||goog.dom.matchesPixelRatio_(1.5)||goog.dom.matchesPixelRatio_(2)||goog.dom.matchesPixelRatio_(3)||1:1}); goog.dom.matchesPixelRatio_=function(a){return goog.dom.getWindow().matchMedia("(-webkit-min-device-pixel-ratio: "+a+"),(min--moz-device-pixel-ratio: "+a+"),(min-resolution: "+a+"dppx)").matches?a:0};goog.dom.DomHelper=function(a){this.document_=a||goog.global.document||document};goog.dom.DomHelper.prototype.getDomHelper=goog.dom.getDomHelper;goog.dom.DomHelper.prototype.setDocument=function(a){this.document_=a};goog.dom.DomHelper.prototype.getDocument=function(){return this.document_}; goog.dom.DomHelper.prototype.getElement=function(a){return goog.dom.getElementHelper_(this.document_,a)};goog.dom.DomHelper.prototype.getRequiredElement=function(a){return goog.dom.getRequiredElementHelper_(this.document_,a)};goog.dom.DomHelper.prototype.$=goog.dom.DomHelper.prototype.getElement;goog.dom.DomHelper.prototype.getElementsByTagNameAndClass=function(a,b,c){return goog.dom.getElementsByTagNameAndClass_(this.document_,a,b,c)}; -goog.dom.DomHelper.prototype.getElementsByClass=function(a,b){return goog.dom.getElementsByClass(a,b||this.document_)};goog.dom.DomHelper.prototype.getElementByClass=function(a,b){return goog.dom.getElementByClass(a,b||this.document_)};goog.dom.DomHelper.prototype.$$=goog.dom.DomHelper.prototype.getElementsByTagNameAndClass;goog.dom.DomHelper.prototype.setProperties=goog.dom.setProperties;goog.dom.DomHelper.prototype.getViewportSize=function(a){return goog.dom.getViewportSize(a||this.getWindow())}; -goog.dom.DomHelper.prototype.getDocumentHeight=function(){return goog.dom.getDocumentHeight_(this.getWindow())};goog.dom.DomHelper.prototype.createDom=function(a,b,c){return goog.dom.createDom_(this.document_,arguments)};goog.dom.DomHelper.prototype.$dom=goog.dom.DomHelper.prototype.createDom;goog.dom.DomHelper.prototype.createElement=function(a){return this.document_.createElement(a)};goog.dom.DomHelper.prototype.createTextNode=function(a){return this.document_.createTextNode(String(a))}; -goog.dom.DomHelper.prototype.createTable=function(a,b,c){return goog.dom.createTable_(this.document_,a,b,!!c)};goog.dom.DomHelper.prototype.htmlToDocumentFragment=function(a){return goog.dom.htmlToDocumentFragment_(this.document_,a)};goog.dom.DomHelper.prototype.isCss1CompatMode=function(){return goog.dom.isCss1CompatMode_(this.document_)};goog.dom.DomHelper.prototype.getWindow=function(){return goog.dom.getWindow_(this.document_)};goog.dom.DomHelper.prototype.getDocumentScrollElement=function(){return goog.dom.getDocumentScrollElement_(this.document_)}; -goog.dom.DomHelper.prototype.getDocumentScroll=function(){return goog.dom.getDocumentScroll_(this.document_)};goog.dom.DomHelper.prototype.getActiveElement=function(a){return goog.dom.getActiveElement(a||this.document_)};goog.dom.DomHelper.prototype.appendChild=goog.dom.appendChild;goog.dom.DomHelper.prototype.append=goog.dom.append;goog.dom.DomHelper.prototype.canHaveChildren=goog.dom.canHaveChildren;goog.dom.DomHelper.prototype.removeChildren=goog.dom.removeChildren; -goog.dom.DomHelper.prototype.insertSiblingBefore=goog.dom.insertSiblingBefore;goog.dom.DomHelper.prototype.insertSiblingAfter=goog.dom.insertSiblingAfter;goog.dom.DomHelper.prototype.insertChildAt=goog.dom.insertChildAt;goog.dom.DomHelper.prototype.removeNode=goog.dom.removeNode;goog.dom.DomHelper.prototype.replaceNode=goog.dom.replaceNode;goog.dom.DomHelper.prototype.flattenElement=goog.dom.flattenElement;goog.dom.DomHelper.prototype.getChildren=goog.dom.getChildren; -goog.dom.DomHelper.prototype.getFirstElementChild=goog.dom.getFirstElementChild;goog.dom.DomHelper.prototype.getLastElementChild=goog.dom.getLastElementChild;goog.dom.DomHelper.prototype.getNextElementSibling=goog.dom.getNextElementSibling;goog.dom.DomHelper.prototype.getPreviousElementSibling=goog.dom.getPreviousElementSibling;goog.dom.DomHelper.prototype.getNextNode=goog.dom.getNextNode;goog.dom.DomHelper.prototype.getPreviousNode=goog.dom.getPreviousNode; -goog.dom.DomHelper.prototype.isNodeLike=goog.dom.isNodeLike;goog.dom.DomHelper.prototype.isElement=goog.dom.isElement;goog.dom.DomHelper.prototype.isWindow=goog.dom.isWindow;goog.dom.DomHelper.prototype.getParentElement=goog.dom.getParentElement;goog.dom.DomHelper.prototype.contains=goog.dom.contains;goog.dom.DomHelper.prototype.compareNodeOrder=goog.dom.compareNodeOrder;goog.dom.DomHelper.prototype.findCommonAncestor=goog.dom.findCommonAncestor;goog.dom.DomHelper.prototype.getOwnerDocument=goog.dom.getOwnerDocument; -goog.dom.DomHelper.prototype.getFrameContentDocument=goog.dom.getFrameContentDocument;goog.dom.DomHelper.prototype.getFrameContentWindow=goog.dom.getFrameContentWindow;goog.dom.DomHelper.prototype.setTextContent=goog.dom.setTextContent;goog.dom.DomHelper.prototype.getOuterHtml=goog.dom.getOuterHtml;goog.dom.DomHelper.prototype.findNode=goog.dom.findNode;goog.dom.DomHelper.prototype.findNodes=goog.dom.findNodes;goog.dom.DomHelper.prototype.isFocusableTabIndex=goog.dom.isFocusableTabIndex; -goog.dom.DomHelper.prototype.setFocusableTabIndex=goog.dom.setFocusableTabIndex;goog.dom.DomHelper.prototype.isFocusable=goog.dom.isFocusable;goog.dom.DomHelper.prototype.getTextContent=goog.dom.getTextContent;goog.dom.DomHelper.prototype.getNodeTextLength=goog.dom.getNodeTextLength;goog.dom.DomHelper.prototype.getNodeTextOffset=goog.dom.getNodeTextOffset;goog.dom.DomHelper.prototype.getNodeAtOffset=goog.dom.getNodeAtOffset;goog.dom.DomHelper.prototype.isNodeList=goog.dom.isNodeList; -goog.dom.DomHelper.prototype.getAncestorByTagNameAndClass=goog.dom.getAncestorByTagNameAndClass;goog.dom.DomHelper.prototype.getAncestorByClass=goog.dom.getAncestorByClass;goog.dom.DomHelper.prototype.getAncestor=goog.dom.getAncestor;goog.dom.vendor={};goog.dom.vendor.getVendorJsPrefix=function(){return goog.userAgent.WEBKIT?"Webkit":goog.userAgent.GECKO?"Moz":goog.userAgent.IE?"ms":goog.userAgent.OPERA?"O":null};goog.dom.vendor.getVendorPrefix=function(){return goog.userAgent.WEBKIT?"-webkit":goog.userAgent.GECKO?"-moz":goog.userAgent.IE?"-ms":goog.userAgent.OPERA?"-o":null}; +goog.dom.DomHelper.prototype.getElementsByClass=function(a,b){return goog.dom.getElementsByClass(a,b||this.document_)};goog.dom.DomHelper.prototype.getElementByClass=function(a,b){return goog.dom.getElementByClass(a,b||this.document_)};goog.dom.DomHelper.prototype.getRequiredElementByClass=function(a,b){return goog.dom.getRequiredElementByClass(a,b||this.document_)};goog.dom.DomHelper.prototype.$$=goog.dom.DomHelper.prototype.getElementsByTagNameAndClass; +goog.dom.DomHelper.prototype.setProperties=goog.dom.setProperties;goog.dom.DomHelper.prototype.getViewportSize=function(a){return goog.dom.getViewportSize(a||this.getWindow())};goog.dom.DomHelper.prototype.getDocumentHeight=function(){return goog.dom.getDocumentHeight_(this.getWindow())};goog.dom.DomHelper.prototype.createDom=function(a,b,c){return goog.dom.createDom_(this.document_,arguments)};goog.dom.DomHelper.prototype.$dom=goog.dom.DomHelper.prototype.createDom; +goog.dom.DomHelper.prototype.createElement=function(a){return this.document_.createElement(a)};goog.dom.DomHelper.prototype.createTextNode=function(a){return this.document_.createTextNode(String(a))};goog.dom.DomHelper.prototype.createTable=function(a,b,c){return goog.dom.createTable_(this.document_,a,b,!!c)};goog.dom.DomHelper.prototype.htmlToDocumentFragment=function(a){return goog.dom.htmlToDocumentFragment_(this.document_,a)};goog.dom.DomHelper.prototype.isCss1CompatMode=function(){return goog.dom.isCss1CompatMode_(this.document_)}; +goog.dom.DomHelper.prototype.getWindow=function(){return goog.dom.getWindow_(this.document_)};goog.dom.DomHelper.prototype.getDocumentScrollElement=function(){return goog.dom.getDocumentScrollElement_(this.document_)};goog.dom.DomHelper.prototype.getDocumentScroll=function(){return goog.dom.getDocumentScroll_(this.document_)};goog.dom.DomHelper.prototype.getActiveElement=function(a){return goog.dom.getActiveElement(a||this.document_)};goog.dom.DomHelper.prototype.appendChild=goog.dom.appendChild; +goog.dom.DomHelper.prototype.append=goog.dom.append;goog.dom.DomHelper.prototype.canHaveChildren=goog.dom.canHaveChildren;goog.dom.DomHelper.prototype.removeChildren=goog.dom.removeChildren;goog.dom.DomHelper.prototype.insertSiblingBefore=goog.dom.insertSiblingBefore;goog.dom.DomHelper.prototype.insertSiblingAfter=goog.dom.insertSiblingAfter;goog.dom.DomHelper.prototype.insertChildAt=goog.dom.insertChildAt;goog.dom.DomHelper.prototype.removeNode=goog.dom.removeNode; +goog.dom.DomHelper.prototype.replaceNode=goog.dom.replaceNode;goog.dom.DomHelper.prototype.flattenElement=goog.dom.flattenElement;goog.dom.DomHelper.prototype.getChildren=goog.dom.getChildren;goog.dom.DomHelper.prototype.getFirstElementChild=goog.dom.getFirstElementChild;goog.dom.DomHelper.prototype.getLastElementChild=goog.dom.getLastElementChild;goog.dom.DomHelper.prototype.getNextElementSibling=goog.dom.getNextElementSibling;goog.dom.DomHelper.prototype.getPreviousElementSibling=goog.dom.getPreviousElementSibling; +goog.dom.DomHelper.prototype.getNextNode=goog.dom.getNextNode;goog.dom.DomHelper.prototype.getPreviousNode=goog.dom.getPreviousNode;goog.dom.DomHelper.prototype.isNodeLike=goog.dom.isNodeLike;goog.dom.DomHelper.prototype.isElement=goog.dom.isElement;goog.dom.DomHelper.prototype.isWindow=goog.dom.isWindow;goog.dom.DomHelper.prototype.getParentElement=goog.dom.getParentElement;goog.dom.DomHelper.prototype.contains=goog.dom.contains;goog.dom.DomHelper.prototype.compareNodeOrder=goog.dom.compareNodeOrder; +goog.dom.DomHelper.prototype.findCommonAncestor=goog.dom.findCommonAncestor;goog.dom.DomHelper.prototype.getOwnerDocument=goog.dom.getOwnerDocument;goog.dom.DomHelper.prototype.getFrameContentDocument=goog.dom.getFrameContentDocument;goog.dom.DomHelper.prototype.getFrameContentWindow=goog.dom.getFrameContentWindow;goog.dom.DomHelper.prototype.setTextContent=goog.dom.setTextContent;goog.dom.DomHelper.prototype.getOuterHtml=goog.dom.getOuterHtml;goog.dom.DomHelper.prototype.findNode=goog.dom.findNode; +goog.dom.DomHelper.prototype.findNodes=goog.dom.findNodes;goog.dom.DomHelper.prototype.isFocusableTabIndex=goog.dom.isFocusableTabIndex;goog.dom.DomHelper.prototype.setFocusableTabIndex=goog.dom.setFocusableTabIndex;goog.dom.DomHelper.prototype.isFocusable=goog.dom.isFocusable;goog.dom.DomHelper.prototype.getTextContent=goog.dom.getTextContent;goog.dom.DomHelper.prototype.getNodeTextLength=goog.dom.getNodeTextLength;goog.dom.DomHelper.prototype.getNodeTextOffset=goog.dom.getNodeTextOffset; +goog.dom.DomHelper.prototype.getNodeAtOffset=goog.dom.getNodeAtOffset;goog.dom.DomHelper.prototype.isNodeList=goog.dom.isNodeList;goog.dom.DomHelper.prototype.getAncestorByTagNameAndClass=goog.dom.getAncestorByTagNameAndClass;goog.dom.DomHelper.prototype.getAncestorByClass=goog.dom.getAncestorByClass;goog.dom.DomHelper.prototype.getAncestor=goog.dom.getAncestor;goog.dom.vendor={};goog.dom.vendor.getVendorJsPrefix=function(){return goog.userAgent.WEBKIT?"Webkit":goog.userAgent.GECKO?"Moz":goog.userAgent.IE?"ms":goog.userAgent.OPERA?"O":null};goog.dom.vendor.getVendorPrefix=function(){return goog.userAgent.WEBKIT?"-webkit":goog.userAgent.GECKO?"-moz":goog.userAgent.IE?"-ms":goog.userAgent.OPERA?"-o":null}; goog.dom.vendor.getPrefixedPropertyName=function(a,b){if(b&&a in b)return a;var c=goog.dom.vendor.getVendorJsPrefix();return c?(c=c.toLowerCase(),c+=goog.string.toTitleCase(a),!goog.isDef(b)||c in b?c:null):null};goog.dom.vendor.getPrefixedEventType=function(a){return((goog.dom.vendor.getVendorJsPrefix()||"")+a).toLowerCase()};goog.math.Box=function(a,b,c,d){this.top=a;this.right=b;this.bottom=c;this.left=d};goog.math.Box.boundingBox=function(a){for(var b=new goog.math.Box(arguments[0].y,arguments[0].x,arguments[0].y,arguments[0].x),c=1;c=goog.events.KeyCodes.ZERO&&a<=goog.events.KeyCodes.NINE||a>=goog.events.KeyCodes.NUM_ZERO&&a<=goog.events.KeyCodes.NUM_MULTIPLY||a>=goog.events.KeyCodes.A&&a<=goog.events.KeyCodes.Z||goog.userAgent.WEBKIT&&0==a)return!0;switch(a){case goog.events.KeyCodes.SPACE:case goog.events.KeyCodes.QUESTION_MARK:case goog.events.KeyCodes.NUM_PLUS:case goog.events.KeyCodes.NUM_MINUS:case goog.events.KeyCodes.NUM_PERIOD:case goog.events.KeyCodes.NUM_DIVISION:case goog.events.KeyCodes.SEMICOLON:case goog.events.KeyCodes.FF_SEMICOLON:case goog.events.KeyCodes.DASH:case goog.events.KeyCodes.EQUALS:case goog.events.KeyCodes.FF_EQUALS:case goog.events.KeyCodes.COMMA:case goog.events.KeyCodes.PERIOD:case goog.events.KeyCodes.SLASH:case goog.events.KeyCodes.APOSTROPHE:case goog.events.KeyCodes.SINGLE_QUOTE:case goog.events.KeyCodes.OPEN_SQUARE_BRACKET:case goog.events.KeyCodes.BACKSLASH:case goog.events.KeyCodes.CLOSE_SQUARE_BRACKET:return!0; default:return!1}};goog.events.KeyCodes.normalizeKeyCode=function(a){return goog.userAgent.GECKO?goog.events.KeyCodes.normalizeGeckoKeyCode(a):goog.userAgent.MAC&&goog.userAgent.WEBKIT?goog.events.KeyCodes.normalizeMacWebKitKeyCode(a):a}; goog.events.KeyCodes.normalizeGeckoKeyCode=function(a){switch(a){case goog.events.KeyCodes.FF_EQUALS:return goog.events.KeyCodes.EQUALS;case goog.events.KeyCodes.FF_SEMICOLON:return goog.events.KeyCodes.SEMICOLON;case goog.events.KeyCodes.FF_DASH:return goog.events.KeyCodes.DASH;case goog.events.KeyCodes.MAC_FF_META:return goog.events.KeyCodes.META;case goog.events.KeyCodes.WIN_KEY_FF_LINUX:return goog.events.KeyCodes.WIN_KEY;default:return a}}; -goog.events.KeyCodes.normalizeMacWebKitKeyCode=function(a){switch(a){case goog.events.KeyCodes.MAC_WK_CMD_RIGHT:return goog.events.KeyCodes.META;default:return a}};goog.events.EventHandler=function(a){goog.Disposable.call(this);this.handler_=a;this.keys_={}};goog.inherits(goog.events.EventHandler,goog.Disposable);goog.events.EventHandler.typeArray_=[];goog.events.EventHandler.prototype.listen=function(a,b,c,d,e){goog.isArray(b)||(goog.events.EventHandler.typeArray_[0]=b,b=goog.events.EventHandler.typeArray_);for(var f=0;fthis.highlightedIndex_?this.getSelectedIndex():this.highlightedIndex_;switch(a.keyCode){case goog.events.KeyCodes.LEFT:-1== -d&&(d=b);if(0=c)return this.setHighlightedIndex(d-c),a.preventDefault(),!0;break;case goog.events.KeyCodes.DOWN:if(-1==d&&(d=-c),dthis.highlightedIndex_?this.getSelectedIndex():this.highlightedIndex_;switch(a.keyCode){case goog.events.KeyCodes.LEFT:if(-1== +d||0==d)d=b;this.setHighlightedIndex(d-1);a.preventDefault();return!0;case goog.events.KeyCodes.RIGHT:return d==b-1&&(d=-1),this.setHighlightedIndex(d+1),a.preventDefault(),!0;case goog.events.KeyCodes.UP:-1==d&&(d=b+c-1);if(d>=c)return this.setHighlightedIndex(d-c),a.preventDefault(),!0;break;case goog.events.KeyCodes.DOWN:if(-1==d&&(d=-c),db.x&&a.xb.y&&a.ythis.lidAngle_:0"!=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");if(!a||!a.firstChild||"xml"!=a.firstChild.nodeName.toLowerCase()||a.firstChild!==a.lastChild)throw"Blockly.Xml.textToDom did not obtain a valid XML tree.";return a.firstChild}; -Blockly.Xml.domToWorkspace=function(a,b){for(var c=Blockly.svgSize().width,d=0,e;e=b.childNodes[d];d++)if("block"==e.nodeName.toLowerCase()){var f=Blockly.Xml.domToBlock_(a,e),g=parseInt(e.getAttribute("x"),10);e=parseInt(e.getAttribute("y"),10);isNaN(g)||isNaN(e)||f.moveBy(Blockly.RTL?c-g:g,e)}}; -Blockly.Xml.domToBlock_=function(a,b){var c=b.getAttribute("type");if(!c)throw"Block type unspecified: \n"+b.outerHTML;var d=new Blockly.Block(a,c);d.initSvg();var e=b.getAttribute("inline");e&&d.setInputsInline("true"==e);(e=b.getAttribute("disabled"))&&d.setDisabled("true"==e);(e=b.getAttribute("deletable"))&&d.setDeletable("true"==e);(e=b.getAttribute("movable"))&&d.setMovable("true"==e);(e=b.getAttribute("editable"))&&d.setEditable("true"==e);for(var f=null,e=0,g;g=b.childNodes[e];e++)if(3!=g.nodeType|| -!g.data.match(/^\s*$/)){for(var f=null,h=0,k;k=g.childNodes[h];h++)3==k.nodeType&&k.data.match(/^\s*$/)||(f=k);h=g.getAttribute("name");switch(g.nodeName.toLowerCase()){case "mutation":d.domToMutation&&d.domToMutation(g);break;case "comment":d.setCommentText(g.textContent);(f=g.getAttribute("pinned"))&&d.comment.setVisible("true"==f);f=parseInt(g.getAttribute("w"),10);g=parseInt(g.getAttribute("h"),10);isNaN(f)||isNaN(g)||d.comment.setBubbleSize(f,g);break;case "title":case "field":d.setFieldValue(g.textContent, -h);break;case "value":case "statement":g=d.getInput(h);if(!g)throw"Input "+h+" does not exist in block "+c;if(f&&"block"==f.nodeName.toLowerCase())if(f=Blockly.Xml.domToBlock_(a,f),f.outputConnection)g.connection.connect(f.outputConnection);else if(f.previousConnection)g.connection.connect(f.previousConnection);else throw"Child block does not have output or previous statement.";break;case "next":if(f&&"block"==f.nodeName.toLowerCase()){if(!d.nextConnection)throw"Next statement does not exist.";if(d.nextConnection.targetConnection)throw"Next statement is already connected."; -f=Blockly.Xml.domToBlock_(a,f);if(!f.previousConnection)throw"Next block does not have previous statement.";d.nextConnection.connect(f.previousConnection)}}}(c=d.nextConnection&&d.nextConnection.targetBlock())?c.render():d.render();(c=b.getAttribute("collapsed"))&&d.setCollapsed("true"==c);return d};Blockly.Xml.deleteNext=function(a){for(var b=0,c;c=a.childNodes[b];b++)if("next"==c.nodeName.toLowerCase()){a.removeChild(c);break}};Blockly.Xml=Blockly.Xml;Blockly.Xml.domToText=Blockly.Xml.domToText; -Blockly.Xml.domToWorkspace=Blockly.Xml.domToWorkspace;Blockly.Xml.textToDom=Blockly.Xml.textToDom;Blockly.Xml.workspaceToDom=Blockly.Xml.workspaceToDom;Blockly.Workspace=function(a,b){this.getMetrics=a;this.setMetrics=b;this.isFlyout=!1;this.topBlocks_=[];this.maxBlocks=Infinity;Blockly.ConnectionDB.init(this)};Blockly.Workspace.SCAN_ANGLE=3;Blockly.Workspace.prototype.dragMode=!1;Blockly.Workspace.prototype.scrollX=0;Blockly.Workspace.prototype.scrollY=0;Blockly.Workspace.prototype.trashcan=null;Blockly.Workspace.prototype.fireChangeEventPid_=null;Blockly.Workspace.prototype.scrollbar=null; +Blockly.Xml.domToWorkspace=function(a,b){for(var c=Blockly.svgSize().width,d=0,e;e=b.childNodes[d];d++)if("block"==e.nodeName.toLowerCase()){var f=Blockly.Xml.domToBlock(a,e),g=parseInt(e.getAttribute("x"),10);e=parseInt(e.getAttribute("y"),10);isNaN(g)||isNaN(e)||f.moveBy(Blockly.RTL?c-g:g,e)}}; +Blockly.Xml.domToBlock=function(a,b,c){var d=null,e=b.getAttribute("type");if(!e)throw"Block type unspecified: \n"+b.outerHTML;var f=b.getAttribute("id");if(c&&f){d=Blockly.Block.getById(f,a);if(!d)throw"Couldn't get Block with id: "+f;f=d.getParent();d.workspace&&d.dispose(!0,!1,!0);d.fill(a,e);d.parent_=f}else d=Blockly.Block.obtain(a,e);d.svg_||d.initSvg();(f=b.getAttribute("inline"))&&d.setInputsInline("true"==f);(f=b.getAttribute("disabled"))&&d.setDisabled("true"==f);(f=b.getAttribute("deletable"))&& +d.setDeletable("true"==f);(f=b.getAttribute("movable"))&&d.setMovable("true"==f);(f=b.getAttribute("editable"))&&d.setEditable("true"==f);for(var g=null,f=0,h;h=b.childNodes[f];f++)if(3!=h.nodeType||!h.data.match(/^\s*$/)){for(var g=null,k=0,l;l=h.childNodes[k];k++)3==l.nodeType&&l.data.match(/^\s*$/)||(g=l);k=h.getAttribute("name");switch(h.nodeName.toLowerCase()){case "mutation":d.domToMutation&&d.domToMutation(h);break;case "comment":d.setCommentText(h.textContent);(g=h.getAttribute("pinned"))&& +d.comment.setVisible("true"==g);g=parseInt(h.getAttribute("w"),10);h=parseInt(h.getAttribute("h"),10);isNaN(g)||isNaN(h)||d.comment.setBubbleSize(g,h);break;case "title":case "field":d.setFieldValue(h.textContent,k);break;case "value":case "statement":h=d.getInput(k);if(!h)throw"Input "+k+" does not exist in block "+e;if(g&&"block"==g.nodeName.toLowerCase())if(g=Blockly.Xml.domToBlock(a,g,c),g.outputConnection)h.connection.connect(g.outputConnection);else if(g.previousConnection)h.connection.connect(g.previousConnection); +else throw"Child block does not have output or previous statement.";break;case "next":if(g&&"block"==g.nodeName.toLowerCase()){if(!d.nextConnection)throw"Next statement does not exist.";if(d.nextConnection.targetConnection)throw"Next statement is already connected.";g=Blockly.Xml.domToBlock(a,g,c);if(!g.previousConnection)throw"Next block does not have previous statement.";d.nextConnection.connect(g.previousConnection)}}}(a=d.nextConnection&&d.nextConnection.targetBlock())?a.render():d.render();(b= +b.getAttribute("collapsed"))&&d.setCollapsed("true"==b);return d};Blockly.Xml.deleteNext=function(a){for(var b=0,c;c=a.childNodes[b];b++)if("next"==c.nodeName.toLowerCase()){a.removeChild(c);break}};Blockly.Xml=Blockly.Xml;Blockly.Xml.domToText=Blockly.Xml.domToText;Blockly.Xml.domToWorkspace=Blockly.Xml.domToWorkspace;Blockly.Xml.textToDom=Blockly.Xml.textToDom;Blockly.Xml.workspaceToDom=Blockly.Xml.workspaceToDom;Blockly.Workspace=function(a,b){this.getMetrics=a;this.setMetrics=b;this.isFlyout=!1;this.topBlocks_=[];this.maxBlocks=Infinity;Blockly.ConnectionDB.init(this)};Blockly.Workspace.SCAN_ANGLE=3;Blockly.Workspace.prototype.dragMode=!1;Blockly.Workspace.prototype.scrollX=0;Blockly.Workspace.prototype.scrollY=0;Blockly.Workspace.prototype.trashcan=null;Blockly.Workspace.prototype.fireChangeEventPid_=null;Blockly.Workspace.prototype.scrollbar=null; Blockly.Workspace.prototype.createDom=function(){this.svgGroup_=Blockly.createSvgElement("g",{},null);this.svgBlockCanvas_=Blockly.createSvgElement("g",{},this.svgGroup_);this.svgBubbleCanvas_=Blockly.createSvgElement("g",{},this.svgGroup_);this.fireChangeEvent();return this.svgGroup_}; Blockly.Workspace.prototype.dispose=function(){this.svgGroup_&&(goog.dom.removeNode(this.svgGroup_),this.svgGroup_=null);this.svgBubbleCanvas_=this.svgBlockCanvas_=null;this.trashcan&&(this.trashcan.dispose(),this.trashcan=null)};Blockly.Workspace.prototype.addTrashcan=function(){if(Blockly.hasTrashcan&&!Blockly.readOnly){this.trashcan=new Blockly.Trashcan(this);var a=this.trashcan.createDom();this.svgGroup_.insertBefore(a,this.svgBlockCanvas_);this.trashcan.init()}}; -Blockly.Workspace.prototype.getCanvas=function(){return this.svgBlockCanvas_};Blockly.Workspace.prototype.getBubbleCanvas=function(){return this.svgBubbleCanvas_};Blockly.Workspace.prototype.addTopBlock=function(a){this.topBlocks_.push(a);this.fireChangeEvent()};Blockly.Workspace.prototype.removeTopBlock=function(a){for(var b=!1,c,d=0;c=this.topBlocks_[d];d++)if(c==a){this.topBlocks_.splice(d,1);b=!0;break}if(!b)throw"Block not present in workspace's list of top-most blocks.";this.fireChangeEvent()}; +Blockly.Workspace.prototype.getCanvas=function(){return this.svgBlockCanvas_};Blockly.Workspace.prototype.getBubbleCanvas=function(){return this.svgBubbleCanvas_};Blockly.Workspace.prototype.addTopBlock=function(a){this.topBlocks_.push(a);Blockly.isRealtimeEnabled()&&this==Blockly.mainWorkspace&&Blockly.Realtime.addTopBlock(a);this.fireChangeEvent()}; +Blockly.Workspace.prototype.removeTopBlock=function(a){for(var b=!1,c,d=0;c=this.topBlocks_[d];d++)if(c==a){this.topBlocks_.splice(d,1);b=!0;break}if(!b)throw"Block not present in workspace's list of top-most blocks.";Blockly.isRealtimeEnabled()&&this==Blockly.mainWorkspace&&Blockly.Realtime.removeTopBlock(a);this.fireChangeEvent()}; Blockly.Workspace.prototype.getTopBlocks=function(a){var b=[].concat(this.topBlocks_);if(a&&1=this.remainingCapacity())){var b=Blockly.Xml.domToBlock_(this,a),c=parseInt(a.getAttribute("x"),10);a=parseInt(a.getAttribute("y"),10);if(!isNaN(c)&&!isNaN(a)){Blockly.RTL&&(c=-c);do for(var d=!1,e=this.getAllBlocks(),f=0,g;g=e[f];f++)g=g.getRelativeToSurfaceXY(),1>=Math.abs(c-g.x)&&1>=Math.abs(a-g.y)&&(c=Blockly.RTL?c-Blockly.SNAP_RADIUS:c+Blockly.SNAP_RADIUS,a+=2*Blockly.SNAP_RADIUS,d=!0);while(d);b.moveBy(c, +Blockly.Workspace.prototype.paste=function(a){if(!(a.getElementsByTagName("block").length>=this.remainingCapacity())){var b=Blockly.Xml.domToBlock(this,a),c=parseInt(a.getAttribute("x"),10);a=parseInt(a.getAttribute("y"),10);if(!isNaN(c)&&!isNaN(a)){Blockly.RTL&&(c=-c);do for(var d=!1,e=this.getAllBlocks(),f=0,g;g=e[f];f++)g=g.getRelativeToSurfaceXY(),1>=Math.abs(c-g.x)&&1>=Math.abs(a-g.y)&&(c=Blockly.RTL?c-Blockly.SNAP_RADIUS:c+Blockly.SNAP_RADIUS,a+=2*Blockly.SNAP_RADIUS,d=!0);while(d);b.moveBy(c, a)}b.select()}};Blockly.Workspace.prototype.remainingCapacity=function(){return Infinity==this.maxBlocks?Infinity:this.maxBlocks-this.getAllBlocks().length};Blockly.Workspace.prototype.clear=Blockly.Workspace.prototype.clear;Blockly.Bubble=function(a,b,c,d,e,f,g){var h=Blockly.Bubble.ARROW_ANGLE;Blockly.RTL&&(h=-h);this.arrow_radians_=h/360*Math.PI*2;this.workspace_=a;this.content_=b;this.shape_=c;a.getBubbleCanvas().appendChild(this.createDom_(b,!(!f||!g)));this.setAnchorLocation(d,e);f&&g||(a=this.content_.getBBox(),f=a.width+2*Blockly.Bubble.BORDER_WIDTH,g=a.height+2*Blockly.Bubble.BORDER_WIDTH);this.setBubbleSize(f,g);this.positionBubble_();this.renderArrow_();this.rendered_=!0;Blockly.readOnly||(Blockly.bindEvent_(this.bubbleBack_, "mousedown",this,this.bubbleMouseDown_),this.resizeGroup_&&Blockly.bindEvent_(this.resizeGroup_,"mousedown",this,this.resizeMouseDown_))};Blockly.Bubble.BORDER_WIDTH=6;Blockly.Bubble.ARROW_THICKNESS=10;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.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.prototype.rendered_=!1;Blockly.Bubble.prototype.anchorX_=0;Blockly.Bubble.prototype.anchorY_=0;Blockly.Bubble.prototype.relativeLeft_=0;Blockly.Bubble.prototype.relativeTop_=0; @@ -776,7 +778,7 @@ Blockly.ContextMenu.show=function(a,b){if(b.length){goog.dom.removeChildren(Bloc 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.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.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.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)}; Blockly.Field.prototype.dispose=function(){this.mouseUpWrapper_&&(Blockly.unbindEvent_(this.mouseUpWrapper_),this.mouseUpWrapper_=null);this.sourceBlock_=null;goog.dom.removeNode(this.fieldGroup_);this.borderRect_=this.textElement_=this.fieldGroup_=null}; Blockly.Field.prototype.updateEditable=function(){this.EDITABLE&&(this.sourceBlock_.isEditable()?(Blockly.addClass_(this.fieldGroup_,"blocklyEditableText"),Blockly.removeClass_(this.fieldGroup_,"blocklyNoNEditableText"),this.fieldGroup_.style.cursor=this.CURSOR):(Blockly.addClass_(this.fieldGroup_,"blocklyNonEditableText"),Blockly.removeClass_(this.fieldGroup_,"blocklyEditableText"),this.fieldGroup_.style.cursor=""))};Blockly.Field.prototype.isVisible=function(){return this.visible_}; @@ -811,23 +813,25 @@ this.block_.render(),this.resizeBubble_(),this.block_.workspace.fireChangeEvent( Blockly.Warning.prototype.createIcon_=function(){Blockly.Icon.prototype.createIcon_.call(this);Blockly.createSvgElement("path",{"class":"blocklyIconShield",d:"M 2,15 Q -1,15 0.5,12 L 6.5,1.7 Q 8,-1 9.5,1.7 L 15.5,12 Q 17,15 14,15 z"},this.iconGroup_);this.iconMark_=Blockly.createSvgElement("text",{"class":"blocklyIconMark",x:Blockly.Icon.RADIUS,y:2*Blockly.Icon.RADIUS-3},this.iconGroup_);this.iconMark_.appendChild(document.createTextNode("!"))}; Blockly.Warning.prototype.textToDom_=function(a){var b=Blockly.createSvgElement("text",{"class":"blocklyText",y:Blockly.Bubble.BORDER_WIDTH},null);a=a.split("\n");for(var c=0;cthis.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)a.stopPropagation();else{Blockly.removeAllRanges();var b=a.clientY-Blockly.Flyout.startDownEvent_.clientY;Math.sqrt(Math.pow(a.clientX-Blockly.Flyout.startDownEvent_.clientX,2)+Math.pow(b,2))>Blockly.DRAG_RADIUS&&Blockly.Flyout.startFlyout_.createBlockFunc_(Blockly.Flyout.startBlock_)(Blockly.Flyout.startDownEvent_)}}; -Blockly.Flyout.prototype.createBlockFunc_=function(a){var b=this;return function(c){if(!Blockly.isRightButton(c)&&!a.disabled){var d=Blockly.Xml.blockToDom_(a),d=Blockly.Xml.domToBlock_(b.targetWorkspace_,d),e=a.getSvgRoot();if(!e)throw"originBlock is not rendered.";var e=Blockly.getSvgXY_(e),f=d.getSvgRoot();if(!f)throw"block is not rendered.";f=Blockly.getSvgXY_(f);d.moveBy(e.x-f.x,e.y-f.y);b.autoClose?b.hide():b.filterForCapacity_();d.onMouseDown_(c)}}}; +Blockly.Flyout.prototype.createBlockFunc_=function(a){var b=this;return function(c){if(!Blockly.isRightButton(c)&&!a.disabled){var d=Blockly.Xml.blockToDom_(a),d=Blockly.Xml.domToBlock(b.targetWorkspace_,d),e=a.getSvgRoot();if(!e)throw"originBlock is not rendered.";var e=Blockly.getSvgXY_(e),f=d.getSvgRoot();if(!f)throw"block is not rendered.";f=Blockly.getSvgXY_(f);d.moveBy(e.x-f.x,e.y-f.y);b.autoClose?b.hide():b.filterForCapacity_();d.onMouseDown_(c)}}}; Blockly.Flyout.prototype.filterForCapacity_=function(){for(var a=this.targetWorkspace_.remainingCapacity(),b=this.workspace_.getTopBlocks(!1),c=0,d;d=b[c];c++){var e=d.getDescendants().length>a;d.setDisabled(e)}}; Blockly.Flyout.terminateDrag_=function(){Blockly.Flyout.onMouseUpWrapper_&&(Blockly.unbindEvent_(Blockly.Flyout.onMouseUpWrapper_),Blockly.Flyout.onMouseUpWrapper_=null);Blockly.Flyout.onMouseMoveWrapper_&&(Blockly.unbindEvent_(Blockly.Flyout.onMouseMoveWrapper_),Blockly.Flyout.onMouseMoveWrapper_=null);Blockly.Flyout.startDownEvent_=null;Blockly.Flyout.startBlock_=null;Blockly.Flyout.startFlyout_=null};Blockly.Toolbox={};Blockly.Toolbox.width=0;Blockly.Toolbox.selectedOption_=null;Blockly.Toolbox.CONFIG_={indentWidth:19,cssRoot:"blocklyTreeRoot",cssHideRoot:"blocklyHidden",cssItem:"",cssTreeRow:"blocklyTreeRow",cssItemLabel:"blocklyTreeLabel",cssTreeIcon:"blocklyTreeIcon",cssExpandedFolderIcon:"blocklyTreeIconOpen",cssFileIcon:"blocklyTreeIconNone",cssSelectedRow:"blocklyTreeSelected"}; Blockly.Toolbox.createDom=function(a,b){Blockly.Toolbox.HtmlDiv=goog.dom.createDom("div","blocklyToolboxDiv");Blockly.Toolbox.HtmlDiv.setAttribute("dir",Blockly.RTL?"RTL":"LTR");b.appendChild(Blockly.Toolbox.HtmlDiv);Blockly.Toolbox.flyout_=new Blockly.Flyout;a.appendChild(Blockly.Toolbox.flyout_.createDom());Blockly.bindEvent_(Blockly.Toolbox.HtmlDiv,"mousedown",null,function(a){Blockly.isRightButton(a)||a.target==Blockly.Toolbox.HtmlDiv?Blockly.hideChaff(!1):Blockly.hideChaff(!0)})}; @@ -927,8 +932,8 @@ Blockly.Toolbox.TreeControl.prototype.createNode=function(a){return new Blockly. Blockly.Toolbox.TreeNode=function(a,b,c){goog.ui.tree.TreeNode.call(this,a,b,c);a=function(){Blockly.fireUiEvent(window,"resize")};goog.events.listen(Blockly.Toolbox.tree_,goog.ui.tree.BaseNode.EventType.EXPAND,a);goog.events.listen(Blockly.Toolbox.tree_,goog.ui.tree.BaseNode.EventType.COLLAPSE,a)};goog.inherits(Blockly.Toolbox.TreeNode,goog.ui.tree.TreeNode);Blockly.Toolbox.TreeNode.prototype.getExpandIconHtml=function(){return""}; Blockly.Toolbox.TreeNode.prototype.getExpandIconElement=function(){return null};Blockly.Toolbox.TreeNode.prototype.onMouseDown=function(a){this.hasChildren()&&this.isUserCollapsible_?(this.toggle(),this.select()):this.isSelected()?this.getTree().setSelectedItem(null):this.select();this.updateRow()};Blockly.Toolbox.TreeNode.prototype.onDoubleClick_=function(a){};Blockly.Variables={};Blockly.Variables.NAME_TYPE="VARIABLE";Blockly.Variables.allVariables=function(a){var b;b=a?a.getDescendants():Blockly.mainWorkspace.getAllBlocks();a=Object.create(null);for(var c=0;cd?1:c>>/g,b);goog.cssom.addCssText(a)}; +Blockly.Procedures.flyoutCategory=function(a,b,c,d){function e(e,f){for(var k=0;kBlockly.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;", "}",".blocklyResizeSW {"," fill: #aaa;"," cursor: sw-resize;","}",".blocklyResizeLine {"," stroke-width: 1;"," stroke: #888;","}",".blocklyHighlightedConnectionPath {"," stroke-width: 4px;"," stroke: #fc3;"," fill: none;","}",".blocklyPathLight {"," fill: none;"," stroke-width: 2;"," stroke-linecap: round;","}",".blocklySelected>.blocklyPath {"," stroke-width: 3px;"," stroke: #fc3;","}",".blocklySelected>.blocklyPathLight {"," display: none;","}",".blocklyDragging>.blocklyPath,",".blocklyDragging>.blocklyPathLight {", " fill-opacity: 0.8;"," stroke-opacity: 0.8;","}",".blocklyDragging>.blocklyPathDark {"," display: none;","}",".blocklyDisabled>.blocklyPath {"," fill-opacity: 0.50;"," stroke-opacity: 0.50;","}",".blocklyDisabled>.blocklyPathLight,",".blocklyDisabled>.blocklyPathDark {"," display: none;","}",".blocklyText {"," cursor: default;"," font-family: sans-serif;"," font-size: 11pt;"," fill: #fff;","}",".blocklyNonEditableText>text {"," pointer-events: none;","}",".blocklyNonEditableText>rect,", diff --git a/blockly_uncompressed.js b/blockly_uncompressed.js index f6e3f0809..9a8dc382e 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.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_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.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.dom', 'goog.color', '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']); @@ -61,6 +61,8 @@ goog.addDependency("../../../" + dir + "/core/warning.js", ['Blockly.Warning'], goog.addDependency("../../../" + dir + "/core/widgetdiv.js", ['Blockly.WidgetDiv'], ['Blockly.Css', 'goog.dom']); goog.addDependency("../../../" + dir + "/core/workspace.js", ['Blockly.Workspace'], ['Blockly.ScrollbarPair', 'Blockly.Trashcan', 'Blockly.Xml']); goog.addDependency("../../../" + dir + "/core/xml.js", ['Blockly.Xml'], []); +goog.addDependency("../../../" + dir + "/realtime/realtime-client-utils.js", [], []); +goog.addDependency("../../../" + dir + "/realtime/realtime.js", ['Blockly.Realtime'], ['goog.array']); goog.addDependency("../../alltests.js", [], []); goog.addDependency("base.js", [], []); goog.addDependency("deps.js", [], []); @@ -951,6 +953,7 @@ goog.require('Blockly.Msg'); goog.require('Blockly.Mutator'); goog.require('Blockly.Names'); goog.require('Blockly.Procedures'); +goog.require('Blockly.Realtime'); goog.require('Blockly.Scrollbar'); goog.require('Blockly.ScrollbarPair'); goog.require('Blockly.Toolbox'); diff --git a/blocks/lists.js b/blocks/lists.js index e687d17c3..24a0547ea 100644 --- a/blocks/lists.js +++ b/blocks/lists.js @@ -75,12 +75,12 @@ Blockly.Blocks['lists_create_with'] = { } }, decompose: function(workspace) { - var containerBlock = new Blockly.Block(workspace, - 'lists_create_with_container'); + var containerBlock = + Blockly.Block.obtain(workspace, 'lists_create_with_container'); containerBlock.initSvg(); var connection = containerBlock.getInput('STACK').connection; for (var x = 0; x < this.itemCount_; x++) { - var itemBlock = new Blockly.Block(workspace, 'lists_create_with_item'); + var itemBlock = Blockly.Block.obtain(workspace, 'lists_create_with_item'); itemBlock.initSvg(); connection.connect(itemBlock.previousConnection); connection = itemBlock.nextConnection; diff --git a/blocks/logic.js b/blocks/logic.js index 46aaf1c38..e4dc30472 100644 --- a/blocks/logic.js +++ b/blocks/logic.js @@ -88,17 +88,17 @@ Blockly.Blocks['controls_if'] = { } }, decompose: function(workspace) { - var containerBlock = new Blockly.Block(workspace, 'controls_if_if'); + var containerBlock = Blockly.Block.obtain(workspace, 'controls_if_if'); containerBlock.initSvg(); var connection = containerBlock.getInput('STACK').connection; for (var x = 1; x <= this.elseifCount_; x++) { - var elseifBlock = new Blockly.Block(workspace, 'controls_if_elseif'); + var elseifBlock = Blockly.Block.obtain(workspace, 'controls_if_elseif'); elseifBlock.initSvg(); connection.connect(elseifBlock.previousConnection); connection = elseifBlock.nextConnection; } if (this.elseCount_) { - var elseBlock = new Blockly.Block(workspace, 'controls_if_else'); + var elseBlock = Blockly.Block.obtain(workspace, 'controls_if_else'); elseBlock.initSvg(); connection.connect(elseBlock.previousConnection); } diff --git a/blocks/procedures.js b/blocks/procedures.js index 01e8f7e59..960289a10 100644 --- a/blocks/procedures.js +++ b/blocks/procedures.js @@ -89,12 +89,12 @@ Blockly.Blocks['procedures_defnoreturn'] = { this.updateParams_(); }, decompose: function(workspace) { - var containerBlock = new Blockly.Block(workspace, + var containerBlock = Blockly.Block.obtain(workspace, 'procedures_mutatorcontainer'); containerBlock.initSvg(); var connection = containerBlock.getInput('STACK').connection; for (var x = 0; x < this.arguments_.length; x++) { - var paramBlock = new Blockly.Block(workspace, 'procedures_mutatorarg'); + var paramBlock = Blockly.Block.obtain(workspace, 'procedures_mutatorarg'); paramBlock.initSvg(); paramBlock.setFieldValue(this.arguments_[x], 'NAME'); // Store the old location. diff --git a/blocks/text.js b/blocks/text.js index 60bc70673..02c9dbe22 100644 --- a/blocks/text.js +++ b/blocks/text.js @@ -82,12 +82,12 @@ Blockly.Blocks['text_join'] = { } }, decompose: function(workspace) { - var containerBlock = new Blockly.Block(workspace, + var containerBlock = Blockly.Block.obtain(workspace, 'text_create_join_container'); containerBlock.initSvg(); var connection = containerBlock.getInput('STACK').connection; for (var x = 0; x < this.itemCount_; x++) { - var itemBlock = new Blockly.Block(workspace, 'text_create_join_item'); + var itemBlock = Blockly.Block.obtain(workspace, 'text_create_join_item'); itemBlock.initSvg(); connection.connect(itemBlock.previousConnection); connection = itemBlock.nextConnection; diff --git a/blocks_compressed.js b/blocks_compressed.js index 6e454ea57..a05b2ed18 100644 --- a/blocks_compressed.js +++ b/blocks_compressed.js @@ -8,7 +8,7 @@ this.setOutput(!0,"Colour");this.setTooltip(Blockly.Msg.COLOUR_RGB_TOOLTIP)}}; Blockly.Blocks.colour_blend={init:function(){this.setHelpUrl(Blockly.Msg.COLOUR_BLEND_HELPURL);this.setColour(20);this.appendValueInput("COLOUR1").setCheck("Colour").setAlign(Blockly.ALIGN_RIGHT).appendField(Blockly.Msg.COLOUR_BLEND_TITLE).appendField(Blockly.Msg.COLOUR_BLEND_COLOUR1);this.appendValueInput("COLOUR2").setCheck("Colour").setAlign(Blockly.ALIGN_RIGHT).appendField(Blockly.Msg.COLOUR_BLEND_COLOUR2);this.appendValueInput("RATIO").setCheck("Number").setAlign(Blockly.ALIGN_RIGHT).appendField(Blockly.Msg.COLOUR_BLEND_RATIO);this.setOutput(!0, "Colour");this.setTooltip(Blockly.Msg.COLOUR_BLEND_TOOLTIP)}};Blockly.Blocks.lists={};Blockly.Blocks.lists_create_empty={init:function(){this.setHelpUrl(Blockly.Msg.LISTS_CREATE_EMPTY_HELPURL);this.setColour(260);this.setOutput(!0,"Array");this.appendDummyInput().appendField(Blockly.Msg.LISTS_CREATE_EMPTY_TITLE);this.setTooltip(Blockly.Msg.LISTS_CREATE_EMPTY_TOOLTIP)}}; Blockly.Blocks.lists_create_with={init:function(){this.setColour(260);this.appendValueInput("ADD0").appendField(Blockly.Msg.LISTS_CREATE_WITH_INPUT_WITH);this.appendValueInput("ADD1");this.appendValueInput("ADD2");this.setOutput(!0,"Array");this.setMutator(new Blockly.Mutator(["lists_create_with_item"]));this.setTooltip(Blockly.Msg.LISTS_CREATE_WITH_TOOLTIP);this.itemCount_=3},mutationToDom:function(a){a=document.createElement("mutation");a.setAttribute("items",this.itemCount_);return a},domToMutation:function(a){for(var b= -0;b} fileIds The IDs of the files to open. + * @param {string} userId The ID of the user. + */ +rtclient.RealtimeLoader.prototype.redirectTo = function(fileIds, userId) { + var params = []; + if (fileIds) { + params.push('fileIds=' + fileIds.join(',')); + } + if (userId) { + params.push('userId=' + userId); + } + // Naive URL construction. + var newUrl = params.length == 0 + ? window.location.pathname + : (window.location.pathname + '#' + params.join('&')); + // Using HTML URL re-write if available. + if (window.history && window.history.replaceState) { + window.history.replaceState('Google Drive Realtime API Playground', + 'Google Drive Realtime API Playground', newUrl); + } else { + window.location.href = newUrl; + } + // We are still here that means the page didn't reload. + rtclient.params = rtclient.getParams(); + for (var index in fileIds) { + gapi.drive.realtime.load(fileIds[index], this.onFileLoaded, + this.initializeModel, this.handleErrors); + } +}; + +/** + * Starts the loader by authorizing. + */ +rtclient.RealtimeLoader.prototype.start = function() { + // Bind to local context to make them suitable for callbacks. + var _this = this; + this.authorizer.start(function() { + if (_this.registerTypes) { + _this.registerTypes(); + } + if (_this.afterAuth) { + _this.afterAuth(); + } + _this.load(); + }); +}; + +/** + * Handles errors thrown by the Realtime API. + * @param {!Error} e Error. + */ +rtclient.RealtimeLoader.prototype.handleErrors = function(e) { + if (e.type == gapi.drive.realtime.ErrorType.TOKEN_REFRESH_REQUIRED) { + this.authorizer.authorize(); + } else if (e.type == gapi.drive.realtime.ErrorType.CLIENT_ERROR) { + alert('An Error happened: ' + e.message); + window.location.href = '/'; + } else if (e.type == gapi.drive.realtime.ErrorType.NOT_FOUND) { + alert('The file was not found. It does not exist or you do not have ' + + 'read access to the file.'); + window.location.href = '/'; + } +}; + +/** + * Loads or creates a Realtime file depending on the fileId and state query + * parameters. + */ +rtclient.RealtimeLoader.prototype.load = function() { + var fileIds = rtclient.params['fileIds']; + if (fileIds) { + fileIds = fileIds.split(','); + } + var userId = this.authorizer.userId; + var state = rtclient.params['state']; + // Creating the error callback. + var authorizer = this.authorizer; + // We have file IDs in the query parameters, so we will use them to load a + // file. + if (fileIds) { + for (var index in fileIds) { + gapi.drive.realtime.load(fileIds[index], this.onFileLoaded, + this.initializeModel, this.handleErrors); + } + return; + } + // We have a state parameter being redirected from the Drive UI. + // We will parse it and redirect to the fileId contained. + else if (state) { + var stateObj = rtclient.parseState(state); + // If opening a file from Drive. + if (stateObj.action == 'open') { + fileIds = stateObj.ids; + userId = stateObj.userId; + this.redirectTo(fileIds, userId); + return; + } + } + if (this.autoCreate) { + this.createNewFileAndRedirect(); + } +}; + +/** + * Creates a new file and redirects to the URL to load it. + */ +rtclient.RealtimeLoader.prototype.createNewFileAndRedirect = function() { + // No fileId or state have been passed. We create a new Realtime file and + // redirect to it. + var _this = this; + rtclient.createRealtimeFile(this.defaultTitle, this.newFileMimeType, + function(file) { + if (file.id) { + _this.redirectTo([file.id], _this.authorizer.userId); + } else { + // File failed to be created, log why and do not attempt to redirect. + console.error('Error creating file.'); + console.error(file); + } + }); +}; diff --git a/realtime/realtime.js b/realtime/realtime.js new file mode 100644 index 000000000..e9e268abb --- /dev/null +++ b/realtime/realtime.js @@ -0,0 +1,478 @@ +/** + * 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 + */ + +/** + * @fileoverview Common support code for Blockly apps using realtime + * collaboration. + * Note that to use this you must set up a project via the Google Developers + * 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_ + * @author markf@google.com (Mark Friedman) + */ +'use strict'; + +goog.provide('Blockly.Realtime'); + +goog.require('goog.array'); + +/** + * Is realtime collaboration enabled? + * @type {boolean} + * @private + */ +Blockly.Realtime.enabled_ = false; + +/** + * The Realtime model of this doc. + * @type {gapi.drive.realtime.Model} + * @private + */ +Blockly.Realtime.model_ = null; + +/** + * The function used to initialize the UI after realtime is initialized. + * @type {Function()} + * @private + */ +Blockly.Realtime.initUi_ = null; + +/** + * A map from block id to blocks. + * @type {gapi.drive.realtime.CollaborativeMap} + * @private + */ +Blockly.Realtime.blocksMap_ = null; + +/** + * Are currently syncing from another instance of this realtime doc. + * @type {boolean} + */ +Blockly.Realtime.withinSync = false; + +/** + * The current instance of the realtime loader client + * @type {rtclient.RealtimeLoader} + * @private + */ +Blockly.Realtime.realtimeLoader_ = null; + +/** + * Returns whether realtime collaboration is enabled. + * @returns {boolean} + */ +Blockly.Realtime.isEnabled = function() { + return Blockly.Realtime.enabled_; +}; + +/** + * 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 + * model. + * @param model {gapi.drive.realtime.Model} model The Realtime root model + * object. + */ +Blockly.Realtime.initializeModel_ = function(model) { + Blockly.Realtime.model_ = model; + var blocksMap = model.createMap(); + model.getRoot().set('blocks', blocksMap); + var topBlocks = model.createList(); + model.getRoot().set('topBlocks', topBlocks); + var string = + model.createString('Chat with your collaborator by typing in this box!'); + model.getRoot().set('text', string); +}; + +/** + * Delete a block from the realtime blocks map. + * @param {!Blockly.Block} block The block to remove. + */ +Blockly.Realtime.removeBlock = function(block) { + Blockly.Realtime.blocksMap_.delete(block.id.toString()); +}; + +/** + * Add to the list of top-level blocks. + * @param {!Blockly.Block} block The block to add. + */ +Blockly.Realtime.addTopBlock = function(block) { + if (Blockly.Realtime.topBlocks_.indexOf(block) == -1) { + Blockly.Realtime.topBlocks_.push(block); + } +}; + +/** + * Delete a block from the list of top-level blocks. + * @param {!Blockly.Block} block The block to remove. + */ +Blockly.Realtime.removeTopBlock = function(block) { + Blockly.Realtime.topBlocks_.removeValue(block); +}; + +/** + * Obtain a newly created block known by the Realtime API. + * @param {!Blockly.Workspace} workspace The workspace to put the block in. + * @param {string} prototypeName The name of the prototype for the block + * @return {!Blockly.Block} + */ +Blockly.Realtime.obtainBlock = function(workspace, prototypeName) { + var newBlock = + Blockly.Realtime.model_.create(Blockly.Block, workspace, prototypeName); + return newBlock; +}; + +/** + * Get an existing block by id. + * @param {string} id The block's id. + * @return {Blockly.Block} The found block. + */ +Blockly.Realtime.getBlockById = function(id) { + return Blockly.Realtime.blocksMap_.get(id); +}; + +/** + * 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) { + 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.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; + 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); + } + Blockly.Realtime.moveBlock_(block2); + }); + } + } + } + } +}; + +/** + * Event handler to call when there is a change to the realtime blocks map. + * @param {gapi.drive.realtime.ValueChangedEvent} evt The event that occurred. + * @private + */ +Blockly.Realtime.onBlocksMapChange_ = function(evt) { + console.log('Blocks Map event:'); + console.log(' id: ' + evt.property); + if (!evt.isLocal) { + var block = evt.newValue; + if (block) { + Blockly.Realtime.placeBlockOnWorkspace_(block, !(evt.oldValue)); + } else { + block = evt.oldValue; + Blockly.Realtime.deleteBlock(block); + } + } +}; + +/** + * A convenient wrapper around code that synchronizes the local model being + * edited with changes from another non-local model. + * @param {!Function()} thunk A thunk of code to call. + * @private + */ +Blockly.Realtime.doWithinSync_ = function(thunk) { + if (Blockly.Realtime.withinSync) { + thunk(); + } else { + try { + Blockly.Realtime.withinSync = true; + thunk(); + } finally { + Blockly.Realtime.withinSync = false; + } + } +}; + +/** + * Places a block to be synced on this docs main workspace. The block might + * already exist on this doc, in which case it is updated and/or moved. + * @param {!Blockly.Block} block The block. + * @param {boolean} addToTop Whether to add the block to the workspace/s list of + * top-level blocks. + * @private + */ +Blockly.Realtime.placeBlockOnWorkspace_ = function(block, addToTop) { + Blockly.Realtime.doWithinSync_(function() { + var blockDom = Blockly.Xml.textToDom(block.xmlDom).firstChild; + var newBlock = + Blockly.Xml.domToBlock(Blockly.mainWorkspace, blockDom, true); + // TODO: The following is for debugging. It should never actually happen. + if (!newBlock) { + return; + } + // Since Blockly.Xml.blockDomToBlock() purposely won't add blocks to + // workspace.topBlocks_ we sometimes need to do it explicitly here. + if (addToTop) { + newBlock.workspace.addTopBlock(newBlock); + } + if (addToTop || + goog.array.contains(Blockly.Realtime.topBlocks_, newBlock)) { + Blockly.Realtime.moveBlock_(newBlock); + } + }); +}; + +/** + * Move a block + * @param {Blockly.Block} block The block to move. + * @private + */ +Blockly.Realtime.moveBlock_ = function(block) { + if (!isNaN(block.relativeX) && !isNaN(block.relativeY)) { + var width = Blockly.svgSize().width; + var curPos = block.getRelativeToSurfaceXY(); + var dx = block.relativeX - curPos.x; + var dy = block.relativeY - curPos.y; + block.moveBy(Blockly.RTL ? width - dx : dx, dy); + } +}; + +/** + * Delete a block. + * @param {!Blockly.Block} block The block to delete. + * @private + */ +Blockly.Realtime.deleteBlock = function(block) { + Blockly.Realtime.doWithinSync_(function() { + block.dispose(true, true, true); + }); +}; + +/** + * Load all the blocks from the realtime model's blocks map and place them + * appropriately on the main Blockly workspace. + * @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); + Blockly.Realtime.placeBlockOnWorkspace_(topBlock, true); + } +}; + +/** + * Cause a changed block to update the realtime model, and therefore to be + * synced with other apps editing this same doc. + * @param {!Blockly.Block} block The block that changed. + */ +Blockly.Realtime.blockChanged = function(block) { + if (block.workspace == Blockly.mainWorkspace) { + var rootBlock = block.getRootBlock(); + var xy = rootBlock.getRelativeToSurfaceXY(); + var changed = false; + var xml = Blockly.Xml.blockToDom_(rootBlock); + xml.setAttribute('id', rootBlock.id); + var topXml = goog.dom.createDom('xml'); + topXml.appendChild(xml); + var newXml = Blockly.Xml.domToText(topXml); + if (newXml != rootBlock.xmlDom) { + changed = true; + rootBlock.xmlDom = newXml; + } + if (rootBlock.relativeX != xy.x || rootBlock.relativeY != xy.y){ + rootBlock.relativeX = xy.x; + rootBlock.relativeY = xy.y; + changed = true; + } + if (changed) { + var blockId = rootBlock.id.toString(); + Blockly.Realtime.blocksMap_.set(blockId, rootBlock); + } + } +}; + +/** + * This function is called when the Realtime file has been loaded. It should + * be used to initialize any user interface components and event handlers + * depending on the Realtime model. In this case, create a text control binder + * and bind it to our string model that we created in initializeModel. + * @param {!gapi.drive.realtime.Document} doc The Realtime document. + * @private + */ +Blockly.Realtime.onFileLoaded_ = function(doc) { + Blockly.Realtime.model_ = doc.getModel(); + Blockly.Realtime.blocksMap_ = + Blockly.Realtime.model_.getRoot().get('blocks'); + Blockly.Realtime.topBlocks_ = + Blockly.Realtime.model_.getRoot().get('topBlocks'); + + Blockly.Realtime.model_.getRoot().addEventListener( + gapi.drive.realtime.EventType.OBJECT_CHANGED, + Blockly.Realtime.onObjectChange_); + Blockly.Realtime.blocksMap_.addEventListener( + gapi.drive.realtime.EventType.VALUE_CHANGED, + Blockly.Realtime.onBlocksMapChange_); + + var string = Blockly.Realtime.model_.getRoot().get('text'); + + // Keeping one box updated with a String binder. + var textArea1 = document.getElementById('chatbox'); + gapi.drive.realtime.databinding.bindString(string, textArea1); + + // Enabling UI Elements. + textArea1.disabled = false; + Blockly.Realtime.initUi_(); + + Blockly.Realtime.loadBlocks_(); + + // Add logic for undo button. + // TODO: Uncomment this when undo/redo are fixed. +/* + var undoButton = document.getElementById('undoButton'); + var redoButton = document.getElementById('redoButton'); + + 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); + */ +}; + +/** + * Register the Blockly types and attributes that are reflected in the realtime + * model. + * @private + */ +Blockly.Realtime.registerTypes_ = function() { + var custom = gapi.drive.realtime.custom; + + 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'); + + custom.setInitializer(Blockly.Block, Blockly.Block.prototype.initialize); +}; + +Blockly.Realtime.REAUTH_INTERVAL_IN_MILLISECONDS_ = 30 * 60 * 1000; + +/** + * What to do after Realtime authorization. + * @private + */ +Blockly.Realtime.afterAuth_ = function() { + // This is a workaround for the fact that the code in realtime-client-utils.js + // doesn't deal with auth timeouts correctly. So we explicitly reauthorize at + // regular intervals. + window.setTimeout( + function() { + Blockly.Realtime.realtimeLoader_.authorizer.authorize( + Blockly.Realtime.afterAuth_); + }, + Blockly.Realtime.REAUTH_INTERVAL_IN_MILLISECONDS_); +}; + +/** + * Options for the Realtime loader. + */ +Blockly.Realtime.realtimeOptions_ = { + /** + * Client ID from the console. + */ + clientId: 'INSERT YOUR CLIENT ID HERE', + + /** + * The ID of the button to click to authorize. Must be a DOM element ID. + */ + authButtonElementId: 'authorizeButton', + + /** + * Function to be called when a Realtime model is first created. + */ + initializeModel: Blockly.Realtime.initializeModel_, + + /** + * Autocreate files right after auth automatically. + */ + autoCreate: true, + + /** + * The name of newly created Drive files. + */ + defaultTitle: 'New Realtime Blockly File', + + /** + * The MIME type of newly created Drive Files. By default the application + * specific MIME type will be used: + * application/vnd.google-apps.drive-sdk. + */ + newFileMimeType: null, // Using default. + + /** + * Function to be called every time a Realtime file is loaded. + */ + onFileLoaded: Blockly.Realtime.onFileLoaded_, + + /** + * Function to be called to initialize custom Collaborative Objects types. + */ + registerTypes: Blockly.Realtime.registerTypes_, + + /** + * Function to be called after authorization and before loading files. + */ + afterAuth: Blockly.Realtime.afterAuth_ +}; + +/** + * Start the Realtime loader with the options. + */ +Blockly.Realtime.startRealtime = function (uiInitialize) { + Blockly.Realtime.enabled_ = true; + Blockly.Realtime.initUi_ = uiInitialize; + Blockly.Realtime.realtimeLoader_ = + new rtclient.RealtimeLoader(Blockly.Realtime.realtimeOptions_); + Blockly.Realtime.realtimeLoader_.start(); +}; diff --git a/tests/playground.html b/tests/playground.html index 2ddda169e..9ed9193f7 100644 --- a/tests/playground.html +++ b/tests/playground.html @@ -40,6 +40,12 @@ + + + + + +