mirror of
https://github.com/google/blockly.git
synced 2026-01-07 00:50:27 +01:00
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:
committed by
alschmiedt
parent
38cddd6ac7
commit
9138bca93c
@@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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'});
|
||||
|
||||
@@ -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');
|
||||
|
||||
162
tests/mocha/jso_deserialization_test.js
Normal file
162
tests/mocha/jso_deserialization_test.js
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
|
||||
@@ -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()">
|
||||
|
||||
<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')">
|
||||
|
||||
<input type="button" value="To Python" onclick="toCode('Python')">
|
||||
|
||||
<input type="button" value="To PHP" onclick="toCode('PHP')">
|
||||
|
||||
<input type="button" value="To Lua" onclick="toCode('Lua')">
|
||||
|
||||
<input type="button" value="To Dart" onclick="toCode('Dart')">
|
||||
<br>
|
||||
<textarea id="importExport" style="width: 26%; height: 12em"
|
||||
|
||||
Reference in New Issue
Block a user