UNPKG

sb-edit

Version:

Import, edit, and export Scratch project files

1,093 lines (1,092 loc) 101 kB
"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; } }