Skip to content

Commit

Permalink
feat: add initialization of blocks and event firing (#5166)
Browse files Browse the repository at this point in the history
* Change playground to use JSO system

* Add tests for initialization and events

* Add initialization of blocks

* PR Comments
  • Loading branch information
BeksOmega committed Sep 17, 2021
1 parent 28378fd commit 09ada55
Show file tree
Hide file tree
Showing 6 changed files with 252 additions and 21 deletions.
48 changes: 45 additions & 3 deletions core/serialization/blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,19 +262,41 @@ 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.
* This is defined internally so that the extra optional parameter doesn't
* 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']);
Expand All @@ -291,6 +313,8 @@ const loadInternal = function(state, workspace, parentConnection = undefined) {
loadFields(block, state);
loadInputBlocks(block, state);
loadNextBlocks(block, state);
initBlock(block);
return block;
};

/**
Expand Down Expand Up @@ -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();
}
};
1 change: 1 addition & 0 deletions tests/deps.mocha.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions tests/mocha/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
162 changes: 162 additions & 0 deletions tests/mocha/jso_deserialization_test.js
Original file line number Diff line number Diff line change
@@ -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');
});
});
});
});
2 changes: 1 addition & 1 deletion tests/mocha/jso_serialization_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
59 changes: 42 additions & 17 deletions tests/playground.html
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@
}
taChange();
if (autoimport) {
fromXml();
load();
}
}

Expand Down Expand Up @@ -203,7 +203,7 @@
}
}

function toXml() {
function saveXml() {
var output = document.getElementById('importExport');
var xml = Blockly.Xml.workspaceToDom(workspace);
output.value = Blockly.Xml.domToPrettyText(xml);
Expand All @@ -212,13 +212,28 @@
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();
}

Expand All @@ -228,20 +243,34 @@
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 {
JSON.parse(save);
} catch (e) {
validJson = false;
}
var validXml = true
try {
Blockly.Xml.textToDom(textarea.value);
Blockly.Xml.textToDom(save);
} catch (e) {
valid = false;
validXml = false;
}
return {
json: validJson,
xml: validXml
}
document.getElementById('import').disabled = !valid;
}

function logEvents(state) {
Expand Down Expand Up @@ -422,18 +451,14 @@ <h1>Blockly Playground</h1>
</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"
Expand Down

0 comments on commit 09ada55

Please sign in to comment.