Skip to content

Commit

Permalink
feat: dynamic json serialization (#1888)
Browse files Browse the repository at this point in the history
* feat: add json serialization for text and list dynamic blocks

* chore: use helpers to append inputs

* chore: add json serialization to if blocks

* fix: support loading stringified XML from JSOn
  • Loading branch information
BeksOmega committed Sep 28, 2023
1 parent f48da17 commit 69778a2
Show file tree
Hide file tree
Showing 6 changed files with 568 additions and 18 deletions.
74 changes: 57 additions & 17 deletions plugins/block-dynamic-connection/src/dynamic_if.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ interface CaseInputPair {
doInput: Blockly.Input;
}

/** Extra state for serializing controls_if blocks. */
interface IfExtraState {
elseIfCount?: number;
hasElse?: boolean;
}

/* eslint-disable @typescript-eslint/naming-convention */
const DYNAMIC_IF_MIXIN = {
/**
Expand Down Expand Up @@ -122,8 +128,7 @@ const DYNAMIC_IF_MIXIN = {
}
const hasElse = xmlElement.getAttribute('else');
if (hasElse == 'true') {
this.appendStatementInput('ELSE')
.appendField(Blockly.Msg['CONTROLS_IF_MSG_ELSE'], 'else');
this.addElseInput();
}
},

Expand All @@ -136,14 +141,49 @@ const DYNAMIC_IF_MIXIN = {
xmlElement.getAttribute('elseif') ?? '0', 10) || 0;
this.elseCount = parseInt(xmlElement.getAttribute('else') ?? '0', 10) || 0;
for (let i = 1; i <= this.elseifCount; i++) {
this.appendValueInput('IF' + i).setCheck('Boolean').appendField(
Blockly.Msg['CONTROLS_IF_MSG_ELSEIF']);
this.appendStatementInput('DO' + i).appendField(
Blockly.Msg['CONTROLS_IF_MSG_THEN']);
this.insertElseIf(this.inputList.length, i);
}
if (this.elseCount) {
this.addElseInput();
}
},

/**
* Returns the state of this block as a JSON serializable object.
* @returns The state of this block, ie the else if count and else state.
*/
saveExtraState: function(this: DynamicIfBlock): IfExtraState | null {
if (!this.elseifCount && !this.elseCount) {
return null;
}
const state = Object.create(null);
if (this.elseifCount) {
state['elseIfCount'] = this.elseifCount;
}
if (this.elseCount) {
this.appendStatementInput('ELSE').appendField(
Blockly.Msg['CONTROLS_IF_MSG_ELSE']);
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(this: DynamicIfBlock, state: IfExtraState | string) {
if (typeof state === 'string') {
this.domToMutation(Blockly.utils.xml.textToDom(state));
return;
}

this.elseifCount = state['elseIfCount'] || 0;
this.elseCount = state['hasElse'] ? 1 : 0;
for (let i = 1; i <= this.elseifCount; i++) {
this.insertElseIf(this.inputList.length, i);
}
if (this.elseCount) {
this.addElseInput();
}
},

Expand Down Expand Up @@ -193,8 +233,7 @@ const DYNAMIC_IF_MIXIN = {
onPendingConnection(
this: DynamicIfBlock, connection: Blockly.Connection): void {
if (connection.type === Blockly.NEXT_STATEMENT && !this.getInput('ELSE')) {
this.appendStatementInput('ELSE')
.appendField(Blockly.Msg['CONTROLS_IF_MSG_ELSE'], 'else');
this.addElseInput();
}
const inputIndex = this.findInputIndexForConnection(connection);
if (inputIndex === null) {
Expand Down Expand Up @@ -232,7 +271,9 @@ const DYNAMIC_IF_MIXIN = {

this.addFirstCase();
this.addCaseInputs(targetCaseConns);
if (targetElseConn) this.addElseInput(targetElseConn);
if (targetElseConn) {
this.addElseInput().connection?.connect(targetElseConn);
}

this.elseifCount = Math.max(targetCaseConns.length - 1, 0);
this.elseCount = targetElseConn ? 1 : 0;
Expand Down Expand Up @@ -288,13 +329,12 @@ const DYNAMIC_IF_MIXIN = {
},

/**
* Adds an else input to this block, and attaches the given connection to it.
* @param targetElseConn The connection to connect to the new else input.
* Adds an else input to this block.
* @returns The appended input.
*/
addElseInput(this: DynamicIfBlock, targetElseConn: Blockly.Connection): void {
this.appendStatementInput('ELSE')
.appendField(Blockly.Msg['CONTROLS_IF_MSG_ELSE'])
.connection?.connect(targetElseConn);
addElseInput(this: DynamicIfBlock): Blockly.Input {
return this.appendStatementInput('ELSE')
.appendField(Blockly.Msg['CONTROLS_IF_MSG_ELSE']);
},

/**
Expand Down
28 changes: 28 additions & 0 deletions plugins/block-dynamic-connection/src/dynamic_list_create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,34 @@ const DYNAMIC_LIST_CREATE_MIXIN = {
}
},

/**
* Returns the state of this block as a JSON serializable object.
* @returns The state of this block, ie the item count.
*/
saveExtraState: function(this: DynamicListCreateBlock): {itemCount: number} {
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(
this: DynamicListCreateBlock, state: ({[x: string]: any} | string)) {
if (typeof state === 'string') {
this.domToMutation(Blockly.utils.xml.textToDom(state));
return;
}

this.itemCount = state['itemCount'];
// minInputs are added automatically.
for (let i = this.minInputs; i < this.itemCount; i++) {
this.appendValueInput('ADD' + i);
}
},

/**
* Check whether a new input should be added and determine where it should go.
* @param connection The connection that has a pending connection.
Expand Down
28 changes: 28 additions & 0 deletions plugins/block-dynamic-connection/src/dynamic_text_join.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,34 @@ const DYNAMIC_TEXT_JOIN_MIXIN = {
}
},

/**
* Returns the state of this block as a JSON serializable object.
* @returns The state of this block, ie the item count.
*/
saveExtraState: function(this: DynamicTextJoinBlock): {itemCount: number} {
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(
this: DynamicTextJoinBlock, state: ({[x: string]: any} | string)) {
if (typeof state === 'string') {
this.domToMutation(Blockly.utils.xml.textToDom(state));
return;
}

this.itemCount = state['itemCount'];
// minInputs are added automatically.
for (let i = this.minInputs; i < this.itemCount; i++) {
this.appendValueInput('ADD' + i);
}
},

/**
* Check whether a new input should be added and determine where it should go.
* @param connection The connection that has a pending connection.
Expand Down
114 changes: 114 additions & 0 deletions plugins/block-dynamic-connection/test/dynamic_if.mocha.js
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,120 @@ suite('If block', function() {
block, [/IF0/, /DO0/, /IF1/, /DO1/, /ELSE/], 'dynamic_if');
},
},
{
title: 'one if one else with children - json',
json: {
'type': 'dynamic_if',
'id': '1',
'extraState': {
'elseIfCount': 1,
'hasElse': true,
},
'inputs': {
'IF0': {
'block': {
'type': 'logic_boolean',
'id': '2',
'fields': {
'BOOL': 'TRUE',
},
},
},
'IF1': {
'block': {
'type': 'logic_boolean',
'id': '3',
'fields': {
'BOOL': 'TRUE',
},
},
},
'ELSE': {
'block': {
'type': 'text_print',
'id': '4',
},
},
},
},
assertBlockStructure: (block) => {
assertBlockStructure(
block, [/IF0/, /DO0/, /IF1/, /DO1/, /ELSE/], 'dynamic_if');
},
},
{
title: 'one if one else with children - json with stringified old XML',
json: {
'type': 'dynamic_if',
'id': '1',
'extraState':
'<mutation inputs="1,2" else="true" next="3"></mutation>',
'inputs': {
'IF1': {
'block': {
'type': 'logic_boolean',
'id': '2',
'fields': {
'BOOL': 'TRUE',
},
},
},
'IF2': {
'block': {
'type': 'logic_boolean',
'id': '3',
'fields': {
'BOOL': 'TRUE',
},
},
},
'ELSE': {
'block': {
'type': 'text_print',
'id': '4',
},
},
},
},
expectedJson: {
'type': 'dynamic_if',
'id': '1',
'extraState': {
'elseIfCount': 1,
'hasElse': true,
},
'inputs': {
'IF0': {
'block': {
'type': 'logic_boolean',
'id': '2',
'fields': {
'BOOL': 'TRUE',
},
},
},
'IF1': {
'block': {
'type': 'logic_boolean',
'id': '3',
'fields': {
'BOOL': 'TRUE',
},
},
},
'ELSE': {
'block': {
'type': 'text_print',
'id': '4',
},
},
},
},
assertBlockStructure: (block) => {
assertBlockStructure(
block, [/IF0/, /DO0/, /IF1/, /DO1/, /ELSE/], 'dynamic_if');
},
},
];
testHelpers.runSerializationTestSuite(testCases);
});
Loading

0 comments on commit 69778a2

Please sign in to comment.