Updated variables and var_fire event listener.

This commit is contained in:
marisaleung 2017-06-15 14:29:15 -07:00
parent d9704b23c3
commit 875ccf5b88
10 changed files with 216 additions and 28 deletions

View file

@ -60,6 +60,8 @@ const domToBlock = function (blockDOM, blocks, isTopBlock, parent) {
{
// Add the field to this block.
const fieldName = xmlChild.attribs.name;
// Add id in case it is a variable field
const fieldId = xmlChild.attribs.id;
let fieldData = '';
if (xmlChild.children.length > 0 && xmlChild.children[0].data) {
fieldData = xmlChild.children[0].data;
@ -70,6 +72,7 @@ const domToBlock = function (blockDOM, blocks, isTopBlock, parent) {
}
block.fields[fieldName] = {
name: fieldName,
id: fieldId,
value: fieldData
};
break;

View file

@ -181,16 +181,19 @@ class Blocks {
// ---------------------------------------------------------------------
/**
* Create event listener for blocks. Handles validation and serves as a generic
* adapter between the blocks and the runtime interface.
* @param {Object} e Blockly "block" event
* Create event listener for blocks and variables. Handles validation and
* serves as a generic adapter between the blocks, variables, and the
* runtime interface.
* @param {object} e Blockly "block" or "variable" event
* @param {?Runtime} optRuntime Optional runtime to forward click events to.
*/
blocklyListen (e, optRuntime) {
// Validate event
if (typeof e !== 'object') return;
if (typeof e.blockId !== 'string') return;
if (typeof e.blockId !== 'string' && typeof e.varId !== 'string') {
return;
}
const stage = optRuntime.getTargetForStage();
// UI event: clicked scripts toggle in the runtime.
if (e.element === 'stackclick') {
@ -243,6 +246,15 @@ class Blocks {
id: e.blockId
});
break;
case 'var_create':
stage.createVariable(e.varId, e.varName);
break;
case 'var_rename':
stage.renameVariable(e.varId, e.newName);
break;
case 'var_delete':
stage.deleteVariable(e.varId);
break;
}
}

View file

@ -137,7 +137,11 @@ const execute = function (sequencer, thread) {
// Add all fields on this block to the argValues.
for (const fieldName in fields) {
if (!fields.hasOwnProperty(fieldName)) continue;
argValues[fieldName] = fields[fieldName].value;
if (fieldName === 'VARIABLE') {
argValues[fieldName] = fields[fieldName].id;
} else {
argValues[fieldName] = fields[fieldName].value;
}
}
// Recursively evaluate input blocks.

View file

@ -72,24 +72,25 @@ class Target extends EventEmitter {
/**
* Look up a variable object, and create it if one doesn't exist.
* Search begins for local variables; then look for globals.
* @param {!string} name Name of the variable.
* @param {string} id Id of the variable.
* @param {string} name Name of the variable.
* @return {!Variable} Variable object.
*/
lookupOrCreateVariable (name) {
lookupOrCreateVariable (id, name) {
// If we have a local copy, return it.
if (this.variables.hasOwnProperty(name)) {
return this.variables[name];
if (this.variables.hasOwnProperty(id)) {
return this.variables[id];
}
// If the stage has a global copy, return it.
if (this.runtime && !this.isStage) {
const stage = this.runtime.getTargetForStage();
if (stage.variables.hasOwnProperty(name)) {
return stage.variables[name];
if (stage.variables.hasOwnProperty(id)) {
return stage.variables[id];
}
}
// No variable with this name exists - create it locally.
const newVariable = new Variable(name, 0, false);
this.variables[name] = newVariable;
const newVariable = new Variable(id, name, 0, false);
this.variables[id] = newVariable;
return newVariable;
}
@ -117,6 +118,44 @@ class Target extends EventEmitter {
return newList;
}
/**
* Creates a variable with the given id and name and adds it to the
* dictionary of variables.
* @param {string} id Id of variable
* @param {string} name Name of variable.
*/
createVariable (id, name) {
if (!this.variables.hasOwnProperty(id)) {
const newVariable = new Variable(id, name, 0,
false);
this.variables[id] = newVariable;
}
}
/**
* Renames the variable with the given id to newName.
* @param {string} id Id of renamed variable.
* @param {string} newName New name for the variable.
*/
renameVariable (id, newName) {
if (this.variables.hasOwnProperty(id)) {
const variable = this.variables[id];
if (variable.id === id) {
variable.name = newName;
}
}
}
/**
* Removes the variable with the given id from the dictionary of variables.
* @param {string} id Id of renamed variable.
*/
deleteVariable (id) {
if (this.variables.hasOwnProperty(id)) {
delete this.variables[id];
}
}
/**
* Post/edit sprite info.
* @param {object} data An object with sprite info data to set.

View file

@ -3,21 +3,25 @@
* Object representing a Scratch variable.
*/
const uid = require('../util/uid');
class Variable {
/**
* @param {!string} name Name of the variable.
* @param {string} id Id of the variable.
* @param {string} name Name of the variable.
* @param {(string|number)} value Value of the variable.
* @param {boolean} isCloud Whether the variable is stored in the cloud.
* @constructor
*/
constructor (name, value, isCloud) {
constructor (id, name, value, isCloud) {
this.id = id || uid();
this.name = name;
this.value = value;
this.isCloud = isCloud;
}
toXML () {
return `<variable type="">${this.name}</variable>`;
return `<variable type="" id="${this.id}">${this.name}</variable>`;
}
}

View file

@ -90,6 +90,7 @@ window.onload = function () {
// Attach scratch-blocks events to VM.
workspace.addChangeListener(vm.blockListener);
workspace.addChangeListener(vm.variableListener);
const flyoutWorkspace = workspace.getFlyout().getWorkspace();
flyoutWorkspace.addChangeListener(vm.flyoutBlockListener);
flyoutWorkspace.addChangeListener(vm.monitorBlockListener);

View file

@ -191,11 +191,13 @@ const parseScratchObject = function (object, runtime, topLevel) {
if (object.hasOwnProperty('variables')) {
for (let j = 0; j < object.variables.length; j++) {
const variable = object.variables[j];
target.variables[variable.name] = new Variable(
const newVariable = new Variable(
null,
variable.name,
variable.value,
variable.isPersistent
);
target.variables[newVariable.id] = newVariable;
}
}
if (object.hasOwnProperty('lists')) {

View file

@ -98,11 +98,13 @@ const parseScratchObject = function (object, runtime) {
if (object.hasOwnProperty('variables')) {
for (let j = 0; j < object.variables.length; j++) {
const variable = object.variables[j];
target.variables[variable.name] = new Variable(
const newVariable = new Variable(
variable.id,
variable.name,
variable.value,
variable.isPersistent
);
target.variables[newVariable.id] = newVariable;
}
}
if (object.hasOwnProperty('lists')) {

View file

@ -62,6 +62,7 @@ class VirtualMachine extends EventEmitter {
this.blockListener = this.blockListener.bind(this);
this.flyoutBlockListener = this.flyoutBlockListener.bind(this);
this.monitorBlockListener = this.monitorBlockListener.bind(this);
this.variableListener = this.variableListener.bind(this);
}
/**
@ -446,6 +447,19 @@ class VirtualMachine extends EventEmitter {
}
}
/**
* Handle a Blockly event for the variable map.
* @param {!Blockly.Event} e Any Blockly event.
*/
variableListener (e) {
// Filter events by type, since blocks only needs to listen to these
// var events.
if (['var_create', 'var_rename', 'var_delete'].indexOf(e.type) !== -1) {
this.runtime.getTargetForStage().blocks.blocklyListen(e,
this.runtime);
}
}
/**
* Set an editing target. An editor UI can use this function to switch
* between editing different targets, sprites, etc.
@ -550,15 +564,6 @@ class VirtualMachine extends EventEmitter {
postSpriteInfo (data) {
this.editingTarget.postSpriteInfo(data);
}
/**
* Create a variable by name.
* @todo this only creates global variables by putting them on the stage
* @param {string} name The name of the variable
*/
createVariable (name) {
this.runtime.getTargetForStage().lookupOrCreateVariable(name);
}
}
module.exports = VirtualMachine;

116
test/unit/engine_target.js Normal file
View file

@ -0,0 +1,116 @@
const test = require('tap').test;
const Target = require('../../src/engine/target');
test('spec', t => {
const target = new Target();
t.type(Target, 'function');
t.type(target, 'object');
t.ok(target instanceof Target);
t.type(target.id, 'string');
t.type(target.blocks, 'object');
t.type(target.variables, 'object');
t.type(target.lists, 'object');
t.type(target._customState, 'object');
t.type(target.createVariable, 'function');
t.type(target.renameVariable, 'function');
t.end();
});
// Create Variable tests.
test('createVariable', t => {
const target = new Target();
target.createVariable('foo', 'bar');
const variables = target.variables;
t.equal(Object.keys(variables).length, 1);
const variable = variables[Object.keys(variables)[0]];
t.equal(variable.id, 'foo');
t.equal(variable.name, 'bar');
t.equal(variable.value, 0);
t.equal(variable.isCloud, false);
t.end();
});
// Create Same Variable twice.
test('createVariable2', t => {
const target = new Target();
target.createVariable('foo', 'bar');
target.createVariable('foo', 'bar');
const variables = target.variables;
t.equal(Object.keys(variables).length, 1);
t.end();
});
// Rename Variable tests.
test('renameVariable', t => {
const target = new Target();
target.createVariable('foo', 'bar');
target.renameVariable('foo', 'bar2');
const variables = target.variables;
t.equal(Object.keys(variables).length, 1);
const variable = variables[Object.keys(variables)[0]];
t.equal(variable.id, 'foo');
t.equal(variable.name, 'bar2');
t.equal(variable.value, 0);
t.equal(variable.isCloud, false);
t.end();
});
// Rename Variable that doesn't exist.
test('renameVariable2', t => {
const target = new Target();
target.renameVariable('foo', 'bar2');
const variables = target.variables;
t.equal(Object.keys(variables).length, 0);
t.end();
});
// Rename Variable that with id that exists as another variable's name.
// Expect no change.
test('renameVariable3', t => {
const target = new Target();
target.createVariable('foo1', 'foo');
target.renameVariable('foo', 'bar2');
const variables = target.variables;
t.equal(Object.keys(variables).length, 1);
const variable = variables[Object.keys(variables)[0]];
t.equal(variable.id, 'foo1');
t.equal(variable.name, 'foo');
t.end();
});
// Delete Variable tests.
test('deleteVariable', t => {
const target = new Target();
target.createVariable('foo', 'bar');
target.deleteVariable('foo');
const variables = target.variables;
t.equal(Object.keys(variables).length, 0);
t.end();
});
// Delete Variable that doesn't exist.
test('deleteVariable2', t => {
const target = new Target();
target.deleteVariable('foo');
const variables = target.variables;
t.equal(Object.keys(variables).length, 0);
t.end();
});