diff --git a/src/engine/blocks.js b/src/engine/blocks.js index cf1b2b7c4..5a0bbd965 100644 --- a/src/engine/blocks.js +++ b/src/engine/blocks.js @@ -45,7 +45,10 @@ class Blocks { * @type {{inputs: {}, procedureParamNames: {}, procedureDefinitions: {}}} * @private */ - Object.defineProperty(this, '_cache', {writable: true, enumerable: false}); + Object.defineProperty(this, '_cache', { + writable: true, + enumerable: false + }); this._cache = { /** * Cache block inputs by block id @@ -123,13 +126,13 @@ class Blocks { } /** - * Get the next block for a particular block - * @param {?string} id ID of block to get the next block for - * @return {?string} ID of next block in the sequence - */ + * Get the next block for a particular block + * @param {?string} id ID of block to get the next block for + * @return {?string} ID of next block in the sequence + */ getNextBlock (id) { const block = this._blocks[id]; - return (typeof block === 'undefined') ? null : block.next; + return typeof block === 'undefined' ? null : block.next; } /** @@ -150,7 +153,7 @@ class Blocks { // Empty C-block? const input = block.inputs[inputName]; - return (typeof input === 'undefined') ? null : input.block; + return typeof input === 'undefined' ? null : input.block; } /** @@ -159,7 +162,7 @@ class Blocks { * @return {?string} the opcode corresponding to that block */ getOpcode (block) { - return (typeof block === 'undefined') ? null : block.opcode; + return typeof block === 'undefined' ? null : block.opcode; } /** @@ -168,7 +171,7 @@ class Blocks { * @return {?object} All fields and their values. */ getFields (block) { - return (typeof block === 'undefined') ? null : block.fields; + return typeof block === 'undefined' ? null : block.fields; } /** @@ -186,8 +189,10 @@ class Blocks { inputs = {}; for (const input in block.inputs) { // Ignore blocks prefixed with branch prefix. - if (input.substring(0, Blocks.BRANCH_INPUT_PREFIX.length) !== - Blocks.BRANCH_INPUT_PREFIX) { + if ( + input.substring(0, Blocks.BRANCH_INPUT_PREFIX.length) !== + Blocks.BRANCH_INPUT_PREFIX + ) { inputs[input] = block.inputs[input]; } } @@ -202,7 +207,7 @@ class Blocks { * @return {?object} Mutation for the block. */ getMutation (block) { - return (typeof block === 'undefined') ? null : block.mutation; + return typeof block === 'undefined' ? null : block.mutation; } /** @@ -231,7 +236,9 @@ class Blocks { } for (const id in this._blocks) { - if (!Object.prototype.hasOwnProperty.call(this._blocks, id)) continue; + if (!Object.prototype.hasOwnProperty.call(this._blocks, id)) { + continue; + } const block = this._blocks[id]; if (block.opcode === 'procedures_definition') { const internal = this._getCustomBlockInternal(block); @@ -267,10 +274,14 @@ class Blocks { } for (const id in this._blocks) { - if (!Object.prototype.hasOwnProperty.call(this._blocks, id)) continue; + if (!Object.prototype.hasOwnProperty.call(this._blocks, id)) { + continue; + } const block = this._blocks[id]; - if (block.opcode === 'procedures_prototype' && - block.mutation.proccode === name) { + if ( + block.opcode === 'procedures_prototype' && + block.mutation.proccode === name + ) { const names = JSON.parse(block.mutation.argumentnames); const ids = JSON.parse(block.mutation.argumentids); const defaults = JSON.parse(block.mutation.argumentdefaults); @@ -301,8 +312,11 @@ class Blocks { blocklyListen (e) { // Validate event if (typeof e !== 'object') return; - if (typeof e.blockId !== 'string' && typeof e.varId !== 'string' && - typeof e.commentId !== 'string') { + if ( + typeof e.blockId !== 'string' && + typeof e.varId !== 'string' && + typeof e.commentId !== 'string' + ) { return; } const stage = this.runtime.getTargetForStage(); @@ -357,8 +371,13 @@ class Blocks { case 'delete': // Don't accept delete events for missing blocks, // or shadow blocks being obscured. - if (!Object.prototype.hasOwnProperty.call(this._blocks, e.blockId) || - this._blocks[e.blockId].shadow) { + if ( + !Object.prototype.hasOwnProperty.call( + this._blocks, + e.blockId + ) || + this._blocks[e.blockId].shadow + ) { return; } // Inform any runtime to forget about glows on this script. @@ -375,9 +394,18 @@ class Blocks { // into a state where a local var was requested for the stage, // create a stage (global) var after checking for name conflicts // on all the sprites. - if (e.isLocal && editingTarget && !editingTarget.isStage && !e.isCloud) { + if ( + e.isLocal && + editingTarget && + !editingTarget.isStage && + !e.isCloud + ) { if (!editingTarget.lookupVariableById(e.varId)) { - editingTarget.createVariable(e.varId, e.varName, e.varType); + editingTarget.createVariable( + e.varId, + e.varName, + e.varType + ); this.emitProjectChanged(); } } else { @@ -386,23 +414,45 @@ class Blocks { return; } // Check for name conflicts in all of the targets - const allTargets = this.runtime.targets.filter(t => t.isOriginal); + const allTargets = this.runtime.targets.filter( + t => t.isOriginal + ); for (const target of allTargets) { - if (target.lookupVariableByNameAndType(e.varName, e.varType, true)) { + if ( + target.lookupVariableByNameAndType( + e.varName, + e.varType, + true + ) + ) { return; } } - stage.createVariable(e.varId, e.varName, e.varType, e.isCloud); + stage.createVariable( + e.varId, + e.varName, + e.varType, + e.isCloud + ); this.emitProjectChanged(); } break; case 'var_rename': - if (editingTarget && Object.prototype.hasOwnProperty.call(editingTarget.variables, e.varId)) { + if ( + editingTarget && + Object.prototype.hasOwnProperty.call( + editingTarget.variables, + e.varId + ) + ) { // This is a local variable, rename on the current target editingTarget.renameVariable(e.varId, e.newName); // Update all the blocks on the current target that use // this variable - editingTarget.blocks.updateBlocksAfterVarRename(e.varId, e.newName); + editingTarget.blocks.updateBlocksAfterVarRename( + e.varId, + e.newName + ); } else { // This is a global variable stage.renameVariable(e.varId, e.newName); @@ -410,14 +460,23 @@ class Blocks { const targets = this.runtime.targets; for (let i = 0; i < targets.length; i++) { const currTarget = targets[i]; - currTarget.blocks.updateBlocksAfterVarRename(e.varId, e.newName); + currTarget.blocks.updateBlocksAfterVarRename( + e.varId, + e.newName + ); } } this.emitProjectChanged(); break; case 'var_delete': { - const target = (editingTarget && Object.prototype.hasOwnProperty.call(editingTarget.variables, e.varId)) ? - editingTarget : stage; + const target = + editingTarget && + Object.prototype.hasOwnProperty.call( + editingTarget.variables, + e.varId + ) ? + editingTarget : + stage; target.deleteVariable(e.varId); this.emitProjectChanged(); break; @@ -425,11 +484,21 @@ class Blocks { case 'comment_create': if (this.runtime.getEditingTarget()) { const currTarget = this.runtime.getEditingTarget(); - currTarget.createComment(e.commentId, e.blockId, e.text, - e.xy.x, e.xy.y, e.width, e.height, e.minimized); + currTarget.createComment( + e.commentId, + e.blockId, + e.text, + e.xy.x, + e.xy.y, + e.width, + e.height, + e.minimized + ); - if (currTarget.comments[e.commentId].x === null && - currTarget.comments[e.commentId].y === null) { + if ( + currTarget.comments[e.commentId].x === null && + currTarget.comments[e.commentId].y === null + ) { // Block comments imported from 2.0 projects are imported with their // x and y coordinates set to null so that scratch-blocks can // auto-position them. If we are receiving a create event for these @@ -445,8 +514,15 @@ class Blocks { case 'comment_change': if (this.runtime.getEditingTarget()) { const currTarget = this.runtime.getEditingTarget(); - if (!Object.prototype.hasOwnProperty.call(currTarget.comments, e.commentId)) { - log.warn(`Cannot change comment with id ${e.commentId} because it does not exist.`); + if ( + !Object.prototype.hasOwnProperty.call( + currTarget.comments, + e.commentId + ) + ) { + log.warn( + `Cannot change comment with id ${e.commentId} because it does not exist.` + ); return; } const comment = currTarget.comments[e.commentId]; @@ -468,8 +544,16 @@ class Blocks { case 'comment_move': if (this.runtime.getEditingTarget()) { const currTarget = this.runtime.getEditingTarget(); - if (currTarget && !Object.prototype.hasOwnProperty.call(currTarget.comments, e.commentId)) { - log.warn(`Cannot change comment with id ${e.commentId} because it does not exist.`); + if ( + currTarget && + !Object.prototype.hasOwnProperty.call( + currTarget.comments, + e.commentId + ) + ) { + log.warn( + `Cannot move comment with id ${e.commentId} because it does not exist.` + ); return; } const comment = currTarget.comments[e.commentId]; @@ -483,7 +567,12 @@ class Blocks { case 'comment_delete': if (this.runtime.getEditingTarget()) { const currTarget = this.runtime.getEditingTarget(); - if (!Object.prototype.hasOwnProperty.call(currTarget.comments, e.commentId)) { + if ( + !Object.prototype.hasOwnProperty.call( + currTarget.comments, + e.commentId + ) + ) { // If we're in this state, we have probably received // a delete event from a workspace that we switched from // (e.g. a delete event for a comment on sprite a's workspace @@ -494,7 +583,9 @@ class Blocks { if (e.blockId) { const block = currTarget.blocks.getBlock(e.blockId); if (!block) { - log.warn(`Could not find block referenced by comment with id: ${e.commentId}`); + log.warn( + `Could not find block referenced by comment with id: ${e.commentId}` + ); return; } delete block.comment; @@ -562,7 +653,9 @@ class Blocks { */ changeBlock (args) { // Validate - if (['field', 'mutation', 'checkbox'].indexOf(args.element) === -1) return; + if (['field', 'mutation', 'checkbox'].indexOf(args.element) === -1) { + return; + } let block = this._blocks[args.id]; if (typeof block === 'undefined') return; switch (args.element) { @@ -576,13 +669,17 @@ class Blocks { // 3. the checkbox should become unchecked if we're not already // monitoring current minute - // Update block value if (!block.fields[args.name]) return; - if (args.name === 'VARIABLE' || args.name === 'LIST' || - args.name === 'BROADCAST_OPTION') { + if ( + args.name === 'VARIABLE' || + args.name === 'LIST' || + args.name === 'BROADCAST_OPTION' + ) { // Get variable name using the id in args.value. - const variable = this.runtime.getEditingTarget().lookupVariableById(args.value); + const variable = this.runtime + .getEditingTarget() + .lookupVariableById(args.value); if (variable) { block.fields[args.name].value = variable.name; block.fields[args.name].id = args.value; @@ -596,19 +693,26 @@ class Blocks { // TODO: (#1787) if (block.opcode === 'sensing_of_object_menu') { if (block.fields.OBJECT.value === '_stage_') { - this._blocks[block.parent].fields.PROPERTY.value = 'backdrop #'; + this._blocks[block.parent].fields.PROPERTY.value = + 'backdrop #'; } else { - this._blocks[block.parent].fields.PROPERTY.value = 'x position'; + this._blocks[block.parent].fields.PROPERTY.value = + 'x position'; } this.runtime.requestBlocksUpdate(); } - const flyoutBlock = block.shadow && block.parent ? this._blocks[block.parent] : block; + const flyoutBlock = + block.shadow && block.parent ? + this._blocks[block.parent] : + block; if (flyoutBlock.isMonitored) { - this.runtime.requestUpdateMonitor(Map({ - id: flyoutBlock.id, - params: this._getBlockParams(flyoutBlock) - })); + this.runtime.requestUpdateMonitor( + Map({ + id: flyoutBlock.id, + params: this._getBlockParams(flyoutBlock) + }) + ); } } break; @@ -619,14 +723,20 @@ class Blocks { // A checkbox usually has a one to one correspondence with the monitor // block but in the case of monitored reporters that have arguments, // map the old id to a new id, creating a new monitor block if necessary - if (block.fields && Object.keys(block.fields).length > 0 && - block.opcode !== 'data_variable' && block.opcode !== 'data_listcontents') { - + if ( + block.fields && + Object.keys(block.fields).length > 0 && + block.opcode !== 'data_variable' && + block.opcode !== 'data_listcontents' + ) { // This block has an argument which needs to get separated out into // multiple monitor blocks with ids based on the selected argument - const newId = getMonitorIdForBlockWithArgs(block.id, block.fields); // Note: we're not just constantly creating a longer and longer id everytime we check // the checkbox because we're using the id of the block in the flyout as the base + const newId = getMonitorIdForBlockWithArgs( + block.id, + block.fields + ); // check if a block with the new id already exists, otherwise create let newBlock = this.runtime.monitorBlocks.getBlock(newId); @@ -645,19 +755,31 @@ class Blocks { // Variable blocks may be sprite specific depending on the owner of the variable let isSpriteLocalVariable = false; if (block.opcode === 'data_variable') { - isSpriteLocalVariable = !(this.runtime.getTargetForStage().variables[block.fields.VARIABLE.id]); + isSpriteLocalVariable = + !this.runtime.getTargetForStage().variables[ + block.fields.VARIABLE.id + ]; } else if (block.opcode === 'data_listcontents') { - isSpriteLocalVariable = !(this.runtime.getTargetForStage().variables[block.fields.LIST.id]); + isSpriteLocalVariable = + !this.runtime.getTargetForStage().variables[ + block.fields.LIST.id + ]; } - const isSpriteSpecific = isSpriteLocalVariable || - (Object.prototype.hasOwnProperty.call(this.runtime.monitorBlockInfo, block.opcode) && - this.runtime.monitorBlockInfo[block.opcode].isSpriteSpecific); + const isSpriteSpecific = + isSpriteLocalVariable || + (Object.prototype.hasOwnProperty.call( + this.runtime.monitorBlockInfo, + block.opcode + ) && + this.runtime.monitorBlockInfo[block.opcode] + .isSpriteSpecific); if (isSpriteSpecific) { // If creating a new sprite specific monitor, the only possible target is // the current editing one b/c you cannot dynamically create monitors. // Also, do not change the targetId if it has already been assigned - block.targetId = block.targetId || this.runtime.getEditingTarget().id; + block.targetId = + block.targetId || this.runtime.getEditingTarget().id; } else { block.targetId = null; } @@ -667,16 +789,25 @@ class Blocks { } else if (!wasMonitored && block.isMonitored) { // Tries to show the monitor for specified block. If it doesn't exist, add the monitor. if (!this.runtime.requestShowMonitor(block.id)) { - this.runtime.requestAddMonitor(MonitorRecord({ - id: block.id, - targetId: block.targetId, - spriteName: block.targetId ? this.runtime.getTargetById(block.targetId).getName() : null, - opcode: block.opcode, - params: this._getBlockParams(block), - // @todo(vm#565) for numerical values with decimals, some countries use comma - value: '', - mode: block.opcode === 'data_listcontents' ? 'list' : 'default' - })); + this.runtime.requestAddMonitor( + MonitorRecord({ + id: block.id, + targetId: block.targetId, + spriteName: block.targetId ? + this.runtime + .getTargetById(block.targetId) + .getName() : + null, + opcode: block.opcode, + params: this._getBlockParams(block), + // @todo(vm#565) for numerical values with decimals, some countries use comma + value: '', + mode: + block.opcode === 'data_listcontents' ? + 'list' : + 'default' + }) + ); } } break; @@ -705,8 +836,8 @@ class Blocks { // Move coordinate changes. if (e.newCoordinate) { - - didChange = (block.x !== e.newCoordinate.x) || (block.y !== e.newCoordinate.y); + didChange = + block.x !== e.newCoordinate.x || block.y !== e.newCoordinate.y; block.x = e.newCoordinate.x; block.y = e.newCoordinate.y; @@ -715,8 +846,10 @@ class Blocks { // Remove from any old parent. if (typeof e.oldParent !== 'undefined') { const oldParent = this._blocks[e.oldParent]; - if (typeof e.oldInput !== 'undefined' && - oldParent.inputs[e.oldInput].block === e.id) { + if ( + typeof e.oldInput !== 'undefined' && + oldParent.inputs[e.oldInput].block === e.id + ) { // This block was connected to the old parent's input. oldParent.inputs[e.oldInput].block = null; } else if (oldParent.next === e.id) { @@ -741,8 +874,14 @@ class Blocks { // Moved to the new parent's input. // Don't obscure the shadow block. let oldShadow = null; - if (Object.prototype.hasOwnProperty.call(this._blocks[e.newParent].inputs, e.newInput)) { - oldShadow = this._blocks[e.newParent].inputs[e.newInput].shadow; + if ( + Object.prototype.hasOwnProperty.call( + this._blocks[e.newParent].inputs, + e.newInput + ) + ) { + oldShadow = + this._blocks[e.newParent].inputs[e.newInput].shadow; } // If the block being attached is itself a shadow, make sure to set @@ -764,7 +903,6 @@ class Blocks { if (didChange) this.emitProjectChanged(); } - /** * Block management: run all blocks. * @param {!object} runtime Runtime to run all blocks in. @@ -777,7 +915,9 @@ class Blocks { const targetId = this.getBlock(blockId).targetId; return { blockId, - target: targetId ? runtime.getTargetById(targetId) : null + target: targetId ? + runtime.getTargetById(targetId) : + null }; }); } @@ -816,8 +956,10 @@ class Blocks { this.deleteBlock(block.inputs[input].block); } // Delete obscured shadow blocks. - if (block.inputs[input].shadow !== null && - block.inputs[input].shadow !== block.inputs[input].block) { + if ( + block.inputs[input].shadow !== null && + block.inputs[input].shadow !== block.inputs[input].block + ) { this.deleteBlock(block.inputs[input].shadow); } } @@ -863,7 +1005,10 @@ class Blocks { } else if (blocks[blockId].fields.LIST) { varOrListField = blocks[blockId].fields.LIST; varType = Variable.LIST_TYPE; - } else if (optIncludeBroadcast && blocks[blockId].fields.BROADCAST_OPTION) { + } else if ( + optIncludeBroadcast && + blocks[blockId].fields.BROADCAST_OPTION + ) { varOrListField = blocks[blockId].fields.BROADCAST_OPTION; varType = Variable.BROADCAST_MESSAGE_TYPE; } @@ -875,10 +1020,12 @@ class Blocks { type: varType }); } else { - allReferences[currVarId] = [{ - referencingField: varOrListField, - type: varType - }]; + allReferences[currVarId] = [ + { + referencingField: varOrListField, + type: varType + } + ]; } } } @@ -915,9 +1062,15 @@ class Blocks { updateTargetSpecificBlocks (isStage) { const blocks = this._blocks; for (const blockId in blocks) { - if (isStage && blocks[blockId].opcode === 'event_whenthisspriteclicked') { + if ( + isStage && + blocks[blockId].opcode === 'event_whenthisspriteclicked' + ) { blocks[blockId].opcode = 'event_whenstageclicked'; - } else if (!isStage && blocks[blockId].opcode === 'event_whenstageclicked') { + } else if ( + !isStage && + blocks[blockId].opcode === 'event_whenstageclicked' + ) { blocks[blockId].opcode = 'event_whenthisspriteclicked'; } } @@ -967,10 +1120,12 @@ class Blocks { let blockUpdated = false; for (const blockId in blocks) { const block = blocks[blockId]; - if (block.opcode === 'sensing_of' && + if ( + block.opcode === 'sensing_of' && block.fields.PROPERTY.value === oldName && // If block and shadow are different, it means a block is inserted to OBJECT, and should be ignored. - block.inputs.OBJECT.block === block.inputs.OBJECT.shadow) { + block.inputs.OBJECT.block === block.inputs.OBJECT.shadow + ) { const inputBlock = this.getBlock(block.inputs.OBJECT.block); if (inputBlock.fields.OBJECT.value === targetName) { block.fields.PROPERTY.value = newName; @@ -991,7 +1146,10 @@ class Blocks { */ _getCostumeField (blockId) { const block = this.getBlock(blockId); - if (block && Object.prototype.hasOwnProperty.call(block.fields, 'COSTUME')) { + if ( + block && + Object.prototype.hasOwnProperty.call(block.fields, 'COSTUME') + ) { return block.fields.COSTUME; } return null; @@ -1006,7 +1164,10 @@ class Blocks { */ _getSoundField (blockId) { const block = this.getBlock(blockId); - if (block && Object.prototype.hasOwnProperty.call(block.fields, 'SOUND_MENU')) { + if ( + block && + Object.prototype.hasOwnProperty.call(block.fields, 'SOUND_MENU') + ) { return block.fields.SOUND_MENU; } return null; @@ -1021,7 +1182,10 @@ class Blocks { */ _getBackdropField (blockId) { const block = this.getBlock(blockId); - if (block && Object.prototype.hasOwnProperty.call(block.fields, 'BACKDROP')) { + if ( + block && + Object.prototype.hasOwnProperty.call(block.fields, 'BACKDROP') + ) { return block.fields.BACKDROP; } return null; @@ -1039,8 +1203,15 @@ class Blocks { if (!block) { return null; } - const spriteMenuNames = ['TOWARDS', 'TO', 'OBJECT', 'VIDEOONMENU2', - 'DISTANCETOMENU', 'TOUCHINGOBJECTMENU', 'CLONE_OPTION']; + const spriteMenuNames = [ + 'TOWARDS', + 'TO', + 'OBJECT', + 'VIDEOONMENU2', + 'DISTANCETOMENU', + 'TOUCHINGOBJECTMENU', + 'CLONE_OPTION' + ]; for (let i = 0; i < spriteMenuNames.length; i++) { const menuName = spriteMenuNames[i]; if (Object.prototype.hasOwnProperty.call(block.fields, menuName)) { @@ -1059,7 +1230,9 @@ class Blocks { * @return {string} String of XML representing this object's blocks. */ toXML (comments) { - return this._scripts.map(script => this.blockToXML(script, comments)).join(); + return this._scripts + .map(script => this.blockToXML(script, comments)) + .join(); } /** @@ -1076,9 +1249,8 @@ class Blocks { // this early exit allows the project to load. if (!block) return; // Encode properties of this block. - const tagName = (block.shadow) ? 'shadow' : 'block'; - let xmlString = - `<${tagName} + const tagName = block.shadow ? 'shadow' : 'block'; + let xmlString = `<${tagName} id="${block.id}" type="${block.opcode}" ${block.topLevel ? `x="${block.x}" y="${block.y}"` : ''} @@ -1089,10 +1261,14 @@ class Blocks { if (Object.prototype.hasOwnProperty.call(comments, commentId)) { xmlString += comments[commentId].toXML(); } else { - log.warn(`Could not find comment with id: ${commentId} in provided comment descriptions.`); + log.warn( + `Could not find comment with id: ${commentId} in provided comment descriptions.` + ); } } else { - log.warn(`Cannot serialize comment with id: ${commentId}; no comment descriptions provided.`); + log.warn( + `Cannot serialize comment with id: ${commentId}; no comment descriptions provided.` + ); } } // Add any mutation. Must come before inputs. @@ -1101,7 +1277,9 @@ class Blocks { } // Add any inputs on this block. for (const input in block.inputs) { - if (!Object.prototype.hasOwnProperty.call(block.inputs, input)) continue; + if (!Object.prototype.hasOwnProperty.call(block.inputs, input)) { + continue; + } const blockInput = block.inputs[input]; // Only encode a value tag if the value input is occupied. if (blockInput.block || blockInput.shadow) { @@ -1109,7 +1287,10 @@ class Blocks { if (blockInput.block) { xmlString += this.blockToXML(blockInput.block, comments); } - if (blockInput.shadow && blockInput.shadow !== blockInput.block) { + if ( + blockInput.shadow && + blockInput.shadow !== blockInput.block + ) { // Obscured shadow. xmlString += this.blockToXML(blockInput.shadow, comments); } @@ -1118,7 +1299,9 @@ class Blocks { } // Add any fields on this block. for (const field in block.fields) { - if (!Object.prototype.hasOwnProperty.call(block.fields, field)) continue; + if (!Object.prototype.hasOwnProperty.call(block.fields, field)) { + continue; + } const blockField = block.fields[field]; xmlString += `<field name="${blockField.name}"`; const fieldId = blockField.id; @@ -1137,7 +1320,10 @@ class Blocks { } // Add blocks connected to the next connection. if (block.next) { - xmlString += `<next>${this.blockToXML(block.next, comments)}</next>`; + xmlString += `<next>${this.blockToXML( + block.next, + comments + )}</next>`; } xmlString += `</${tagName}>`; return xmlString; @@ -1152,8 +1338,10 @@ class Blocks { let mutationString = `<${mutation.tagName}`; for (const prop in mutation) { if (prop === 'children' || prop === 'tagName') continue; - let mutationValue = (typeof mutation[prop] === 'string') ? - xmlEscape(mutation[prop]) : mutation[prop]; + let mutationValue = + typeof mutation[prop] === 'string' ? + xmlEscape(mutation[prop]) : + mutation[prop]; // Handle dynamic extension blocks if (prop === 'blockInfo') { diff --git a/src/engine/runtime.js b/src/engine/runtime.js index 452ff51c2..4a34e3cb8 100644 --- a/src/engine/runtime.js +++ b/src/engine/runtime.js @@ -385,7 +385,8 @@ class Runtime extends EventEmitter { * being added. * @type {function} */ - this.addCloudVariable = this._initializeAddCloudVariable(newCloudDataManager); + this.addCloudVariable = + this._initializeAddCloudVariable(newCloudDataManager); /** * A function which updates the runtime's cloud variable limit @@ -393,7 +394,8 @@ class Runtime extends EventEmitter { * if the last of the cloud variables is being removed. * @type {function} */ - this.removeCloudVariable = this._initializeRemoveCloudVariable(newCloudDataManager); + this.removeCloudVariable = + this._initializeRemoveCloudVariable(newCloudDataManager); /** * A string representing the origin of the current project from outside of the @@ -737,24 +739,24 @@ class Runtime extends EventEmitter { // Helper function for initializing the addCloudVariable function _initializeAddCloudVariable (newCloudDataManager) { // The addCloudVariable function - return (() => { + return () => { const hadCloudVarsBefore = this.hasCloudData(); newCloudDataManager.addCloudVariable(); if (!hadCloudVarsBefore && this.hasCloudData()) { this.emit(Runtime.HAS_CLOUD_DATA_UPDATE, true); } - }); + }; } // Helper function for initializing the removeCloudVariable function _initializeRemoveCloudVariable (newCloudDataManager) { - return (() => { + return () => { const hadCloudVarsBefore = this.hasCloudData(); newCloudDataManager.removeCloudVariable(); if (hadCloudVarsBefore && !this.hasCloudData()) { this.emit(Runtime.HAS_CLOUD_DATA_UPDATE, false); } - }); + }; } /** @@ -764,14 +766,26 @@ class Runtime extends EventEmitter { */ _registerBlockPackages () { for (const packageName in defaultBlockPackages) { - if (Object.prototype.hasOwnProperty.call(defaultBlockPackages, packageName)) { + if ( + Object.prototype.hasOwnProperty.call( + defaultBlockPackages, + packageName + ) + ) { // @todo pass a different runtime depending on package privilege? - const packageObject = new (defaultBlockPackages[packageName])(this); + const packageObject = new defaultBlockPackages[packageName]( + this + ); // Collect primitives from package. if (packageObject.getPrimitives) { const packagePrimitives = packageObject.getPrimitives(); for (const op in packagePrimitives) { - if (Object.prototype.hasOwnProperty.call(packagePrimitives, op)) { + if ( + Object.prototype.hasOwnProperty.call( + packagePrimitives, + op + ) + ) { this._primitives[op] = packagePrimitives[op].bind(packageObject); } @@ -781,14 +795,23 @@ class Runtime extends EventEmitter { if (packageObject.getHats) { const packageHats = packageObject.getHats(); for (const hatName in packageHats) { - if (Object.prototype.hasOwnProperty.call(packageHats, hatName)) { + if ( + Object.prototype.hasOwnProperty.call( + packageHats, + hatName + ) + ) { this._hats[hatName] = packageHats[hatName]; } } } // Collect monitored from package. if (packageObject.getMonitored) { - this.monitorBlockInfo = Object.assign({}, this.monitorBlockInfo, packageObject.getMonitored()); + this.monitorBlockInfo = Object.assign( + {}, + this.monitorBlockInfo, + packageObject.getMonitored() + ); } } } @@ -818,7 +841,9 @@ class Runtime extends EventEmitter { const context = {}; target = target || this.getEditingTarget() || this.getTargetForStage(); if (target) { - context.targetType = (target.isStage ? TargetType.STAGE : TargetType.SPRITE); + context.targetType = target.isStage ? + TargetType.STAGE : + TargetType.SPRITE; } } @@ -851,8 +876,14 @@ class Runtime extends EventEmitter { this._fillExtensionCategory(categoryInfo, extensionInfo); for (const fieldTypeName in categoryInfo.customFieldTypes) { - if (Object.prototype.hasOwnProperty.call(extensionInfo.customFieldTypes, fieldTypeName)) { - const fieldTypeInfo = categoryInfo.customFieldTypes[fieldTypeName]; + if ( + Object.prototype.hasOwnProperty.call( + extensionInfo.customFieldTypes, + fieldTypeName + ) + ) { + const fieldTypeInfo = + categoryInfo.customFieldTypes[fieldTypeName]; // Emit events for custom field types from extension this.emit(Runtime.EXTENSION_FIELD_ADDED, { @@ -871,7 +902,9 @@ class Runtime extends EventEmitter { * @private */ _refreshExtensionPrimitives (extensionInfo) { - const categoryInfo = this._blockInfo.find(info => info.id === extensionInfo.id); + const categoryInfo = this._blockInfo.find( + info => info.id === extensionInfo.id + ); if (categoryInfo) { categoryInfo.name = maybeFormatMessage(extensionInfo.name); this._fillExtensionCategory(categoryInfo, extensionInfo); @@ -894,15 +927,29 @@ class Runtime extends EventEmitter { categoryInfo.menuInfo = {}; for (const menuName in extensionInfo.menus) { - if (Object.prototype.hasOwnProperty.call(extensionInfo.menus, menuName)) { + if ( + Object.prototype.hasOwnProperty.call( + extensionInfo.menus, + menuName + ) + ) { const menuInfo = extensionInfo.menus[menuName]; - const convertedMenu = this._buildMenuForScratchBlocks(menuName, menuInfo, categoryInfo); + const convertedMenu = this._buildMenuForScratchBlocks( + menuName, + menuInfo, + categoryInfo + ); categoryInfo.menus.push(convertedMenu); categoryInfo.menuInfo[menuName] = menuInfo; } } for (const fieldTypeName in extensionInfo.customFieldTypes) { - if (Object.prototype.hasOwnProperty.call(extensionInfo.customFieldTypes, fieldTypeName)) { + if ( + Object.prototype.hasOwnProperty.call( + extensionInfo.customFieldTypes, + fieldTypeName + ) + ) { const fieldType = extensionInfo.customFieldTypes[fieldTypeName]; const fieldTypeInfo = this._buildCustomFieldInfo( fieldTypeName, @@ -917,22 +964,32 @@ class Runtime extends EventEmitter { for (const blockInfo of extensionInfo.blocks) { try { - const convertedBlock = this._convertForScratchBlocks(blockInfo, categoryInfo); + const convertedBlock = this._convertForScratchBlocks( + blockInfo, + categoryInfo + ); categoryInfo.blocks.push(convertedBlock); if (convertedBlock.json) { const opcode = convertedBlock.json.type; if (blockInfo.blockType !== BlockType.EVENT) { this._primitives[opcode] = convertedBlock.info.func; } - if (blockInfo.blockType === BlockType.EVENT || blockInfo.blockType === BlockType.HAT) { + if ( + blockInfo.blockType === BlockType.EVENT || + blockInfo.blockType === BlockType.HAT + ) { this._hats[opcode] = { edgeActivated: blockInfo.isEdgeActivated, - restartExistingThreads: blockInfo.shouldRestartExistingThreads + restartExistingThreads: + blockInfo.shouldRestartExistingThreads }; } } } catch (e) { - log.error('Error parsing block: ', {block: blockInfo, error: e}); + log.error('Error parsing block: ', { + block: blockInfo, + error: e + }); } } } @@ -948,14 +1005,25 @@ class Runtime extends EventEmitter { if (typeof menuItems !== 'function') { const extensionMessageContext = this.makeMessageContextForTarget(); return menuItems.map(item => { - const formattedItem = maybeFormatMessage(item, extensionMessageContext); + const formattedItem = maybeFormatMessage( + item, + extensionMessageContext + ); switch (typeof formattedItem) { case 'string': return [formattedItem, formattedItem]; case 'object': - return [maybeFormatMessage(item.text, extensionMessageContext), item.value]; + return [ + maybeFormatMessage( + item.text, + extensionMessageContext + ), + item.value + ]; default: - throw new Error(`Can't interpret menu item: ${JSON.stringify(item)}`); + throw new Error( + `Can't interpret menu item: ${JSON.stringify(item)}` + ); } }); } @@ -985,7 +1053,8 @@ class Runtime extends EventEmitter { colourSecondary: categoryInfo.color2, colourTertiary: categoryInfo.color3, outputShape: menuInfo.acceptReporters ? - ScratchBlocksConstants.OUTPUT_SHAPE_ROUND : ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE, + ScratchBlocksConstants.OUTPUT_SHAPE_ROUND : + ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE, args0: [ { type: 'field_dropdown', @@ -1027,7 +1096,12 @@ class Runtime extends EventEmitter { * @param {object} categoryInfo - The category the field belongs to (Used to set its colors) * @returns {object} - Object to be inserted into scratch-blocks */ - _buildCustomFieldTypeForScratchBlocks (fieldName, output, outputShape, categoryInfo) { + _buildCustomFieldTypeForScratchBlocks ( + fieldName, + output, + outputShape, + categoryInfo + ) { return { json: { type: fieldName, @@ -1114,15 +1188,13 @@ class Runtime extends EventEmitter { const separatorJSON = { type: 'field_vertical_separator' }; - blockJSON.args0 = [ - iconJSON, - separatorJSON - ]; + blockJSON.args0 = [iconJSON, separatorJSON]; } switch (blockInfo.blockType) { case BlockType.COMMAND: - blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE; + blockJSON.outputShape = + ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE; blockJSON.previousStatement = null; // null = available connection; undefined = hat if (!blockInfo.isTerminal) { blockJSON.nextStatement = null; // null = available connection; undefined = terminal @@ -1130,25 +1202,34 @@ class Runtime extends EventEmitter { break; case BlockType.REPORTER: blockJSON.output = 'String'; // TODO: distinguish number & string here? - blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_ROUND; + blockJSON.outputShape = + ScratchBlocksConstants.OUTPUT_SHAPE_ROUND; break; case BlockType.BOOLEAN: blockJSON.output = 'Boolean'; - blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_HEXAGONAL; + blockJSON.outputShape = + ScratchBlocksConstants.OUTPUT_SHAPE_HEXAGONAL; break; case BlockType.HAT: case BlockType.EVENT: - if (!Object.prototype.hasOwnProperty.call(blockInfo, 'isEdgeActivated')) { + if ( + !Object.prototype.hasOwnProperty.call( + blockInfo, + 'isEdgeActivated' + ) + ) { // if absent, this property defaults to true blockInfo.isEdgeActivated = true; } - blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE; + blockJSON.outputShape = + ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE; blockJSON.nextStatement = null; // null = available connection; undefined = terminal break; case BlockType.CONDITIONAL: case BlockType.LOOP: blockInfo.branchCount = blockInfo.branchCount || 1; - blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE; + blockJSON.outputShape = + ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE; blockJSON.previousStatement = null; // null = available connection; undefined = hat if (!blockInfo.isTerminal) { blockJSON.nextStatement = null; // null = available connection; undefined = terminal @@ -1156,19 +1237,33 @@ class Runtime extends EventEmitter { break; } - const blockText = Array.isArray(blockInfo.text) ? blockInfo.text : [blockInfo.text]; + const blockText = Array.isArray(blockInfo.text) ? + blockInfo.text : + [blockInfo.text]; let inTextNum = 0; // text for the next block "arm" is blockText[inTextNum] let inBranchNum = 0; // how many branches have we placed into the JSON so far? let outLineNum = 0; // used for scratch-blocks `message${outLineNum}` and `args${outLineNum}` - const convertPlaceholders = this._convertPlaceholders.bind(this, context); + const convertPlaceholders = this._convertPlaceholders.bind( + this, + context + ); const extensionMessageContext = this.makeMessageContextForTarget(); // alternate between a block "arm" with text on it and an open slot for a substack - while (inTextNum < blockText.length || inBranchNum < blockInfo.branchCount) { + while ( + inTextNum < blockText.length || + inBranchNum < blockInfo.branchCount + ) { if (inTextNum < blockText.length) { context.outLineNum = outLineNum; - const lineText = maybeFormatMessage(blockText[inTextNum], extensionMessageContext); - const convertedText = lineText.replace(/\[(.+?)]/g, convertPlaceholders); + const lineText = maybeFormatMessage( + blockText[inTextNum], + extensionMessageContext + ); + const convertedText = lineText.replace( + /\[(.+?)]/g, + convertPlaceholders + ); if (blockJSON[`message${outLineNum}`]) { blockJSON[`message${outLineNum}`] += convertedText; } else { @@ -1179,10 +1274,14 @@ class Runtime extends EventEmitter { } if (inBranchNum < blockInfo.branchCount) { blockJSON[`message${outLineNum}`] = '%1'; - blockJSON[`args${outLineNum}`] = [{ - type: 'input_statement', - name: `SUBSTACK${inBranchNum > 0 ? inBranchNum + 1 : ''}` - }]; + blockJSON[`args${outLineNum}`] = [ + { + type: 'input_statement', + name: `SUBSTACK${ + inBranchNum > 0 ? inBranchNum + 1 : '' + }` + } + ]; ++inBranchNum; ++outLineNum; } @@ -1196,18 +1295,22 @@ class Runtime extends EventEmitter { // Add icon to the bottom right of a loop block blockJSON[`lastDummyAlign${outLineNum}`] = 'RIGHT'; blockJSON[`message${outLineNum}`] = '%1'; - blockJSON[`args${outLineNum}`] = [{ - type: 'field_image', - src: './static/blocks-media/repeat.svg', // TODO: use a constant or make this configurable? - width: 24, - height: 24, - alt: '*', // TODO remove this since we don't use collapsed blocks in scratch - flip_rtl: true - }]; + blockJSON[`args${outLineNum}`] = [ + { + type: 'field_image', + src: './static/blocks-media/repeat.svg', // TODO: use a constant or make this configurable? + width: 24, + height: 24, + alt: '*', // TODO remove this since we don't use collapsed blocks in scratch + flip_rtl: true + } + ]; ++outLineNum; } - const mutation = blockInfo.isDynamic ? `<mutation blockInfo="${xmlEscape(JSON.stringify(blockInfo))}"/>` : ''; + const mutation = blockInfo.isDynamic ? + `<mutation blockInfo="${xmlEscape(JSON.stringify(blockInfo))}"/>` : + ''; const inputs = context.inputList.join(''); const blockXML = `<block type="${extendedOpcode}">${mutation}${inputs}</block>`; @@ -1242,13 +1345,22 @@ class Runtime extends EventEmitter { */ _convertButtonForScratchBlocks (buttonInfo) { // for now we only support these pre-defined callbacks handled in scratch-blocks - const supportedCallbackKeys = ['MAKE_A_LIST', 'MAKE_A_PROCEDURE', 'MAKE_A_VARIABLE']; + const supportedCallbackKeys = [ + 'MAKE_A_LIST', + 'MAKE_A_PROCEDURE', + 'MAKE_A_VARIABLE' + ]; if (supportedCallbackKeys.indexOf(buttonInfo.func) < 0) { - log.error(`Custom button callbacks not supported yet: ${buttonInfo.func}`); + log.error( + `Custom button callbacks not supported yet: ${buttonInfo.func}` + ); } const extensionMessageContext = this.makeMessageContextForTarget(); - const buttonText = maybeFormatMessage(buttonInfo.text, extensionMessageContext); + const buttonText = maybeFormatMessage( + buttonInfo.text, + extensionMessageContext + ); return { info: buttonInfo, xml: `<button text="${buttonText}" callbackKey="${buttonInfo.func}"></button>` @@ -1263,7 +1375,9 @@ class Runtime extends EventEmitter { */ _constructInlineImageJson (argInfo) { if (!argInfo.dataURI) { - log.warn('Missing data URI in extension block with argument type IMAGE'); + log.warn( + 'Missing data URI in extension block with argument type IMAGE' + ); } return { type: 'field_image', @@ -1296,8 +1410,13 @@ class Runtime extends EventEmitter { let argTypeInfo = ArgumentTypeMap[argInfo.type] || {}; // Field type not a standard field type, see if extension has registered custom field type - if (!ArgumentTypeMap[argInfo.type] && context.categoryInfo.customFieldTypes[argInfo.type]) { - argTypeInfo = context.categoryInfo.customFieldTypes[argInfo.type].argumentTypeInfo; + if ( + !ArgumentTypeMap[argInfo.type] && + context.categoryInfo.customFieldTypes[argInfo.type] + ) { + argTypeInfo = + context.categoryInfo.customFieldTypes[argInfo.type] + .argumentTypeInfo; } // Start to construct the scratch-blocks style JSON defining how the block should be @@ -1318,8 +1437,14 @@ class Runtime extends EventEmitter { }; const defaultValue = - typeof argInfo.defaultValue === 'undefined' ? '' : - xmlEscape(maybeFormatMessage(argInfo.defaultValue, this.makeMessageContextForTarget()).toString()); + typeof argInfo.defaultValue === 'undefined' ? + '' : + xmlEscape( + maybeFormatMessage( + argInfo.defaultValue, + this.makeMessageContextForTarget() + ).toString() + ); if (argTypeInfo.check) { // Right now the only type of 'check' we have specifies that the @@ -1335,7 +1460,10 @@ class Runtime extends EventEmitter { const menuInfo = context.categoryInfo.menuInfo[argInfo.menu]; if (menuInfo.acceptReporters) { valueName = placeholder; - shadowType = this._makeExtensionMenuId(argInfo.menu, context.categoryInfo.id); + shadowType = this._makeExtensionMenuId( + argInfo.menu, + context.categoryInfo.id + ); fieldName = argInfo.menu; } else { argJSON.type = 'field_dropdown'; @@ -1346,8 +1474,11 @@ class Runtime extends EventEmitter { } } else { valueName = placeholder; - shadowType = (argTypeInfo.shadow && argTypeInfo.shadow.type) || null; - fieldName = (argTypeInfo.shadow && argTypeInfo.shadow.fieldName) || null; + shadowType = + (argTypeInfo.shadow && argTypeInfo.shadow.type) || null; + fieldName = + (argTypeInfo.shadow && argTypeInfo.shadow.fieldName) || + null; } // <value> is the ScratchBlocks name for a block input. @@ -1364,7 +1495,9 @@ class Runtime extends EventEmitter { // A <field> displays a dynamic value: a user-editable text field, a drop-down menu, etc. // Leave out the field if defaultValue or fieldName are not specified if (defaultValue && fieldName) { - context.inputList.push(`<field name="${fieldName}">${defaultValue}</field>`); + context.inputList.push( + `<field name="${fieldName}">${defaultValue}</field>` + ); } if (shadowType) { @@ -1377,7 +1510,8 @@ class Runtime extends EventEmitter { } const argsName = `args${context.outLineNum}`; - const blockArgs = (context.blockJSON[argsName] = context.blockJSON[argsName] || []); + const blockArgs = (context.blockJSON[argsName] = + context.blockJSON[argsName] || []); if (argJSON) blockArgs.push(argJSON); const argNum = blockArgs.length; context.argsMap[placeholder] = argNum; @@ -1419,8 +1553,7 @@ class Runtime extends EventEmitter { } else if (categoryInfo.blockIconURI) { menuIconURI = categoryInfo.blockIconURI; } - const menuIconXML = menuIconURI ? - `iconURI="${menuIconURI}"` : ''; + const menuIconXML = menuIconURI ? `iconURI="${menuIconURI}"` : ''; let statusButtonXML = ''; if (categoryInfo.showStatusButton) { @@ -1429,8 +1562,11 @@ class Runtime extends EventEmitter { return { id: categoryInfo.id, - xml: `<category name="${name}" id="${categoryInfo.id}" ${statusButtonXML} ${colorXML} ${menuIconXML}>${ - paletteBlocks.map(block => block.xml).join('')}</category>` + xml: `<category name="${name}" id="${ + categoryInfo.id + }" ${statusButtonXML} ${colorXML} ${menuIconXML}>${paletteBlocks + .map(block => block.xml) + .join('')}</category>` }; }); } @@ -1440,7 +1576,12 @@ class Runtime extends EventEmitter { */ getBlocksJSON () { return this._blockInfo.reduce( - (result, categoryInfo) => result.concat(categoryInfo.blocks.map(blockInfo => blockInfo.json)), []); + (result, categoryInfo) => + result.concat( + categoryInfo.blocks.map(blockInfo => blockInfo.json) + ), + [] + ); } /** @@ -1449,7 +1590,8 @@ class Runtime extends EventEmitter { _initScratchLink () { // Check that we're actually in a real browser, not Node.js or JSDOM, and we have a valid-looking origin. // note that `if (self?....)` will throw if `self` is undefined, so check for that first! - if (typeof self !== 'undefined' && + if ( + typeof self !== 'undefined' && typeof document !== 'undefined' && document.getElementById && self.origin && @@ -1462,7 +1604,9 @@ class Runtime extends EventEmitter { ) ) { // Create a script tag for the Scratch Link browser extension, unless one already exists - const scriptElement = document.getElementById('scratch-link-extension-script'); + const scriptElement = document.getElementById( + 'scratch-link-extension-script' + ); if (!scriptElement) { const script = document.createElement('script'); script.id = 'scratch-link-extension-script'; @@ -1481,7 +1625,8 @@ class Runtime extends EventEmitter { * @returns {ScratchLinkSocket} The scratch link socket. */ getScratchLinkSocket (type) { - const factory = this._linkSocketFactory || this._defaultScratchLinkSocketFactory; + const factory = + this._linkSocketFactory || this._defaultScratchLinkSocketFactory; return factory(type); } @@ -1501,10 +1646,15 @@ class Runtime extends EventEmitter { */ _defaultScratchLinkSocketFactory (type) { const Scratch = self.Scratch; - const ScratchLinkSafariSocket = Scratch && Scratch.ScratchLinkSafariSocket; + const ScratchLinkSafariSocket = + Scratch && Scratch.ScratchLinkSafariSocket; // detect this every time in case the user turns on the extension after loading the page - const useSafariSocket = ScratchLinkSafariSocket && ScratchLinkSafariSocket.isSafariHelperCompatible(); - return useSafariSocket ? new ScratchLinkSafariSocket(type) : new ScratchLinkWebSocket(type); + const useSafariSocket = + ScratchLinkSafariSocket && + ScratchLinkSafariSocket.isSafariHelperCompatible(); + return useSafariSocket ? + new ScratchLinkSafariSocket(type) : + new ScratchLinkWebSocket(type); } /** @@ -1593,11 +1743,12 @@ class Runtime extends EventEmitter { * @return {boolean} True if the op is known to be a edge-activated hat. */ getIsEdgeActivatedHat (opcode) { - return Object.prototype.hasOwnProperty.call(this._hats, opcode) && - this._hats[opcode].edgeActivated; + return ( + Object.prototype.hasOwnProperty.call(this._hats, opcode) && + this._hats[opcode].edgeActivated + ); } - /** * Attach the audio engine * @param {!AudioEngine} audioEngine The audio engine to attach @@ -1701,10 +1852,10 @@ class Runtime extends EventEmitter { */ isActiveThread (thread) { return ( - ( - thread.stack.length > 0 && - thread.status !== Thread.STATUS_DONE) && - this.threads.indexOf(thread) > -1); + thread.stack.length > 0 && + thread.status !== Thread.STATUS_DONE && + this.threads.indexOf(thread) > -1 + ); } /** @@ -1729,18 +1880,29 @@ class Runtime extends EventEmitter { * determines whether we show a visual report when turning on the script. */ toggleScript (topBlockId, opts) { - opts = Object.assign({ - target: this._editingTarget, - stackClick: false - }, opts); + opts = Object.assign( + { + target: this._editingTarget, + stackClick: false + }, + opts + ); // Remove any existing thread. for (let i = 0; i < this.threads.length; i++) { // Toggling a script that's already running turns it off - if (this.threads[i].topBlock === topBlockId && this.threads[i].status !== Thread.STATUS_DONE) { + if ( + this.threads[i].topBlock === topBlockId && + this.threads[i].status !== Thread.STATUS_DONE + ) { const blockContainer = opts.target.blocks; - const opcode = blockContainer.getOpcode(blockContainer.getBlock(topBlockId)); + const opcode = blockContainer.getOpcode( + blockContainer.getBlock(topBlockId) + ); - if (this.getIsEdgeActivatedHat(opcode) && this.threads[i].stackClick !== opts.stackClick) { + if ( + this.getIsEdgeActivatedHat(opcode) && + this.threads[i].stackClick !== opts.stackClick + ) { // Allow edge activated hat thread stack click to coexist with // edge activated hat thread that runs every frame continue; @@ -1762,8 +1924,11 @@ class Runtime extends EventEmitter { if (!optTarget) optTarget = this._editingTarget; for (let i = 0; i < this.threads.length; i++) { // Don't re-add the script if it's already running - if (this.threads[i].topBlock === topBlockId && this.threads[i].status !== Thread.STATUS_DONE && - this.threads[i].updateMonitor) { + if ( + this.threads[i].topBlock === topBlockId && + this.threads[i].status !== Thread.STATUS_DONE && + this.threads[i].updateMonitor + ) { return; } } @@ -1801,7 +1966,10 @@ class Runtime extends EventEmitter { } for (let t = targets.length - 1; t >= 0; t--) { const target = targets[t]; - const scripts = BlocksRuntimeCache.getScripts(target.blocks, opcode); + const scripts = BlocksRuntimeCache.getScripts( + target.blocks, + opcode + ); for (let j = 0; j < scripts.length; j++) { f(scripts[j], target); } @@ -1815,9 +1983,13 @@ class Runtime extends EventEmitter { * @param {Target=} optTarget Optionally, a target to restrict to. * @return {Array.<Thread>} List of threads started by this function. */ - startHats (requestedHatOpcode, - optMatchFields, optTarget) { - if (!Object.prototype.hasOwnProperty.call(this._hats, requestedHatOpcode)) { + startHats (requestedHatOpcode, optMatchFields, optTarget) { + if ( + !Object.prototype.hasOwnProperty.call( + this._hats, + requestedHatOpcode + ) + ) { // No known hat with this opcode. return; } @@ -1827,58 +1999,71 @@ class Runtime extends EventEmitter { const hatMeta = instance._hats[requestedHatOpcode]; for (const opts in optMatchFields) { - if (!Object.prototype.hasOwnProperty.call(optMatchFields, opts)) continue; + if (!Object.prototype.hasOwnProperty.call(optMatchFields, opts)) { + continue; + } optMatchFields[opts] = optMatchFields[opts].toUpperCase(); } // Consider all scripts, looking for hats with opcode `requestedHatOpcode`. - this.allScriptsByOpcodeDo(requestedHatOpcode, (script, target) => { - const { - blockId: topBlockId, - fieldsOfInputs: hatFields - } = script; + this.allScriptsByOpcodeDo( + requestedHatOpcode, + (script, target) => { + const {blockId: topBlockId, fieldsOfInputs: hatFields} = + script; - // Match any requested fields. - // For example: ensures that broadcasts match. - // This needs to happen before the block is evaluated - // (i.e., before the predicate can be run) because "broadcast and wait" - // needs to have a precise collection of started threads. - for (const matchField in optMatchFields) { - if (hatFields[matchField].value !== optMatchFields[matchField]) { - // Field mismatch. - return; - } - } - - if (hatMeta.restartExistingThreads) { - // If `restartExistingThreads` is true, we should stop - // any existing threads starting with the top block. - for (let i = 0; i < this.threads.length; i++) { - if (this.threads[i].target === target && - this.threads[i].topBlock === topBlockId && - // stack click threads and hat threads can coexist - !this.threads[i].stackClick) { - newThreads.push(this._restartThread(this.threads[i])); + // Match any requested fields. + // For example: ensures that broadcasts match. + // This needs to happen before the block is evaluated + // (i.e., before the predicate can be run) because "broadcast and wait" + // needs to have a precise collection of started threads. + for (const matchField in optMatchFields) { + if ( + hatFields[matchField].value !== + optMatchFields[matchField] + ) { + // Field mismatch. return; } } - } else { - // If `restartExistingThreads` is false, we should - // give up if any threads with the top block are running. - for (let j = 0; j < this.threads.length; j++) { - if (this.threads[j].target === target && - this.threads[j].topBlock === topBlockId && - // stack click threads and hat threads can coexist - !this.threads[j].stackClick && - this.threads[j].status !== Thread.STATUS_DONE) { - // Some thread is already running. - return; + + if (hatMeta.restartExistingThreads) { + // If `restartExistingThreads` is true, we should stop + // any existing threads starting with the top block. + for (let i = 0; i < this.threads.length; i++) { + if ( + this.threads[i].target === target && + this.threads[i].topBlock === topBlockId && + // stack click threads and hat threads can coexist + !this.threads[i].stackClick + ) { + newThreads.push( + this._restartThread(this.threads[i]) + ); + return; + } + } + } else { + // If `restartExistingThreads` is false, we should + // give up if any threads with the top block are running. + for (let j = 0; j < this.threads.length; j++) { + if ( + this.threads[j].target === target && + this.threads[j].topBlock === topBlockId && + // stack click threads and hat threads can coexist + !this.threads[j].stackClick && + this.threads[j].status !== Thread.STATUS_DONE + ) { + // Some thread is already running. + return; + } } } - } - // Start the thread with this top block. - newThreads.push(this._pushThread(topBlockId, target)); - }, optTarget); + // Start the thread with this top block. + newThreads.push(this._pushThread(topBlockId, target)); + }, + optTarget + ); // For compatibility with Scratch 2, edge triggered hats need to be processed before // threads are stepped. See ScratchRuntime.as for original implementation newThreads.forEach(thread => { @@ -1888,7 +2073,6 @@ class Runtime extends EventEmitter { return newThreads; } - /** * Dispose all targets. Return to clean state. */ @@ -1920,8 +2104,10 @@ class Runtime extends EventEmitter { const newCloudDataManager = cloudDataManager(); this.hasCloudData = newCloudDataManager.hasCloudVariables; this.canAddCloudVariable = newCloudDataManager.canAddCloudVariable; - this.addCloudVariable = this._initializeAddCloudVariable(newCloudDataManager); - this.removeCloudVariable = this._initializeRemoveCloudVariable(newCloudDataManager); + this.addCloudVariable = + this._initializeAddCloudVariable(newCloudDataManager); + this.removeCloudVariable = + this._initializeRemoveCloudVariable(newCloudDataManager); } /** @@ -1953,7 +2139,10 @@ class Runtime extends EventEmitter { newIndex = this.executableTargets.length; } if (newIndex <= 0) { - if (this.executableTargets.length > 0 && this.executableTargets[0].isStage) { + if ( + this.executableTargets.length > 0 && + this.executableTargets[0].isStage + ) { newIndex = 1; } else { newIndex = 0; @@ -2033,7 +2222,10 @@ class Runtime extends EventEmitter { } const newRunId = uuid.v1(); - this.storage.scratchFetch.setMetadata(this.storage.scratchFetch.RequestMetadata.RunId, newRunId); + this.storage.scratchFetch.setMetadata( + this.storage.scratchFetch.RequestMetadata.RunId, + newRunId + ); } /** @@ -2062,8 +2254,13 @@ class Runtime extends EventEmitter { const newTargets = []; for (let i = 0; i < this.targets.length; i++) { this.targets[i].onStopAll(); - if (Object.prototype.hasOwnProperty.call(this.targets[i], 'isOriginal') && - !this.targets[i].isOriginal) { + if ( + Object.prototype.hasOwnProperty.call( + this.targets[i], + 'isOriginal' + ) && + !this.targets[i].isOriginal + ) { this.targets[i].dispose(); } else { newTargets.push(this.targets[i]); @@ -2097,7 +2294,9 @@ class Runtime extends EventEmitter { // Find all edge-activated hats, and add them to threads to be evaluated. for (const hatType in this._hats) { - if (!Object.prototype.hasOwnProperty.call(this._hats, hatType)) continue; + if (!Object.prototype.hasOwnProperty.call(this._hats, hatType)) { + continue; + } const hat = this._hats[hatType]; if (hat.edgeActivated) { this.startHats(hatType); @@ -2107,7 +2306,9 @@ class Runtime extends EventEmitter { this._pushMonitors(); if (this.profiler !== null) { if (stepThreadsProfilerId === -1) { - stepThreadsProfilerId = this.profiler.idByName('Sequencer.stepThreads'); + stepThreadsProfilerId = this.profiler.idByName( + 'Sequencer.stepThreads' + ); } this.profiler.start(stepThreadsProfilerId); } @@ -2119,8 +2320,10 @@ class Runtime extends EventEmitter { // Add done threads so that even if a thread finishes within 1 frame, the green // flag will still indicate that a script ran. this._emitProjectRunStatus( - this.threads.length + doneThreads.length - - this._getMonitorThreadCount([...this.threads, ...doneThreads])); + this.threads.length + + doneThreads.length - + this._getMonitorThreadCount([...this.threads, ...doneThreads]) + ); // Store threads that completed this iteration for testing and other // internal purposes. this._lastStepDoneThreads = doneThreads; @@ -2128,7 +2331,8 @@ class Runtime extends EventEmitter { // @todo: Only render when this.redrawRequested or clones rendered. if (this.profiler !== null) { if (rendererDrawProfilerId === -1) { - rendererDrawProfilerId = this.profiler.idByName('RenderWebGL.draw'); + rendererDrawProfilerId = + this.profiler.idByName('RenderWebGL.draw'); } this.profiler.start(rendererDrawProfilerId); } @@ -2139,7 +2343,10 @@ class Runtime extends EventEmitter { } if (this._refreshTargets) { - this.emit(Runtime.TARGETS_UPDATE, false /* Don't emit project changed */); + this.emit( + Runtime.TARGETS_UPDATE, + false /* Don't emit project changed */ + ); this._refreshTargets = false; } @@ -2226,12 +2433,12 @@ class Runtime extends EventEmitter { if (target === this._editingTarget) { const blockForThread = thread.blockGlowInFrame; if (thread.requestScriptGlowInFrame || thread.stackClick) { - let script = target.blocks.getTopLevelScript(blockForThread); + let script = + target.blocks.getTopLevelScript(blockForThread); if (!script) { // Attempt to find in flyout blocks. - script = this.flyoutBlocks.getTopLevelScript( - blockForThread - ); + script = + this.flyoutBlocks.getTopLevelScript(blockForThread); } if (script) { requestedGlowsThisFrame.push(script); @@ -2349,7 +2556,8 @@ class Runtime extends EventEmitter { */ requestAddMonitor (monitor) { const id = monitor.get('id'); - if (!this.requestUpdateMonitor(monitor)) { // update monitor if it exists in the state + if (!this.requestUpdateMonitor(monitor)) { + // update monitor if it exists in the state // if the monitor did not exist in the state, add it this._monitorState = this._monitorState.set(id, monitor); } @@ -2367,12 +2575,15 @@ class Runtime extends EventEmitter { if (this._monitorState.has(id)) { this._monitorState = // Use mergeWith here to prevent undefined values from overwriting existing ones - this._monitorState.set(id, this._monitorState.get(id).mergeWith((prev, next) => { - if (typeof next === 'undefined' || next === null) { - return prev; - } - return next; - }, monitor)); + this._monitorState.set( + id, + this._monitorState.get(id).mergeWith((prev, next) => { + if (typeof next === 'undefined' || next === null) { + return prev; + } + return next; + }, monitor) + ); return true; } return false; @@ -2393,10 +2604,12 @@ class Runtime extends EventEmitter { * @return {boolean} true if monitor exists and was updated, false otherwise */ requestHideMonitor (monitorId) { - return this.requestUpdateMonitor(new Map([ - ['id', monitorId], - ['visible', false] - ])); + return this.requestUpdateMonitor( + new Map([ + ['id', monitorId], + ['visible', false] + ]) + ); } /** @@ -2406,10 +2619,12 @@ class Runtime extends EventEmitter { * @return {boolean} true if monitor exists and was updated, false otherwise */ requestShowMonitor (monitorId) { - return this.requestUpdateMonitor(new Map([ - ['id', monitorId], - ['visible', true] - ])); + return this.requestUpdateMonitor( + new Map([ + ['id', monitorId], + ['visible', true] + ]) + ); } /** @@ -2418,7 +2633,9 @@ class Runtime extends EventEmitter { * @param {!string} targetId Remove all monitors with given target ID. */ requestRemoveMonitorByTargetId (targetId) { - this._monitorState = this._monitorState.filterNot(value => value.targetId === targetId); + this._monitorState = this._monitorState.filterNot( + value => value.targetId === targetId + ); } /** @@ -2538,7 +2755,10 @@ class Runtime extends EventEmitter { getAllVarNamesOfType (varType) { let varNames = []; for (const target of this.targets) { - const targetVarNames = target.getAllVariableNamesInScopeByType(varType, true); + const targetVarNames = target.getAllVariableNamesInScopeByType( + varType, + true + ); varNames = varNames.concat(targetVarNames); } return varNames; @@ -2579,7 +2799,8 @@ class Runtime extends EventEmitter { * @return {Variable} The new variable that was created. */ createNewGlobalVariable (variableName, optVarId, optVarType) { - const varType = (typeof optVarType === 'string') ? optVarType : Variable.SCALAR_TYPE; + const varType = + typeof optVarType === 'string' ? optVarType : Variable.SCALAR_TYPE; const allVariableNames = this.getAllVarNamesOfType(varType); const newName = StringUtil.unusedName(variableName, allVariableNames); const variable = new Variable(optVarId || uid(), newName, varType);