sb-edit
Version:
Import, edit, and export Scratch project files
1,093 lines (1,092 loc) • 101 kB
JavaScript
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
Object.defineProperty(exports, "__esModule", { value: true });
var OpCode_1 = require("../../OpCode");
var prettier = __importStar(require("prettier"));
var Data_1 = require("../../Data");
/**
* Words which are invalid for any JavaScript identifier to be, when it isn't
* on a namespace (like `this` or `this.vars`).
*
* This list may be more comprehensive than it needs to be in every case,
* erring to avoid potential issues.
*
* Mostly pulled from MDN:
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#reserved_words
*/
var JS_RESERVED_WORDS = [
"arguments",
"await",
"break",
"case",
"catch",
"class",
"const",
"continue",
"debugger",
"default",
"delete",
"do",
"else",
"enum",
"eval",
"export",
"extends",
"false",
"finally",
"for",
"function",
"if",
"implements",
"import",
"in",
"instanceof",
"interface",
"let",
"new",
"null",
"package",
"private",
"protected",
"public",
"return",
"static",
"super",
"switch",
"this",
"throw",
"true",
"try",
"typeof",
"yield",
"var",
"void",
"while",
"with"
];
/**
* Input shapes are the basic attribute controlling which of a set of syntaxes
* is returned for any given block (or primitive value). Provide an input shape
* to inputToJS to specify what kind of value should be provided as the value
* in that input. If the content of input does not match the desired shape, for
* example because it is a block which returns a different type than desired,
* it will be automatically cast to the correct type for use in the block.
*/
var InputShape;
(function (InputShape) {
/**
* Generic shape indicating that any kind of input is acceptable. The input
* will never be cast, and may be null, undefined, or any JavaScript value.
*/
InputShape["Any"] = "Any";
/**
* Number input shape. If the input block isn't guaranteed to be a number,
* it is automatically wrapped with this.toNumber(), which has particular
* behavior to match Scratch.
*/
InputShape["Number"] = "Number";
/**
* Special "index" shape, representing an arbitrary number which has been
* decremented (decreased by 1). Scratch lists are 1-based while JavaScript
* arrays and strings are indexed starting from 0, so all indexes converted
* from Scratch must be decreased to match. The "index" shape allows number
* primitives to be statically decremented, and blocks which include a plus
* or minus operator to automtaically "absorb" the following decrement.
*/
InputShape["Index"] = "Index";
/**
* String input shape. If the input block isn't guaranteed to be a string,
* it is automatically wrapped with this.toString(), which is just a wrapper
* around the built-in String() op but is written so for consistency.
*
* The string input shape also guarantees that primitive values which could
* be statically converted to a number, e.g. the string "1.234", will NOT be
* converted.
*/
InputShape["String"] = "String";
/**
* Boolean input shape. If the input block isn't guaranteed to be a boolean,
* it is automatically wrapped with this.toBoolean(), which has particular
* behavior to match Scratch. Note that Scratch doesn't have a concept of
* boolean primitives (no "true" or "false" blocks, nor a "switch" type
* control for directly inputting true/false as in Snap!).
*/
InputShape["Boolean"] = "Boolean";
/**
* "Stack" block, referring to blocks which can be put one after another and
* together represent a sequence of steps. Stack inputs may be empty and
* otherwise are one or more blocks. In JavaScript, there's no fundamental
* difference between a "function" for reporting values and a "command" for
* applying effects, so no additional syntax is required to cast any given
* input value to a stack.
*/
InputShape["Stack"] = "Stack";
})(InputShape || (InputShape = {}));
function uniqueNameGenerator(reservedNames) {
if (reservedNames === void 0) { reservedNames = []; }
var usedNames = new Set(reservedNames);
return uniqueName;
function uniqueName(name) {
if (!usedNames.has(name)) {
usedNames.add(name);
return name;
}
var numResult = /\d+$/.exec(name);
if (numResult === null) {
return uniqueName(name + "2");
}
return uniqueName(name.slice(0, numResult.index) + String(parseInt(numResult[0], 10) + 1));
}
}
function camelCase(name, upper) {
if (upper === void 0) { upper = false; }
var validChars = /[^a-zA-Z0-9]/;
var ignoredChars = /[']/g;
var parts = name.replace(ignoredChars, "").split(validChars);
parts = parts.map(function (part) { return part.trim(); });
parts = parts.map(function (part) { return part.slice(0, 1).toUpperCase() + part.slice(1).toLowerCase(); });
if (!upper) {
parts[0] = parts[0].toLowerCase();
}
var result = parts.join("");
// A blank string is no good
if (result.length === 0) {
result = "_";
}
// Variable names cannot start with a number
if (!isNaN(parseInt(result[0], 10))) {
result = "_" + result;
}
return result;
}
function toLeopard(project, inOptions, prettierConfig) {
if (inOptions === void 0) { inOptions = {}; }
if (prettierConfig === void 0) { prettierConfig = {}; }
var defaultOptions = {
leopardJSURL: "https://unpkg.com/leopard@^1/dist/index.esm.js",
leopardCSSURL: "https://unpkg.com/leopard@^1/dist/index.min.css",
getTargetURL: function (_a) {
var name = _a.name, from = _a.from;
switch (from) {
case "index":
return "./".concat(name, "/").concat(name, ".js");
case "target":
return "../".concat(name, "/").concat(name, ".js");
}
},
getAssetURL: function (_a) {
var type = _a.type, target = _a.target, name = _a.name, ext = _a.ext;
switch (type) {
case "costume":
return "./".concat(target, "/costumes/").concat(name, ".").concat(ext);
case "sound":
return "./".concat(target, "/sounds/").concat(name, ".").concat(ext);
}
},
indexURL: "./index.js",
autoplay: true
};
var options = __assign(__assign({}, defaultOptions), inOptions);
// Sprite identifier must not conflict with module-level/global identifiers,
// imports and any others that are referenced in generated code.
//
// Only classes and similar capitalized namespaces need to be listed here:
// generated sprite names will never conflict with identifiers whose first
// letter is lowercase. (This is also why JavaScript reserved words aren't
// listed here - they're all lowercase, so sprite names won't conflict.)
var uniqueSpriteName = uniqueNameGenerator(["Color", "Costume", "Sound", "Sprite", "Trigger", "Watcher"]);
var targetNameMap = {};
var customBlockArgNameMap = new Map();
var variableNameMap = {}; // ID to unique (Leopard) name
for (var _i = 0, _a = __spreadArray([project.stage], project.sprites, true); _i < _a.length; _i++) {
var target = _a[_i];
var newTargetName = uniqueSpriteName(camelCase(target.name, true));
targetNameMap[target.name] = newTargetName;
target.setName(newTargetName);
// Variables are uniquely named per-target. These are on an empty namespace
// so don't have any conflicts.
//
// Note: since variables are serialized as properties on an object (this.vars),
// these never conflict with reserved JavaScript words like "class" or "new".
var uniqueVariableName = uniqueNameGenerator();
for (var _b = 0, _c = __spreadArray(__spreadArray([], target.lists, true), target.variables, true); _b < _c.length; _b++) {
var _d = _c[_b], id = _d.id, name_1 = _d.name;
var newName = uniqueVariableName(camelCase(name_1));
variableNameMap[id] = newName;
}
// Scripts are uniquely named per-target. These are on the sprite's main
// namespace, so must not conflict with properties and methods defined on
// all sprites/targets by Leopard.
//
// The list of reserved names is technically different between BaseSprite,
// Sprite, and Stage, but all three are considered together here, whatever
// kind of target will actually be getting script names here.
//
// Note: since scripts are serialized as class methods, these never conflict
// with reserved JavaScript words like "class" or "new" (they're accessed
// with the same typeof syntax, e.g. this.whenGreenFlagClicked).
var uniqueScriptName = uniqueNameGenerator([
// Essential data
"costumes",
"effectChain",
"effects",
"height",
"name",
"sounds",
"triggers",
"vars",
"watchers",
"width",
// Other objects
"andClones",
"clones",
"stage",
"sprites",
"parent",
// Motion
"direction",
"glide",
"goto",
"move",
"ifOnEdgeBounce",
"rotationStyle",
"x",
"y",
// Looks
"costumeNumber",
"costume",
"moveAhead",
"moveBehind",
"say",
"sayAndWait",
"size",
"think",
"thinkAndWait",
"visible",
// Sounds
"audioEffects",
"getSound",
"getSoundsPlayedByMe",
"playSoundUntilDone",
"startSound",
"stapAllOfMySounds",
"stopAllSounds",
// Control & events
"broadcast",
"broadcastAndWait",
"createClone",
"deleteThisClone",
"fireBackdropChanged",
"wait",
"warp",
// Opeartors - casting
"toNumber",
"toBoolean",
"toString",
"compare",
// Operators - strings
"stringIncludes",
"letterOf",
// Operators - numbers
"degToRad",
"degToScratch",
"radToDeg",
"radToScratch",
"random",
"scratchToDeg",
"scratchToRad",
"normalizeDeg",
// Sensing
"answer",
"askAndWait",
"colorTouching",
"keyPressed",
"loudness",
"mouse",
"restartTimer",
"timer",
"touching",
// Lists (arrays)
"arrayIncludes",
"indexInArray",
"itemOf",
// Pen
"clearPen",
"penColor",
"penDown",
"penSize",
"stamp"
]);
for (var _e = 0, _f = target.scripts; _e < _f.length; _e++) {
var script = _f[_e];
script.setName(uniqueScriptName(camelCase(script.name)));
var argNameMap = {};
customBlockArgNameMap.set(script, argNameMap);
// Parameter names aren't defined on a namespace at all, so must not conflict
// with JavaScript reserved words.
var uniqueParamName = uniqueNameGenerator(JS_RESERVED_WORDS);
for (var _g = 0, _h = script.blocks; _g < _h.length; _g++) {
var block = _h[_g];
if (block.opcode === OpCode_1.OpCode.procedures_definition) {
for (var _j = 0, _k = block.inputs.ARGUMENTS.value; _j < _k.length; _j++) {
var argument = _k[_j];
if (argument.type !== "label") {
var newName = uniqueParamName(camelCase(argument.name));
argNameMap[argument.name] = newName;
argument.name = newName;
}
}
}
}
}
}
// Cache a set of variables which are for the stage since whether or not a variable
// is local has to be known every time any variable block is converted. We check the
// stage because all non-stage variables are "for this sprite only" and because it's
// marginally quicker to iterate over a shorter set than a longer one [an assumption
// made about projects with primarily "for this sprite only" variables].
var stageVariables = new Set();
for (var _l = 0, _m = project.stage.variables; _l < _m.length; _l++) {
var variable = _m[_l];
stageVariables.add(variable.id);
}
for (var _o = 0, _p = project.stage.lists; _o < _p.length; _o++) {
var list = _p[_o];
stageVariables.add(list.id);
}
function staticBlockInputToLiteral(value, desiredInputShape) {
// Short-circuit for string inputs. These must never return number syntax.
if (desiredInputShape === InputShape.String) {
return JSON.stringify(value);
}
// Other input shapes which static inputs may fulfill: number, index, any.
// These are all OK to return JavaScript number literals for.
var asNum = Number(value);
if (!isNaN(asNum) && value !== "") {
if (desiredInputShape === InputShape.Index) {
return JSON.stringify(asNum - 1);
}
else {
return JSON.stringify(asNum);
}
}
return JSON.stringify(value);
}
function triggerInitCode(script, target) {
var hat = script.hat;
if (hat === null) {
return null;
}
var triggerInitStr = function (name, options) {
var optionsStr = "";
if (options) {
var optionValues = [];
for (var _i = 0, _a = Object.entries(options); _i < _a.length; _i++) {
var _b = _a[_i], optionName = _b[0], optionValue = _b[1];
optionValues.push("".concat(optionName, ": ").concat(optionValue));
}
optionsStr = ", {".concat(optionValues.join(", "), "}");
}
return "new Trigger(Trigger.".concat(name).concat(optionsStr, ", this.").concat(script.name, ")");
};
switch (hat.opcode) {
case OpCode_1.OpCode.event_whenflagclicked:
return triggerInitStr("GREEN_FLAG");
case OpCode_1.OpCode.event_whenkeypressed:
return triggerInitStr("KEY_PRESSED", { key: JSON.stringify(hat.inputs.KEY_OPTION.value) });
case OpCode_1.OpCode.event_whenthisspriteclicked:
case OpCode_1.OpCode.event_whenstageclicked:
return triggerInitStr("CLICKED");
case OpCode_1.OpCode.event_whenbroadcastreceived:
return triggerInitStr("BROADCAST", { name: JSON.stringify(hat.inputs.BROADCAST_OPTION.value) });
case OpCode_1.OpCode.event_whengreaterthan: {
var valueInput = hat.inputs.VALUE;
// If the "greater than" value is a literal, we can include it directly.
// Otherwise, it's a block that may depend on sprite state and needs to
// be a function.
var value = valueInput.type === "block"
? "() => ".concat(blockToJSWithContext(valueInput.value, target))
: staticBlockInputToLiteral(valueInput.value, InputShape.Number);
return triggerInitStr("".concat(hat.inputs.WHENGREATERTHANMENU.value, "_GREATER_THAN"), {
VALUE: value
});
}
case OpCode_1.OpCode.control_start_as_clone:
return triggerInitStr("CLONE_START");
default:
return null;
}
}
function scriptToJS(script, target) {
var body = script.body.map(function (block) { return blockToJSWithContext(block, target, script); }).join(";\n");
if (script.hat && script.hat.opcode === OpCode_1.OpCode.procedures_definition) {
return "\n * ".concat(script.name, "(").concat(script.hat.inputs.ARGUMENTS.value
.filter(function (arg) { return arg.type !== "label"; })
.map(function (arg) { return arg.name; })
.join(", "), ") {\n ").concat(body, "\n }\n ");
}
return "\n * ".concat(script.name, "() {\n ").concat(body, "\n }\n ");
}
function blockToJSWithContext(block, target, script) {
return blockToJS(block);
function increase(leftSide, input, allowIncrementDecrement) {
var n = parseNumber(input);
if (typeof n !== "number") {
return "".concat(leftSide, " += ").concat(inputToJS(input, InputShape.Number));
}
if (allowIncrementDecrement && n === 1) {
return "".concat(leftSide, "++");
}
else if (allowIncrementDecrement && n === -1) {
return "".concat(leftSide, "--");
}
else if (n >= 0) {
return "".concat(leftSide, " += ").concat(JSON.stringify(n));
}
else {
return "".concat(leftSide, " -= ").concat(JSON.stringify(-n));
}
}
function decrease(leftSide, input, allowIncrementDecrement) {
var n = parseNumber(input);
if (typeof n !== "number") {
return "".concat(leftSide, " -= ").concat(inputToJS(input, InputShape.Number));
}
if (allowIncrementDecrement && n === 1) {
return "".concat(leftSide, "--");
}
else if (allowIncrementDecrement && n === -1) {
return "".concat(leftSide, "++");
}
else if (n > 0) {
return "".concat(leftSide, " -= ").concat(JSON.stringify(n));
}
else {
return "".concat(leftSide, " += ").concat(JSON.stringify(-n));
}
}
function parseNumber(input) {
// Returns a number if the input was a primitive (static) value and was
// able to be parsed as a number; otherwise, returns null.
if (input.type === "block") {
return null;
}
var n = Number(input.value);
if (isNaN(n)) {
return null;
}
return n;
}
function spriteInputToJS(input) {
return "this.sprites[".concat(JSON.stringify(targetNameMap[input.value]), "]");
}
function colorInputToJS(input) {
if (input.type === "color") {
var _a = input.value, r = _a.r, g = _a.g, b = _a.b;
return "Color.rgb(".concat(r, ", ").concat(g, ", ").concat(b, ")");
}
else {
var num = inputToJS(input, InputShape.Number);
return "Color.num(".concat(num, ")");
}
}
function inputToJS(input, desiredInputShape) {
var _a, _b;
// TODO: Right now, inputs can be completely undefined if imported from
// the .sb3 format (because sb3 is weird). This little check will replace
// undefined inputs with the value `null`. In theory, this should
// eventually be removed when the sb3 import script is improved.
if (input === undefined) {
return "null";
}
switch (input.type) {
case "block": {
var inputSource = blockToJS(input.value, desiredInputShape);
if (desiredInputShape === InputShape.Stack) {
return inputSource;
}
else {
return "(".concat(inputSource, ")");
}
}
case "blocks": {
return (_b = (_a = input.value) === null || _a === void 0 ? void 0 : _a.map(function (block) { return blockToJS(block); }).join(";\n")) !== null && _b !== void 0 ? _b : "";
}
default: {
return staticBlockInputToLiteral(input.value, desiredInputShape);
}
}
}
function blockToJS(block, desiredInputShape) {
var warp = script && script.hat && script.hat.opcode === OpCode_1.OpCode.procedures_definition && script.hat.inputs.WARP.value;
// If the block contains a variable or list dropdown,
// get the code to grab that variable now for convenience
// TODO: set these to null and restructure control flow to avoid null checks
var selectedVarSource = "";
var selectedWatcherSource = "";
var varInputId = null;
if ("VARIABLE" in block.inputs) {
varInputId = block.inputs.VARIABLE.value.id;
}
else if ("LIST" in block.inputs) {
varInputId = block.inputs.LIST.value.id;
}
if (varInputId) {
var newName = variableNameMap[varInputId];
if (target === project.stage || !stageVariables.has(varInputId)) {
selectedVarSource = "this.vars.".concat(newName);
selectedWatcherSource = "this.watchers.".concat(newName);
}
else {
selectedVarSource = "this.stage.vars.".concat(newName);
selectedWatcherSource = "this.stage.watchers.".concat(newName);
}
}
var stage = "this" + (target.isStage ? "" : ".stage");
var satisfiesInputShape;
var blockSource;
makeBlockSource: switch (block.opcode) {
case OpCode_1.OpCode.motion_movesteps: {
satisfiesInputShape = InputShape.Stack;
var steps = inputToJS(block.inputs.STEPS, InputShape.Number);
blockSource = "this.move(".concat(steps, ")");
break;
}
case OpCode_1.OpCode.motion_turnright: {
satisfiesInputShape = InputShape.Stack;
blockSource = increase("this.direction", block.inputs.DEGREES, false);
break;
}
case OpCode_1.OpCode.motion_turnleft: {
satisfiesInputShape = InputShape.Stack;
blockSource = decrease("this.direction", block.inputs.DEGREES, false);
break;
}
case OpCode_1.OpCode.motion_goto: {
satisfiesInputShape = InputShape.Stack;
var x = void 0;
var y = void 0;
switch (block.inputs.TO.value) {
case "_random_": {
x = "this.random(-240, 240)";
y = "this.random(-180, 180)";
break;
}
case "_mouse_": {
x = "this.mouse.x";
y = "this.mouse.y";
break;
}
default: {
var sprite = spriteInputToJS(block.inputs.TO);
x = "".concat(sprite, ".x");
y = "".concat(sprite, ".y");
break;
}
}
blockSource = "this.goto(".concat(x, ", ").concat(y, ")");
break;
}
case OpCode_1.OpCode.motion_gotoxy: {
satisfiesInputShape = InputShape.Stack;
var x = inputToJS(block.inputs.X, InputShape.Number);
var y = inputToJS(block.inputs.Y, InputShape.Number);
blockSource = "this.goto(".concat(x, ", ").concat(y, ")");
break;
}
case OpCode_1.OpCode.motion_glideto: {
satisfiesInputShape = InputShape.Stack;
var secs = inputToJS(block.inputs.SECS, InputShape.Number);
var x = void 0;
var y = void 0;
switch (block.inputs.TO.value) {
case "_random_": {
x = "this.random(-240, 240)";
y = "this.random(-180, 180)";
break;
}
case "_mouse_": {
x = "this.mouse.x";
y = "this.mouse.y";
break;
}
default: {
var sprite = spriteInputToJS(block.inputs.TO);
x = "".concat(sprite, ".x");
y = "".concat(sprite, ".y");
break;
}
}
blockSource = "yield* this.glide(".concat(secs, ", ").concat(x, ", ").concat(y, ")");
break;
}
case OpCode_1.OpCode.motion_glidesecstoxy: {
satisfiesInputShape = InputShape.Stack;
var secs = inputToJS(block.inputs.SECS, InputShape.Number);
var x = inputToJS(block.inputs.X, InputShape.Number);
var y = inputToJS(block.inputs.Y, InputShape.Number);
blockSource = "yield* this.glide(".concat(secs, ", ").concat(x, ", ").concat(y, ")");
break;
}
case OpCode_1.OpCode.motion_pointindirection: {
satisfiesInputShape = InputShape.Stack;
var direction = inputToJS(block.inputs.DIRECTION, InputShape.Number);
blockSource = "this.direction = ".concat(direction);
break;
}
case OpCode_1.OpCode.motion_pointtowards: {
satisfiesInputShape = InputShape.Stack;
var x = void 0;
var y = void 0;
switch (block.inputs.TOWARDS.value) {
case "_mouse_": {
x = "this.mouse.x";
y = "this.mouse.y";
break;
}
default: {
var sprite = spriteInputToJS(block.inputs.TOWARDS);
x = "".concat(sprite, ".x");
y = "".concat(sprite, ".y");
break;
}
}
blockSource = "this.direction = this.radToScratch(Math.atan2(".concat(y, " - this.y, ").concat(x, " - this.x))");
break;
}
case OpCode_1.OpCode.motion_changexby: {
satisfiesInputShape = InputShape.Stack;
blockSource = increase("this.x", block.inputs.DX, false);
break;
}
case OpCode_1.OpCode.motion_setx: {
satisfiesInputShape = InputShape.Stack;
var x = inputToJS(block.inputs.X, InputShape.Number);
blockSource = "this.x = ".concat(x);
break;
}
case OpCode_1.OpCode.motion_changeyby: {
satisfiesInputShape = InputShape.Stack;
blockSource = increase("this.y", block.inputs.DY, false);
break;
}
case OpCode_1.OpCode.motion_sety: {
satisfiesInputShape = InputShape.Stack;
var y = inputToJS(block.inputs.Y, InputShape.Number);
blockSource = "this.y = ".concat(y);
break;
}
case OpCode_1.OpCode.motion_ifonedgebounce: {
satisfiesInputShape = InputShape.Stack;
blockSource = "this.ifOnEdgeBounce()";
break;
}
case OpCode_1.OpCode.motion_setrotationstyle: {
satisfiesInputShape = InputShape.Stack;
var style = void 0;
switch (block.inputs.STYLE.value) {
case "left-right": {
style = "LEFT_RIGHT";
break;
}
case "don't rotate": {
style = "DONT_ROTATE";
break;
}
case "all around": {
style = "ALL_AROUND";
break;
}
}
blockSource = "this.rotationStyle = Sprite.RotationStyle.".concat(style);
break;
}
case OpCode_1.OpCode.motion_xposition: {
satisfiesInputShape = InputShape.Number;
blockSource = "this.x";
break;
}
case OpCode_1.OpCode.motion_yposition: {
satisfiesInputShape = InputShape.Number;
blockSource = "this.y";
break;
}
case OpCode_1.OpCode.motion_direction: {
satisfiesInputShape = InputShape.Number;
blockSource = "this.direction";
break;
}
// Obsolete no-op blocks:
case OpCode_1.OpCode.motion_scroll_right:
case OpCode_1.OpCode.motion_scroll_up:
case OpCode_1.OpCode.motion_align_scene: {
satisfiesInputShape = InputShape.Stack;
blockSource = "";
break;
}
case OpCode_1.OpCode.motion_xscroll:
case OpCode_1.OpCode.motion_yscroll: {
satisfiesInputShape = InputShape.Any;
blockSource = "undefined"; // Compatibility with Scratch 3.0 \:)/
break;
}
case OpCode_1.OpCode.looks_sayforsecs: {
satisfiesInputShape = InputShape.Stack;
var message = inputToJS(block.inputs.MESSAGE, InputShape.Any);
var secs = inputToJS(block.inputs.SECS, InputShape.Number);
blockSource = "yield* this.sayAndWait(".concat(message, ", ").concat(secs, ")");
break;
}
case OpCode_1.OpCode.looks_say: {
satisfiesInputShape = InputShape.Stack;
var message = inputToJS(block.inputs.MESSAGE, InputShape.Any);
blockSource = "this.say(".concat(message, ")");
break;
}
case OpCode_1.OpCode.looks_thinkforsecs: {
satisfiesInputShape = InputShape.Stack;
var message = inputToJS(block.inputs.MESSAGE, InputShape.Any);
var secs = inputToJS(block.inputs.SECS, InputShape.Number);
blockSource = "yield* this.thinkAndWait(".concat(message, ", ").concat(secs, ")");
break;
}
case OpCode_1.OpCode.looks_think: {
satisfiesInputShape = InputShape.Stack;
var message = inputToJS(block.inputs.MESSAGE, InputShape.Any);
blockSource = "this.think(".concat(message, ")");
break;
}
case OpCode_1.OpCode.looks_switchcostumeto: {
satisfiesInputShape = InputShape.Stack;
var costume = inputToJS(block.inputs.COSTUME, InputShape.Any);
blockSource = "this.costume = ".concat(costume);
break;
}
case OpCode_1.OpCode.looks_nextcostume: {
satisfiesInputShape = InputShape.Stack;
blockSource = "this.costumeNumber++";
break;
}
case OpCode_1.OpCode.looks_switchbackdropto: {
satisfiesInputShape = InputShape.Stack;
var backdrop = inputToJS(block.inputs.BACKDROP, InputShape.Any);
blockSource = "".concat(stage, ".costume = ").concat(backdrop);
break;
}
case OpCode_1.OpCode.looks_nextbackdrop: {
satisfiesInputShape = InputShape.Stack;
blockSource = "".concat(stage, ".costumeNumber++");
break;
}
case OpCode_1.OpCode.looks_changesizeby: {
satisfiesInputShape = InputShape.Stack;
blockSource = increase("this.size", block.inputs.CHANGE, false);
break;
}
case OpCode_1.OpCode.looks_setsizeto: {
satisfiesInputShape = InputShape.Stack;
var size = inputToJS(block.inputs.SIZE, InputShape.Number);
blockSource = "this.size = ".concat(size);
break;
}
case OpCode_1.OpCode.looks_changeeffectby: {
satisfiesInputShape = InputShape.Stack;
var effect = block.inputs.EFFECT.value.toLowerCase();
blockSource = increase("this.effects.".concat(effect), block.inputs.CHANGE, false);
break;
}
case OpCode_1.OpCode.looks_seteffectto: {
satisfiesInputShape = InputShape.Stack;
var effect = block.inputs.EFFECT.value.toLowerCase();
var value = inputToJS(block.inputs.VALUE, InputShape.Number);
blockSource = "this.effects.".concat(effect, " = ").concat(value);
break;
}
case OpCode_1.OpCode.looks_cleargraphiceffects: {
satisfiesInputShape = InputShape.Stack;
blockSource = "this.effects.clear()";
break;
}
case OpCode_1.OpCode.looks_show: {
satisfiesInputShape = InputShape.Stack;
blockSource = "this.visible = true";
break;
}
case OpCode_1.OpCode.looks_hide: {
satisfiesInputShape = InputShape.Stack;
blockSource = "this.visible = false";
break;
}
case OpCode_1.OpCode.looks_gotofrontback: {
satisfiesInputShape = InputShape.Stack;
switch (block.inputs.FRONT_BACK.value) {
case "front": {
blockSource = "this.moveAhead()";
break;
}
case "back":
default: {
blockSource = "this.moveBehind()";
break;
}
}
break;
}
case OpCode_1.OpCode.looks_goforwardbackwardlayers: {
satisfiesInputShape = InputShape.Stack;
var num = inputToJS(block.inputs.NUM, InputShape.Number);
switch (block.inputs.FORWARD_BACKWARD.value) {
case "forward": {
blockSource = "this.moveAhead(".concat(num, ")");
break;
}
case "backward":
default: {
blockSource = "this.moveBehind(".concat(num, ")");
break;
}
}
break;
}
// Obsolete no-op blocks:
case OpCode_1.OpCode.looks_hideallsprites:
case OpCode_1.OpCode.looks_changestretchby:
case OpCode_1.OpCode.looks_setstretchto: {
satisfiesInputShape = InputShape.Stack;
blockSource = "";
break;
}
case OpCode_1.OpCode.looks_costumenumbername: {
switch (block.inputs.NUMBER_NAME.value) {
case "name": {
satisfiesInputShape = InputShape.String;
blockSource = "this.costume.name";
break;
}
case "number":
default: {
satisfiesInputShape = InputShape.Number;
blockSource = "this.costumeNumber";
break;
}
}
break;
}
case OpCode_1.OpCode.looks_backdropnumbername: {
switch (block.inputs.NUMBER_NAME.value) {
case "name": {
satisfiesInputShape = InputShape.String;
blockSource = "".concat(stage, ".costume.name");
break;
}
case "number":
default: {
satisfiesInputShape = InputShape.Number;
blockSource = "".concat(stage, ".costumeNumber");
break;
}
}
break;
}
case OpCode_1.OpCode.looks_size: {
satisfiesInputShape = InputShape.Number;
blockSource = "this.size";
break;
}
case OpCode_1.OpCode.sound_playuntildone: {
satisfiesInputShape = InputShape.Stack;
var sound = inputToJS(block.inputs.SOUND_MENU, InputShape.Any);
blockSource = "yield* this.playSoundUntilDone(".concat(sound, ")");
break;
}
case OpCode_1.OpCode.sound_play: {
satisfiesInputShape = InputShape.Stack;
var sound = inputToJS(block.inputs.SOUND_MENU, InputShape.Any);
blockSource = "yield* this.startSound(".concat(sound, ")");
break;
}
case OpCode_1.OpCode.sound_setvolumeto: {
satisfiesInputShape = InputShape.Stack;
var volume = inputToJS(block.inputs.VOLUME, InputShape.Number);
blockSource = "this.audioEffects.volume = ".concat(volume);
break;
}
case OpCode_1.OpCode.sound_changevolumeby: {
satisfiesInputShape = InputShape.Stack;
blockSource = increase("this.audioEffects.volume", block.inputs.VOLUME, false);
break;
}
case OpCode_1.OpCode.sound_volume: {
satisfiesInputShape = InputShape.Number;
blockSource = "this.audioEffects.volume";
break;
}
case OpCode_1.OpCode.sound_seteffectto: {
satisfiesInputShape = InputShape.Stack;
var value = inputToJS(block.inputs.VALUE, InputShape.Number);
if (block.inputs.EFFECT.type === "soundEffect") {
var effect = block.inputs.EFFECT.value.toLowerCase();
blockSource = "this.audioEffects.".concat(effect, " = ").concat(value);
}
else {
var effect = inputToJS(block.inputs.EFFECT, InputShape.Any);
blockSource = "this.audioEffects[".concat(effect, "] = ").concat(value);
}
break;
}
case OpCode_1.OpCode.sound_changeeffectby: {
satisfiesInputShape = InputShape.Stack;
var value = block.inputs.VALUE;
if (block.inputs.EFFECT.type === "soundEffect") {
var effect = block.inputs.EFFECT.value.toLowerCase();
blockSource = increase("this.audioEffects.".concat(effect), value, false);
}
else {
var effect = inputToJS(block.inputs.EFFECT, InputShape.Any);
blockSource = increase("this.audioEffects[".concat(effect, "]"), value, false);
}
break;
}
case OpCode_1.OpCode.sound_cleareffects: {
satisfiesInputShape = InputShape.Stack;
blockSource = "this.audioEffects.clear()";
break;
}
case OpCode_1.OpCode.sound_stopallsounds: {
satisfiesInputShape = InputShape.Stack;
blockSource = "this.stopAllSounds()";
break;
}
case OpCode_1.OpCode.event_broadcast: {
satisfiesInputShape = InputShape.Stack;
var message = inputToJS(block.inputs.BROADCAST_INPUT, InputShape.String);
blockSource = "this.broadcast(".concat(message, ")");
break;
}
case OpCode_1.OpCode.event_broadcastandwait: {
satisfiesInputShape = InputShape.Stack;
var message = inputToJS(block.inputs.BROADCAST_INPUT, InputShape.String);
blockSource = "yield* this.broadcastAndWait(".concat(message, ")");
break;
}
case OpCode_1.OpCode.control_wait: {
satisfiesInputShape = InputShape.Stack;
var duration = inputToJS(block.inputs.DURATION, InputShape.Number);
blockSource = "yield* this.wait(".concat(duration, ")");
break;
}
case OpCode_1.OpCode.control_repeat: {
satisfiesInputShape = InputShape.Stack;
var times = inputToJS(block.inputs.TIMES, InputShape.Number);
var substack = inputToJS(block.inputs.SUBSTACK, InputShape.Stack);
blockSource = "for (let i = 0; i < ".concat(times, "; i++) {\n ").concat(substack, ";\n ").concat(warp ? "" : "yield;", "\n }");
break;
}
case OpCode_1.OpCode.control_forever: {
satisfiesInputShape = InputShape.Stack;
var substack = inputToJS(block.inputs.SUBSTACK, InputShape.Stack);
blockSource = "while (true) {\n ".concat(substack, ";\n ").concat(warp ? "" : "yield;", "\n }");
break;
}
case OpCode_1.OpCode.control_if: {
satisfiesInputShape = InputShape.Stack;
var condition = inputToJS(block.inputs.CONDITION, InputShape.Boolean);
var substack = inputToJS(block.inputs.SUBSTACK, InputShape.Stack);
blockSource = "if (".concat(condition, ") {\n ").concat(substack, "\n }");
break;
}
case OpCode_1.OpCode.control_if_else: {
satisfiesInputShape = InputShape.Stack;
var condition = inputToJS(block.inputs.CONDITION, InputShape.Boolean);
var substack1 = inputToJS(block.inputs.SUBSTACK, InputShape.Stack);
var substack2 = inputToJS(block.inputs.SUBSTACK2, InputShape.Stack);
blockSource = "if (".concat(condition, ") {\n ").concat(substack1, "\n } else {\n ").concat(substack2, "\n }");
break;
}
case OpCode_1.OpCode.control_wait_until: {
satisfiesInputShape = InputShape.Stack;
var condition = inputToJS(block.inputs.CONDITION, InputShape.Boolean);
blockSource = "while (!".concat(condition, ") { yield; }");
break;
}
case OpCode_1.OpCode.control_repeat_until: {
satisfiesInputShape = InputShape.Stack;
var condition = inputToJS(block.inputs.CONDITION, InputShape.Boolean);
var substack = inputToJS(block.inputs.SUBSTACK, InputShape.Stack);
blockSource = "while (!".concat(condition, ") {\n ").concat(substack, "\n ").concat(warp ? "" : "yield;", "\n }");
break;
}
case OpCode_1.OpCode.control_while: {
satisfiesInputShape = InputShape.Stack;
var condition = inputToJS(block.inputs.CONDITION, InputShape.Boolean);
var substack = inputToJS(block.inputs.SUBSTACK, InputShape.Stack);
blockSource = "while (".concat(condition, ") {\n ").concat(substack, "\n ").concat(warp ? "" : "yield;", "\n }");
break;
}
case OpCode_1.OpCode.control_for_each: {
satisfiesInputShape = InputShape.Stack;
var value = inputToJS(block.inputs.VALUE, InputShape.Number);
var substack = inputToJS(block.inputs.SUBSTACK, InputShape.Stack);
// TODO: Verify compatibility if variable changes during evaluation
blockSource = "for (".concat(selectedVarSource, " = 1; ").concat(selectedVarSource, " <= ").concat(value, "; ").concat(selectedVarSource, "++) {\n ").concat(substack, "\n ").concat(warp ? "" : "yield;", "\n }");
break;
}
case OpCode_1.OpCode.control_all_at_once: {
satisfiesInputShape = InputShape.Stack;
blockSource = inputToJS(block.inputs.SUBSTACK, InputShape.Stack);
break;
}
case OpCode_1.OpCode.control_stop: {
satisfiesInputShape = InputShape.Stack;
switch (block.inputs.STOP_OPTION.value) {
case "this script": {
blockSource = "return;";
break;
}
default: {
blockSource = "/* TODO: Implement stop ".concat(block.inputs.STOP_OPTION.value, " */ null");
break;
}
}