Skip to content

Commit

Permalink
feat: upgrade block defs to have JSO serialization hooks (google#5329)
Browse files Browse the repository at this point in the history
* Respect nulls from blocks.save

* Upgrade list blocks to use JSO serialization

* Upgrade logic blocks to use JSO serialization

* Upgrade math blocks to use JSO serialization

* Upgrade text blocks to use JSO serialization

* Upgrade procedure blocks to use JSO serialization

* Add more mutator tests

* Fix firing enabled events

* PR Comments
  • Loading branch information
BeksOmega authored and alschmiedt committed Sep 20, 2021
1 parent bd77b4a commit ee78b41
Show file tree
Hide file tree
Showing 9 changed files with 293 additions and 13 deletions.
44 changes: 43 additions & 1 deletion blocks/lists.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ Blockly.Blocks['lists_create_with'] = {
},
/**
* Create XML to represent list inputs.
* Backwards compatible serialization implementation.
* @return {!Element} XML storage element.
* @this {Blockly.Block}
*/
Expand All @@ -141,13 +142,31 @@ Blockly.Blocks['lists_create_with'] = {
},
/**
* Parse XML to restore the list inputs.
* Backwards compatible serialization implementation.
* @param {!Element} xmlElement XML storage element.
* @this {Blockly.Block}
*/
domToMutation: function(xmlElement) {
this.itemCount_ = parseInt(xmlElement.getAttribute('items'), 10);
this.updateShape_();
},
/**
* Returns the state of this block as a JSON serializable object.
* @return {{itemCount: number}} The state of this block, ie the item count.
*/
saveExtraState: function() {
return {
'itemCount': this.itemCount_,
};
},
/**
* Applies the given state to this block.
* @param {*} state The state to apply to this block, ie the item count.
*/
loadExtraState: function(state) {
this.itemCount_ = state['itemCount'];
this.updateShape_();
},
/**
* Populate the mutator's dialog with this block's components.
* @param {!Blockly.Workspace} workspace Mutator's workspace.
Expand Down Expand Up @@ -424,6 +443,12 @@ Blockly.Blocks['lists_getIndex'] = {
var isAt = (xmlElement.getAttribute('at') != 'false');
this.updateAt_(isAt);
},

// This block does not need JSO serialization hooks (saveExtraState and
// loadExtraState) because the state of this object is already encoded in the
// dropdown values.
// XML hooks are kept for backwards compatibility.

/**
* Switch between a value block and a statement block.
* @param {boolean} newStatement True if the block should be a statement.
Expand Down Expand Up @@ -584,6 +609,12 @@ Blockly.Blocks['lists_setIndex'] = {
var isAt = (xmlElement.getAttribute('at') != 'false');
this.updateAt_(isAt);
},

// This block does not need JSO serialization hooks (saveExtraState and
// loadExtraState) because the state of this object is already encoded in the
// dropdown values.
// XML hooks are kept for backwards compatibility.

/**
* Create or delete an input for the numeric index.
* @param {boolean} isAt True if the input should exist.
Expand Down Expand Up @@ -684,6 +715,12 @@ Blockly.Blocks['lists_getSublist'] = {
this.updateAt_(1, isAt1);
this.updateAt_(2, isAt2);
},

// This block does not need JSO serialization hooks (saveExtraState and
// loadExtraState) because the state of this object is already encoded in the
// dropdown values.
// XML hooks are kept for backwards compatibility.

/**
* Create or delete an input for a numeric index.
* This block has two such inputs, independent of each other.
Expand Down Expand Up @@ -857,5 +894,10 @@ Blockly.Blocks['lists_split'] = {
*/
domToMutation: function(xmlElement) {
this.updateType_(xmlElement.getAttribute('mode'));
}
},

// This block does not need JSO serialization hooks (saveExtraState and
// loadExtraState) because the state of this object is already encoded in the
// dropdown values.
// XML hooks are kept for backwards compatibility.
};
30 changes: 30 additions & 0 deletions blocks/logic.js
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ Blockly.Constants.Logic.CONTROLS_IF_MUTATOR_MIXIN = {

/**
* Create XML to represent the number of else-if and else inputs.
* Backwards compatible serialization implementation.
* @return {Element} XML storage element.
* @this {Blockly.Block}
*/
Expand All @@ -321,6 +322,7 @@ Blockly.Constants.Logic.CONTROLS_IF_MUTATOR_MIXIN = {
},
/**
* Parse XML to restore the else-if and else inputs.
* Backwards compatible serialization implementation.
* @param {!Element} xmlElement XML storage element.
* @this {Blockly.Block}
*/
Expand All @@ -329,6 +331,34 @@ Blockly.Constants.Logic.CONTROLS_IF_MUTATOR_MIXIN = {
this.elseCount_ = parseInt(xmlElement.getAttribute('else'), 10) || 0;
this.rebuildShape_();
},
/**
* Returns the state of this block as a JSON serializable object.
* @return {?{elseIfCount: (number|undefined), haseElse: (boolean|undefined)}}
* The state of this block, ie the else if count and else state.
*/
saveExtraState: function() {
if (!this.elseifCount_ && !this.elseCount_) {
return null;
}
var state = Object.create(null);
if (this.elseifCount_) {
state['elseIfCount'] = this.elseifCount_;
}
if (this.elseCount_) {
state['hasElse'] = true;
}
return state;
},
/**
* Applies the given state to this block.
* @param {*} state The state to apply to this block, ie the else if count and
* else state.
*/
loadExtraState: function(state) {
this.elseifCount_ = state['elseIfCount'] || 0;
this.elseCount_ = state['hasElse'] ? 1 : 0;
this.updateShape_();
},
/**
* Populate the mutator's dialog with this block's components.
* @param {!Blockly.Workspace} workspace Mutator's workspace.
Expand Down
17 changes: 16 additions & 1 deletion blocks/math.js
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,7 @@ Blockly.Extensions.register('math_op_tooltip',
Blockly.Constants.Math.IS_DIVISIBLEBY_MUTATOR_MIXIN = {
/**
* Create XML to represent whether the 'divisorInput' should be present.
* Backwards compatible serialization implementation.
* @return {!Element} XML storage element.
* @this {Blockly.Block}
*/
Expand All @@ -457,13 +458,20 @@ Blockly.Constants.Math.IS_DIVISIBLEBY_MUTATOR_MIXIN = {
},
/**
* Parse XML to restore the 'divisorInput'.
* Backwards compatible serialization implementation.
* @param {!Element} xmlElement XML storage element.
* @this {Blockly.Block}
*/
domToMutation: function(xmlElement) {
var divisorInput = (xmlElement.getAttribute('divisor_input') == 'true');
this.updateShape_(divisorInput);
},

// This block does not need JSO serialization hooks (saveExtraState and
// loadExtraState) because the state of this object is already encoded in the
// dropdown values.
// XML hooks are kept for backwards compatibility.

/**
* Modify this block to have (or not have) an input for 'is divisible by'.
* @param {boolean} divisorInput True if this block has a divisor input.
Expand Down Expand Up @@ -531,6 +539,7 @@ Blockly.Constants.Math.LIST_MODES_MUTATOR_MIXIN = {
},
/**
* Create XML to represent the output type.
* Backwards compatible serialization implementation.
* @return {!Element} XML storage element.
* @this {Blockly.Block}
*/
Expand All @@ -541,12 +550,18 @@ Blockly.Constants.Math.LIST_MODES_MUTATOR_MIXIN = {
},
/**
* Parse XML to restore the output type.
* Backwards compatible serialization implementation.
* @param {!Element} xmlElement XML storage element.
* @this {Blockly.Block}
*/
domToMutation: function(xmlElement) {
this.updateType_(xmlElement.getAttribute('op'));
}
},

// This block does not need JSO serialization hooks (saveExtraState and
// loadExtraState) because the state of this object is already encoded in the
// dropdown values.
// XML hooks are kept for backwards compatibility.
};

/**
Expand Down
92 changes: 91 additions & 1 deletion blocks/procedures.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ Blockly.Blocks['procedures_defnoreturn'] = {
},
/**
* Create XML to represent the argument inputs.
* Backwards compatible serialization implementation.
* @param {boolean=} opt_paramIds If true include the IDs of the parameter
* quarks. Used by Blockly.Procedures.mutateCallers for reconnection.
* @return {!Element} XML storage element.
Expand Down Expand Up @@ -124,6 +125,7 @@ Blockly.Blocks['procedures_defnoreturn'] = {
},
/**
* Parse XML to restore the argument inputs.
* Backwards compatible serialization implementation.
* @param {!Element} xmlElement XML storage element.
* @this {Blockly.Block}
*/
Expand All @@ -150,6 +152,54 @@ Blockly.Blocks['procedures_defnoreturn'] = {
// Show or hide the statement input.
this.setStatements_(xmlElement.getAttribute('statements') !== 'false');
},
/**
* Returns the state of this block as a JSON serializable object.
* @return {?{params: (!Array<{name: string, id: string}>|undefined),
* hasStatements: (boolean|undefined)}} The state of this block, eg the
* parameters and statements.
*/
saveExtraState: function() {
if (!this.argumentVarModels_.length && this.hasStatements_) {
return null;
}
var state = Object.create(null);
if (this.argumentVarModels_.length) {
state['params'] = [];
for (var i = 0; i < this.argumentVarModels_.length; i++) {
state['params'].push({
// We don't need to serialize the name, but just in case we decide
// to separate params from variables.
'name': this.argumentVarModels_[i].name,
'id': this.argumentVarModels_[i].getId()
});
}
}
if (!this.hasStatements_) {
state['hasStatements'] = false;
}
return state;
},
/**
* Applies the given state to this block.
* @param {*} state The state to apply to this block, eg the parameters and
* statements.
*/
loadExtraState: function(state) {
this.arguments_ = [];
this.argumentVarModels_ = [];
if (state['params']) {
for (var i = 0; i < state['params'].length; i++) {
var param = state['params'][i];
var variable = Blockly.Variables.getOrCreateVariablePackage(
this.workspace, param['id'], param['name'], '');
this.arguments_.push(variable.name);
this.argumentVarModels_.push(variable);
}
}
this.updateParams_();
Blockly.Procedures.mutateCallers(this);
this.setStatements_(state['hasStatements'] === false ? false : true);
},
/**
* Populate the mutator's dialog with this block's components.
* @param {!Blockly.Workspace} workspace Mutator's workspace.
Expand Down Expand Up @@ -436,6 +486,8 @@ Blockly.Blocks['procedures_defreturn'] = {
updateParams_: Blockly.Blocks['procedures_defnoreturn'].updateParams_,
mutationToDom: Blockly.Blocks['procedures_defnoreturn'].mutationToDom,
domToMutation: Blockly.Blocks['procedures_defnoreturn'].domToMutation,
saveExtraState: Blockly.Blocks['procedures_defnoreturn'].saveExtraState,
loadExtraState: Blockly.Blocks['procedures_defnoreturn'].loadExtraState,
decompose: Blockly.Blocks['procedures_defnoreturn'].decompose,
compose: Blockly.Blocks['procedures_defnoreturn'].compose,
/**
Expand Down Expand Up @@ -776,6 +828,7 @@ Blockly.Blocks['procedures_callnoreturn'] = {
},
/**
* Create XML to represent the (non-editable) name and arguments.
* Backwards compatible serialization implementation.
* @return {!Element} XML storage element.
* @this {Blockly.Block}
*/
Expand All @@ -791,6 +844,7 @@ Blockly.Blocks['procedures_callnoreturn'] = {
},
/**
* Parse XML to restore the (non-editable) name and parameters.
* Backwards compatible serialization implementation.
* @param {!Element} xmlElement XML storage element.
* @this {Blockly.Block}
*/
Expand All @@ -807,6 +861,34 @@ Blockly.Blocks['procedures_callnoreturn'] = {
}
this.setProcedureParameters_(args, paramIds);
},
/**
* Returns the state of this block as a JSON serializable object.
* @return {{name: string, params:(!Array<string>|undefined)}} The state of
* this block, ie the params and procedure name.
*/
saveExtraState: function() {
var state = Object.create(null);
state['name'] = this.getProcedureCall();
if (this.arguments_.length) {
state['params'] = this.arguments_;
}
return state;
},
/**
* Applies the given state to this block.
* @param {*} state The state to apply to this block, ie the params and
* procedure name.
*/
loadExtraState: function(state) {
this.renameProcedure(this.getProcedureCall(), state['name']);
const params = state['params'];
if (params) {
const ids = [];
ids.length = params.length;
ids.fill(null);
this.setProcedureParameters_(params, ids);
}
},
/**
* Return all variables referenced by this block.
* @return {!Array<string>} List of variable names.
Expand Down Expand Up @@ -975,6 +1057,8 @@ Blockly.Blocks['procedures_callreturn'] = {
updateShape_: Blockly.Blocks['procedures_callnoreturn'].updateShape_,
mutationToDom: Blockly.Blocks['procedures_callnoreturn'].mutationToDom,
domToMutation: Blockly.Blocks['procedures_callnoreturn'].domToMutation,
saveExtraState: Blockly.Blocks['procedures_callnoreturn'].saveExtraState,
loadExtraState: Blockly.Blocks['procedures_callnoreturn'].loadExtraState,
getVars: Blockly.Blocks['procedures_callnoreturn'].getVars,
getVarModels: Blockly.Blocks['procedures_callnoreturn'].getVarModels,
onchange: Blockly.Blocks['procedures_callnoreturn'].onchange,
Expand Down Expand Up @@ -1026,14 +1110,20 @@ Blockly.Blocks['procedures_ifreturn'] = {
.appendField(Blockly.Msg['PROCEDURES_DEFRETURN_RETURN']);
}
},

// This block does not need JSO serialization hooks (saveExtraState and
// loadExtraState) because the state of this block is already encoded in the
// block's position in the workspace.
// XML hooks are kept for backwards compatibility.

/**
* Called whenever anything on the workspace changes.
* Add warning if this flow block is not nested inside a loop.
* @param {!Blockly.Events.Abstract} _e Change event.
* @this {Blockly.Block}
*/
onchange: function(_e) {
if (!this.workspace.isDragging || this.workspace.isDragging()) {
if (this.workspace.isDragging && this.workspace.isDragging()) {
return; // Don't change state at the start of a drag.
}
var legal = false;
Expand Down
Loading

0 comments on commit ee78b41

Please sign in to comment.