feat: add initialization of blocks and event firing (#5166)

* Change playground to use JSO system

* Add tests for initialization and events

* Add initialization of blocks

* PR Comments
This commit is contained in:
Beka Westberg
2021-08-10 20:14:37 +00:00
committed by alschmiedt
parent 38cddd6ac7
commit 9138bca93c
6 changed files with 252 additions and 21 deletions

View File

@@ -262,10 +262,31 @@ 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.
* @return {!Block} The block that was just loaded.
*/
const load = function(state, workspace) {
loadInternal(state, workspace);
// We only want to fire an event for the top block.
Blockly.Events.disable();
const block = loadInternal(state, workspace);
Blockly.Events.enable();
Blockly.Events.fire(
new (Blockly.Events.get(Blockly.Events.BLOCK_CREATE))(block));
// Adding connections to the connection db is expensive. This defers that
// operation to decrease load time.
if (block instanceof Blockly.BlockSvg) {
setTimeout(() => {
if (!block.disposed) {
block.setConnectionTracking(true);
}
}, 1);
}
return block;
};
exports.load = load;
/**
* Loads the block represented by the given state into the given workspace.
@@ -273,8 +294,9 @@ const load = function(state, workspace) {
* clutter our external API.
* @param {!State} state The state of a block to deserialize into the workspace.
* @param {!Workspace} workspace The workspace to add the block to.
* @param {!Connection} parentConnection The optional parent connection to
* @param {!Connection=} parentConnection The optional parent connection to
* attach the block to.
* @return {!Block} The block that was just loaded.
*/
const loadInternal = function(state, workspace, parentConnection = undefined) {
const block = workspace.newBlock(state['type'], state['id']);
@@ -291,6 +313,8 @@ const loadInternal = function(state, workspace, parentConnection = undefined) {
loadFields(block, state);
loadInputBlocks(block, state);
loadNextBlocks(block, state);
initBlock(block);
return block;
};
/**
@@ -424,4 +448,22 @@ const loadConnection = function(connection, connectionState) {
connection);
}
};
exports.load = load;
// TODO(#5146): Remove this from the serialization system.
/**
* Initializes the give block, eg init the model, inits the svg, renders, etc.
* @param {!Block} block The block to initialize.
*/
const initBlock = function(block) {
if (block instanceof Blockly.BlockSvg) {
// 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();
}
};

View File

@@ -331,6 +331,7 @@ goog.addDependency('../../tests/mocha/generator_test.js', ['Blockly.test.generat
goog.addDependency('../../tests/mocha/gesture_test.js', ['Blockly.test.gesture'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../tests/mocha/input_test.js', ['Blockly.test.input'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../tests/mocha/insertion_marker_test.js', ['Blockly.test.insertionMarker'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../tests/mocha/jso_deserialization_test.js', ['Blockly.test.jsoDeserialization'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../tests/mocha/jso_serialization_test.js', ['Blockly.test.jsoSerialization'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../tests/mocha/json_test.js', ['Blockly.test.json'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../tests/mocha/keydown_test.js', ['Blockly.test.keydown'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'});

View File

@@ -82,6 +82,7 @@
goog.require('Blockly.test.gesture');
goog.require('Blockly.test.input');
goog.require('Blockly.test.insertionMarker');
goog.require('Blockly.test.jsoDeserialization');
goog.require('Blockly.test.jsoSerialization');
goog.require('Blockly.test.json');
goog.require('Blockly.test.keydown');

View File

@@ -0,0 +1,162 @@
/**
* @license
* Copyright 2021 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
goog.module('Blockly.test.jsoDeserialization');
const {assertEventFired, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers');
suite('JSO Deserialization', function() {
setup(function() {
sharedTestSetup.call(this);
this.workspace = new Blockly.Workspace();
});
teardown(function() {
workspaceTeardown.call(this, this.workspace);
sharedTestTeardown.call(this);
});
suite('Events', function() {
test.skip('Finished loading', function() {
const state = {
'blocks': {
'blocks': [
{
'type': 'controls_if',
'id': 'testId',
'x': 42,
'y': 42
},
]
}
};
Blockly.serialization.workspaces.load(state, this.workspace);
assertEventFired(
this.eventsFireStub,
Blockly.Events.FinishedLoading,
{},
this.workspace.id);
});
suite('Var create', function() {
test('Just var', function() {
const state = {
'variables': [
{
'name': 'test',
'id': 'testId',
}
]
};
Blockly.serialization.workspaces.load(state, this.workspace);
assertEventFired(
this.eventsFireStub,
Blockly.Events.VarCreate,
{'varName': 'test', 'varId': 'testId', 'varType': ''},
this.workspace.id);
});
test('Only fire one event with var and var on 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.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);
});
});
suite('Block create', function() {
test('Simple', function() {
const state = {
'blocks': {
'blocks': [
{
'type': 'controls_if',
'id': 'testId',
'x': 42,
'y': 42
},
]
}
};
Blockly.serialization.workspaces.load(state, this.workspace);
assertEventFired(
this.eventsFireStub,
Blockly.Events.BlockCreate,
{},
this.workspace.id,
'testId');
});
test('Only fire event for top block', 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': 'id3'
}
}
},
]
}
};
Blockly.serialization.workspaces.load(state, this.workspace);
assertEventFired(
this.eventsFireStub,
Blockly.Events.BlockCreate,
{},
this.workspace.id,
'id1');
});
});
});
});

View File

@@ -9,7 +9,7 @@ goog.module('Blockly.test.jsoSerialization');
const {defineStackBlock, defineRowBlock, defineStatementBlock, createGenUidStubWithReturns, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers');
suite('JSO', function() {
suite('JSO Serialization', function() {
setup(function() {
sharedTestSetup.call(this);
this.workspace = new Blockly.Workspace();

View File

@@ -145,7 +145,7 @@ function start() {
}
taChange();
if (autoimport) {
fromXml();
load();
}
}
@@ -203,7 +203,7 @@ function initToolbox(workspace) {
}
}
function toXml() {
function saveXml() {
var output = document.getElementById('importExport');
var xml = Blockly.Xml.workspaceToDom(workspace);
output.value = Blockly.Xml.domToPrettyText(xml);
@@ -212,13 +212,28 @@ function toXml() {
taChange();
}
function fromXml() {
function saveJson() {
var output = document.getElementById('importExport');
var state = Blockly.serialization.workspaces.save(workspace);
output.value = JSON.stringify(state, null, 2);
output.focus();
output.select();
taChange();
}
function load() {
var input = document.getElementById('importExport');
if (!input.value) {
return;
}
var xml = Blockly.Xml.textToDom(input.value);
Blockly.Xml.domToWorkspace(xml, workspace);
var valid = saveIsValid(input.value);
if (valid.json) {
var state = JSON.parse(input.value);
Blockly.serialization.workspaces.load(state, workspace);
} else if (valid.xml) {
var xml = Blockly.Xml.textToDom(input.value);
Blockly.Xml.domToWorkspace(xml, workspace);
}
taChange();
}
@@ -228,20 +243,34 @@ function toCode(lang) {
taChange();
}
// Disable the "Import from XML" button if the XML is invalid.
// Disable the "Load" button if the save state is invalid.
// Preserve text between page reloads.
function taChange() {
var textarea = document.getElementById('importExport');
if (sessionStorage) {
sessionStorage.setItem('textarea', textarea.value);
}
var valid = true;
var valid = saveIsValid(textarea.value);
document.getElementById('import').disabled = !valid.json && !valid.xml;
}
function saveIsValid(save) {
var validJson = true;
try {
Blockly.Xml.textToDom(textarea.value);
JSON.parse(save);
} catch (e) {
valid = false;
validJson = false;
}
var validXml = true
try {
Blockly.Xml.textToDom(save);
} catch (e) {
validXml = false;
}
return {
json: validJson,
xml: validXml
}
document.getElementById('import').disabled = !valid;
}
function logEvents(state) {
@@ -422,18 +451,14 @@ var spaghettiXml = [
</select>
</form>
<p>
<input type="button" value="Export to XML" onclick="toXml()">
&nbsp;
<input type="button" value="Import from XML" onclick="fromXml()" id="import">
<input type="button" value="Save JSON" onclick="saveJson()">
<input type="button" value="Save XML" onclick="saveXml()">
<input type="button" value="Load" onclick="load()" id="import">
<br>
<input type="button" value="To JavaScript" onclick="toCode('JavaScript')">
&nbsp;
<input type="button" value="To Python" onclick="toCode('Python')">
&nbsp;
<input type="button" value="To PHP" onclick="toCode('PHP')">
&nbsp;
<input type="button" value="To Lua" onclick="toCode('Lua')">
&nbsp;
<input type="button" value="To Dart" onclick="toCode('Dart')">
<br>
<textarea id="importExport" style="width: 26%; height: 12em"