mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-26 07:52:50 -05:00
248 lines
8.6 KiB
JavaScript
248 lines
8.6 KiB
JavaScript
const Cast = require('../util/cast');
|
|
|
|
class Scratch3DataBlocks {
|
|
constructor (runtime) {
|
|
/**
|
|
* The runtime instantiating this block package.
|
|
* @type {Runtime}
|
|
*/
|
|
this.runtime = runtime;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the block primitives implemented by this package.
|
|
* @return {object.<string, Function>} Mapping of opcode to Function.
|
|
*/
|
|
getPrimitives () {
|
|
return {
|
|
data_variable: this.getVariable,
|
|
data_setvariableto: this.setVariableTo,
|
|
data_changevariableby: this.changeVariableBy,
|
|
data_hidevariable: this.hideVariable,
|
|
data_showvariable: this.showVariable,
|
|
data_listcontents: this.getListContents,
|
|
data_addtolist: this.addToList,
|
|
data_deleteoflist: this.deleteOfList,
|
|
data_deletealloflist: this.deleteAllOfList,
|
|
data_insertatlist: this.insertAtList,
|
|
data_replaceitemoflist: this.replaceItemOfList,
|
|
data_itemoflist: this.getItemOfList,
|
|
data_itemnumoflist: this.getItemNumOfList,
|
|
data_lengthoflist: this.lengthOfList,
|
|
data_listcontainsitem: this.listContainsItem,
|
|
data_hidelist: this.hideList,
|
|
data_showlist: this.showList
|
|
};
|
|
}
|
|
|
|
getVariable (args, util) {
|
|
const variable = util.target.lookupOrCreateVariable(
|
|
args.VARIABLE.id, args.VARIABLE.name);
|
|
return variable.value;
|
|
}
|
|
|
|
setVariableTo (args, util) {
|
|
const variable = util.target.lookupOrCreateVariable(
|
|
args.VARIABLE.id, args.VARIABLE.name);
|
|
variable.value = args.VALUE;
|
|
}
|
|
|
|
changeVariableBy (args, util) {
|
|
const variable = util.target.lookupOrCreateVariable(
|
|
args.VARIABLE.id, args.VARIABLE.name);
|
|
const castedValue = Cast.toNumber(variable.value);
|
|
const dValue = Cast.toNumber(args.VALUE);
|
|
variable.value = castedValue + dValue;
|
|
}
|
|
|
|
changeMonitorVisibility (id, visible) {
|
|
// Send the monitor blocks an event like the flyout checkbox event.
|
|
// This both updates the monitor state and changes the isMonitored block flag.
|
|
this.runtime.monitorBlocks.changeBlock({
|
|
id: id, // Monitor blocks for variables are the variable ID.
|
|
element: 'checkbox', // Mimic checkbox event from flyout.
|
|
value: visible
|
|
}, this.runtime);
|
|
}
|
|
|
|
showVariable (args) {
|
|
this.changeMonitorVisibility(args.VARIABLE.id, true);
|
|
}
|
|
|
|
hideVariable (args) {
|
|
this.changeMonitorVisibility(args.VARIABLE.id, false);
|
|
}
|
|
|
|
showList (args) {
|
|
this.changeMonitorVisibility(args.LIST.id, true);
|
|
}
|
|
|
|
hideList (args) {
|
|
this.changeMonitorVisibility(args.LIST.id, false);
|
|
}
|
|
|
|
getListContents (args, util) {
|
|
const list = util.target.lookupOrCreateList(
|
|
args.LIST.id, args.LIST.name);
|
|
|
|
// If block is running for monitors, return copy of list as an array if changed.
|
|
if (util.thread.updateMonitor) {
|
|
// Return original list value if up-to-date, which doesn't trigger monitor update.
|
|
if (list._monitorUpToDate) return list.value;
|
|
// If value changed, reset the flag and return a copy to trigger monitor update.
|
|
// Because monitors use Immutable data structures, only new objects trigger updates.
|
|
list._monitorUpToDate = true;
|
|
return list.value.slice();
|
|
}
|
|
|
|
// Determine if the list is all single letters.
|
|
// If it is, report contents joined together with no separator.
|
|
// If it's not, report contents joined together with a space.
|
|
let allSingleLetters = true;
|
|
for (let i = 0; i < list.value.length; i++) {
|
|
const listItem = list.value[i];
|
|
if (!((typeof listItem === 'string') &&
|
|
(listItem.length === 1))) {
|
|
allSingleLetters = false;
|
|
break;
|
|
}
|
|
}
|
|
if (allSingleLetters) {
|
|
return list.value.join('');
|
|
}
|
|
return list.value.join(' ');
|
|
|
|
}
|
|
|
|
addToList (args, util) {
|
|
const list = util.target.lookupOrCreateList(
|
|
args.LIST.id, args.LIST.name);
|
|
if (list.value.length < Scratch3DataBlocks.LIST_ITEM_LIMIT) {
|
|
list.value.push(args.ITEM);
|
|
list._monitorUpToDate = false;
|
|
}
|
|
}
|
|
|
|
deleteOfList (args, util) {
|
|
const list = util.target.lookupOrCreateList(
|
|
args.LIST.id, args.LIST.name);
|
|
const index = Cast.toListIndex(args.INDEX, list.value.length);
|
|
if (index === Cast.LIST_INVALID) {
|
|
return;
|
|
} else if (index === Cast.LIST_ALL) {
|
|
list.value = [];
|
|
return;
|
|
}
|
|
list.value.splice(index - 1, 1);
|
|
list._monitorUpToDate = false;
|
|
}
|
|
|
|
deleteAllOfList (args, util) {
|
|
const list = util.target.lookupOrCreateList(
|
|
args.LIST.id, args.LIST.name);
|
|
list.value = [];
|
|
return;
|
|
}
|
|
|
|
insertAtList (args, util) {
|
|
const item = args.ITEM;
|
|
const list = util.target.lookupOrCreateList(
|
|
args.LIST.id, args.LIST.name);
|
|
const index = Cast.toListIndex(args.INDEX, list.value.length + 1);
|
|
if (index === Cast.LIST_INVALID) {
|
|
return;
|
|
}
|
|
const listLimit = Scratch3DataBlocks.LIST_ITEM_LIMIT;
|
|
if (index > listLimit) return;
|
|
list.value.splice(index - 1, 0, item);
|
|
if (list.value.length > listLimit) {
|
|
// If inserting caused the list to grow larger than the limit,
|
|
// remove the last element in the list
|
|
list.value.pop();
|
|
}
|
|
list._monitorUpToDate = false;
|
|
}
|
|
|
|
replaceItemOfList (args, util) {
|
|
const item = args.ITEM;
|
|
const list = util.target.lookupOrCreateList(
|
|
args.LIST.id, args.LIST.name);
|
|
const index = Cast.toListIndex(args.INDEX, list.value.length);
|
|
if (index === Cast.LIST_INVALID) {
|
|
return;
|
|
}
|
|
list.value.splice(index - 1, 1, item);
|
|
list._monitorUpToDate = false;
|
|
}
|
|
|
|
getItemOfList (args, util) {
|
|
const list = util.target.lookupOrCreateList(
|
|
args.LIST.id, args.LIST.name);
|
|
const index = Cast.toListIndex(args.INDEX, list.value.length);
|
|
if (index === Cast.LIST_INVALID) {
|
|
return '';
|
|
}
|
|
return list.value[index - 1];
|
|
}
|
|
|
|
getItemNumOfList (args, util) {
|
|
const item = args.ITEM;
|
|
const list = util.target.lookupOrCreateList(
|
|
args.LIST.id, args.LIST.name);
|
|
|
|
// Go through the list items one-by-one using Cast.compare. This is for
|
|
// cases like checking if 123 is contained in a list [4, 7, '123'] --
|
|
// Scratch considers 123 and '123' to be equal.
|
|
for (let i = 0; i < list.value.length; i++) {
|
|
if (Cast.compare(list.value[i], item) === 0) {
|
|
return i + 1;
|
|
}
|
|
}
|
|
|
|
// We don't bother using .indexOf() at all, because it would end up with
|
|
// edge cases such as the index of '123' in [4, 7, 123, '123', 9].
|
|
// If we use indexOf(), this block would return 4 instead of 3, because
|
|
// indexOf() sees the first occurence of the string 123 as the fourth
|
|
// item in the list. With Scratch, this would be confusing -- after all,
|
|
// '123' and 123 look the same, so one would expect the block to say
|
|
// that the first occurrence of '123' (or 123) to be the third item.
|
|
|
|
// Default to 0 if there's no match. Since Scratch lists are 1-indexed,
|
|
// we don't have to worry about this conflicting with the "this item is
|
|
// the first value" number (in JS that is 0, but in Scratch it's 1).
|
|
return 0;
|
|
}
|
|
|
|
lengthOfList (args, util) {
|
|
const list = util.target.lookupOrCreateList(
|
|
args.LIST.id, args.LIST.name);
|
|
return list.value.length;
|
|
}
|
|
|
|
listContainsItem (args, util) {
|
|
const item = args.ITEM;
|
|
const list = util.target.lookupOrCreateList(
|
|
args.LIST.id, args.LIST.name);
|
|
if (list.value.indexOf(item) >= 0) {
|
|
return true;
|
|
}
|
|
// Try using Scratch comparison operator on each item.
|
|
// (Scratch considers the string '123' equal to the number 123).
|
|
for (let i = 0; i < list.value.length; i++) {
|
|
if (Cast.compare(list.value[i], item) === 0) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Type representation for list variables.
|
|
* @const {string}
|
|
*/
|
|
static get LIST_ITEM_LIMIT () {
|
|
return 200000;
|
|
}
|
|
}
|
|
|
|
module.exports = Scratch3DataBlocks;
|