From 05fc1ad91bd67f52f0095b86d20cafb1631b4edb Mon Sep 17 00:00:00 2001 From: Neil Fraser Date: Fri, 25 Mar 2016 19:03:18 -0700 Subject: [PATCH] Add workspace.clearUndo --- blockly_compressed.js | 10 +- core/blockly.js | 1 + core/events.js | 10 + core/workspace.js | 12 ++ demos/blockfactory/blocks.js | 357 ++++++++++++++++++---------------- demos/blockfactory/factory.js | 10 +- demos/blockfactory/index.html | 12 +- demos/graph/index.html | 4 +- demos/plane/plane.js | 1 + 9 files changed, 240 insertions(+), 177 deletions(-) diff --git a/blockly_compressed.js b/blockly_compressed.js index ef0187a94..108144e9a 100644 --- a/blockly_compressed.js +++ b/blockly_compressed.js @@ -857,9 +857,9 @@ Blockly.Workspace.prototype.addTopBlock=function(a){this.topBlocks_.push(a)};Blo Blockly.Workspace.prototype.getTopBlocks=function(a){var b=[].concat(this.topBlocks_);if(a&&1this.MAX_UNDO&&this.undoStack_.unshift());for(var b=0,c;c=this.listeners_[b];b++)c(a)};Blockly.Workspace.WorkspaceDB_=Object.create(null);Blockly.Workspace.getById=function(a){return Blockly.Workspace.WorkspaceDB_[a]||null}; -Blockly.Workspace.prototype.clear=Blockly.Workspace.prototype.clear;Blockly.Workspace.prototype.addChangeListener=Blockly.Workspace.prototype.addChangeListener;Blockly.Workspace.prototype.removeChangeListener=Blockly.Workspace.prototype.removeChangeListener;Blockly.Bubble=function(a,b,c,d,e,f,g){this.workspace_=a;this.content_=b;this.shape_=c;c=Blockly.Bubble.ARROW_ANGLE;this.workspace_.RTL&&(c=-c);this.arrow_radians_=goog.math.toRadians(c);a.getBubbleCanvas().appendChild(this.createDom_(b,!(!f||!g)));this.setAnchorLocation(d,e);f&&g||(b=this.content_.getBBox(),f=b.width+2*Blockly.Bubble.BORDER_WIDTH,g=b.height+2*Blockly.Bubble.BORDER_WIDTH);this.setBubbleSize(f,g);this.positionBubble_();this.renderArrow_();this.rendered_=!0;a.options.readOnly||(Blockly.bindEvent_(this.bubbleBack_, +Blockly.Workspace.prototype.undo=function(a){var b=a?this.redoStack_:this.undoStack_,c=a?this.undoStack_:this.redoStack_,d=b.pop();if(d){for(var e=[d];b.length&&d.group&&d.group==b[b.length-1].group;)e.push(b.pop());for(b=0;d=e[b];b++)c.push(d);e=Blockly.Events.filter(e,a);Blockly.Events.recordUndo=!1;for(b=0;d=e[b];b++)d.run(a);Blockly.Events.recordUndo=!0}};Blockly.Workspace.prototype.clearUndo=function(){this.undoStack_.length=0;this.redoStack_.length=0;Blockly.Events.clearPendingUndo()}; +Blockly.Workspace.prototype.addChangeListener=function(a){this.listeners_.push(a);return a};Blockly.Workspace.prototype.removeChangeListener=function(a){a=this.listeners_.indexOf(a);-1!=a&&this.listeners_.splice(a,1)};Blockly.Workspace.prototype.fireChangeListener=function(a){a.recordUndo&&(this.undoStack_.push(a),this.redoStack_.length=0,this.undoStack_.length>this.MAX_UNDO&&this.undoStack_.unshift());for(var b=0,c;c=this.listeners_[b];b++)c(a)};Blockly.Workspace.WorkspaceDB_=Object.create(null); +Blockly.Workspace.getById=function(a){return Blockly.Workspace.WorkspaceDB_[a]||null};Blockly.Workspace.prototype.clear=Blockly.Workspace.prototype.clear;Blockly.Workspace.prototype.clearUndo=Blockly.Workspace.prototype.clearUndo;Blockly.Workspace.prototype.addChangeListener=Blockly.Workspace.prototype.addChangeListener;Blockly.Workspace.prototype.removeChangeListener=Blockly.Workspace.prototype.removeChangeListener;Blockly.Bubble=function(a,b,c,d,e,f,g){this.workspace_=a;this.content_=b;this.shape_=c;c=Blockly.Bubble.ARROW_ANGLE;this.workspace_.RTL&&(c=-c);this.arrow_radians_=goog.math.toRadians(c);a.getBubbleCanvas().appendChild(this.createDom_(b,!(!f||!g)));this.setAnchorLocation(d,e);f&&g||(b=this.content_.getBBox(),f=b.width+2*Blockly.Bubble.BORDER_WIDTH,g=b.height+2*Blockly.Bubble.BORDER_WIDTH);this.setBubbleSize(f,g);this.positionBubble_();this.renderArrow_();this.rendered_=!0;a.options.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; Blockly.Bubble.prototype.width_=0;Blockly.Bubble.prototype.height_=0;Blockly.Bubble.prototype.autoLayout_=!0; @@ -1177,7 +1177,7 @@ Blockly.BlockSvg.prototype.renderDrawLeft_=function(a,b,c,d){this.outputConnecti ",-0.5 q "+-.19*Blockly.BlockSvg.TAB_WIDTH+",-5.5 0,-11"),b.push("m",.92*Blockly.BlockSvg.TAB_WIDTH+",1 V 0.5 H 1")),this.width+=Blockly.BlockSvg.TAB_WIDTH):this.RTL||(this.squareTopLeftCorner_?b.push("V",.5):b.push("V",Blockly.BlockSvg.CORNER_RADIUS));a.push("z")};Blockly.Events={};Blockly.Events.group_="";Blockly.Events.recordUndo=!0;Blockly.Events.disabled_=0;Blockly.Events.CREATE="create";Blockly.Events.DELETE="delete";Blockly.Events.CHANGE="change";Blockly.Events.MOVE="move";Blockly.Events.FIRE_QUEUE_=[];Blockly.Events.fire=function(a){Blockly.Events.isEnabled()&&(Blockly.Events.FIRE_QUEUE_.length||setTimeout(Blockly.Events.fireNow_,0),Blockly.Events.FIRE_QUEUE_.push(a))}; Blockly.Events.fireNow_=function(){for(var a=Blockly.Events.filter(Blockly.Events.FIRE_QUEUE_,!0),b=Blockly.Events.FIRE_QUEUE_.length=0,c;c=a[b];b++){var d=Blockly.Workspace.getById(c.workspaceId);d&&d.fireChangeListener(c)}}; Blockly.Events.filter=function(a,b){var c=goog.array.clone(a);b||c.reverse();for(var d=0,e;e=c[d];d++)for(var f=d+1,g;g=c[f];f++)e.type==Blockly.Events.MOVE&&g.type==Blockly.Events.MOVE&&e.blockId==g.blockId?(e.newParentId=g.newParentId,e.newInputName=g.newInputName,e.newCoordinate=g.newCoordinate,c.splice(f,1),f--):e.type==Blockly.Events.CHANGE&&g.type==Blockly.Events.CHANGE&&e.blockId==g.blockId&&e.element==g.element&&e.name==g.name&&(e.newValue=g.newValue,c.splice(f,1),f--);for(d=c.length-1;0<= -d;d--)c[d].isNull()&&c.splice(d,1);b||c.reverse();for(d=1;e=c[d];d++)e.type==Blockly.Events.CHANGE&&"mutation"==e.element&&c.unshift(c.splice(d,1)[0]);return c};Blockly.Events.disable=function(){Blockly.Events.disabled_++};Blockly.Events.enable=function(){Blockly.Events.disabled_--};Blockly.Events.isEnabled=function(){return 0==Blockly.Events.disabled_};Blockly.Events.getGroup=function(){return Blockly.Events.group_}; +d;d--)c[d].isNull()&&c.splice(d,1);b||c.reverse();for(d=1;e=c[d];d++)e.type==Blockly.Events.CHANGE&&"mutation"==e.element&&c.unshift(c.splice(d,1)[0]);return c};Blockly.Events.clearPendingUndo=function(){for(var a=0,b;b=Blockly.Events.FIRE_QUEUE_[a];a++)b.recordUndo=!1};Blockly.Events.disable=function(){Blockly.Events.disabled_++};Blockly.Events.enable=function(){Blockly.Events.disabled_--};Blockly.Events.isEnabled=function(){return 0==Blockly.Events.disabled_};Blockly.Events.getGroup=function(){return Blockly.Events.group_}; Blockly.Events.setGroup=function(a){Blockly.Events.group_="boolean"==typeof a?a?Blockly.genUid():"":a};Blockly.Events.Abstract=function(a){this.blockId=a.id;this.workspaceId=a.workspace.id;this.group=Blockly.Events.group_;this.recordUndo=Blockly.Events.recordUndo};Blockly.Events.Abstract.prototype.toJson=function(){var a={type:this.type,blockId:this.blockId,workspaceId:this.workspaceId};this.group&&(a.group=this.group);return a};Blockly.Events.Abstract.prototype.isNull=function(){return!1}; Blockly.Events.Create=function(a){Blockly.Events.Create.superClass_.constructor.call(this,a);this.xml=Blockly.Xml.blockToDomWithXY(a)};goog.inherits(Blockly.Events.Create,Blockly.Events.Abstract);Blockly.Events.Create.prototype.type=Blockly.Events.CREATE;Blockly.Events.Create.prototype.toJson=function(){var a=Blockly.Events.Create.superClass_.toJson.call(this);a.xml=Blockly.Xml.domToText(this.xml);return a}; Blockly.Events.Create.prototype.run=function(a){if(a){a=Blockly.Workspace.getById(this.workspaceId);var b=goog.dom.createDom("xml");b.appendChild(this.xml);Blockly.Xml.domToWorkspace(a,b)}else(a=Blockly.Block.getById(this.blockId))?a.dispose(!1,!0):console.warn("Can't delete non-existant block: "+this.blockId)};Blockly.Events.Delete=function(a){if(a.getParent())throw"Connected blocks cannot be deleted.";Blockly.Events.Delete.superClass_.constructor.call(this,a);this.oldXml=Blockly.Xml.blockToDomWithXY(a)}; @@ -1351,7 +1351,7 @@ Blockly.onMouseUp_=function(a){a=Blockly.getMainWorkspace();Blockly.Css.setCurso Blockly.onMouseMove_=function(a){if(!(a.touches&&2<=a.touches.length)){var b=Blockly.getMainWorkspace();if(b.isScrolling){Blockly.removeAllRanges();var c=a.clientX-b.startDragMouseX,d=a.clientY-b.startDragMouseY,e=b.startDragMetrics,f=b.startScrollX+c,g=b.startScrollY+d,f=Math.min(f,-e.contentLeft),g=Math.min(g,-e.contentTop),f=Math.max(f,e.viewWidth-e.contentLeft-e.contentWidth),g=Math.max(g,e.viewHeight-e.contentTop-e.contentHeight);b.scrollbar.set(-f-e.contentLeft,-g-e.contentTop);Math.sqrt(c* c+d*d)>Blockly.DRAG_RADIUS&&Blockly.longStop_();a.stopPropagation()}}}; Blockly.onKeyDown_=function(a){if(!Blockly.isTargetInput_(a)){var b=!1;if(27==a.keyCode)Blockly.hideChaff();else if(8==a.keyCode||46==a.keyCode)try{Blockly.selected&&Blockly.selected.isDeletable()&&(b=!0)}finally{a.preventDefault()}else if(a.altKey||a.ctrlKey||a.metaKey)Blockly.selected&&Blockly.selected.isDeletable()&&Blockly.selected.isMovable()&&(67==a.keyCode?(Blockly.hideChaff(),Blockly.copy_(Blockly.selected)):88==a.keyCode&&(Blockly.copy_(Blockly.selected),b=!0)),86==a.keyCode?Blockly.clipboardXml_&& -Blockly.clipboardSource_.paste(Blockly.clipboardXml_):90==a.keyCode&&Blockly.mainWorkspace.undo(a.shiftKey);b&&(Blockly.hideChaff(),Blockly.selected.dispose(2!=Blockly.dragMode_,!0),Blockly.highlightedConnection_&&(Blockly.highlightedConnection_.unhighlight(),Blockly.highlightedConnection_=null))}};Blockly.terminateDrag_=function(){Blockly.BlockSvg.terminateDrag_();Blockly.Flyout.terminateDrag_()};Blockly.longPid_=0; +Blockly.clipboardSource_.paste(Blockly.clipboardXml_):90==a.keyCode&&(Blockly.hideChaff(),Blockly.mainWorkspace.undo(a.shiftKey));b&&(Blockly.hideChaff(),Blockly.selected.dispose(2!=Blockly.dragMode_,!0),Blockly.highlightedConnection_&&(Blockly.highlightedConnection_.unhighlight(),Blockly.highlightedConnection_=null))}};Blockly.terminateDrag_=function(){Blockly.BlockSvg.terminateDrag_();Blockly.Flyout.terminateDrag_()};Blockly.longPid_=0; Blockly.longStart_=function(a,b){Blockly.longStop_();Blockly.longPid_=setTimeout(function(){a.button=2;b.onMouseDown_(a)},Blockly.LONGPRESS)};Blockly.longStop_=function(){Blockly.longPid_&&(clearTimeout(Blockly.longPid_),Blockly.longPid_=0)};Blockly.copy_=function(a){var b=Blockly.Xml.blockToDom(a);2!=Blockly.dragMode_&&Blockly.Xml.deleteNext(b);var c=a.getRelativeToSurfaceXY();b.setAttribute("x",a.RTL?-c.x:c.x);b.setAttribute("y",c.y);Blockly.clipboardXml_=b;Blockly.clipboardSource_=a.workspace}; Blockly.duplicate_=function(a){var b=Blockly.clipboardXml_,c=Blockly.clipboardSource_;Blockly.copy_(a);a.workspace.paste(Blockly.clipboardXml_);Blockly.clipboardXml_=b;Blockly.clipboardSource_=c};Blockly.onContextMenu_=function(a){Blockly.isTargetInput_(a)||a.preventDefault()};Blockly.hideChaff=function(a){Blockly.Tooltip.hide();Blockly.WidgetDiv.hide();a||(a=Blockly.getMainWorkspace(),a.toolbox_&&a.toolbox_.flyout_&&a.toolbox_.flyout_.autoClose&&a.toolbox_.clearSelection())}; Blockly.getMainWorkspaceMetrics_=function(){var a=Blockly.svgSize(this.getParentSvg());this.toolbox_&&(a.width-=this.toolbox_.width);var b=Blockly.Flyout.prototype.CORNER_RADIUS-1,c=a.width-b,d=a.height-b,e=this.getBlocksBoundingBox(),f=e.width*this.scale,g=e.height*this.scale,h=e.x*this.scale,k=e.y*this.scale;this.scrollbar?(b=Math.min(h-c/2,h+f-c),c=Math.max(h+f+c/2,h+c),f=Math.min(k-d/2,k+g-d),d=Math.max(k+g+d/2,k+d)):(b=e.x,c=b+e.width,f=e.y,d=f+e.height);e=0;!this.RTL&&this.toolbox_&&(e=this.toolbox_.width); diff --git a/core/blockly.js b/core/blockly.js index 134a75b88..8f6359555 100644 --- a/core/blockly.js +++ b/core/blockly.js @@ -263,6 +263,7 @@ Blockly.onKeyDown_ = function(e) { } } else if (e.keyCode == 90) { // 'z' for undo 'Z' is for redo. + Blockly.hideChaff(); Blockly.mainWorkspace.undo(e.shiftKey); } } diff --git a/core/events.js b/core/events.js index 12179eb30..d532a6a5f 100644 --- a/core/events.js +++ b/core/events.js @@ -164,6 +164,16 @@ Blockly.Events.filter = function(queueIn, forward) { return queue; }; +/** + * Modify pending undo events so that when they are fired they don't land + * in the undo stack. Called by Blockly.Workspace.clearUndo. + */ +Blockly.Events.clearPendingUndo = function() { + for (var i = 0, event; event = Blockly.Events.FIRE_QUEUE_[i]; i++) { + event.recordUndo = false; + } +}; + /** * Stop sending events. Every call to this function MUST also call enable. */ diff --git a/core/workspace.js b/core/workspace.js index 2e1fe50ed..c7557f43f 100644 --- a/core/workspace.js +++ b/core/workspace.js @@ -240,6 +240,16 @@ Blockly.Workspace.prototype.undo = function(redo) { Blockly.Events.recordUndo = true; }; +/** + * Clear the undo/redo stacks. + */ +Blockly.Workspace.prototype.clearUndo = function() { + this.undoStack_.length = 0; + this.redoStack_.length = 0; + // Stop any events already in the firing queue from being undoable. + Blockly.Events.clearPendingUndo(); +}; + /** * When something in this workspace changes, call a function. * @param {!Function} func Function to call. @@ -296,6 +306,8 @@ Blockly.Workspace.getById = function(id) { // Export symbols that would otherwise be renamed by Closure compiler. Blockly.Workspace.prototype['clear'] = Blockly.Workspace.prototype.clear; +Blockly.Workspace.prototype['clearUndo'] = + Blockly.Workspace.prototype.clearUndo; Blockly.Workspace.prototype['addChangeListener'] = Blockly.Workspace.prototype.addChangeListener; Blockly.Workspace.prototype['removeChangeListener'] = diff --git a/demos/blockfactory/blocks.js b/demos/blockfactory/blocks.js index 3a3101956..6c59c1d92 100644 --- a/demos/blockfactory/blocks.js +++ b/demos/blockfactory/blocks.js @@ -53,14 +53,6 @@ Blockly.Blocks['factory_base'] = { this.appendValueInput('COLOUR') .setCheck('Colour') .appendField('colour'); - /* - this.appendValueInput('TOOLTIP') - .setCheck('String') - .appendField('tooltip'); - this.appendValueInput('HELP') - .setCheck('String') - .appendField('help url'); - */ this.setTooltip('Build a custom block by plugging\n' + 'fields, inputs and other blocks here.'); this.setHelpUrl( @@ -112,34 +104,57 @@ Blockly.Blocks['factory_base'] = { } }; -var ALIGNMENT_OPTIONS = - [['left', 'LEFT'], ['right', 'RIGHT'], ['centre', 'CENTRE']]; +var FIELD_MESSAGE = 'fields %1 %2'; +var FIELD_ARGS = [ + { + "type": "field_dropdown", + "name": "ALIGN", + "options": [['left', 'LEFT'], ['right', 'RIGHT'], ['centre', 'CENTRE']], + }, + { + "type": "input_statement", + "name": "FIELDS", + "check": "Field" + } +]; + +var TYPE_MESSAGE = 'type %1'; +var TYPE_ARGS = [ + { + "type": "input_value", + "name": "TYPE", + "check": "Type", + "align": "RIGHT" + } +]; Blockly.Blocks['input_value'] = { // Value input. init: function() { - this.setColour(210); - this.appendDummyInput() - .appendField('value input') - .appendField(new Blockly.FieldTextInput('NAME'), 'INPUTNAME'); - this.appendStatementInput('FIELDS') - .setCheck('Field') - .appendField('fields') - .appendField(new Blockly.FieldDropdown(ALIGNMENT_OPTIONS), 'ALIGN'); - this.appendValueInput('TYPE') - .setCheck('Type') - .setAlign(Blockly.ALIGN_RIGHT) - .appendField('type'); - this.setPreviousStatement(true, 'Input'); - this.setNextStatement(true, 'Input'); - this.setTooltip('A value socket for horizontal connections.'); - this.setHelpUrl('https://www.youtube.com/watch?v=s2_xaEvcVI0#t=71'); + this.jsonInit({ + "message0": "value input %1 %2", + "args0": [ + { + "type": "field_input", + "name": "INPUTNAME", + "text": "NAME" + }, + { + "type": "input_dummy" + } + ], + "message1": FIELD_MESSAGE, + "args1": FIELD_ARGS, + "message2": TYPE_MESSAGE, + "args2": TYPE_ARGS, + "previousStatement": "Input", + "nextStatement": "Input", + "colour": 210, + "tooltip": "A value socket for horizontal connections.", + "helpUrl": "https://www.youtube.com/watch?v=s2_xaEvcVI0#t=71" + }); }, onchange: function() { - if (!this.workspace) { - // Block has been deleted. - return; - } inputNameCheck(this); } }; @@ -147,28 +162,30 @@ Blockly.Blocks['input_value'] = { Blockly.Blocks['input_statement'] = { // Statement input. init: function() { - this.setColour(210); - this.appendDummyInput() - .appendField('statement input') - .appendField(new Blockly.FieldTextInput('NAME'), 'INPUTNAME'); - this.appendStatementInput('FIELDS') - .setCheck('Field') - .appendField('fields') - .appendField(new Blockly.FieldDropdown(ALIGNMENT_OPTIONS), 'ALIGN'); - this.appendValueInput('TYPE') - .setCheck('Type') - .setAlign(Blockly.ALIGN_RIGHT) - .appendField('type'); - this.setPreviousStatement(true, 'Input'); - this.setNextStatement(true, 'Input'); - this.setTooltip('A statement socket for enclosed vertical stacks.'); - this.setHelpUrl('https://www.youtube.com/watch?v=s2_xaEvcVI0#t=246'); + this.jsonInit({ + "message0": "statement input %1 %2", + "args0": [ + { + "type": "field_input", + "name": "INPUTNAME", + "text": "NAME" + }, + { + "type": "input_dummy" + }, + ], + "message1": FIELD_MESSAGE, + "args1": FIELD_ARGS, + "message2": TYPE_MESSAGE, + "args2": TYPE_ARGS, + "previousStatement": "Input", + "nextStatement": "Input", + "colour": 210, + "tooltip": "A statement socket for enclosed vertical stacks.", + "helpUrl": "https://www.youtube.com/watch?v=s2_xaEvcVI0#t=246" + }); }, onchange: function() { - if (!this.workspace) { - // Block has been deleted. - return; - } inputNameCheck(this); } }; @@ -176,19 +193,28 @@ Blockly.Blocks['input_statement'] = { Blockly.Blocks['input_dummy'] = { // Dummy input. init: function() { - this.setColour(210); - this.appendDummyInput() - .appendField('dummy input'); - this.appendStatementInput('FIELDS') - .setCheck('Field') - .appendField('fields') - .appendField(new Blockly.FieldDropdown(ALIGNMENT_OPTIONS), 'ALIGN'); - this.setPreviousStatement(true, 'Input'); - this.setNextStatement(true, 'Input'); - this.setTooltip('For adding fields on a separate row with no ' + - 'connections. Alignment options (left, right, centre) ' + - 'apply only to multi-line fields.'); - this.setHelpUrl('https://www.youtube.com/watch?v=s2_xaEvcVI0#t=293'); + this.jsonInit({ + "message0": "dummy input %1 %2", + "args0": [ + { + "type": "field_input", + "name": "INPUTNAME", + "text": "NAME" + }, + { + "type": "input_dummy" + }, + ], + "message1": FIELD_MESSAGE, + "args1": FIELD_ARGS, + "previousStatement": "Input", + "nextStatement": "Input", + "colour": 210, + "tooltip": "For adding fields on a separate row with no " + + "connections. Alignment options (left, right, centre) " + + "apply only to multi-line fields.", + "helpUrl": "https://www.youtube.com/watch?v=s2_xaEvcVI0#t=293" + }); } }; @@ -221,10 +247,6 @@ Blockly.Blocks['field_input'] = { this.setHelpUrl('https://www.youtube.com/watch?v=s2_xaEvcVI0#t=319'); }, onchange: function() { - if (!this.workspace) { - // Block has been deleted. - return; - } fieldNameCheck(this); } }; @@ -244,10 +266,6 @@ Blockly.Blocks['field_angle'] = { this.setHelpUrl('https://www.youtube.com/watch?v=s2_xaEvcVI0#t=372'); }, onchange: function() { - if (!this.workspace) { - // Block has been deleted. - return; - } fieldNameCheck(this); } }; @@ -255,51 +273,35 @@ Blockly.Blocks['field_angle'] = { Blockly.Blocks['field_dropdown'] = { // Dropdown menu. init: function() { - this.setColour(160); this.appendDummyInput() .appendField('dropdown') .appendField(new Blockly.FieldTextInput('NAME'), 'FIELDNAME'); - this.appendDummyInput('OPTION0') - .appendField(new Blockly.FieldTextInput('option'), 'USER0') - .appendField(',') - .appendField(new Blockly.FieldTextInput('OPTIONNAME'), 'CPU0'); - this.appendDummyInput('OPTION1') - .appendField(new Blockly.FieldTextInput('option'), 'USER1') - .appendField(',') - .appendField(new Blockly.FieldTextInput('OPTIONNAME'), 'CPU1'); - this.appendDummyInput('OPTION2') - .appendField(new Blockly.FieldTextInput('option'), 'USER2') - .appendField(',') - .appendField(new Blockly.FieldTextInput('OPTIONNAME'), 'CPU2'); + this.optionCount_ = 3; + this.updateShape_(); this.setPreviousStatement(true, 'Field'); this.setNextStatement(true, 'Field'); this.setMutator(new Blockly.Mutator(['field_dropdown_option'])); + this.setColour(160); this.setTooltip('Dropdown menu with a list of options.'); this.setHelpUrl('https://www.youtube.com/watch?v=s2_xaEvcVI0#t=386'); - this.optionCount_ = 3; }, mutationToDom: function(workspace) { + // Create XML to represent menu options. var container = document.createElement('mutation'); container.setAttribute('options', this.optionCount_); return container; }, domToMutation: function(container) { - for (var x = 0; x < this.optionCount_; x++) { - this.removeInput('OPTION' + x); - } + // Parse XML to restore the menu options. this.optionCount_ = parseInt(container.getAttribute('options'), 10); - for (var x = 0; x < this.optionCount_; x++) { - var input = this.appendDummyInput('OPTION' + x); - input.appendField(new Blockly.FieldTextInput('option'), 'USER' + x); - input.appendField(','); - input.appendField(new Blockly.FieldTextInput('OPTIONNAME'), 'CPU' + x); - } + this.updateShape_(); }, decompose: function(workspace) { + // Populate the mutator's dialog with this block's components. var containerBlock = workspace.newBlock('field_dropdown_container'); containerBlock.initSvg(); var connection = containerBlock.getInput('STACK').connection; - for (var x = 0; x < this.optionCount_; x++) { + for (var i = 0; i < this.optionCount_; i++) { var optionBlock = workspace.newBlock('field_dropdown_option'); optionBlock.initSvg(); connection.connect(optionBlock.previousConnection); @@ -308,43 +310,54 @@ Blockly.Blocks['field_dropdown'] = { return containerBlock; }, compose: function(containerBlock) { - // Disconnect all input blocks and remove all inputs. - for (var x = this.optionCount_ - 1; x >= 0; x--) { - this.removeInput('OPTION' + x); - } - this.optionCount_ = 0; - // Rebuild the block's inputs. + // Reconfigure this block based on the mutator dialog's components. var optionBlock = containerBlock.getInputTargetBlock('STACK'); + // Count number of inputs. + var data = []; while (optionBlock) { - this.appendDummyInput('OPTION' + this.optionCount_) - .appendField(new Blockly.FieldTextInput( - optionBlock.userData_ || 'option'), 'USER' + this.optionCount_) - .appendField(',') - .appendField(new Blockly.FieldTextInput( - optionBlock.cpuData_ || 'OPTIONNAME'), 'CPU' + this.optionCount_); - this.optionCount_++; + data.push([optionBlock.userData_, optionBlock.cpuData_]); optionBlock = optionBlock.nextConnection && optionBlock.nextConnection.targetBlock(); } + this.optionCount_ = data.length; + this.updateShape_(); + // Restore any data. + for (var i = 0; i < this.optionCount_; i++) { + this.setFieldValue(data[i][0] || 'option', 'USER' + i); + this.setFieldValue(data[i][1] || 'OPTIONNAME', 'CPU' + i); + } }, saveConnections: function(containerBlock) { // Store names and values for each option. var optionBlock = containerBlock.getInputTargetBlock('STACK'); - var x = 0; + var i = 0; while (optionBlock) { - optionBlock.userData_ = this.getFieldValue('USER' + x); - optionBlock.cpuData_ = this.getFieldValue('CPU' + x); - x++; + optionBlock.userData_ = this.getFieldValue('USER' + i); + optionBlock.cpuData_ = this.getFieldValue('CPU' + i); + i++; optionBlock = optionBlock.nextConnection && optionBlock.nextConnection.targetBlock(); } }, - onchange: function() { - if (!this.workspace) { - // Block has been deleted. - return; + updateShape_: function() { + // Modify this block to have the correct number of options. + // Add new options. + for (var i = 0; i < this.optionCount_; i++) { + if (!this.getInput('OPTION' + i)) { + this.appendDummyInput('OPTION' + i) + .appendField(new Blockly.FieldTextInput('option'), 'USER' + i) + .appendField(',') + .appendField(new Blockly.FieldTextInput('OPTIONNAME'), 'CPU' + i); + } } - if (this.optionCount_ < 1) { + // Remove deleted options. + while (this.getInput('OPTION' + i)) { + this.removeInput('OPTION' + i); + i++; + } + }, + onchange: function() { + if (this.workspace && this.optionCount_ < 1) { this.setWarningText('Drop down menu must\nhave at least one option.'); } else { fieldNameCheck(this); @@ -395,10 +408,6 @@ Blockly.Blocks['field_checkbox'] = { this.setHelpUrl('https://www.youtube.com/watch?v=s2_xaEvcVI0#t=485'); }, onchange: function() { - if (!this.workspace) { - // Block has been deleted. - return; - } fieldNameCheck(this); } }; @@ -418,10 +427,6 @@ Blockly.Blocks['field_colour'] = { this.setHelpUrl('https://www.youtube.com/watch?v=s2_xaEvcVI0#t=495'); }, onchange: function() { - if (!this.workspace) { - // Block has been deleted. - return; - } fieldNameCheck(this); } }; @@ -440,10 +445,6 @@ Blockly.Blocks['field_date'] = { this.setTooltip('Date input field.'); }, onchange: function() { - if (!this.workspace) { - // Block has been deleted. - return; - } fieldNameCheck(this); } }; @@ -463,10 +464,6 @@ Blockly.Blocks['field_variable'] = { this.setHelpUrl('https://www.youtube.com/watch?v=s2_xaEvcVI0#t=510'); }, onchange: function() { - if (!this.workspace) { - // Block has been deleted. - return; - } fieldNameCheck(this); } }; @@ -500,28 +497,27 @@ Blockly.Blocks['field_image'] = { Blockly.Blocks['type_group'] = { // Group of types. init: function() { - this.setColour(230); - this.appendValueInput('TYPE0') - .setCheck('Type') - .appendField('any of'); - this.appendValueInput('TYPE1') - .setCheck('Type'); + this.typeCount_ = 2; + this.updateShape_(); this.setOutput(true, 'Type'); this.setMutator(new Blockly.Mutator(['type_group_item'])); + this.setColour(230); this.setTooltip('Allows more than one type to be accepted.'); this.setHelpUrl('https://www.youtube.com/watch?v=s2_xaEvcVI0#t=677'); - this.typeCount_ = 2; }, mutationToDom: function(workspace) { + // Create XML to represent a group of types. var container = document.createElement('mutation'); container.setAttribute('types', this.typeCount_); return container; }, domToMutation: function(container) { + // Parse XML to restore the group of types. + this.typeCount_ = parseInt(container.getAttribute('types'), 10); + this.updateShape_(); for (var x = 0; x < this.typeCount_; x++) { this.removeInput('TYPE' + x); } - this.typeCount_ = parseInt(container.getAttribute('types'), 10); for (var x = 0; x < this.typeCount_; x++) { var input = this.appendValueInput('TYPE' + x) .setCheck('Type'); @@ -531,10 +527,11 @@ Blockly.Blocks['type_group'] = { } }, decompose: function(workspace) { + // Populate the mutator's dialog with this block's components. var containerBlock = workspace.newBlock('type_group_container'); containerBlock.initSvg(); var connection = containerBlock.getInput('STACK').connection; - for (var x = 0; x < this.typeCount_; x++) { + for (var i = 0; i < this.typeCount_; i++) { var typeBlock = workspace.newBlock('type_group_item'); typeBlock.initSvg(); connection.connect(typeBlock.previousConnection); @@ -543,39 +540,57 @@ Blockly.Blocks['type_group'] = { return containerBlock; }, compose: function(containerBlock) { - // Disconnect all input blocks and remove all inputs. - for (var x = this.typeCount_ - 1; x >= 0; x--) { - this.removeInput('TYPE' + x); - } - this.typeCount_ = 0; - // Rebuild the block's inputs. + // Reconfigure this block based on the mutator dialog's components. var typeBlock = containerBlock.getInputTargetBlock('STACK'); + // Count number of inputs. + var connections = []; while (typeBlock) { - var input = this.appendValueInput('TYPE' + this.typeCount_) - .setCheck('Type'); - if (this.typeCount_ == 0) { - input.appendField('any of'); - } - // Reconnect any child blocks. - if (typeBlock.valueConnection_) { - input.connection.connect(typeBlock.valueConnection_); - } - this.typeCount_++; + connections.push(typeBlock.valueConnection_); typeBlock = typeBlock.nextConnection && typeBlock.nextConnection.targetBlock(); } + // Disconnect any children that don't belong. + for (var i = 0; i < this.typeCount_; i++) { + var connection = this.getInput('TYPE' + i).connection.targetConnection; + if (connection && connections.indexOf(connection) == -1) { + connection.disconnect(); + } + } + this.typeCount_ = connections.length; + this.updateShape_(); + // Reconnect any child blocks. + for (var i = 0; i < this.typeCount_; i++) { + Blockly.Mutator.reconnect(connections[i], this, 'TYPE' + i); + } }, saveConnections: function(containerBlock) { // Store a pointer to any connected child blocks. var typeBlock = containerBlock.getInputTargetBlock('STACK'); - var x = 0; + var i = 0; while (typeBlock) { - var input = this.getInput('TYPE' + x); + var input = this.getInput('TYPE' + i); typeBlock.valueConnection_ = input && input.connection.targetConnection; - x++; + i++; typeBlock = typeBlock.nextConnection && typeBlock.nextConnection.targetBlock(); } + }, + updateShape_: function() { + // Modify this block to have the correct number of inputs. + // Add new inputs. + for (var i = 0; i < this.typeCount_; i++) { + if (!this.getInput('TYPE' + i)) { + var input = this.appendValueInput('TYPE' + i); + if (i == 0) { + input.appendField('any of'); + } + } + } + // Remove deleted inputs. + while (this.getInput('TYPE' + i)) { + this.removeInput('TYPE' + i); + i++; + } } }; @@ -625,7 +640,7 @@ Blockly.Blocks['type_boolean'] = { init: function() { this.setColour(230); this.appendDummyInput() - .appendField('boolean'); + .appendField('Boolean'); this.setOutput(true, 'Type'); this.setTooltip('Booleans (true/false) are allowed.'); this.setHelpUrl('https://www.youtube.com/watch?v=s2_xaEvcVI0#t=602'); @@ -638,7 +653,7 @@ Blockly.Blocks['type_number'] = { init: function() { this.setColour(230); this.appendDummyInput() - .appendField('number'); + .appendField('Number'); this.setOutput(true, 'Type'); this.setTooltip('Numbers (int/float) are allowed.'); this.setHelpUrl('https://www.youtube.com/watch?v=s2_xaEvcVI0#t=602'); @@ -651,7 +666,7 @@ Blockly.Blocks['type_string'] = { init: function() { this.setColour(230); this.appendDummyInput() - .appendField('string'); + .appendField('String'); this.setOutput(true, 'Type'); this.setTooltip('Strings (text) are allowed.'); this.setHelpUrl('https://www.youtube.com/watch?v=s2_xaEvcVI0#t=602'); @@ -664,7 +679,7 @@ Blockly.Blocks['type_list'] = { init: function() { this.setColour(230); this.appendDummyInput() - .appendField('list'); + .appendField('Array'); this.setOutput(true, 'Type'); this.setTooltip('Arrays (lists) are allowed.'); this.setHelpUrl('https://www.youtube.com/watch?v=s2_xaEvcVI0#t=602'); @@ -717,10 +732,14 @@ Blockly.Blocks['colour_hue'] = { * @param {!Blockly.Block} referenceBlock Block to check. */ function fieldNameCheck(referenceBlock) { + if (!referenceBlock.workspace) { + // Block has been deleted. + return; + } var name = referenceBlock.getFieldValue('FIELDNAME').toLowerCase(); var count = 0; var blocks = referenceBlock.workspace.getAllBlocks(); - for (var x = 0, block; block = blocks[x]; x++) { + for (var i = 0, block; block = blocks[i]; i++) { var otherName = block.getFieldValue('FIELDNAME'); if (!block.disabled && !block.getInheritedDisabled() && otherName && otherName.toLowerCase() == name) { @@ -738,10 +757,14 @@ function fieldNameCheck(referenceBlock) { * @param {!Blockly.Block} referenceBlock Block to check. */ function inputNameCheck(referenceBlock) { + if (!referenceBlock.workspace) { + // Block has been deleted. + return; + } var name = referenceBlock.getFieldValue('INPUTNAME').toLowerCase(); var count = 0; var blocks = referenceBlock.workspace.getAllBlocks(); - for (var x = 0, block; block = blocks[x]; x++) { + for (var i = 0, block; block = blocks[i]; i++) { var otherName = block.getFieldValue('INPUTNAME'); if (!block.disabled && !block.getInheritedDisabled() && otherName && otherName.toLowerCase() == name) { diff --git a/demos/blockfactory/factory.js b/demos/blockfactory/factory.js index c8dc2e0ba..4cda24f80 100644 --- a/demos/blockfactory/factory.js +++ b/demos/blockfactory/factory.js @@ -151,7 +151,9 @@ function formatJson_(blockType, rootBlock) { } } JS.message0 = message.join(' '); - JS.args0 = args; + if (args.length) { + JS.args0 = args; + } // Generate inline/external switch. if (rootBlock.getFieldValue('INLINE') == 'EXT') { JS.inputsInline = false; @@ -685,6 +687,7 @@ function updatePreview() { previewBlock.setMovable(false); previewBlock.setDeletable(false); previewBlock.moveBy(15, 10); + previewWorkspace.clearUndo(); updateGenerator(previewBlock); } finally { @@ -774,7 +777,9 @@ function init() { var toolbox = document.getElementById('toolbox'); mainWorkspace = Blockly.inject('blockly', - {toolbox: toolbox, media: '../../media/'}); + {collapse: false, + toolbox: toolbox, + media: '../../media/'}); // Create the root block. if ('BlocklyStorage' in window && window.location.hash.length > 1) { @@ -784,6 +789,7 @@ function init() { var xml = ''; Blockly.Xml.domToWorkspace(mainWorkspace, Blockly.Xml.textToDom(xml)); } + mainWorkspace.clearUndo(); mainWorkspace.addChangeListener(updateLanguage); document.getElementById('direction') diff --git a/demos/blockfactory/index.html b/demos/blockfactory/index.html index 447588557..a0ba9616e 100644 --- a/demos/blockfactory/index.html +++ b/demos/blockfactory/index.html @@ -177,8 +177,16 @@