mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-23 14:32:59 -05:00
Updated variables and var_fire event listener.
This commit is contained in:
parent
d9704b23c3
commit
875ccf5b88
10 changed files with 216 additions and 28 deletions
|
@ -60,6 +60,8 @@ const domToBlock = function (blockDOM, blocks, isTopBlock, parent) {
|
||||||
{
|
{
|
||||||
// Add the field to this block.
|
// Add the field to this block.
|
||||||
const fieldName = xmlChild.attribs.name;
|
const fieldName = xmlChild.attribs.name;
|
||||||
|
// Add id in case it is a variable field
|
||||||
|
const fieldId = xmlChild.attribs.id;
|
||||||
let fieldData = '';
|
let fieldData = '';
|
||||||
if (xmlChild.children.length > 0 && xmlChild.children[0].data) {
|
if (xmlChild.children.length > 0 && xmlChild.children[0].data) {
|
||||||
fieldData = xmlChild.children[0].data;
|
fieldData = xmlChild.children[0].data;
|
||||||
|
@ -70,6 +72,7 @@ const domToBlock = function (blockDOM, blocks, isTopBlock, parent) {
|
||||||
}
|
}
|
||||||
block.fields[fieldName] = {
|
block.fields[fieldName] = {
|
||||||
name: fieldName,
|
name: fieldName,
|
||||||
|
id: fieldId,
|
||||||
value: fieldData
|
value: fieldData
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -181,16 +181,19 @@ class Blocks {
|
||||||
// ---------------------------------------------------------------------
|
// ---------------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create event listener for blocks. Handles validation and serves as a generic
|
* Create event listener for blocks and variables. Handles validation and
|
||||||
* adapter between the blocks and the runtime interface.
|
* serves as a generic adapter between the blocks, variables, and the
|
||||||
* @param {Object} e Blockly "block" event
|
* runtime interface.
|
||||||
|
* @param {object} e Blockly "block" or "variable" event
|
||||||
* @param {?Runtime} optRuntime Optional runtime to forward click events to.
|
* @param {?Runtime} optRuntime Optional runtime to forward click events to.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
blocklyListen (e, optRuntime) {
|
blocklyListen (e, optRuntime) {
|
||||||
// Validate event
|
// Validate event
|
||||||
if (typeof e !== 'object') return;
|
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.
|
// UI event: clicked scripts toggle in the runtime.
|
||||||
if (e.element === 'stackclick') {
|
if (e.element === 'stackclick') {
|
||||||
|
@ -243,6 +246,15 @@ class Blocks {
|
||||||
id: e.blockId
|
id: e.blockId
|
||||||
});
|
});
|
||||||
break;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -137,8 +137,12 @@ const execute = function (sequencer, thread) {
|
||||||
// Add all fields on this block to the argValues.
|
// Add all fields on this block to the argValues.
|
||||||
for (const fieldName in fields) {
|
for (const fieldName in fields) {
|
||||||
if (!fields.hasOwnProperty(fieldName)) continue;
|
if (!fields.hasOwnProperty(fieldName)) continue;
|
||||||
|
if (fieldName === 'VARIABLE') {
|
||||||
|
argValues[fieldName] = fields[fieldName].id;
|
||||||
|
} else {
|
||||||
argValues[fieldName] = fields[fieldName].value;
|
argValues[fieldName] = fields[fieldName].value;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Recursively evaluate input blocks.
|
// Recursively evaluate input blocks.
|
||||||
for (const inputName in inputs) {
|
for (const inputName in inputs) {
|
||||||
|
|
|
@ -72,24 +72,25 @@ class Target extends EventEmitter {
|
||||||
/**
|
/**
|
||||||
* Look up a variable object, and create it if one doesn't exist.
|
* Look up a variable object, and create it if one doesn't exist.
|
||||||
* Search begins for local variables; then look for globals.
|
* 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.
|
* @return {!Variable} Variable object.
|
||||||
*/
|
*/
|
||||||
lookupOrCreateVariable (name) {
|
lookupOrCreateVariable (id, name) {
|
||||||
// If we have a local copy, return it.
|
// If we have a local copy, return it.
|
||||||
if (this.variables.hasOwnProperty(name)) {
|
if (this.variables.hasOwnProperty(id)) {
|
||||||
return this.variables[name];
|
return this.variables[id];
|
||||||
}
|
}
|
||||||
// If the stage has a global copy, return it.
|
// If the stage has a global copy, return it.
|
||||||
if (this.runtime && !this.isStage) {
|
if (this.runtime && !this.isStage) {
|
||||||
const stage = this.runtime.getTargetForStage();
|
const stage = this.runtime.getTargetForStage();
|
||||||
if (stage.variables.hasOwnProperty(name)) {
|
if (stage.variables.hasOwnProperty(id)) {
|
||||||
return stage.variables[name];
|
return stage.variables[id];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// No variable with this name exists - create it locally.
|
// No variable with this name exists - create it locally.
|
||||||
const newVariable = new Variable(name, 0, false);
|
const newVariable = new Variable(id, name, 0, false);
|
||||||
this.variables[name] = newVariable;
|
this.variables[id] = newVariable;
|
||||||
return newVariable;
|
return newVariable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,6 +118,44 @@ class Target extends EventEmitter {
|
||||||
return newList;
|
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.
|
* Post/edit sprite info.
|
||||||
* @param {object} data An object with sprite info data to set.
|
* @param {object} data An object with sprite info data to set.
|
||||||
|
|
|
@ -3,21 +3,25 @@
|
||||||
* Object representing a Scratch variable.
|
* Object representing a Scratch variable.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const uid = require('../util/uid');
|
||||||
|
|
||||||
class Variable {
|
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 {(string|number)} value Value of the variable.
|
||||||
* @param {boolean} isCloud Whether the variable is stored in the cloud.
|
* @param {boolean} isCloud Whether the variable is stored in the cloud.
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
constructor (name, value, isCloud) {
|
constructor (id, name, value, isCloud) {
|
||||||
|
this.id = id || uid();
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
this.isCloud = isCloud;
|
this.isCloud = isCloud;
|
||||||
}
|
}
|
||||||
|
|
||||||
toXML () {
|
toXML () {
|
||||||
return `<variable type="">${this.name}</variable>`;
|
return `<variable type="" id="${this.id}">${this.name}</variable>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -90,6 +90,7 @@ window.onload = function () {
|
||||||
|
|
||||||
// Attach scratch-blocks events to VM.
|
// Attach scratch-blocks events to VM.
|
||||||
workspace.addChangeListener(vm.blockListener);
|
workspace.addChangeListener(vm.blockListener);
|
||||||
|
workspace.addChangeListener(vm.variableListener);
|
||||||
const flyoutWorkspace = workspace.getFlyout().getWorkspace();
|
const flyoutWorkspace = workspace.getFlyout().getWorkspace();
|
||||||
flyoutWorkspace.addChangeListener(vm.flyoutBlockListener);
|
flyoutWorkspace.addChangeListener(vm.flyoutBlockListener);
|
||||||
flyoutWorkspace.addChangeListener(vm.monitorBlockListener);
|
flyoutWorkspace.addChangeListener(vm.monitorBlockListener);
|
||||||
|
|
|
@ -191,11 +191,13 @@ const parseScratchObject = function (object, runtime, topLevel) {
|
||||||
if (object.hasOwnProperty('variables')) {
|
if (object.hasOwnProperty('variables')) {
|
||||||
for (let j = 0; j < object.variables.length; j++) {
|
for (let j = 0; j < object.variables.length; j++) {
|
||||||
const variable = object.variables[j];
|
const variable = object.variables[j];
|
||||||
target.variables[variable.name] = new Variable(
|
const newVariable = new Variable(
|
||||||
|
null,
|
||||||
variable.name,
|
variable.name,
|
||||||
variable.value,
|
variable.value,
|
||||||
variable.isPersistent
|
variable.isPersistent
|
||||||
);
|
);
|
||||||
|
target.variables[newVariable.id] = newVariable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (object.hasOwnProperty('lists')) {
|
if (object.hasOwnProperty('lists')) {
|
||||||
|
|
|
@ -98,11 +98,13 @@ const parseScratchObject = function (object, runtime) {
|
||||||
if (object.hasOwnProperty('variables')) {
|
if (object.hasOwnProperty('variables')) {
|
||||||
for (let j = 0; j < object.variables.length; j++) {
|
for (let j = 0; j < object.variables.length; j++) {
|
||||||
const variable = object.variables[j];
|
const variable = object.variables[j];
|
||||||
target.variables[variable.name] = new Variable(
|
const newVariable = new Variable(
|
||||||
|
variable.id,
|
||||||
variable.name,
|
variable.name,
|
||||||
variable.value,
|
variable.value,
|
||||||
variable.isPersistent
|
variable.isPersistent
|
||||||
);
|
);
|
||||||
|
target.variables[newVariable.id] = newVariable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (object.hasOwnProperty('lists')) {
|
if (object.hasOwnProperty('lists')) {
|
||||||
|
|
|
@ -62,6 +62,7 @@ class VirtualMachine extends EventEmitter {
|
||||||
this.blockListener = this.blockListener.bind(this);
|
this.blockListener = this.blockListener.bind(this);
|
||||||
this.flyoutBlockListener = this.flyoutBlockListener.bind(this);
|
this.flyoutBlockListener = this.flyoutBlockListener.bind(this);
|
||||||
this.monitorBlockListener = this.monitorBlockListener.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
|
* Set an editing target. An editor UI can use this function to switch
|
||||||
* between editing different targets, sprites, etc.
|
* between editing different targets, sprites, etc.
|
||||||
|
@ -550,15 +564,6 @@ class VirtualMachine extends EventEmitter {
|
||||||
postSpriteInfo (data) {
|
postSpriteInfo (data) {
|
||||||
this.editingTarget.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;
|
module.exports = VirtualMachine;
|
||||||
|
|
116
test/unit/engine_target.js
Normal file
116
test/unit/engine_target.js
Normal 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();
|
||||||
|
});
|
Loading…
Reference in a new issue