UNPKG

scratchblocks

Version:

Make pictures of Scratch blocks from text.

1,967 lines (1,953 loc) 230 kB
/** * scratchblocks v3.6.3 * https://scratchblocks.github.io/ * Render scratchblocks code to SVG images. * * Copyright 2013–2023, Tim Radvan * @license MIT */ var scratchblocks = (function () { 'use strict'; function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } function _objectSpread2(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); } function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); } function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function () {}; return { s: F, n: function () { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function (e) { throw e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function () { it = it.call(o); }, n: function () { var step = it.next(); normalCompletion = step.done; return step; }, e: function (e) { didErr = true; err = e; }, f: function () { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; } function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); } function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); } /* When a new extension is added: 1) Add it to extensions object 2) Add its blocks to commands.js 3) Add icon width/height to scratch3/blocks.js IconView 4) Add icon to scratch3/style.js */ // Moved extensions: key is scratch3, value is scratch2 var movedExtensions = { pen: "pen", video: "sensing", music: "sound" }; var extensions = _objectSpread2(_objectSpread2({}, movedExtensions), {}, { tts: "tts", translate: "translate", microbit: "microbit", wedo: "wedo", makeymakey: "makeymakey", ev3: "ev3", boost: "boost", gdxfor: "gdxfor" }); // Alias extensions: unlike movedExtensions, this is handled for both scratch2 and scratch3. // Key is alias, value is real extension name var aliasExtensions = { wedo2: "wedo", text2speech: "tts" }; var scratchCommands = [{ id: "MOTION_MOVESTEPS", selector: "forward:", spec: "move %1 steps", inputs: ["%n"], shape: "stack", category: "motion" }, { id: "MOTION_TURNRIGHT", selector: "turnRight:", spec: "turn @turnRight %1 degrees", inputs: ["%n"], shape: "stack", category: "motion" }, { id: "MOTION_TURNLEFT", selector: "turnLeft:", spec: "turn @turnLeft %1 degrees", inputs: ["%n"], shape: "stack", category: "motion" }, { id: "MOTION_POINTINDIRECTION", selector: "heading:", spec: "point in direction %1", inputs: ["%d.direction"], shape: "stack", category: "motion" }, { id: "MOTION_POINTTOWARDS", selector: "pointTowards:", spec: "point towards %1", inputs: ["%m.spriteOrMouse"], shape: "stack", category: "motion" }, { id: "MOTION_GOTOXY", selector: "gotoX:y:", spec: "go to x:%1 y:%2", inputs: ["%n", "%n"], shape: "stack", category: "motion" }, { id: "MOTION_GOTO", selector: "gotoSpriteOrMouse:", spec: "go to %1", inputs: ["%m.location"], shape: "stack", category: "motion" }, { id: "MOTION_GLIDESECSTOXY", selector: "glideSecs:toX:y:elapsed:from:", spec: "glide %1 secs to x:%2 y:%3", inputs: ["%n", "%n", "%n"], shape: "stack", category: "motion" }, { id: "MOTION_GLIDETO", spec: "glide %1 secs to %2", inputs: ["%n", "%m.location"], shape: "stack", category: "motion" }, { id: "MOTION_CHANGEXBY", selector: "changeXposBy:", spec: "change x by %1", inputs: ["%n"], shape: "stack", category: "motion" }, { id: "MOTION_SETX", selector: "xpos:", spec: "set x to %1", inputs: ["%n"], shape: "stack", category: "motion" }, { id: "MOTION_CHANGEYBY", selector: "changeYposBy:", spec: "change y by %1", inputs: ["%n"], shape: "stack", category: "motion" }, { id: "MOTION_SETY", selector: "ypos:", spec: "set y to %1", inputs: ["%n"], shape: "stack", category: "motion" }, { id: "MOTION_SETROTATIONSTYLE", selector: "setRotationStyle", spec: "set rotation style %1", inputs: ["%m.rotationStyle"], shape: "stack", category: "motion" }, { id: "LOOKS_SAYFORSECS", selector: "say:duration:elapsed:from:", spec: "say %1 for %2 seconds", inputs: ["%s", "%n"], shape: "stack", category: "looks" }, { id: "LOOKS_SAY", selector: "say:", spec: "say %1", inputs: ["%s"], shape: "stack", category: "looks" }, { id: "LOOKS_THINKFORSECS", selector: "think:duration:elapsed:from:", spec: "think %1 for %2 seconds", inputs: ["%s", "%n"], shape: "stack", category: "looks" }, { id: "LOOKS_THINK", selector: "think:", spec: "think %1", inputs: ["%s"], shape: "stack", category: "looks" }, { id: "LOOKS_SHOW", selector: "show", spec: "show", inputs: [], shape: "stack", category: "looks" }, { id: "LOOKS_HIDE", selector: "hide", spec: "hide", inputs: [], shape: "stack", category: "looks" }, { id: "LOOKS_SWITCHCOSTUMETO", selector: "lookLike:", spec: "switch costume to %1", inputs: ["%m.costume"], shape: "stack", category: "looks" }, { id: "LOOKS_NEXTCOSTUME", selector: "nextCostume", spec: "next costume", inputs: [], shape: "stack", category: "looks" }, { id: "LOOKS_NEXTBACKDROP_BLOCK", selector: "nextScene", spec: "next backdrop", inputs: [], shape: "stack", category: "looks" }, { id: "LOOKS_SWITCHBACKDROPTO", selector: "startScene", spec: "switch backdrop to %1", inputs: ["%m.backdrop"], shape: "stack", category: "looks" }, { id: "LOOKS_SWITCHBACKDROPTOANDWAIT", selector: "startSceneAndWait", spec: "switch backdrop to %1 and wait", inputs: ["%m.backdrop"], shape: "stack", category: "looks" }, { id: "LOOKS_CHANGEEFFECTBY", selector: "changeGraphicEffect:by:", spec: "change %1 effect by %2", inputs: ["%m.effect", "%n"], shape: "stack", category: "looks" }, { id: "LOOKS_SETEFFECTTO", selector: "setGraphicEffect:to:", spec: "set %1 effect to %2", inputs: ["%m.effect", "%n"], shape: "stack", category: "looks" }, { id: "LOOKS_CLEARGRAPHICEFFECTS", selector: "filterReset", spec: "clear graphic effects", inputs: [], shape: "stack", category: "looks" }, { id: "LOOKS_CHANGESIZEBY", selector: "changeSizeBy:", spec: "change size by %1", inputs: ["%n"], shape: "stack", category: "looks" }, { id: "LOOKS_SETSIZETO", selector: "setSizeTo:", spec: "set size to %1%", inputs: ["%n"], shape: "stack", category: "looks" }, { selector: "comeToFront", spec: "go to front", inputs: [], shape: "stack", category: "looks" }, { id: "LOOKS_GOTOFRONTBACK", spec: "go to %1 layer", inputs: ["%m"], shape: "stack", category: "looks" }, { selector: "goBackByLayers:", spec: "go back %1 layers", inputs: ["%n"], shape: "stack", category: "looks" }, { id: "LOOKS_GOFORWARDBACKWARDLAYERS", spec: "go %1 %2 layers", inputs: ["%m", "%n"], shape: "stack", category: "looks" }, { id: "SOUND_PLAY", selector: "playSound:", spec: "start sound %1", inputs: ["%m.sound"], shape: "stack", category: "sound" }, { id: "SOUND_CHANGEEFFECTBY", spec: "change %1 effect by %2", inputs: ["%m", "%n"], shape: "stack", category: "sound" }, { id: "SOUND_SETEFFECTO", // sic spec: "set %1 effect to %2", inputs: ["%m", "%n"], shape: "stack", category: "sound" }, { id: "SOUND_CLEAREFFECTS", spec: "clear sound effects", inputs: [], shape: "stack", category: "sound" }, { id: "SOUND_PLAYUNTILDONE", selector: "doPlaySoundAndWait", spec: "play sound %1 until done", inputs: ["%m.sound"], shape: "stack", category: "sound" }, { id: "SOUND_STOPALLSOUNDS", selector: "stopAllSounds", spec: "stop all sounds", inputs: [], shape: "stack", category: "sound" }, { id: "music.playDrumForBeats", selector: "playDrum", spec: "play drum %1 for %2 beats", inputs: ["%d.drum", "%n"], shape: "stack", category: "music" }, { id: "music.restForBeats", selector: "rest:elapsed:from:", spec: "rest for %1 beats", inputs: ["%n"], shape: "stack", category: "music" }, { id: "music.playNoteForBeats", selector: "noteOn:duration:elapsed:from:", spec: "play note %1 for %2 beats", inputs: ["%d.note", "%n"], shape: "stack", category: "music" }, { id: "music.setInstrument", selector: "instrument:", spec: "set instrument to %1", inputs: ["%d.instrument"], shape: "stack", category: "music" }, { id: "SOUND_CHANGEVOLUMEBY", selector: "changeVolumeBy:", spec: "change volume by %1", inputs: ["%n"], shape: "stack", category: "sound" }, { id: "SOUND_SETVOLUMETO", selector: "setVolumeTo:", spec: "set volume to %1%", inputs: ["%n"], shape: "stack", category: "sound" }, { id: "music.changeTempo", selector: "changeTempoBy:", spec: "change tempo by %1", inputs: ["%n"], shape: "stack", category: "music" }, { selector: "setTempoTo:", spec: "set tempo to %1 bpm", inputs: ["%n"], shape: "stack", category: "sound" }, { id: "music.setTempo", selector: "setTempoTo:", spec: "set tempo to %1", inputs: ["%n"], shape: "stack", category: "music" }, { id: "pen.clear", selector: "clearPenTrails", spec: "erase all", inputs: [], shape: "stack", category: "pen" }, { id: "pen.stamp", selector: "stampCostume", spec: "stamp", inputs: [], shape: "stack", category: "pen" }, { id: "pen.penDown", selector: "putPenDown", spec: "pen down", inputs: [], shape: "stack", category: "pen" }, { id: "pen.penUp", selector: "putPenUp", spec: "pen up", inputs: [], shape: "stack", category: "pen" }, { id: "pen.setColor", selector: "penColor:", spec: "set pen color to %1", inputs: ["%c"], shape: "stack", category: "pen" }, { id: "pen.changeHue", selector: "changePenHueBy:", spec: "change pen color by %1", inputs: ["%n"], shape: "stack", category: "pen" }, { id: "pen.setColorParam", spec: "set pen %1 to %2", inputs: ["%m.color", "%c"], shape: "stack", category: "pen" }, { id: "pen.changeColorParam", spec: "change pen %1 by %2", inputs: ["%m.color", "%n"], shape: "stack", category: "pen" }, { id: "pen.setHue", selector: "setPenHueTo:", spec: "set pen color to %1", inputs: ["%n"], shape: "stack", category: "pen" }, { id: "pen.changeShade", selector: "changePenShadeBy:", spec: "change pen shade by %1", inputs: ["%n"], shape: "stack", category: "pen" }, { id: "pen.setShade", selector: "setPenShadeTo:", spec: "set pen shade to %1", inputs: ["%n"], shape: "stack", category: "pen" }, { id: "pen.changeSize", selector: "changePenSizeBy:", spec: "change pen size by %1", inputs: ["%n"], shape: "stack", category: "pen" }, { id: "pen.setSize", selector: "penSize:", spec: "set pen size to %1", inputs: ["%n"], shape: "stack", category: "pen" }, { id: "EVENT_WHENFLAGCLICKED", selector: "whenGreenFlag", spec: "when @greenFlag clicked", inputs: [], shape: "hat", category: "events" }, { id: "EVENT_WHENKEYPRESSED", selector: "whenKeyPressed", spec: "when %1 key pressed", inputs: ["%m.key"], shape: "hat", category: "events" }, { id: "EVENT_WHENTHISSPRITECLICKED", selector: "whenClicked", spec: "when this sprite clicked", inputs: [], shape: "hat", category: "events" }, { id: "EVENT_WHENSTAGECLICKED", spec: "when stage clicked", inputs: [], shape: "hat", category: "events" }, { id: "EVENT_WHENBACKDROPSWITCHESTO", selector: "whenSceneStarts", spec: "when backdrop switches to %1", inputs: ["%m.backdrop"], shape: "hat", category: "events" }, { id: "EVENT_WHENGREATERTHAN", selector: "whenSensorGreaterThan", spec: "when %1 > %2", inputs: ["%m.triggerSensor", "%n"], shape: "hat", category: "events" }, { id: "EVENT_WHENBROADCASTRECEIVED", selector: "whenIReceive", spec: "when I receive %1", inputs: ["%m.broadcast"], shape: "hat", category: "events" }, { id: "EVENT_BROADCAST", selector: "broadcast:", spec: "broadcast %1", inputs: ["%m.broadcast"], shape: "stack", category: "events" }, { id: "EVENT_BROADCASTANDWAIT", selector: "doBroadcastAndWait", spec: "broadcast %1 and wait", inputs: ["%m.broadcast"], shape: "stack", category: "events" }, { id: "CONTROL_WAIT", selector: "wait:elapsed:from:", spec: "wait %1 seconds", inputs: ["%n"], shape: "stack", category: "control" }, { id: "CONTROL_REPEAT", selector: "doRepeat", spec: "repeat %1", inputs: ["%n"], shape: "c-block", category: "control", hasLoopArrow: true }, { id: "CONTROL_FOREVER", selector: "doForever", spec: "forever", inputs: [], shape: "c-block cap", category: "control", hasLoopArrow: true }, { id: "CONTROL_IF", selector: "doIf", spec: "if %1 then", inputs: ["%b"], shape: "c-block", category: "control" }, { id: "CONTROL_WAITUNTIL", selector: "doWaitUntil", spec: "wait until %1", inputs: ["%b"], shape: "stack", category: "control" }, { id: "CONTROL_REPEATUNTIL", selector: "doUntil", spec: "repeat until %1", inputs: ["%b"], shape: "c-block", category: "control", hasLoopArrow: true }, { id: "CONTROL_STOP", selector: "stopScripts", spec: "stop %1", inputs: ["%m.stop"], shape: "cap", category: "control" }, { id: "CONTROL_STARTASCLONE", selector: "whenCloned", spec: "when I start as a clone", inputs: [], shape: "hat", category: "control" }, { id: "CONTROL_CREATECLONEOF", selector: "createCloneOf", spec: "create clone of %1", inputs: ["%m.spriteOnly"], shape: "stack", category: "control" }, { id: "CONTROL_DELETETHISCLONE", selector: "deleteClone", spec: "delete this clone", inputs: [], shape: "cap", category: "control" }, { id: "SENSING_ASKANDWAIT", selector: "doAsk", spec: "ask %1 and wait", inputs: ["%s"], shape: "stack", category: "sensing" }, { id: "videoSensing.videoToggle", selector: "setVideoState", spec: "turn video %1", inputs: ["%m.videoState"], shape: "stack", category: "video" }, { id: "videoSensing.setVideoTransparency", selector: "setVideoTransparency", spec: "set video transparency to %1%", inputs: ["%n"], shape: "stack", category: "video" }, { id: "videoSensing.whenMotionGreaterThan", spec: "when video motion > %1", inputs: ["%n"], shape: "hat", category: "video" }, { id: "SENSING_RESETTIMER", selector: "timerReset", spec: "reset timer", inputs: [], shape: "stack", category: "sensing" }, { id: "DATA_SETVARIABLETO", selector: "setVar:to:", spec: "set %1 to %2", inputs: ["%m.var", "%s"], shape: "stack", category: "variables" }, { id: "DATA_CHANGEVARIABLEBY", selector: "changeVar:by:", spec: "change %1 by %2", inputs: ["%m.var", "%n"], shape: "stack", category: "variables" }, { id: "DATA_SHOWVARIABLE", selector: "showVariable:", spec: "show variable %1", inputs: ["%m.var"], shape: "stack", category: "variables" }, { id: "DATA_HIDEVARIABLE", selector: "hideVariable:", spec: "hide variable %1", inputs: ["%m.var"], shape: "stack", category: "variables" }, { id: "DATA_ADDTOLIST", selector: "append:toList:", spec: "add %1 to %2", inputs: ["%s", "%m.list"], shape: "stack", category: "list" }, { id: "DATA_DELETEOFLIST", selector: "deleteLine:ofList:", spec: "delete %1 of %2", inputs: ["%d.listDeleteItem", "%m.list"], shape: "stack", category: "list" }, { id: "DATA_DELETEALLOFLIST", spec: "delete all of %1", inputs: ["%m.list"], shape: "stack", category: "list" }, { id: "MOTION_IFONEDGEBOUNCE", selector: "bounceOffEdge", spec: "if on edge, bounce", inputs: [], shape: "stack", category: "motion" }, { id: "DATA_INSERTATLIST", selector: "insert:at:ofList:", spec: "insert %1 at %2 of %3", inputs: ["%s", "%d.listItem", "%m.list"], shape: "stack", category: "list" }, { id: "DATA_REPLACEITEMOFLIST", selector: "setLine:ofList:to:", spec: "replace item %1 of %2 with %3", inputs: ["%d.listItem", "%m.list", "%s"], shape: "stack", category: "list" }, { id: "DATA_SHOWLIST", selector: "showList:", spec: "show list %1", inputs: ["%m.list"], shape: "stack", category: "list" }, { id: "DATA_HIDELIST", selector: "hideList:", spec: "hide list %1", inputs: ["%m.list"], shape: "stack", category: "list" }, { id: "SENSING_OF_XPOSITION", selector: "xpos", spec: "x position", inputs: [], shape: "reporter", category: "motion" }, { id: "SENSING_OF_YPOSITION", selector: "ypos", spec: "y position", inputs: [], shape: "reporter", category: "motion" }, { id: "SENSING_OF_DIRECTION", selector: "heading", spec: "direction", inputs: [], shape: "reporter", category: "motion" }, { id: "SENSING_OF_COSTUMENUMBER", selector: "costumeIndex", spec: "costume #", inputs: [], shape: "reporter", category: "looks" }, { id: "LOOKS_COSTUMENUMBERNAME", selector: "LOOKS_COSTUMENUMBERNAME", spec: "costume %1", inputs: ["%m"], shape: "reporter", category: "looks" }, { id: "SENSING_OF_SIZE", selector: "scale", spec: "size", inputs: [], shape: "reporter", category: "looks" }, { id: "SENSING_OF_BACKDROPNAME", selector: "sceneName", spec: "backdrop name", inputs: [], shape: "reporter", category: "looks" }, { id: "LOOKS_BACKDROPNUMBERNAME", spec: "backdrop %1", inputs: ["%m"], shape: "reporter", category: "looks" }, { id: "SENSING_OF_BACKDROPNUMBER", selector: "backgroundIndex", spec: "backdrop #", inputs: [], shape: "reporter", category: "looks" }, { id: "SOUND_VOLUME", selector: "volume", spec: "volume", inputs: [], shape: "reporter", category: "sound" }, { id: "music.getTempo", selector: "tempo", spec: "tempo", inputs: [], shape: "reporter", category: "music" }, { id: "SENSING_TOUCHINGOBJECT", selector: "touching:", spec: "touching %1?", inputs: ["%m.touching"], shape: "boolean", category: "sensing" }, { id: "SENSING_TOUCHINGCOLOR", selector: "touchingColor:", spec: "touching color %1?", inputs: ["%c"], shape: "boolean", category: "sensing" }, { id: "SENSING_COLORISTOUCHINGCOLOR", selector: "color:sees:", spec: "color %1 is touching %2?", inputs: ["%c", "%c"], shape: "boolean", category: "sensing" }, { id: "SENSING_DISTANCETO", selector: "distanceTo:", spec: "distance to %1", inputs: ["%m.spriteOrMouse"], shape: "reporter", category: "sensing" }, { id: "SENSING_ANSWER", selector: "answer", spec: "answer", inputs: [], shape: "reporter", category: "sensing" }, { id: "SENSING_KEYPRESSED", selector: "keyPressed:", spec: "key %1 pressed?", inputs: ["%m.key"], shape: "boolean", category: "sensing" }, { id: "SENSING_MOUSEDOWN", selector: "mousePressed", spec: "mouse down?", inputs: [], shape: "boolean", category: "sensing" }, { id: "SENSING_MOUSEX", selector: "mouseX", spec: "mouse x", inputs: [], shape: "reporter", category: "sensing" }, { id: "SENSING_MOUSEY", selector: "mouseY", spec: "mouse y", inputs: [], shape: "reporter", category: "sensing" }, { id: "SENSING_SETDRAGMODE", spec: "set drag mode %1", inputs: ["%m"], shape: "stack", category: "sensing" }, { id: "SENSING_LOUDNESS", selector: "soundLevel", spec: "loudness", inputs: [], shape: "reporter", category: "sensing" }, { id: "videoSensing.videoOn", selector: "senseVideoMotion", spec: "video %1 on %2", inputs: ["%m.videoMotionType", "%m.stageOrThis"], shape: "reporter", category: "video" }, { id: "SENSING_TIMER", selector: "timer", spec: "timer", inputs: [], shape: "reporter", category: "sensing" }, { id: "SENSING_OF", selector: "getAttribute:of:", spec: "%1 of %2", inputs: ["%m.attribute", "%m.spriteOrStage"], shape: "reporter", category: "sensing" }, { id: "SENSING_CURRENT", selector: "timeAndDate", spec: "current %1", inputs: ["%m.timeAndDate"], shape: "reporter", category: "sensing" }, { id: "SENSING_DAYSSINCE2000", selector: "timestamp", spec: "days since 2000", inputs: [], shape: "reporter", category: "sensing" }, { id: "SENSING_USERNAME", selector: "getUserName", spec: "username", inputs: [], shape: "reporter", category: "sensing" }, { id: "OPERATORS_ADD", selector: "+", spec: "%1 + %2", inputs: ["%n", "%n"], shape: "reporter", category: "operators" }, { id: "OPERATORS_SUBTRACT", selector: "-", spec: "%1 - %2", inputs: ["%n", "%n"], shape: "reporter", category: "operators" }, { id: "OPERATORS_MULTIPLY", selector: "*", spec: "%1 * %2", inputs: ["%n", "%n"], shape: "reporter", category: "operators" }, { id: "OPERATORS_DIVIDE", selector: "/", spec: "%1 / %2", inputs: ["%n", "%n"], shape: "reporter", category: "operators" }, { id: "OPERATORS_RANDOM", selector: "randomFrom:to:", spec: "pick random %1 to %2", inputs: ["%n", "%n"], shape: "reporter", category: "operators" }, { id: "OPERATORS_LT", selector: "<", spec: "%1 < %2", inputs: ["%s", "%s"], shape: "boolean", category: "operators" }, { id: "OPERATORS_EQUALS", selector: "=", spec: "%1 = %2", inputs: ["%s", "%s"], shape: "boolean", category: "operators" }, { id: "OPERATORS_GT", selector: ">", spec: "%1 > %2", inputs: ["%s", "%s"], shape: "boolean", category: "operators" }, { id: "OPERATORS_AND", selector: "&", spec: "%1 and %2", inputs: ["%b", "%b"], shape: "boolean", category: "operators" }, { id: "OPERATORS_OR", selector: "|", spec: "%1 or %2", inputs: ["%b", "%b"], shape: "boolean", category: "operators" }, { id: "OPERATORS_NOT", selector: "not", spec: "not %1", inputs: ["%b"], shape: "boolean", category: "operators" }, { id: "OPERATORS_JOIN", selector: "concatenate:with:", spec: "join %1 %2", inputs: ["%s", "%s"], shape: "reporter", category: "operators" }, { id: "OPERATORS_LETTEROF", selector: "letter:of:", spec: "letter %1 of %2", inputs: ["%n", "%s"], shape: "reporter", category: "operators" }, { id: "OPERATORS_LENGTH", selector: "stringLength:", spec: "length of %1", inputs: ["%s"], shape: "reporter", category: "operators" }, { id: "OPERATORS_MOD", selector: "%", spec: "%1 mod %2", inputs: ["%n", "%n"], shape: "reporter", category: "operators" }, { id: "OPERATORS_ROUND", selector: "rounded", spec: "round %1", inputs: ["%n"], shape: "reporter", category: "operators" }, { id: "OPERATORS_MATHOP", selector: "computeFunction:of:", spec: "%1 of %2", inputs: ["%m.mathOp", "%n"], shape: "reporter", category: "operators" }, { id: "OPERATORS_CONTAINS", spec: "%1 contains %2?", inputs: ["%s", "%s"], shape: "boolean", category: "operators" }, { id: "DATA_ITEMOFLIST", selector: "getLine:ofList:", spec: "item %1 of %2", inputs: ["%d.listItem", "%m.list"], shape: "reporter", category: "list" }, { id: "DATA_ITEMNUMOFLIST", spec: "item # of %1 in %2", inputs: ["%s", "%m.list"], shape: "reporter", category: "list" }, { id: "DATA_LENGTHOFLIST", selector: "lineCountOfList:", spec: "length of %1", inputs: ["%m.list"], shape: "reporter", category: "list" }, { id: "DATA_LISTCONTAINSITEM", selector: "list:contains:", spec: "%1 contains %2?", inputs: ["%m.list", "%s"], shape: "boolean", category: "list" }, { id: "CONTROL_ELSE", spec: "else", inputs: [], shape: "celse", category: "control" }, { id: "scratchblocks:end", spec: "end", inputs: [], shape: "cend", category: "control" }, { id: "scratchblocks:ellipsis", spec: ". . .", inputs: [], shape: "stack", category: "grey" }, { id: "scratchblocks:addInput", spec: "%1 @addInput", inputs: ["%n"], shape: "ring", category: "grey" }, { id: "SENSING_USERID", spec: "user id", inputs: [], shape: "reporter", category: "obsolete" }, { selector: "doIf", spec: "if %1", inputs: ["%b"], shape: "c-block", category: "obsolete" }, { selector: "doForeverIf", spec: "forever if %1", inputs: ["%b"], shape: "c-block cap", category: "obsolete" }, { selector: "doReturn", spec: "stop script", inputs: [], shape: "cap", category: "obsolete" }, { selector: "stopAll", spec: "stop all", inputs: [], shape: "cap", category: "obsolete" }, { selector: "lookLike:", spec: "switch to costume %1", inputs: ["%m.costume"], shape: "stack", category: "obsolete" }, { selector: "nextScene", spec: "next background", inputs: [], shape: "stack", category: "obsolete" }, { selector: "startScene", spec: "switch to background %1", inputs: ["%m.backdrop"], shape: "stack", category: "obsolete" }, { selector: "backgroundIndex", spec: "background #", inputs: [], shape: "reporter", category: "obsolete" }, { id: "SENSING_LOUD", selector: "isLoud", spec: "loud?", inputs: [], shape: "boolean", category: "obsolete" }, // TODO define { id: "text2speech.speakAndWaitBlock", spec: "speak %1", inputs: ["%s"], shape: "stack", category: "tts" }, { id: "text2speech.setVoiceBlock", spec: "set voice to %1", inputs: ["%m"], shape: "stack", category: "tts" }, { id: "text2speech.setLanguageBlock", spec: "set language to %1", inputs: ["%m"], shape: "stack", category: "tts" }, { id: "translate.translateBlock", spec: "translate %1 to %2", inputs: ["%s", "%m"], shape: "reporter", category: "translate" }, { id: "translate.viewerLanguage", spec: "language", shape: "reporter", category: "translate" }, { id: "makeymakey.whenKeyPressed", spec: "when %1 key pressed", inputs: ["%m"], // this is not %m.key shape: "hat", category: "makeymakey" }, { id: "makeymakey.whenKeysPressedInOrder", spec: "when %1 pressed in order", inputs: ["%m"], shape: "hat", category: "makeymakey" }, { id: "microbit.whenButtonPressed", spec: "when %1 button pressed", inputs: ["%m"], shape: "hat", category: "microbit" }, { id: "microbit.isButtonPressed", spec: "%1 button pressed?", inputs: ["%m"], shape: "boolean", category: "microbit" }, { id: "microbit.whenGesture", spec: "when %1", inputs: ["%m"], shape: "hat", category: "microbit" }, { id: "microbit.displaySymbol", spec: "display %1", inputs: ["%m"], // TODO add matrix support shape: "stack", category: "microbit" }, { id: "microbit.displayText", spec: "display text %1", inputs: ["%s"], shape: "stack", category: "microbit" }, { id: "microbit.clearDisplay", spec: "clear display", shape: "stack", category: "microbit" }, { id: "microbit.whenTilted", spec: "when tilted %1", inputs: ["%m"], shape: "hat", category: "microbit" }, { id: "microbit.isTilted", spec: "tilted %1?", inputs: ["%m"], shape: "boolean", category: "microbit" }, { id: "microbit.tiltAngle", spec: "tilt angle %1", inputs: ["%m"], shape: "reporter", category: "microbit" }, { id: "microbit.whenPinConnected", spec: "when pin %1 connected", inputs: ["%m"], shape: "hat", category: "microbit" }, { id: "ev3.motorTurnClockwise", spec: "motor %1 turn this way for %2 seconds", inputs: ["%m", "%n"], shape: "stack", category: "ev3" }, { id: "ev3.motorTurnCounterClockwise", spec: "motor %1 turn that way for %2 seconds", inputs: ["%m", "%n"], shape: "stack", category: "ev3" }, { id: "ev3.motorSetPower", spec: "motor %1 set power %2%", inputs: ["%m", "%n"], shape: "stack", category: "ev3" }, { id: "ev3.getMotorPosition", spec: "motor %1 position", inputs: ["%m"], shape: "reporter", category: "ev3" }, { id: "ev3.whenButtonPressed", spec: "when button %1 pressed", inputs: ["%m"], shape: "hat", category: "ev3" }, { id: "ev3.whenDistanceLessThan", spec: "when distance < %1", inputs: ["%n"], shape: "hat", category: "ev3" }, { id: "ev3.whenBrightnessLessThan", spec: "when brightness < %1", inputs: ["%n"], shape: "hat", category: "ev3" }, { id: "ev3.buttonPressed", spec: "button %1 pressed?", inputs: ["%m"], shape: "boolean", category: "ev3" }, { id: "ev3.getDistance", spec: "distance", shape: "reporter", category: "ev3" }, { id: "ev3.getBrightness", spec: "brightness", shape: "reporter", category: "ev3" }, { id: "ev3.beepNote", spec: "beep note %1 for %2 secs", inputs: ["%d.note", "%n"], // we can use %d.note here shape: "stack", category: "ev3" }, { id: "wedo2.motorOn", spec: "turn %1 on", inputs: ["%m.motor"], shape: "stack", category: "wedo" }, { id: "wedo2.motorOff", spec: "turn %1 off", inputs: ["%m.motor"], shape: "stack", category: "wedo" }, { id: "wedo2.startMotorPower", spec: "set %1 power to %2", inputs: ["%m.motor", "%n"], shape: "stack", category: "wedo" }, { id: "wedo2.setMotorDirection", spec: "set %1 direction to %2", inputs: ["%m.motor2", "%m.motorDirection"], shape: "stack", category: "wedo" }, { id: "wedo2.whenDistance", spec: "when distance %1 %2", inputs: ["%m.lessMore", "%n"], shape: "hat", category: "wedo" }, { id: "wedo2.getDistance", spec: "distance", inputs: [], shape: "reporter", category: "wedo" }, { id: "wedo2.motorOnFor", spec: "turn %1 on for %2 seconds", inputs: ["%m.motor", "%n"], shape: "stack", category: "wedo" }, { id: "wedo2.setLightHue", spec: "set light color to %1", inputs: ["%n"], shape: "stack", category: "wedo" }, { id: "wedo2.playNoteFor", spec: "play note %1 for %2 seconds", inputs: ["%n", "%n"], shape: "stack", category: "wedo" }, { id: "wedo2.whenTilted", spec: "when tilted %1", inputs: ["%m.xxx"], shape: "hat", category: "wedo" }, { id: "wedo2.isTilted", spec: "tilted %1?", inputs: ["%m"], shape: "boolean", category: "wedo" }, { id: "wedo2.getTiltAngle", spec: "tilt angle %1", inputs: ["%m.xxx"], shape: "reporter", category: "wedo" }, { id: "gdxfor.whenGesture", spec: "when %1", inputs: ["%m"], shape: "hat", category: "gdxfor" }, { id: "gdxfor.whenForcePushedOrPulled", spec: "when force sensor %1", inputs: ["%m"], shape: "hat", category: "gdxfor" }, { id: "gdxfor.getForce", spec: "force", shape: "reporter", category: "gdxfor" }, { id: "gdxfor.whenTilted", spec: "when tilted %1", inputs: ["%m"], shape: "hat", category: "gdxfor" }, { id: "gdxfor.isTilted", spec: "tilted %1?", inputs: ["%m"], shape: "boolean", category: "gdxfor" }, { id: "gdxfor.getTilt", spec: "tilt angle %1", inputs: ["%m"], shape: "reporter", category: "gdxfor" }, { id: "gdxfor.isFreeFalling", spec: "falling?", shape: "boolean", category: "gdxfor" }, { id: "gdxfor.getSpin", spec: "spin speed %1", inputs: ["%m"], shape: "reporter", category: "gdxfor" }, { id: "gdxfor.getAcceleration", spec: "acceleration %1", inputs: ["%m"], shape: "reporter", category: "gdxfor" }, { id: "boost.motorOnFor", spec: "turn motor %1 for %2 seconds", inputs: ["%m", "%n"], shape: "stack", category: "boost" }, { id: "boost.motorOnForRotation", spec: "turn motor %1 for %2 rotations", inputs: ["%m", "%n"], shape: "stack", category: "boost" }, { id: "boost.motorOn", spec: "turn motor %1 on", inputs: ["%m"], shape: "stack", category: "boost" }, { id: "boost.motorOff", spec: "turn motor %1 off", inputs: ["%m"], shape: "stack", category: "boost" }, { id: "boost.setMotorPower", spec: "set motor %1 speed to %2%", inputs: ["%m", "%n"], shape: "stack", category: "boost" }, { id: "boost.setMotorDirection", spec: "set motor %1 direction %2", inputs: ["%m", "%m"], shape: "stack", category: "boost" }, { id: "boost.getMotorPosition", spec: "motor %1 position", inputs: ["%m"], shape: "reporter", category: "boost" }, { id: "boost.whenColor", spec: "when %1 brick seen", inputs: ["%m"], shape: "hat", category: "boost" }, { id: "boost.seeingColor", spec: "seeing %1 brick?", inputs: ["%m"], shape: "boolean", category: "boost" }, { id: "boost.whenTilted", spec: "when tilted %1", inputs: ["%m"], shape: "hat", category: "boost" }, { id: "boost.getTiltAngle", spec: "tilt angle %1", inputs: ["%m"], shape: "reporter", category: "boost" }, { id: "boost.setLightHue", spec: "set light color to %1", inputs: ["%n"], shape: "stack", category: "boost" }]; // List of classes we're allowed to override. var overrideCategories = ["motion", "looks", "sound", "variables", "list", "events", "control", "sensing", "operators", "custom", "custom-arg", "extension", "grey", "obsolete"].concat(_toConsumableArray(Object.keys(extensions)), _toConsumableArray(Object.keys(aliasExtensions))); var overrideShapes = ["hat", "cap", "stack", "boolean", "reporter", "ring", "cat"]; // languages that should be displayed right to left var rtlLanguages = ["ar", "ckb", "fa", "he"]; var inputNumberPat = /%([0-9]+)/; var inputPat = /(%[a-zA-Z0-9](?:\.[a-zA-Z0-9]+)?)/; var inputPatGlobal = new RegExp(inputPat.source, "g"); var iconPat = /(@[a-zA-Z]+)/; var splitPat = new RegExp(inputPat.source + "|" + iconPat.source + "| +", "g"); var hexColorPat = /^#(?:[0-9a-fA-F]{3}){1,2}?$/; function parseInputNumber(part) { var m = inputNumberPat.exec(part); return m ? +m[1] : 0; } // used for procDefs function parseSpec(spec) { var parts = spec.split(splitPat).filter(function (x) { return x; }); var inputs = parts.filter(function (p) { return inputPat.test(p); }); return { spec: spec, parts: parts, inputs: inputs, hash: hashSpec(spec) }; } function hashSpec(spec) { return minifyHash(spec.replace(inputPatGlobal, " _ ")); } function minifyHash(hash) { return hash.replace(/_/g, " _ ").replace(/ +/g, " ").replace(/[,%?:]/g, "").replace(/ß/g, "ss").replace(/ä/g, "a").replace(/ö/g, "o").replace(/ü/g, "u").replace(". . .", "...").replace(/^…$/, "...").trim().toLowerCase(); } var blocksById = {}; var allBlocks = scratchCommands.map(function (def) { if (!def.id) { if (!def.selector) { throw new Error("Missing ID: " + def.spec); } def.id = "sb2:" + def.selector; } if (!def.spec) { throw new Error("Missing spec: " + def.id); } var info = { id: def.id, // Used for Scratch 3 translations spec: def.spec, // Used for Scratch 2 translations parts: def.spec.split(splitPat).filter(function (x) { return x; }), selector: def.selector || "sb3:" + def.id, // Used for JSON marshalling inputs: def.inputs == null ? [] : def.inputs, shape: def.shape, category: def.category, hasLoopArrow: !!def.hasLoopArrow }; if (blocksById[info.id]) { throw new Error("Duplicate ID: " + info.id); } blocksById[info.id] = info; return info; }); var unicodeIcons = { "@greenFlag": "⚑", "@turnRight": "↻", "@turnLeft": "↺", "@addInput": "▸", "@delInput": "◂" }; var allLanguages = {}; function loadLanguage(code, language) { var blocksByHash = language.blocksByHash = {}; Object.keys(language.commands).forEach(function (blockId) { var nativeSpec = language.commands[blockId]; var block = blocksById[blockId]; var nativeHash = hashSpec(nativeSpec); if (!blocksByHash[nativeHash]) { blocksByHash[nativeHash] = []; } blocksByHash[nativeHash].push(block); // fallback image replacement, for languages without aliases var m = iconPat.exec(block.spec); if (m) { var image = m[0]; var hash = nativeHash.replace(hashSpec(image), unicodeIcons[image]); if (!blocksByHash[hash]) { blocksByHash[hash] = []; } blocksByHash[hash].push(block); } }); language.nativeAliases = {}; Object.keys(language.aliases).forEach(function (alias) { var blockId = language.aliases[alias]; var block = blocksById[blockId]; if (block === undefined) { throw new Error("Invalid alias '" + blockId + "'"); } var aliasHash = hashSpec(alias); if (!blocksByHash[aliasHash]) { blocksByHash[aliasHash] = []; } blocksByHash[aliasHash].push(block); if (!language.nativeAliases[blockId]) { language.nativeAliases[blockId] = []; } language.nativeAliases[blockId].push(alias); }); // Some English blocks were renamed between Scratch 2 and Scratch 3. Wire them // into language.blocksByHash Object.keys(language.renamedBlocks || {}).forEach(function (alt) { var id = language.renamedBlocks[alt]; if (!blocksById[id]) { throw new Error("Unknown ID: " + id); } var block = blocksById[id]; var hash = hashSpec(alt); if (!english.blocksByHash[hash]) { english.blocksByHash[hash] = []; } english.blocksByHash[hash].push(block); }); language.nativeDropdowns = {}; Object.keys(language.dropdowns).forEach(function (name) { var nativeName = language.dropdowns[name]; language.nativeDropdowns[nativeName] = name; }); language.code = code; allLanguages[code] = language; } function loadLanguages(languages) { Object.keys(languages).forEach(function (code) { return loadLanguage(code, languages[code]); }); } var english = { aliases: { "turn ccw %1 degrees": "MOTION_TURNLEFT", "turn left %1 degrees": "MOTION_TURNLEFT", "turn cw %1 degrees": "MOTION_TURNRIGHT", "turn right %1 degrees": "MOTION_TURNRIGHT", "when flag clicked": "EVENT_WHENFLAGCLICKED", "when gf clicked": "EVENT_WHENFLAGCLICKED", "when green flag clicked": "EVENT_WHENFLAGCLICKED" }, renamedBlocks: { "say %1 for %2 secs": "LOOKS_SAYFORSECS", "think %1 for %2 secs": "LOOKS_THINKFORSECS", "play sound %1": "SOUND_PLAY", "wait %1 secs": "CONTROL_WAIT", clear: "pen.clear" }, definePrefix: ["define"], defineSuffix: [], // For ignoring the lt sign in the "when distance < _" block ignorelt: ["when distance"], // Valid arguments to "of" dropdown, for resolving ambiguous situations math: ["abs", "floor", "ceiling", "sqrt", "sin", "cos", "tan", "asin", "acos", "atan", "ln", "log", "e ^", "10 ^"], // Valid arguments to "sound effect" dropdown, for resolving ambiguous situations soundEffects: ["pitch", "pan left/right"], // Valid arguments to "microbit when" dropdown microbitWhen: ["moved", "shaken", "jumped"], // For detecting the "stop" cap / stack block osis: ["other scripts in sprite", "other scripts in stage"], dropdowns: {}, commands: {} }; allBlocks.forEach(function (info) { english.commands[info.id] = info.spec; }); loadLanguages({ en: english }); /*****************************************************************************/ function registerCheck(id, func) { if (!blocksById[id]) { throw new Error("Unknown ID: " + id); } blocksById[id].accepts = func; } function specialCase(id, func) { if (!blocksById[id]) { throw new Error("Unknown ID: " + id); } blocksById[id].specialCase = func; } function disambig(id1, id2, test) { registerCheck(id1, function (_, children, lang) { return test(children, lang); }); registerCheck(id2, function (_, children, lang) { return !test(children, lang); }); } disambig("OPERATORS_MATHOP", "SENSING_OF", function (children, lang) { // Operators if math function, otherwise sensing "attribute of" block var first = children[0]; if (!first.isInput) { return; } var name = first.value; return lang.math.includes(name); }); disambig("SOUND_CHANGEEFFECTBY", "LOOKS_CHANGEEFFECTBY", function (children, lang) { // Sound if sound effect, otherwise default to graphic effect var _iterator = _createForOfIteratorHelper(children), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var child = _step.value; if (child.shape === "dropdown") { var name = child.value; var _iterator2 = _createForOfIteratorHelper(lang.soundEffects), _step2; try { for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { var effect = _step2.value; if (minifyHash(effect) === minifyHash(name)) { return true; } } } catch (err) { _iterator2.e(err); } finally { _iterator2.f(); } } } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } return false; }); disambig("SOUND_SETEFFECTO", "LOOKS_SETEFFECTTO", function (children, lang) { // Sound if sound effect, otherwise default to graphic effect var _iterator3 = _createForOfIteratorHelper(children), _step3; try { for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) { var child = _step3.value; if (child.shape === "dropdown") { var name = child.value; var _iterator4 = _createForOfIteratorHelper(lang.soundEffects), _step4; try { for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) { var effect = _step4.value; if (minifyHash(effect) === minifyHash(name)) { return true; } } } catch (err) { _iterator4.e(err); } finally { _iterator4.f(); } } } } catch (err) { _iterator3.e(err); } finally { _iterator3.f(); } return false; }); disambig("DATA_LENGTHOFLIST", "OPERATORS_LENGTH", function (children, _lang) { // List block if dropdown, otherwise operators var last = children[children.length - 1]; if (!last.isInput) { return; } return last.shape === "dropdown"; }); disambig("DATA_LISTCONTAINSITEM", "OPERATORS_CONTAINS", function (children, _lang) { // List block if dropdown, otherwise operators var first = children[0]; if (!first.is