From 599566dd872fd41ba9e7cf3467b389c119ba733e Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Thu, 22 Jul 2021 15:42:36 +0000 Subject: [PATCH 1/9] Add parameter for recording undo. This sets up the most common default behavior, but also makes it clear to people that it is happening, because it might not be expected. --- core/serialization/blocks.js | 23 ++++- core/serialization/variables.js | 11 +- core/serialization/workspaces.js | 17 +++- tests/mocha/jso_deserialization_test.js | 127 +++++++++++++++++++++++- 4 files changed, 165 insertions(+), 13 deletions(-) diff --git a/core/serialization/blocks.js b/core/serialization/blocks.js index 40d2a130922..e37808b52dc 100644 --- a/core/serialization/blocks.js +++ b/core/serialization/blocks.js @@ -13,6 +13,7 @@ goog.module('Blockly.serialization.blocks'); goog.module.declareLegacyNamespace(); +<<<<<<< HEAD // eslint-disable-next-line no-unused-vars const Block = goog.requireType('Blockly.Block'); // eslint-disable-next-line no-unused-vars @@ -21,6 +22,11 @@ const Connection = goog.requireType('Blockly.Connection'); const Workspace = goog.requireType('Blockly.Workspace'); const Xml = goog.require('Blockly.Xml'); const inputTypes = goog.require('Blockly.inputTypes'); +======= +goog.require('Blockly.Xml'); +goog.require('Blockly.inputTypes'); +const Events = goog.require('Blockly.Events'); +>>>>>>> bdbbe5bd (Add parameter for recording undo.) // TODO: Remove this once lint is fixed. @@ -262,17 +268,24 @@ const saveConnection = function(connection) { * Loads the block represented by the given state into the given workspace. * @param {!State} state The state of a block to deserialize into the workspace. * @param {!Workspace} workspace The workspace to add the block to. + * @param {{recordUndo: (boolean|undefined)}=} param1 + * recordUndo: If true, events triggered by this function will be undo-able + * by the user. False by default. * @return {!Block} The block that was just loaded. */ -const load = function(state, workspace) { +const load = function(state, workspace, {recordUndo = false} = {}) { + const prevRecordUndo = Events.recordUndo; + Events.recordUndo = recordUndo; + // We only want to fire an event for the top block. - Blockly.Events.disable(); + Events.disable(); const block = loadInternal(state, workspace); - Blockly.Events.enable(); - Blockly.Events.fire( - new (Blockly.Events.get(Blockly.Events.BLOCK_CREATE))(block)); + Events.enable(); + Events.fire(new (Events.get(Events.BLOCK_CREATE))(block)); + + Events.recordUndo = prevRecordUndo; // Adding connections to the connection db is expensive. This defers that // operation to decrease load time. diff --git a/core/serialization/variables.js b/core/serialization/variables.js index 8bcb7b40861..06dda13958b 100644 --- a/core/serialization/variables.js +++ b/core/serialization/variables.js @@ -13,6 +13,7 @@ goog.module('Blockly.serialization.variables'); goog.module.declareLegacyNamespace(); +const Events = goog.require('Blockly.Events'); // eslint-disable-next-line no-unused-vars const VariableModel = goog.requireType('Blockly.VariableModel'); // eslint-disable-next-line no-unused-vars @@ -54,9 +55,17 @@ exports.save = save; * @param {!State} state The state of a variable to deserialize into the * workspace. * @param {!Workspace} workspace The workspace to add the variable to. + * @param {{recordUndo: (boolean|undefined)}=} param1 + * recordUndo: If true, events triggered by this function will be undo-able + * by the user. False by default. */ -const load = function(state, workspace) { +const load = function(state, workspace, {recordUndo = false} = {}) { + const prevRecordUndo = Events.recordUndo; + Events.recordUndo = recordUndo; + workspace.createVariable(state['name'], state['type'], state['id']); + + Events.recordUndo = prevRecordUndo; }; /** @package */ exports.load = load; diff --git a/core/serialization/workspaces.js b/core/serialization/workspaces.js index b4600f9879a..e031b9b3d8f 100644 --- a/core/serialization/workspaces.js +++ b/core/serialization/workspaces.js @@ -13,6 +13,7 @@ goog.module('Blockly.serialization.workspaces'); goog.module.declareLegacyNamespace(); +const Events = goog.require('Blockly.Events'); // eslint-disable-next-line no-unused-vars const Workspace = goog.require('Blockly.Workspace'); const blocks = goog.require('Blockly.serialization.blocks'); @@ -58,23 +59,33 @@ exports.save = save; * @param {!Object} state The state of the workspace to deserialize * into the workspace. * @param {!Workspace} workspace The workspace to add the new state to. + * @param {{recordUndo: (boolean|undefined)}=} param1 + * recordUndo: If true, events triggered by this function will be undo-able + * by the user. False by default. */ -const load = function(state, workspace) { +const load = function(state, workspace, {recordUndo = false} = {}) { // TODO: Switch this to use plugin serialization system (once it is built). // TODO: Add something for clearing the state before deserializing. + const prevRecordUndo = Events.recordUndo; + Events.recordUndo = recordUndo; + if (state['variables']) { const variableStates = state['variables']; for (let i = 0; i < variableStates.length; i++) { - variables.load(variableStates[i], workspace); + variables.load(variableStates[i], workspace, {recordUndo}); } } if (state['blocks']) { const blockStates = state['blocks']['blocks']; for (let i = 0; i < blockStates.length; i++) { - blocks.load(blockStates[i], workspace); + blocks.load(blockStates[i], workspace, {recordUndo}); } } + + Events.fire(new (Events.get(Events.FINISHED_LOADING))(workspace)); + + Events.recordUndo = prevRecordUndo; }; exports.load = load; diff --git a/tests/mocha/jso_deserialization_test.js b/tests/mocha/jso_deserialization_test.js index d2ed44c4bdb..16370f87193 100644 --- a/tests/mocha/jso_deserialization_test.js +++ b/tests/mocha/jso_deserialization_test.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -suite('JSO Deserialization', function() { +suite.only('JSO Deserialization', function() { setup(function() { sharedTestSetup.call(this); this.workspace = new Blockly.Workspace(); @@ -16,7 +16,7 @@ suite('JSO Deserialization', function() { }); suite('Events', function() { - test.skip('Finished loading', function() { + test('Finished loading', function() { const state = { 'blocks': { 'blocks': [ @@ -51,7 +51,71 @@ suite('JSO Deserialization', function() { assertEventFired( this.eventsFireStub, Blockly.Events.VarCreate, - {'varName': 'test', 'varId': 'testId', 'varType': ''}, + { + 'varName': 'test', + 'varId': 'testId', + 'varType': '', + 'recordUndo': false + }, + this.workspace.id); + }); + + test('Just var, directly', function() { + const state = { + 'name': 'test', + 'id': 'testId', + }; + Blockly.serialization.variables.load(state, this.workspace); + assertEventFired( + this.eventsFireStub, + Blockly.Events.VarCreate, + { + 'varName': 'test', + 'varId': 'testId', + 'varType': '', + 'recordUndo': false + }, + this.workspace.id); + }); + + test('Just var, record undo', function() { + const state = { + 'variables': [ + { + 'name': 'test', + 'id': 'testId', + } + ] + }; + Blockly.serialization.load(state, this.workspace, {recordUndo: true}); + assertEventFired( + this.eventsFireStub, + Blockly.Events.VarCreate, + { + 'varName': 'test', + 'varId': 'testId', + 'varType': '', + 'recordUndo': true + }, + this.workspace.id); + }); + + test('Just var, directly, record undo', function() { + const state = { + 'name': 'test', + 'id': 'testId', + }; + Blockly.serialization.variables + .load(state, this.workspace, {recordUndo: true}); + assertEventFired( + this.eventsFireStub, + Blockly.Events.VarCreate, + { + 'varName': 'test', + 'varId': 'testId', + 'varType': '', + 'recordUndo': true + }, this.workspace.id); }); @@ -112,7 +176,62 @@ suite('JSO Deserialization', function() { assertEventFired( this.eventsFireStub, Blockly.Events.BlockCreate, - {}, + {'recordUndo': false}, + this.workspace.id, + 'testId'); + }); + + test('No children, directly', function() { + const state = { + 'type': 'controls_if', + 'id': 'testId', + 'x': 42, + 'y': 42 + }; + Blockly.serialization.blocks.load(state, this.workspace); + assertEventFired( + this.eventsFireStub, + Blockly.Events.BlockCreate, + {'recordUndo': false}, + this.workspace.id, + 'testId'); + }); + + test('No children, record undo', function() { + const state = { + 'blocks': { + 'blocks': [ + { + 'type': 'controls_if', + 'id': 'testId', + 'x': 42, + 'y': 42 + }, + ] + } + }; + Blockly.serialization.load(state, this.workspace, {'recordUndo': true}); + assertEventFired( + this.eventsFireStub, + Blockly.Events.BlockCreate, + {'recordUndo': true}, + this.workspace.id, + 'testId'); + }); + + test('No children, directly, record undo', function() { + const state = { + 'type': 'controls_if', + 'id': 'testId', + 'x': 42, + 'y': 42 + }; + Blockly.serialization.blocks + .load(state, this.workspace, {'recordUndo': true}); + assertEventFired( + this.eventsFireStub, + Blockly.Events.BlockCreate, + {'recordUndo': true}, this.workspace.id, 'testId'); }); From 641b4dcdd2c01bd15692a5a3a4128ac5efb09b73 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Thu, 22 Jul 2021 18:42:59 +0000 Subject: [PATCH 2/9] Add grouping of events --- core/serialization/blocks.js | 5 + core/serialization/variables.js | 5 + core/serialization/workspaces.js | 5 + tests/mocha/jso_deserialization_test.js | 527 ++++++++++++++++++------ 4 files changed, 408 insertions(+), 134 deletions(-) diff --git a/core/serialization/blocks.js b/core/serialization/blocks.js index e37808b52dc..99ac19ae24f 100644 --- a/core/serialization/blocks.js +++ b/core/serialization/blocks.js @@ -276,6 +276,10 @@ const saveConnection = function(connection) { const load = function(state, workspace, {recordUndo = false} = {}) { const prevRecordUndo = Events.recordUndo; Events.recordUndo = recordUndo; + const existingGroup = Events.getGroup(); + if (!existingGroup) { + Events.setGroup(true); + } // We only want to fire an event for the top block. Events.disable(); @@ -285,6 +289,7 @@ const load = function(state, workspace, {recordUndo = false} = {}) { Events.enable(); Events.fire(new (Events.get(Events.BLOCK_CREATE))(block)); + Events.setGroup(existingGroup); Events.recordUndo = prevRecordUndo; // Adding connections to the connection db is expensive. This defers that diff --git a/core/serialization/variables.js b/core/serialization/variables.js index 06dda13958b..1984a63d04d 100644 --- a/core/serialization/variables.js +++ b/core/serialization/variables.js @@ -62,9 +62,14 @@ exports.save = save; const load = function(state, workspace, {recordUndo = false} = {}) { const prevRecordUndo = Events.recordUndo; Events.recordUndo = recordUndo; + const existingGroup = Events.getGroup(); + if (!existingGroup) { + Events.setGroup(true); + } workspace.createVariable(state['name'], state['type'], state['id']); + Events.setGroup(existingGroup); Events.recordUndo = prevRecordUndo; }; /** @package */ diff --git a/core/serialization/workspaces.js b/core/serialization/workspaces.js index e031b9b3d8f..7e8ac31b42a 100644 --- a/core/serialization/workspaces.js +++ b/core/serialization/workspaces.js @@ -69,6 +69,10 @@ const load = function(state, workspace, {recordUndo = false} = {}) { const prevRecordUndo = Events.recordUndo; Events.recordUndo = recordUndo; + const existingGroup = Events.getGroup(); + if (!existingGroup) { + Events.setGroup(true); + } if (state['variables']) { const variableStates = state['variables']; @@ -86,6 +90,7 @@ const load = function(state, workspace, {recordUndo = false} = {}) { Events.fire(new (Events.get(Events.FINISHED_LOADING))(workspace)); + Events.setGroup(existingGroup); Events.recordUndo = prevRecordUndo; }; exports.load = load; diff --git a/tests/mocha/jso_deserialization_test.js b/tests/mocha/jso_deserialization_test.js index 16370f87193..d07a82d141b 100644 --- a/tests/mocha/jso_deserialization_test.js +++ b/tests/mocha/jso_deserialization_test.js @@ -40,86 +40,52 @@ suite.only('JSO Deserialization', function() { suite('Var create', function() { test('Just var', function() { const state = { - 'variables': [ - { - 'name': 'test', - 'id': 'testId', - } - ] + 'blocks': { + 'blocks': [ + { + 'type': 'controls_if', + 'id': 'testId', + 'x': 42, + 'y': 42 + }, + ] + } }; Blockly.serialization.workspaces.load(state, this.workspace); assertEventFired( this.eventsFireStub, - Blockly.Events.VarCreate, - { - 'varName': 'test', - 'varId': 'testId', - 'varType': '', - 'recordUndo': false - }, - this.workspace.id); - }); - - test('Just var, directly', function() { - const state = { - 'name': 'test', - 'id': 'testId', - }; - Blockly.serialization.variables.load(state, this.workspace); - assertEventFired( - this.eventsFireStub, - Blockly.Events.VarCreate, - { - 'varName': 'test', - 'varId': 'testId', - 'varType': '', - 'recordUndo': false - }, - this.workspace.id); - }); - - test('Just var, record undo', function() { - const state = { - 'variables': [ - { - 'name': 'test', - 'id': 'testId', - } - ] - }; - Blockly.serialization.load(state, this.workspace, {recordUndo: true}); - assertEventFired( - this.eventsFireStub, - Blockly.Events.VarCreate, - { - 'varName': 'test', - 'varId': 'testId', - 'varType': '', - 'recordUndo': true - }, + Blockly.Events.FinishedLoading, + {}, this.workspace.id); }); - test('Just var, directly, record undo', function() { + test('Explicit group', function() { const state = { - 'name': 'test', - 'id': 'testId', + 'blocks': { + 'blocks': [ + { + 'type': 'controls_if', + 'id': 'testId', + 'x': 42, + 'y': 42 + }, + ] + } }; - Blockly.serialization.variables - .load(state, this.workspace, {recordUndo: true}); + Blockly.Events.setGroup('my group'); + Blockly.serialization.load(state, this.workspace); assertEventFired( this.eventsFireStub, - Blockly.Events.VarCreate, - { - 'varName': 'test', - 'varId': 'testId', - 'varType': '', - 'recordUndo': true - }, + Blockly.Events.FinishedLoading, + {'group': 'my group'}, this.workspace.id); }); +<<<<<<< HEAD test('Only fire one event with var and var on block', function() { +======= + test('Automatic group', function() { +>>>>>>> 44418b3d (Add grouping of events) const state = { 'variables': [ { @@ -143,33 +109,52 @@ suite.only('JSO Deserialization', function() { }; Blockly.serialization.workspaces.load(state, this.workspace); const calls = this.eventsFireStub.getCalls(); - const count = calls.reduce((acc, call) => { - if (call.args[0] instanceof Blockly.Events.VarCreate) { - return acc + 1; - } - return acc; - }, 0); - chai.assert.equal(count, 1); - assertEventFired( - this.eventsFireStub, - Blockly.Events.VarCreate, - {'varName': 'test', 'varId': 'testId', 'varType': ''}, - this.workspace.id); + const group = calls[0].args[0].group; + chai.assert.isTrue(calls.every(call => call.args[0].group == group)); }); }); +<<<<<<< HEAD suite('Block create', function() { test('Simple', function() { const state = { 'blocks': { 'blocks': [ +======= + suite('Var create', function() { + suite('Top-level call', function() { + test('Just var', function() { + const state = { + 'variables': [ +>>>>>>> 44418b3d (Add grouping of events) { - 'type': 'controls_if', + 'name': 'test', 'id': 'testId', - 'x': 42, - 'y': 42 + } + ] + }; + Blockly.serialization.load(state, this.workspace); + assertEventFired( + this.eventsFireStub, + Blockly.Events.VarCreate, + { + 'varName': 'test', + 'varId': 'testId', + 'varType': '', + 'recordUndo': false }, + this.workspace.id); + }); + + test('Record undo', function() { + const state = { + 'variables': [ + { + 'name': 'test', + 'id': 'testId', + } ] +<<<<<<< HEAD } }; Blockly.serialization.workspaces.load(state, this.workspace); @@ -180,79 +165,288 @@ suite.only('JSO Deserialization', function() { this.workspace.id, 'testId'); }); +======= + }; + Blockly.serialization.load(state, this.workspace, {recordUndo: true}); + assertEventFired( + this.eventsFireStub, + Blockly.Events.VarCreate, + { + 'varName': 'test', + 'varId': 'testId', + 'varType': '', + 'recordUndo': true + }, + this.workspace.id); + }); +>>>>>>> 44418b3d (Add grouping of events) - test('No children, directly', function() { - const state = { - 'type': 'controls_if', - 'id': 'testId', - 'x': 42, - 'y': 42 - }; - Blockly.serialization.blocks.load(state, this.workspace); - assertEventFired( - this.eventsFireStub, - Blockly.Events.BlockCreate, - {'recordUndo': false}, - this.workspace.id, - 'testId'); - }); + test('Grouping', function() { + const state = { + 'variables': [ + { + 'name': 'test', + 'id': 'testId', + } + ] + }; + Blockly.Events.setGroup('my group'); + Blockly.serialization.load(state, this.workspace); + assertEventFired( + this.eventsFireStub, + Blockly.Events.VarCreate, + { + 'varName': 'test', + 'varId': 'testId', + 'varType': '', + 'group': 'my group' + }, + this.workspace.id); + }); - test('No children, record undo', function() { - const state = { - 'blocks': { - 'blocks': [ + test('Multiple vars grouped', function() { + const state = { + 'variables': [ { - 'type': 'controls_if', + 'name': 'test', 'id': 'testId', - 'x': 42, - 'y': 42 }, + { + 'name': 'test2', + 'id': 'testId2', + } ] - } - }; - Blockly.serialization.load(state, this.workspace, {'recordUndo': true}); - assertEventFired( - this.eventsFireStub, - Blockly.Events.BlockCreate, - {'recordUndo': true}, - this.workspace.id, - 'testId'); - }); + }; + Blockly.serialization.load(state, this.workspace); + const calls = this.eventsFireStub.getCalls(); + const group = calls[0].args[0].group; + chai.assert.isTrue(calls.every(call => call.args[0].group == group)); + }); - test('No children, directly, record undo', function() { - const state = { - 'type': 'controls_if', - 'id': 'testId', - 'x': 42, - 'y': 42 - }; - Blockly.serialization.blocks - .load(state, this.workspace, {'recordUndo': true}); - assertEventFired( - this.eventsFireStub, - Blockly.Events.BlockCreate, - {'recordUndo': true}, - this.workspace.id, - 'testId'); + test('Var with block', function() { + const state = { + 'variables': [ + { + 'name': 'test', + 'id': 'testId', + } + ], + 'blocks': { + 'blocks': [ + { + 'type': 'variables_get', + 'id': 'blockId', + 'x': 42, + 'y': 42, + 'fields': { + 'VAR': 'testId' + } + }, + ] + } + }; + Blockly.serialization.load(state, this.workspace); + const calls = this.eventsFireStub.getCalls(); + const count = calls.reduce((acc, call) => { + if (call.args[0] instanceof Blockly.Events.VarCreate) { + return acc + 1; + } + return acc; + }, 0); + chai.assert.equal(count, 1); + assertEventFired( + this.eventsFireStub, + Blockly.Events.VarCreate, + {'varName': 'test', 'varId': 'testId', 'varType': ''}, + this.workspace.id); + }); }); +<<<<<<< HEAD test('Only fire event for top block', function() { const state = { 'blocks': { 'blocks': [ +======= + suite('Direct call', function() { + test('Just var', function() { + const state = { + 'name': 'test', + 'id': 'testId', + }; + Blockly.serialization.variables.load(state, this.workspace); + assertEventFired( + this.eventsFireStub, + Blockly.Events.VarCreate, +>>>>>>> 44418b3d (Add grouping of events) { - 'type': 'controls_if', - 'id': 'id1', - 'x': 42, - 'y': 42, - 'inputs': { - 'DO0': { + 'varName': 'test', + 'varId': 'testId', + 'varType': '', + 'recordUndo': false + }, + this.workspace.id); + }); + + test('Record undo', function() { + const state = { + 'name': 'test', + 'id': 'testId', + }; + Blockly.serialization.variables + .load(state, this.workspace, {recordUndo: true}); + assertEventFired( + this.eventsFireStub, + Blockly.Events.VarCreate, + { + 'varName': 'test', + 'varId': 'testId', + 'varType': '', + 'recordUndo': true + }, + this.workspace.id); + }); + + test('Grouping', function() { + const state = { + 'name': 'test', + 'id': 'testId', + }; + Blockly.Events.setGroup('my group'); + Blockly.serialization.variables.load(state, this.workspace); + assertEventFired( + this.eventsFireStub, + Blockly.Events.VarCreate, + { + 'varName': 'test', + 'varId': 'testId', + 'varType': '', + 'group': 'my group' + }, + this.workspace.id); + }); + }); + }); + + suite('Block create', function() { + suite('Top-level call', function() { + test('No children', function() { + const state = { + 'blocks': { + 'blocks': [ + { + 'type': 'controls_if', + 'id': 'testId', + 'x': 42, + 'y': 42 + }, + ] + } + }; + Blockly.serialization.load(state, this.workspace); + assertEventFired( + this.eventsFireStub, + Blockly.Events.BlockCreate, + {'recordUndo': false}, + this.workspace.id, + 'testId'); + }); + + test('Record undo', function() { + const state = { + 'blocks': { + 'blocks': [ + { + 'type': 'controls_if', + 'id': 'testId', + 'x': 42, + 'y': 42 + }, + ] + } + }; + Blockly.serialization.load(state, this.workspace, {'recordUndo': true}); + assertEventFired( + this.eventsFireStub, + Blockly.Events.BlockCreate, + {'recordUndo': true}, + this.workspace.id, + 'testId'); + }); + + test('Grouping', function() { + const state = { + 'blocks': { + 'blocks': [ + { + 'type': 'controls_if', + 'id': 'testId', + 'x': 42, + 'y': 42 + }, + ] + } + }; + Blockly.Events.setGroup('my group'); + Blockly.serialization.load(state, this.workspace); + assertEventFired( + this.eventsFireStub, + Blockly.Events.BlockCreate, + {'group': 'my group'}, + this.workspace.id, + 'testId'); + }); + + test('Multiple blocks grouped', function() { + const state = { + 'blocks': { + 'blocks': [ + { + 'type': 'controls_if', + 'id': 'testId', + 'x': 42, + 'y': 42 + }, + { + 'type': 'controls_if', + 'id': 'testId', + 'x': 84, + 'y': 84 + }, + ] + } + }; + Blockly.serialization.load(state, this.workspace); + const calls = this.eventsFireStub.getCalls(); + const group = calls[0].args[0].group; + chai.assert.isTrue(calls.every(call => call.args[0].group == group)); + }); + + test('With children', function() { + const state = { + 'blocks': { + 'blocks': [ + { + 'type': 'controls_if', + 'id': 'id1', + 'x': 42, + 'y': 42, + 'inputs': { + 'DO0': { + 'block': { + 'type': 'controls_if', + 'id': 'id2' + } + } + }, + 'next': { 'block': { 'type': 'controls_if', - 'id': 'id2' + 'id': 'id3' } } }, +<<<<<<< HEAD 'next': { 'block': { 'type': 'controls_if', @@ -270,6 +464,71 @@ suite.only('JSO Deserialization', function() { {}, this.workspace.id, 'id1'); +======= + ] + } + }; + Blockly.serialization.load(state, this.workspace); + assertEventFired( + this.eventsFireStub, + Blockly.Events.BlockCreate, + {}, + this.workspace.id, + 'id1'); + }); + }); + + suite('Direct call', function() { + test('No children', function() { + const state = { + 'type': 'controls_if', + 'id': 'testId', + 'x': 42, + 'y': 42 + }; + Blockly.serialization.blocks.load(state, this.workspace); + assertEventFired( + this.eventsFireStub, + Blockly.Events.BlockCreate, + {'recordUndo': false}, + this.workspace.id, + 'testId'); + }); + + test('Record undo', function() { + const state = { + 'type': 'controls_if', + 'id': 'testId', + 'x': 42, + 'y': 42 + }; + Blockly.serialization.blocks + .load(state, this.workspace, {'recordUndo': true}); + assertEventFired( + this.eventsFireStub, + Blockly.Events.BlockCreate, + {'recordUndo': true}, + this.workspace.id, + 'testId'); + }); + + test('Grouping', function() { + const state = { + 'type': 'controls_if', + 'id': 'testId', + 'x': 42, + 'y': 42 + }; + Blockly.Events.setGroup('my group'); + Blockly.serialization.blocks.load(state, this.workspace); + assertEventFired( + this.eventsFireStub, + Blockly.Events.BlockCreate, + {'group': 'my group'}, + this.workspace.id, + 'testId'); + }); +>>>>>>> 44418b3d (Add grouping of events) }); }); }); From 21fdd3beefbb0fd493ca5e535ceaad078ec80494 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Thu, 22 Jul 2021 18:45:23 +0000 Subject: [PATCH 3/9] Add text width caching --- core/serialization/workspaces.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/serialization/workspaces.js b/core/serialization/workspaces.js index 7e8ac31b42a..cc8b5e0096e 100644 --- a/core/serialization/workspaces.js +++ b/core/serialization/workspaces.js @@ -17,6 +17,7 @@ const Events = goog.require('Blockly.Events'); // eslint-disable-next-line no-unused-vars const Workspace = goog.require('Blockly.Workspace'); const blocks = goog.require('Blockly.serialization.blocks'); +const dom = goog.require('Blockly.utils.dom'); const variables = goog.require('Blockly.serialization.variables'); @@ -74,6 +75,8 @@ const load = function(state, workspace, {recordUndo = false} = {}) { Events.setGroup(true); } + dom.startTextWidthCache(); + if (state['variables']) { const variableStates = state['variables']; for (let i = 0; i < variableStates.length; i++) { @@ -88,6 +91,8 @@ const load = function(state, workspace, {recordUndo = false} = {}) { } } + dom.stopTextWidthCache(); + Events.fire(new (Events.get(Events.FINISHED_LOADING))(workspace)); Events.setGroup(existingGroup); From 2872475d68d964df5432fe17bfbf224917525607 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Thu, 22 Jul 2021 20:49:12 +0000 Subject: [PATCH 4/9] Add disabling workspace resizing --- core/serialization/workspaces.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/serialization/workspaces.js b/core/serialization/workspaces.js index cc8b5e0096e..efcc6d794f1 100644 --- a/core/serialization/workspaces.js +++ b/core/serialization/workspaces.js @@ -76,6 +76,9 @@ const load = function(state, workspace, {recordUndo = false} = {}) { } dom.startTextWidthCache(); + if (workspace.setResizesEnabled) { + workspace.setResizesEnabled(false); + } if (state['variables']) { const variableStates = state['variables']; @@ -91,6 +94,9 @@ const load = function(state, workspace, {recordUndo = false} = {}) { } } + if (workspace.setResizesEnabled) { + workspace.setResizesEnabled(true); + } dom.stopTextWidthCache(); Events.fire(new (Events.get(Events.FINISHED_LOADING))(workspace)); From 1028961c079a89ba729fbb76efd02c39f59f006d Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Thu, 22 Jul 2021 21:45:23 +0000 Subject: [PATCH 5/9] Add performance optimizations --- core/serialization/blocks.js | 17 +++--- tests/mocha/jso_deserialization_test.js | 52 ------------------ tests/playground.html | 71 ++++++++++++++++++++++++- 3 files changed, 76 insertions(+), 64 deletions(-) diff --git a/core/serialization/blocks.js b/core/serialization/blocks.js index 99ac19ae24f..d91e6803e94 100644 --- a/core/serialization/blocks.js +++ b/core/serialization/blocks.js @@ -13,20 +13,15 @@ goog.module('Blockly.serialization.blocks'); goog.module.declareLegacyNamespace(); -<<<<<<< HEAD // eslint-disable-next-line no-unused-vars const Block = goog.requireType('Blockly.Block'); // eslint-disable-next-line no-unused-vars const Connection = goog.requireType('Blockly.Connection'); +const Events = goog.require('Blockly.Events'); // eslint-disable-next-line no-unused-vars const Workspace = goog.requireType('Blockly.Workspace'); const Xml = goog.require('Blockly.Xml'); const inputTypes = goog.require('Blockly.inputTypes'); -======= -goog.require('Blockly.Xml'); -goog.require('Blockly.inputTypes'); -const Events = goog.require('Blockly.Events'); ->>>>>>> bdbbe5bd (Add parameter for recording undo.) // TODO: Remove this once lint is fixed. @@ -294,7 +289,7 @@ const load = function(state, workspace, {recordUndo = false} = {}) { // Adding connections to the connection db is expensive. This defers that // operation to decrease load time. - if (block instanceof Blockly.BlockSvg) { + if (workspace.rendered) { setTimeout(() => { if (!block.disposed) { block.setConnectionTracking(true); @@ -331,7 +326,7 @@ const loadInternal = function(state, workspace, parentConnection = undefined) { loadFields(block, state); loadInputBlocks(block, state); loadNextBlocks(block, state); - initBlock(block); + initBlock(block, workspace.rendered); return block; }; @@ -471,16 +466,16 @@ const loadConnection = function(connection, connectionState) { /** * Initializes the give block, eg init the model, inits the svg, renders, etc. * @param {!Block} block The block to initialize. + * @param {boolean} rendered Whether the block is a rendered or headless block. */ -const initBlock = function(block) { - if (block instanceof Blockly.BlockSvg) { +const initBlock = function(block, rendered) { + if (rendered) { // Adding connections to the connection db is expensive. This defers that // operation to decrease load time. block.setConnectionTracking(false); block.initSvg(); block.render(false); - block.updateDisabled(); } else { block.initModel(); } diff --git a/tests/mocha/jso_deserialization_test.js b/tests/mocha/jso_deserialization_test.js index d07a82d141b..f6067cab6dd 100644 --- a/tests/mocha/jso_deserialization_test.js +++ b/tests/mocha/jso_deserialization_test.js @@ -81,11 +81,7 @@ suite.only('JSO Deserialization', function() { this.workspace.id); }); -<<<<<<< HEAD - test('Only fire one event with var and var on block', function() { -======= test('Automatic group', function() { ->>>>>>> 44418b3d (Add grouping of events) const state = { 'variables': [ { @@ -114,19 +110,11 @@ suite.only('JSO Deserialization', function() { }); }); -<<<<<<< HEAD - suite('Block create', function() { - test('Simple', function() { - const state = { - 'blocks': { - 'blocks': [ -======= suite('Var create', function() { suite('Top-level call', function() { test('Just var', function() { const state = { 'variables': [ ->>>>>>> 44418b3d (Add grouping of events) { 'name': 'test', 'id': 'testId', @@ -154,18 +142,6 @@ suite.only('JSO Deserialization', function() { 'id': 'testId', } ] -<<<<<<< HEAD - } - }; - Blockly.serialization.workspaces.load(state, this.workspace); - assertEventFired( - this.eventsFireStub, - Blockly.Events.BlockCreate, - {'recordUndo': false}, - this.workspace.id, - 'testId'); - }); -======= }; Blockly.serialization.load(state, this.workspace, {recordUndo: true}); assertEventFired( @@ -179,7 +155,6 @@ suite.only('JSO Deserialization', function() { }, this.workspace.id); }); ->>>>>>> 44418b3d (Add grouping of events) test('Grouping', function() { const state = { @@ -262,12 +237,6 @@ suite.only('JSO Deserialization', function() { }); }); -<<<<<<< HEAD - test('Only fire event for top block', function() { - const state = { - 'blocks': { - 'blocks': [ -======= suite('Direct call', function() { test('Just var', function() { const state = { @@ -278,7 +247,6 @@ suite.only('JSO Deserialization', function() { assertEventFired( this.eventsFireStub, Blockly.Events.VarCreate, ->>>>>>> 44418b3d (Add grouping of events) { 'varName': 'test', 'varId': 'testId', @@ -446,25 +414,6 @@ suite.only('JSO Deserialization', function() { } } }, -<<<<<<< HEAD - 'next': { - 'block': { - 'type': 'controls_if', - 'id': 'id3' - } - } - }, - ] - } - }; - Blockly.serialization.workspaces.load(state, this.workspace); - assertEventFired( - this.eventsFireStub, - Blockly.Events.BlockCreate, - {}, - this.workspace.id, - 'id1'); -======= ] } }; @@ -528,7 +477,6 @@ suite.only('JSO Deserialization', function() { this.workspace.id, 'testId'); }); ->>>>>>> 44418b3d (Add grouping of events) }); }); }); diff --git a/tests/playground.html b/tests/playground.html index 457caf9a756..04a4b01ad74 100644 --- a/tests/playground.html +++ b/tests/playground.html @@ -380,6 +380,74 @@ ' ', ' '].join('\n'); + function jsoSpaghetti(n) { + var str = spaghettiJs; + for (var i = 0; i < n; i++) { + str = str.replace(/{}/g, `{"block":${spaghettiJs}}`); + } + var obj = { + 'blocks': { + 'blocks': [ + JSON.parse(str) + ] + } + }; + console.time('Spaghetti serialization'); + Blockly.serialization.load(obj, workspace); + console.timeEnd('Spaghetti serialization'); + } + var spaghettiJs = JSON.stringify({ + 'type': 'controls_if', + 'inputs': { + 'IF0': { + 'block': { + 'type': 'logic_compare', + 'fields': { + 'OP': 'EQ', + }, + 'inputs': { + 'A': { + 'block': { + 'type': 'math_arithmetic', + 'fields': { + 'OP': 'MULTIPLY', + }, + 'inputs': { + 'A': { + 'block': { + 'type': 'math_number', + 'fields': { + 'NUM': 6 + } + } + }, + 'B': { + 'block': { + 'type': 'math_number', + 'fields': { + 'NUM': 7 + } + } + } + } + } + }, + 'B': { + 'block': { + 'type': 'math_number', + 'fields': { + 'NUM': 42 + } + } + } + } + } + }, + 'DO0': { } + }, + 'next': { } + }); +