From 413d113dda2c8ba738ab850be316f64be6123b96 Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Fri, 8 Dec 2017 12:19:11 -0500 Subject: [PATCH 01/33] Adding support for blocks to be plugged into the input of broadcast blocks. SB2 Import to come. --- src/blocks/scratch3_event.js | 25 +++++++++++++++++++++---- src/engine/execute.js | 29 ++++++++++++++++++++++++++++- src/engine/target.js | 19 ++++++++++++++++++- 3 files changed, 67 insertions(+), 6 deletions(-) diff --git a/src/blocks/scratch3_event.js b/src/blocks/scratch3_event.js index 5ee2fab1df..3b4050b62d 100644 --- a/src/blocks/scratch3_event.js +++ b/src/blocks/scratch3_event.js @@ -55,9 +55,27 @@ class Scratch3EventBlocks { return false; } + /** + * Helper function to process broadcast block input (whether it's + * input from the dropdown menu or from a plugged in input block) + * @param {object} args The given arguments for the broadcast blocks + * @param {object} util The utility associated with this block. + * @return {?Variable} The broadcast message variable that matches + * the provided input. + */ + processBroadcastInput_ (args, util) { + let broadcastInput; + if (args.BROADCAST_OPTION) { + broadcastInput = util.runtime.getTargetForStage().lookupBroadcastMsg( + args.BROADCAST_OPTION.id, args.BROADCAST_OPTION.name); + } else { + broadcastInput = util.runtime.getTargetForStage().lookupBroadcastByInputValue(args.BROADCAST_INPUT.name); + } + return broadcastInput; + } + broadcast (args, util) { - const broadcastVar = util.runtime.getTargetForStage().lookupBroadcastMsg( - args.BROADCAST_OPTION.id, args.BROADCAST_OPTION.name); + const broadcastVar = this.processBroadcastInput_(args, util); if (broadcastVar) { const broadcastOption = broadcastVar.name; util.startHats('event_whenbroadcastreceived', { @@ -67,8 +85,7 @@ class Scratch3EventBlocks { } broadcastAndWait (args, util) { - const broadcastVar = util.runtime.getTargetForStage().lookupBroadcastMsg( - args.BROADCAST_OPTION.id, args.BROADCAST_OPTION.name); + const broadcastVar = this.processBroadcastInput_(args, util); if (broadcastVar) { const broadcastOption = broadcastVar.name; // Have we run before, starting threads? diff --git a/src/engine/execute.js b/src/engine/execute.js index 7dcf80d84a..a4b9404d89 100644 --- a/src/engine/execute.js +++ b/src/engine/execute.js @@ -2,6 +2,7 @@ const BlockUtility = require('./block-utility'); const log = require('../util/log'); const Thread = require('./thread'); const {Map} = require('immutable'); +const cast = require('../util/cast'); /** * Single BlockUtility instance reused by execute for every pritimive ran. @@ -211,7 +212,33 @@ const execute = function (sequencer, thread) { currentStackFrame.waitingReporter = null; thread.popStack(); } - argValues[inputName] = currentStackFrame.reported[inputName]; + const inputValue = currentStackFrame.reported[inputName]; + if (inputName === 'BROADCAST_INPUT') { + const broadcastInput = inputs[inputName]; + // Check if something is plugged into the broadcast block, or + // if the shadow dropdown menu is being used. + // Differentiate between these two cases by giving argValues + // a 'BROADCAST_INPUT' field or a 'BROADCAST_OPTION' field + // respectively. + if (broadcastInput.block === broadcastInput.shadow) { + // Shadow dropdown menu is being used. + // Get the appropriate information out of it. + const shadow = blockContainer.getBlock(broadcastInput.shadow); + const broadcastField = shadow.fields.BROADCAST_OPTION; + argValues.BROADCAST_OPTION = { + id: broadcastField.id, + name: broadcastField.value + }; + } else { + // Something is plugged into the broadcast input. + // Cast it to a string. We don't need an id here. + argValues.BROADCAST_INPUT = { + name: cast.toString(inputValue) + }; + } + } else { + argValues[inputName] = inputValue; + } } // Add any mutation to args (e.g., for procedures). diff --git a/src/engine/target.js b/src/engine/target.js index 4e574177ea..480b1e83c1 100644 --- a/src/engine/target.js +++ b/src/engine/target.js @@ -115,6 +115,23 @@ class Target extends EventEmitter { } } + /** + * Look up a broadcast message with the given name and return the variable + * if it exists. Does not create a new broadcast message variable if + * it doesn't exist. + * @param {string} name Name of the variable. + * @return {?Variable} Variable object. + */ + lookupBroadcastByInputValue (name) { + const vars = this.variables; + for (const propName in vars) { + if ((vars[propName].type === Variable.BROADCAST_MESSAGE_TYPE) && + (vars[propName].name.toLowerCase() === name.toLowerCase())) { + return vars[propName]; + } + } + } + /** * Look up a variable object. * Search begins for local variables; then look for globals. @@ -141,7 +158,7 @@ class Target extends EventEmitter { * Search begins for local lists; then look for globals. * @param {!string} id Id of the list. * @param {!string} name Name of the list. - * @return {!List} List object. + * @return {!Varible} Variable object representing the found/created list. */ lookupOrCreateList (id, name) { const list = this.lookupVariableById(id); From c0e4ae455c42c49927d6a66d2fcb1213cc4eefe5 Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Wed, 13 Dec 2017 13:41:22 -0500 Subject: [PATCH 02/33] Streamlining broadcast block execution. When arg information is provided to execute function, corresponding broadcast message is looked up by id, if provided, or if not, then name. If neither id nor name is provided, we can't look up the broadcast message and something is wrong, so an error is logged. We no longer need to differentiate between the shadow menu vs. plugged in input cases via the argValue name (e.g. 'BROADCAST_OPTION' vs. 'BROADCAST_INPUT'). This refactor also helps with sb2 import, coming in the next commit. --- src/blocks/scratch3_event.js | 25 ++++--------------------- src/engine/execute.js | 5 +---- src/engine/target.js | 13 ++++++++++--- 3 files changed, 15 insertions(+), 28 deletions(-) diff --git a/src/blocks/scratch3_event.js b/src/blocks/scratch3_event.js index 3b4050b62d..5ee2fab1df 100644 --- a/src/blocks/scratch3_event.js +++ b/src/blocks/scratch3_event.js @@ -55,27 +55,9 @@ class Scratch3EventBlocks { return false; } - /** - * Helper function to process broadcast block input (whether it's - * input from the dropdown menu or from a plugged in input block) - * @param {object} args The given arguments for the broadcast blocks - * @param {object} util The utility associated with this block. - * @return {?Variable} The broadcast message variable that matches - * the provided input. - */ - processBroadcastInput_ (args, util) { - let broadcastInput; - if (args.BROADCAST_OPTION) { - broadcastInput = util.runtime.getTargetForStage().lookupBroadcastMsg( - args.BROADCAST_OPTION.id, args.BROADCAST_OPTION.name); - } else { - broadcastInput = util.runtime.getTargetForStage().lookupBroadcastByInputValue(args.BROADCAST_INPUT.name); - } - return broadcastInput; - } - broadcast (args, util) { - const broadcastVar = this.processBroadcastInput_(args, util); + const broadcastVar = util.runtime.getTargetForStage().lookupBroadcastMsg( + args.BROADCAST_OPTION.id, args.BROADCAST_OPTION.name); if (broadcastVar) { const broadcastOption = broadcastVar.name; util.startHats('event_whenbroadcastreceived', { @@ -85,7 +67,8 @@ class Scratch3EventBlocks { } broadcastAndWait (args, util) { - const broadcastVar = this.processBroadcastInput_(args, util); + const broadcastVar = util.runtime.getTargetForStage().lookupBroadcastMsg( + args.BROADCAST_OPTION.id, args.BROADCAST_OPTION.name); if (broadcastVar) { const broadcastOption = broadcastVar.name; // Have we run before, starting threads? diff --git a/src/engine/execute.js b/src/engine/execute.js index a4b9404d89..13742ec768 100644 --- a/src/engine/execute.js +++ b/src/engine/execute.js @@ -217,9 +217,6 @@ const execute = function (sequencer, thread) { const broadcastInput = inputs[inputName]; // Check if something is plugged into the broadcast block, or // if the shadow dropdown menu is being used. - // Differentiate between these two cases by giving argValues - // a 'BROADCAST_INPUT' field or a 'BROADCAST_OPTION' field - // respectively. if (broadcastInput.block === broadcastInput.shadow) { // Shadow dropdown menu is being used. // Get the appropriate information out of it. @@ -232,7 +229,7 @@ const execute = function (sequencer, thread) { } else { // Something is plugged into the broadcast input. // Cast it to a string. We don't need an id here. - argValues.BROADCAST_INPUT = { + argValues.BROADCAST_OPTION = { name: cast.toString(inputValue) }; } diff --git a/src/engine/target.js b/src/engine/target.js index 480b1e83c1..9861c455d6 100644 --- a/src/engine/target.js +++ b/src/engine/target.js @@ -98,12 +98,19 @@ class Target extends EventEmitter { * if it exists. * @param {string} id Id of the variable. * @param {string} name Name of the variable. - * @return {!Variable} Variable object. + * @return {?Variable} Variable object. */ lookupBroadcastMsg (id, name) { - const broadcastMsg = this.lookupVariableById(id); + let broadcastMsg; + if (id) { + broadcastMsg = this.lookupVariableById(id); + } else if (name) { + broadcastMsg = this.lookupBroadcastByInputValue(name); + } else { + log.error('Cannot find broadcast message if neither id nor name are provided.'); + } if (broadcastMsg) { - if (broadcastMsg.name !== name) { + if (name && (broadcastMsg.name.toLowerCase() !== name.toLowerCase())) { log.error(`Found broadcast message with id: ${id}, but` + `its name, ${broadcastMsg.name} did not match expected name ${name}.`); } From 154987fbb064f64c99067d8ca2046b25c15f50ad Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Wed, 13 Dec 2017 17:15:18 -0500 Subject: [PATCH 03/33] SB2 Import functionality for broadcast blocks with pluggable inputs. --- src/serialization/sb2.js | 37 +++++++++++++++++++++++++++----- src/serialization/sb2_specmap.js | 10 +++++---- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/src/serialization/sb2.js b/src/serialization/sb2.js index d19175dca5..a8b750b3da 100644 --- a/src/serialization/sb2.js +++ b/src/serialization/sb2.js @@ -170,12 +170,17 @@ const generateVariableIdGetter = (function () { const globalBroadcastMsgStateGenerator = (function () { let broadcastMsgNameMap = {}; + const emptyStringName = uid(); return function (topLevel) { if (topLevel) broadcastMsgNameMap = {}; return { broadcastMsgMapUpdater: function (name) { + name = name.toLowerCase(); + if (name === '') { + name = emptyStringName; + } broadcastMsgNameMap[name] = `broadcastMsgId-${name}`; - return broadcastMsgNameMap[name]; + return {name: name, id: broadcastMsgNameMap[name]}; }, globalBroadcastMsgs: broadcastMsgNameMap }; @@ -494,15 +499,33 @@ const parseBlock = function (sb2block, addBroadcastMsg, getVariableId, extension if (shadowObscured) { fieldValue = '#990000'; } + } else if (expectedArg.inputOp === 'event_broadcast_menu') { + fieldName = 'BROADCAST_OPTION'; + if (shadowObscured) { + fieldValue = ''; + } } else if (shadowObscured) { // Filled drop-down menu. fieldValue = ''; } const fields = {}; fields[fieldName] = { - name: fieldName, - value: fieldValue + name: fieldName }; + // event_broadcast_menus have some extra properties to add to the + // field and a different value than the rest + if (expectedArg.inputOp === 'event_broadcast_menu') { + if (!shadowObscured) { + const broadcastInfo = addBroadcastMsg(fieldValue); + fields[fieldName].id = broadcastInfo.id; + // Re-assign the value, because the name could have changed + // if the scratch2 message was an empty string + fields[fieldName].value = broadcastInfo.name; + } + fields[fieldName].variableType = expectedArg.variableType; + } else { + fields[fieldName].value = fieldValue; + } activeBlock.children.push({ id: inputUid, opcode: expectedArg.inputOp, @@ -530,8 +553,12 @@ const parseBlock = function (sb2block, addBroadcastMsg, getVariableId, extension activeBlock.fields[expectedArg.fieldName].id = getVariableId(providedArg); } else if (expectedArg.fieldName === 'BROADCAST_OPTION') { // add the name in this field to the broadcast msg name map - const broadcastId = addBroadcastMsg(providedArg); - activeBlock.fields[expectedArg.fieldName].id = broadcastId; + const broadcastInfo = addBroadcastMsg(providedArg); + activeBlock.fields[expectedArg.fieldName].id = broadcastInfo.id; + // Need to reassign field value using the sb3 name from broadcastInfo + // because the sb2 message name (e.g. providedArg) could have changed + // if the original (providedArg) was an empty string + activeBlock.fields[expectedArg.fieldName].value = broadcastInfo.name; } const varType = expectedArg.variableType; if (typeof varType === 'string') { diff --git a/src/serialization/sb2_specmap.js b/src/serialization/sb2_specmap.js index cc0a569640..b829fc1745 100644 --- a/src/serialization/sb2_specmap.js +++ b/src/serialization/sb2_specmap.js @@ -665,8 +665,9 @@ const specMap = { opcode: 'event_broadcast', argMap: [ { - type: 'field', - fieldName: 'BROADCAST_OPTION', + type: 'input', + inputOp: 'event_broadcast_menu', + inputName: 'BROADCAST_INPUT', variableType: Variable.BROADCAST_MESSAGE_TYPE } ] @@ -675,8 +676,9 @@ const specMap = { opcode: 'event_broadcastandwait', argMap: [ { - type: 'field', - fieldName: 'BROADCAST_OPTION', + type: 'input', + inputOp: 'event_broadcast_menu', + inputName: 'BROADCAST_INPUT', variableType: Variable.BROADCAST_MESSAGE_TYPE } ] From bcbe91064370bbe52f21cc0a66b165d3cf72b7f2 Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Fri, 15 Dec 2017 11:02:26 -0500 Subject: [PATCH 04/33] Translating empty string messages from sb2 into messageN, where N is an integer >= n such that messageN is a fresh name. --- src/serialization/sb2.js | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/serialization/sb2.js b/src/serialization/sb2.js index a8b750b3da..313768dc21 100644 --- a/src/serialization/sb2.js +++ b/src/serialization/sb2.js @@ -170,19 +170,23 @@ const generateVariableIdGetter = (function () { const globalBroadcastMsgStateGenerator = (function () { let broadcastMsgNameMap = {}; + const allBroadcastInputsAndFields = []; const emptyStringName = uid(); return function (topLevel) { if (topLevel) broadcastMsgNameMap = {}; return { - broadcastMsgMapUpdater: function (name) { + broadcastMsgMapUpdater: function (name, block) { name = name.toLowerCase(); if (name === '') { name = emptyStringName; } broadcastMsgNameMap[name] = `broadcastMsgId-${name}`; + allBroadcastInputsAndFields.push(block); return {name: name, id: broadcastMsgNameMap[name]}; }, - globalBroadcastMsgs: broadcastMsgNameMap + globalBroadcastMsgs: broadcastMsgNameMap, + allBroadcastInputsAndFields: allBroadcastInputsAndFields, + emptyMsgName: emptyStringName }; }; }()); @@ -345,6 +349,23 @@ const parseScratchObject = function (object, runtime, extensions, topLevel) { // all other targets have finished processing. if (target.isStage) { const allBroadcastMsgs = globalBroadcastMsgObj.globalBroadcastMsgs; + const allBroadcastMsgInputsAndFields = globalBroadcastMsgObj.allBroadcastInputsAndFields; + const emptyName = globalBroadcastMsgObj.emptyMsgName; + if (allBroadcastMsgs[emptyName]) { + // look through allBroadcastMsgs to see if 'messageN' is used + let currIndex = 1; + while (allBroadcastMsgs[`message${currIndex}`]) { + currIndex += 1; + } + const newEmptyMsgName = `message${currIndex}`; + allBroadcastMsgs[newEmptyMsgName] = allBroadcastMsgs[emptyName]; + delete allBroadcastMsgs[emptyName]; + for (let i = 0; i < allBroadcastMsgInputsAndFields.length; i++) { + if (allBroadcastMsgInputsAndFields[i].value === emptyName) { + allBroadcastMsgInputsAndFields[i].value = newEmptyMsgName; + } + } + } for (const msgName in allBroadcastMsgs) { const msgId = allBroadcastMsgs[msgName]; const newMsg = new Variable( @@ -516,7 +537,7 @@ const parseBlock = function (sb2block, addBroadcastMsg, getVariableId, extension // field and a different value than the rest if (expectedArg.inputOp === 'event_broadcast_menu') { if (!shadowObscured) { - const broadcastInfo = addBroadcastMsg(fieldValue); + const broadcastInfo = addBroadcastMsg(fieldValue, fields[fieldName]); fields[fieldName].id = broadcastInfo.id; // Re-assign the value, because the name could have changed // if the scratch2 message was an empty string @@ -553,7 +574,7 @@ const parseBlock = function (sb2block, addBroadcastMsg, getVariableId, extension activeBlock.fields[expectedArg.fieldName].id = getVariableId(providedArg); } else if (expectedArg.fieldName === 'BROADCAST_OPTION') { // add the name in this field to the broadcast msg name map - const broadcastInfo = addBroadcastMsg(providedArg); + const broadcastInfo = addBroadcastMsg(providedArg, activeBlock.fields[expectedArg.fieldName]); activeBlock.fields[expectedArg.fieldName].id = broadcastInfo.id; // Need to reassign field value using the sb3 name from broadcastInfo // because the sb2 message name (e.g. providedArg) could have changed From 720c22db0e8dacf717f7c916bc58162a13428247 Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Fri, 15 Dec 2017 14:00:53 -0500 Subject: [PATCH 05/33] Cleanup/refactoring. --- src/serialization/sb2.js | 72 ++++++++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 29 deletions(-) diff --git a/src/serialization/sb2.js b/src/serialization/sb2.js index 313768dc21..40304ce2d6 100644 --- a/src/serialization/sb2.js +++ b/src/serialization/sb2.js @@ -170,22 +170,22 @@ const generateVariableIdGetter = (function () { const globalBroadcastMsgStateGenerator = (function () { let broadcastMsgNameMap = {}; - const allBroadcastInputsAndFields = []; + const allBroadcastFields = []; const emptyStringName = uid(); return function (topLevel) { if (topLevel) broadcastMsgNameMap = {}; return { - broadcastMsgMapUpdater: function (name, block) { + broadcastMsgMapUpdater: function (name, field) { name = name.toLowerCase(); if (name === '') { name = emptyStringName; } broadcastMsgNameMap[name] = `broadcastMsgId-${name}`; - allBroadcastInputsAndFields.push(block); - return {name: name, id: broadcastMsgNameMap[name]}; + allBroadcastFields.push(field); + return broadcastMsgNameMap[name]; }, globalBroadcastMsgs: broadcastMsgNameMap, - allBroadcastInputsAndFields: allBroadcastInputsAndFields, + allBroadcastFields: allBroadcastFields, emptyMsgName: emptyStringName }; }; @@ -349,23 +349,31 @@ const parseScratchObject = function (object, runtime, extensions, topLevel) { // all other targets have finished processing. if (target.isStage) { const allBroadcastMsgs = globalBroadcastMsgObj.globalBroadcastMsgs; - const allBroadcastMsgInputsAndFields = globalBroadcastMsgObj.allBroadcastInputsAndFields; - const emptyName = globalBroadcastMsgObj.emptyMsgName; - if (allBroadcastMsgs[emptyName]) { - // look through allBroadcastMsgs to see if 'messageN' is used + const allBroadcastMsgFields = globalBroadcastMsgObj.allBroadcastFields; + const oldEmptyMsgName = globalBroadcastMsgObj.emptyMsgName; + if (allBroadcastMsgs[oldEmptyMsgName]) { + // Find a fresh 'messageN' let currIndex = 1; while (allBroadcastMsgs[`message${currIndex}`]) { currIndex += 1; } const newEmptyMsgName = `message${currIndex}`; - allBroadcastMsgs[newEmptyMsgName] = allBroadcastMsgs[emptyName]; - delete allBroadcastMsgs[emptyName]; - for (let i = 0; i < allBroadcastMsgInputsAndFields.length; i++) { - if (allBroadcastMsgInputsAndFields[i].value === emptyName) { - allBroadcastMsgInputsAndFields[i].value = newEmptyMsgName; + // Add the new empty message name to the broadcast message + // name map, and assign it the old id. + // Then, delete the old entry in map. + allBroadcastMsgs[newEmptyMsgName] = allBroadcastMsgs[oldEmptyMsgName]; + delete allBroadcastMsgs[oldEmptyMsgName]; + // Now update all the broadcast message fields with + // the new empty message name. + for (let i = 0; i < allBroadcastMsgFields.length; i++) { + if (allBroadcastMsgFields[i].value === '') { + allBroadcastMsgFields[i].value = newEmptyMsgName; } } } + // Traverse the broadcast message name map and create + // broadcast messages as variables on the stage (which is this + // target). for (const msgName in allBroadcastMsgs) { const msgId = allBroadcastMsgs[msgName]; const newMsg = new Variable( @@ -531,21 +539,25 @@ const parseBlock = function (sb2block, addBroadcastMsg, getVariableId, extension } const fields = {}; fields[fieldName] = { - name: fieldName + name: fieldName, + value: fieldValue }; // event_broadcast_menus have some extra properties to add to the // field and a different value than the rest if (expectedArg.inputOp === 'event_broadcast_menu') { if (!shadowObscured) { - const broadcastInfo = addBroadcastMsg(fieldValue, fields[fieldName]); - fields[fieldName].id = broadcastInfo.id; - // Re-assign the value, because the name could have changed - // if the scratch2 message was an empty string - fields[fieldName].value = broadcastInfo.name; + // Need to update the broadcast message name map with + // the value of this field. + // Also need to provide the fields[fieldName] object, + // so that we can later update its value property, e.g. + // if sb2 message name is empty string, we will later + // replace this field's value with messageN + // once we can traverse through all the existing message names + // and come up with a fresh messageN. + const broadcastId = addBroadcastMsg(fieldValue, fields[fieldName]); + fields[fieldName].id = broadcastId; } fields[fieldName].variableType = expectedArg.variableType; - } else { - fields[fieldName].value = fieldValue; } activeBlock.children.push({ id: inputUid, @@ -573,13 +585,15 @@ const parseBlock = function (sb2block, addBroadcastMsg, getVariableId, extension // Add `id` property to variable fields activeBlock.fields[expectedArg.fieldName].id = getVariableId(providedArg); } else if (expectedArg.fieldName === 'BROADCAST_OPTION') { - // add the name in this field to the broadcast msg name map - const broadcastInfo = addBroadcastMsg(providedArg, activeBlock.fields[expectedArg.fieldName]); - activeBlock.fields[expectedArg.fieldName].id = broadcastInfo.id; - // Need to reassign field value using the sb3 name from broadcastInfo - // because the sb2 message name (e.g. providedArg) could have changed - // if the original (providedArg) was an empty string - activeBlock.fields[expectedArg.fieldName].value = broadcastInfo.name; + // Add the name in this field to the broadcast msg name map. + // Also need to provide the fields[fieldName] object, + // so that we can later update its value property, e.g. + // if sb2 message name is empty string, we will later + // replace this field's value with messageN + // once we can traverse through all the existing message names + // and come up with a fresh messageN. + const broadcastId = addBroadcastMsg(providedArg, activeBlock.fields[expectedArg.fieldName]); + activeBlock.fields[expectedArg.fieldName].id = broadcastId; } const varType = expectedArg.variableType; if (typeof varType === 'string') { From 6fc554daa5a2600f96b78f9233724d2401ac5615 Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Thu, 21 Dec 2017 18:34:19 -0500 Subject: [PATCH 06/33] Implicit deletion behavior for broadcast blocks. --- src/virtual-machine.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/virtual-machine.js b/src/virtual-machine.js index e0bb9961b4..7b997dbdd8 100644 --- a/src/virtual-machine.js +++ b/src/virtual-machine.js @@ -8,6 +8,7 @@ const sb2 = require('./serialization/sb2'); const sb3 = require('./serialization/sb3'); const StringUtil = require('./util/string-util'); const formatMessage = require('format-message'); +const Variable = require('./engine/variable'); const {loadCostume} = require('./import/load-costume.js'); const {loadSound} = require('./import/load-sound.js'); @@ -677,6 +678,35 @@ class VirtualMachine extends EventEmitter { * of the current editing target's blocks. */ emitWorkspaceUpdate () { + // Create a list of broadcast message Ids according to the stage variables + const stageVariables = this.runtime.getTargetForStage().variables; + let messageIds = []; + for (const varId in stageVariables) { + if (stageVariables[varId].type === Variable.BROADCAST_MESSAGE_TYPE) { + messageIds.push(varId); + } + } + // Go through all blocks on all targets, removing referenced + // broadcast ids from the list. + for (let i = 0; i < this.runtime.targets.length; i++) { + const currTarget = this.runtime.targets[i]; + const currBlocks = currTarget.blocks._blocks; + for (const blockId in currBlocks) { + if (currBlocks[blockId].fields.BROADCAST_OPTION) { + const id = currBlocks[blockId].fields.BROADCAST_OPTION.id; + const index = messageIds.indexOf(id); + if (index !== -1) { + messageIds = messageIds.slice(0, index) + .concat(messageIds.slice(index + 1)); + } + } + } + } + // Anything left in messageIds is not referenced by a block, so delete it. + for (let i = 0; i < messageIds.length; i++) { + const id = messageIds[i]; + delete this.runtime.getTargetForStage().variables[id]; + } const variableMap = Object.assign({}, this.runtime.getTargetForStage().variables, this.editingTarget.variables From 29c08c3583985bd1b9af6f867bdf179a17ca0ced Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Thu, 21 Dec 2017 18:44:29 -0500 Subject: [PATCH 07/33] Fixing unit test. --- test/unit/virtual-machine.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/unit/virtual-machine.js b/test/unit/virtual-machine.js index 40bd6bb541..8dd283cf6b 100644 --- a/test/unit/virtual-machine.js +++ b/test/unit/virtual-machine.js @@ -287,12 +287,18 @@ test('emitWorkspaceUpdate', t => { global: { toXML: () => 'global' } + }, + blocks: { + toXML: () => 'blocks' } }, { variables: { unused: { toXML: () => 'unused' } + }, + blocks: { + toXML: () => 'blocks' } }, { variables: { From 5dbffe67ac55d7cddd4fe6a0234721e3066d1eb9 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford Date: Fri, 22 Dec 2017 10:47:11 -0800 Subject: [PATCH 08/33] Fix legacy `setPenHueToNumber` and `changePenHueBy` These two blocks were not doing the same shade-oriented math as `setPenShadeToNumber`: effectively these blocks were using the new color model instead of Scratch 2.0's hue-shade model. This change pulls the hue-shade color update logic into a separate function, now called by all the legacy pen blocks. --- src/extensions/scratch3_pen/index.js | 44 ++++++++++++++++++---------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/src/extensions/scratch3_pen/index.js b/src/extensions/scratch3_pen/index.js index b3a8818d39..e4a0ea8439 100644 --- a/src/extensions/scratch3_pen/index.js +++ b/src/extensions/scratch3_pen/index.js @@ -673,6 +673,8 @@ class Scratch3PenBlocks { const hueValue = Cast.toNumber(args.HUE); const colorValue = hueValue / 2; this._setOrChangeColorParam(ColorParam.COLOR, colorValue, penState, false); + + this._legacyUpdatePenColor(penState); } /** @@ -686,6 +688,8 @@ class Scratch3PenBlocks { const hueChange = Cast.toNumber(args.HUE); const colorChange = hueChange / 2; this._setOrChangeColorParam(ColorParam.COLOR, colorChange, penState, true); + + this._legacyUpdatePenColor(penState); } /** @@ -705,25 +709,10 @@ class Scratch3PenBlocks { newShade = newShade % 200; if (newShade < 0) newShade += 200; - // Create the new color in RGB using the scratch 2 "shade" model - let rgb = Color.hsvToRgb({h: penState.color * 360 / 100, s: 1, v: 1}); - const shade = (newShade > 100) ? 200 - newShade : newShade; - if (shade < 50) { - rgb = Color.mixRgb(Color.RGB_BLACK, rgb, (10 + shade) / 60); - } else { - rgb = Color.mixRgb(rgb, Color.RGB_WHITE, (shade - 50) / 60); - } - - // Update the pen state according to new color - const hsv = Color.rgbToHsv(rgb); - penState.color = 100 * hsv.h / 360; - penState.saturation = 100 * hsv.s; - penState.brightness = 100 * hsv.v; - // And store the shade that was used to compute this new color for later use. penState._shade = newShade; - this._updatePenColor(penState); + this._legacyUpdatePenColor(penState); } /** @@ -739,6 +728,29 @@ class Scratch3PenBlocks { this.setPenShadeToNumber({SHADE: penState._shade + shadeChange}, util); } + /** + * Update the pen state's color from its hue & shade values, Scratch 2.0 style. + * @param {object} penState - update the HSV & RGB values in this pen state from its hue & shade values. + * @private + */ + _legacyUpdatePenColor (penState) { + // Create the new color in RGB using the scratch 2 "shade" model + let rgb = Color.hsvToRgb({h: penState.color * 360 / 100, s: 1, v: 1}); + const shade = (penState._shade > 100) ? 200 - penState._shade : penState._shade; + if (shade < 50) { + rgb = Color.mixRgb(Color.RGB_BLACK, rgb, (10 + shade) / 60); + } else { + rgb = Color.mixRgb(rgb, Color.RGB_WHITE, (shade - 50) / 60); + } + + // Update the pen state according to new color + const hsv = Color.rgbToHsv(rgb); + penState.color = 100 * hsv.h / 360; + penState.saturation = 100 * hsv.s; + penState.brightness = 100 * hsv.v; + + this._updatePenColor(penState); + } } module.exports = Scratch3PenBlocks; From ed1fc5f35300364e8f7902e7ef2e6d0e2dd0df0a Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford Date: Fri, 22 Dec 2017 11:08:05 -0800 Subject: [PATCH 09/33] Use Scratch 2.0 block labels for legacy blocks --- src/extensions/scratch3_pen/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/extensions/scratch3_pen/index.js b/src/extensions/scratch3_pen/index.js index e4a0ea8439..abd46db129 100644 --- a/src/extensions/scratch3_pen/index.js +++ b/src/extensions/scratch3_pen/index.js @@ -446,7 +446,7 @@ class Scratch3PenBlocks { blockType: BlockType.COMMAND, text: formatMessage({ id: 'pen.setHue', - default: 'set pen hue to [HUE]', + default: 'set pen color to [HUE]', description: 'legacy pen blocks - set pen color to number' }), arguments: { @@ -462,7 +462,7 @@ class Scratch3PenBlocks { blockType: BlockType.COMMAND, text: formatMessage({ id: 'pen.changeHue', - default: 'change pen hue by [HUE]', + default: 'change pen color by [HUE]', description: 'legacy pen blocks - change pen color' }), arguments: { From d001a855719147316ec0133fd027c073de731887 Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Thu, 28 Dec 2017 12:31:23 -0500 Subject: [PATCH 10/33] Add setDragMode primitive and unit test --- src/blocks/scratch3_sensing.js | 5 +++++ test/unit/blocks_sensing.js | 14 ++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/blocks/scratch3_sensing.js b/src/blocks/scratch3_sensing.js index 75ee571c74..0ee1286eb6 100644 --- a/src/blocks/scratch3_sensing.js +++ b/src/blocks/scratch3_sensing.js @@ -40,6 +40,7 @@ class Scratch3SensingBlocks { sensing_of: this.getAttributeOf, sensing_mousex: this.getMouseX, sensing_mousey: this.getMouseY, + sensing_setdragmode: this.setDragMode, sensing_mousedown: this.getMouseDown, sensing_keypressed: this.getKeyPressed, sensing_current: this.current, @@ -162,6 +163,10 @@ class Scratch3SensingBlocks { return Math.sqrt((dx * dx) + (dy * dy)); } + setDragMode (args, util) { + util.target.draggable = args.DRAG_MODE === 'draggable'; + } + getTimer (args, util) { return util.ioQuery('clock', 'projectTimer'); } diff --git a/test/unit/blocks_sensing.js b/test/unit/blocks_sensing.js index 5c2b927a8f..221742b995 100644 --- a/test/unit/blocks_sensing.js +++ b/test/unit/blocks_sensing.js @@ -65,3 +65,17 @@ test('ask and answer with a visible target', t => { s.askAndWait({QUESTION: expectedQuestion}, util); }); + +test('set drag mode', t => { + const rt = new Runtime(); + const s = new Sensing(rt); + const util = {target: {draggable: true}}; + + s.setDragMode({DRAG_MODE: 'not draggable'}, util); + t.strictEqual(util.target.draggable, false); + + s.setDragMode({DRAG_MODE: 'draggable'}, util); + t.strictEqual(util.target.draggable, true); + + t.end(); +}); From fe51bd645080449310b814e328d678b8ebdda130 Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Thu, 28 Dec 2017 13:14:00 -0500 Subject: [PATCH 11/33] Update layering blocks --- src/blocks/scratch3_looks.js | 22 ++++++++++++++++------ src/sprites/rendered-target.js | 25 ++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/src/blocks/scratch3_looks.js b/src/blocks/scratch3_looks.js index 1429b6dd79..c694fb38ea 100644 --- a/src/blocks/scratch3_looks.js +++ b/src/blocks/scratch3_looks.js @@ -233,8 +233,8 @@ class Scratch3LooksBlocks { looks_cleargraphiceffects: this.clearEffects, looks_changesizeby: this.changeSize, looks_setsizeto: this.setSize, - looks_gotofront: this.goToFront, - looks_gobacklayers: this.goBackLayers, + looks_gotofrontback: this.goToFrontBack, + looks_goforwardbackwardlayers: this.goForwardBackwardLayers, looks_size: this.getSize, looks_costumeorder: this.getCostumeIndex, looks_backdroporder: this.getBackdropIndex, @@ -409,14 +409,24 @@ class Scratch3LooksBlocks { util.target.setSize(size); } - goToFront (args, util) { + goToFrontBack (args, util) { if (!util.target.isStage) { - util.target.goToFront(); + if (args.FRONT_BACK === 'front') { + util.target.goToFront(); + } else { + util.target.goToBack(); + } } } - goBackLayers (args, util) { - util.target.goBackLayers(args.NUM); + goForwardBackwardLayers (args, util) { + if (!util.target.isStage) { + if (args.FORWARD_BACKWARD === 'forward') { + util.target.goForwardLayers(Cast.toNumber(args.NUM)); + } else { + util.target.goBackwardLayers(Cast.toNumber(args.NUM)); + } + } } getSize (args, util) { diff --git a/src/sprites/rendered-target.js b/src/sprites/rendered-target.js index c41b967a35..85c1d53659 100644 --- a/src/sprites/rendered-target.js +++ b/src/sprites/rendered-target.js @@ -699,10 +699,29 @@ class RenderedTarget extends Target { } /** - * Move back a number of layers. - * @param {number} nLayers How many layers to go back. + * Move to the back layer. */ - goBackLayers (nLayers) { + goToBack () { + if (this.renderer) { + this.renderer.setDrawableOrder(this.drawableID, -Infinity, false, 1); + } + } + + /** + * Move forward a number of layers. + * @param {number} nLayers How many layers to go forward. + */ + goForwardLayers (nLayers) { + if (this.renderer) { + this.renderer.setDrawableOrder(this.drawableID, nLayers, true, 1); + } + } + + /** + * Move backward a number of layers. + * @param {number} nLayers How many layers to go backward. + */ + goBackwardLayers (nLayers) { if (this.renderer) { this.renderer.setDrawableOrder(this.drawableID, -nLayers, true, 1); } From 477ef531ba8982c4048d0728938eefd491cf31b1 Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Thu, 28 Dec 2017 14:52:06 -0500 Subject: [PATCH 12/33] Update specmap and sb2 import to add new fields to layering blocks. --- src/serialization/sb2.js | 14 ++++++++++++++ src/serialization/sb2_specmap.js | 4 ++-- test/unit/sprites_rendered-target.js | 6 +++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/serialization/sb2.js b/src/serialization/sb2.js index 40304ce2d6..85f55910ae 100644 --- a/src/serialization/sb2.js +++ b/src/serialization/sb2.js @@ -601,6 +601,20 @@ const parseBlock = function (sb2block, addBroadcastMsg, getVariableId, extension } } } + + // Updated layering blocks + if (oldOpcode === 'comeToFront') { + activeBlock.fields.FRONT_BACK = { + name: 'FRONT_BACK', + value: 'front' + }; + } else if (oldOpcode === 'goBackByLayers:') { + activeBlock.fields.FORWARD_BACKWARD = { + name: 'FORWARD_BACKWARD', + value: 'backward' + }; + } + // Special cases to generate mutations. if (oldOpcode === 'stopScripts') { // Mutation for stop block: if the argument is 'other scripts', diff --git a/src/serialization/sb2_specmap.js b/src/serialization/sb2_specmap.js index b829fc1745..a8ca435406 100644 --- a/src/serialization/sb2_specmap.js +++ b/src/serialization/sb2_specmap.js @@ -345,12 +345,12 @@ const specMap = { ] }, 'comeToFront': { - opcode: 'looks_gotofront', + opcode: 'looks_gotofrontback', argMap: [ ] }, 'goBackByLayers:': { - opcode: 'looks_gobacklayers', + opcode: 'looks_goforwardbackwardlayers', argMap: [ { type: 'input', diff --git a/test/unit/sprites_rendered-target.js b/test/unit/sprites_rendered-target.js index 1592c357f0..332a78111e 100644 --- a/test/unit/sprites_rendered-target.js +++ b/test/unit/sprites_rendered-target.js @@ -293,8 +293,12 @@ test('layers', t => { a.renderer = renderer; a.goToFront(); t.equals(a.renderer.order, 5); - a.goBackLayers(2); + a.goBackwardLayers(2); t.equals(a.renderer.order, 3); + a.goToBack(); + t.equals(a.renderer.order, 1); + a.goForwardLayers(1); + t.equals(a.renderer.order, 2); o.drawableID = 999; a.goBehindOther(o); t.equals(a.renderer.order, 1); From b726c4c18dc5032cd2827409cc21ee4f94636a01 Mon Sep 17 00:00:00 2001 From: sjhuang26 Date: Fri, 29 Dec 2017 08:31:59 -0500 Subject: [PATCH 13/33] Changed postSpriteInfo logic --- src/sprites/rendered-target.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/sprites/rendered-target.js b/src/sprites/rendered-target.js index c41b967a35..13f9bd3255 100644 --- a/src/sprites/rendered-target.js +++ b/src/sprites/rendered-target.js @@ -845,11 +845,10 @@ class RenderedTarget extends Target { */ postSpriteInfo (data) { const force = data.hasOwnProperty('force') ? data.force : null; - if (data.hasOwnProperty('x')) { - this.setXY(data.x, this.y, force); - } - if (data.hasOwnProperty('y')) { - this.setXY(this.x, data.y, force); + let isXChanged = data.hasOwnProperty('x'); + let isYChanged = data.hasOwnProperty('y'); + if (isXChanged || isYChanged) { + this.setXY(isXChanged ? data.x : this.x, isYChanged ? data.y : this.y, force); } if (data.hasOwnProperty('direction')) { this.setDirection(data.direction); From f7d80459c7cb693cbc442d99cb38f0e70f6f4ffa Mon Sep 17 00:00:00 2001 From: sjhuang26 Date: Fri, 29 Dec 2017 09:01:30 -0500 Subject: [PATCH 14/33] Fixed pen behavior when dragging --- src/extensions/scratch3_pen/index.js | 16 ++++++++++------ src/sprites/rendered-target.js | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/extensions/scratch3_pen/index.js b/src/extensions/scratch3_pen/index.js index abd46db129..c65400163d 100644 --- a/src/extensions/scratch3_pen/index.js +++ b/src/extensions/scratch3_pen/index.js @@ -181,14 +181,18 @@ class Scratch3PenBlocks { * @param {RenderedTarget} target - the target which has moved. * @param {number} oldX - the previous X position. * @param {number} oldY - the previous Y position. + * @param {boolean} isForce - whether the movement was forced. * @private */ - _onTargetMoved (target, oldX, oldY) { - const penSkinId = this._getPenLayerID(); - if (penSkinId >= 0) { - const penState = this._getPenState(target); - this.runtime.renderer.penLine(penSkinId, penState.penAttributes, oldX, oldY, target.x, target.y); - this.runtime.requestRedraw(); + _onTargetMoved (target, oldX, oldY, isForce) { + // Only move the pen if the movement isn't forced (ie. dragged). + if (!isForce) { + const penSkinId = this._getPenLayerID(); + if (penSkinId >= 0) { + const penState = this._getPenState(target); + this.runtime.renderer.penLine(penSkinId, penState.penAttributes, oldX, oldY, target.x, target.y); + this.runtime.requestRedraw(); + } } } diff --git a/src/sprites/rendered-target.js b/src/sprites/rendered-target.js index 13f9bd3255..fe9ede298f 100644 --- a/src/sprites/rendered-target.js +++ b/src/sprites/rendered-target.js @@ -203,7 +203,7 @@ class RenderedTarget extends Target { this.x = x; this.y = y; } - this.emit(RenderedTarget.EVENT_TARGET_MOVED, this, oldX, oldY); + this.emit(RenderedTarget.EVENT_TARGET_MOVED, this, oldX, oldY, force); this.runtime.requestTargetsUpdate(this); } From 72dee72148d56d65d4d209b236638d8aa2f9a306 Mon Sep 17 00:00:00 2001 From: sjhuang26 Date: Fri, 29 Dec 2017 09:13:18 -0500 Subject: [PATCH 15/33] Change let to const --- src/sprites/rendered-target.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sprites/rendered-target.js b/src/sprites/rendered-target.js index fe9ede298f..ee1109b5d6 100644 --- a/src/sprites/rendered-target.js +++ b/src/sprites/rendered-target.js @@ -845,8 +845,8 @@ class RenderedTarget extends Target { */ postSpriteInfo (data) { const force = data.hasOwnProperty('force') ? data.force : null; - let isXChanged = data.hasOwnProperty('x'); - let isYChanged = data.hasOwnProperty('y'); + const isXChanged = data.hasOwnProperty('x'); + const isYChanged = data.hasOwnProperty('y'); if (isXChanged || isYChanged) { this.setXY(isXChanged ? data.x : this.x, isYChanged ? data.y : this.y, force); } From 6d90a34ff14019c9997b29a40a2aed45902c3e85 Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Thu, 28 Dec 2017 12:31:23 -0500 Subject: [PATCH 16/33] Add setDragMode primitive and unit test --- src/blocks/scratch3_sensing.js | 2 +- test/unit/blocks_sensing.js | 18 +++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/blocks/scratch3_sensing.js b/src/blocks/scratch3_sensing.js index 0ee1286eb6..4f977a6124 100644 --- a/src/blocks/scratch3_sensing.js +++ b/src/blocks/scratch3_sensing.js @@ -164,7 +164,7 @@ class Scratch3SensingBlocks { } setDragMode (args, util) { - util.target.draggable = args.DRAG_MODE === 'draggable'; + util.target.setDraggable(args.DRAG_MODE === 'draggable'); } getTimer (args, util) { diff --git a/test/unit/blocks_sensing.js b/test/unit/blocks_sensing.js index 221742b995..33b4844668 100644 --- a/test/unit/blocks_sensing.js +++ b/test/unit/blocks_sensing.js @@ -1,6 +1,8 @@ const test = require('tap').test; const Sensing = require('../../src/blocks/scratch3_sensing'); const Runtime = require('../../src/engine/runtime'); +const Sprite = require('../../src/sprites/sprite'); +const RenderedTarget = require('../../src/sprites/rendered-target'); test('getPrimitives', t => { const rt = new Runtime(); @@ -67,15 +69,17 @@ test('ask and answer with a visible target', t => { }); test('set drag mode', t => { - const rt = new Runtime(); - const s = new Sensing(rt); - const util = {target: {draggable: true}}; + const runtime = new Runtime(); + runtime.requestTargetsUpdate = () => {}; // noop for testing + const sensing = new Sensing(runtime); + const s = new Sprite(); + const rt = new RenderedTarget(s, runtime); - s.setDragMode({DRAG_MODE: 'not draggable'}, util); - t.strictEqual(util.target.draggable, false); + sensing.setDragMode({DRAG_MODE: 'not draggable'}, {target: rt}); + t.strictEqual(rt.draggable, false); - s.setDragMode({DRAG_MODE: 'draggable'}, util); - t.strictEqual(util.target.draggable, true); + sensing.setDragMode({DRAG_MODE: 'draggable'}, {target: rt}); + t.strictEqual(rt.draggable, true); t.end(); }); From d6f1c48c086c40518ab383a3d95d33ae5615550a Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Thu, 4 Jan 2018 10:04:54 -0500 Subject: [PATCH 17/33] Add opcodes for new looks reporters with menu for costume number/name --- src/blocks/scratch3_looks.js | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/blocks/scratch3_looks.js b/src/blocks/scratch3_looks.js index c694fb38ea..546f594df3 100644 --- a/src/blocks/scratch3_looks.js +++ b/src/blocks/scratch3_looks.js @@ -236,9 +236,8 @@ class Scratch3LooksBlocks { looks_gotofrontback: this.goToFrontBack, looks_goforwardbackwardlayers: this.goForwardBackwardLayers, looks_size: this.getSize, - looks_costumeorder: this.getCostumeIndex, - looks_backdroporder: this.getBackdropIndex, - looks_backdropname: this.getBackdropName + looks_costumenumbername: this.getCostumeNumberName, + looks_backdropnumbername: this.getBackdropNumberName }; } @@ -433,18 +432,21 @@ class Scratch3LooksBlocks { return Math.round(util.target.size); } - getBackdropIndex () { - const stage = this.runtime.getTargetForStage(); - return stage.currentCostume + 1; - } - - getBackdropName () { + getBackdropNumberName (args) { const stage = this.runtime.getTargetForStage(); + if (args.NUMBER_NAME === 'number') { + return stage.currentCostume + 1; + } + // Else return name return stage.sprite.costumes[stage.currentCostume].name; } - getCostumeIndex (args, util) { - return util.target.currentCostume + 1; + getCostumeNumberName (args, util) { + if (args.NUMBER_NAME === 'number') { + return util.target.currentCostume + 1; + } + // Else return name + return util.target.sprite.costumes[util.target.currentCostume].name; } } From 63be5bc48780cc0fe94a6b7544742a446ab0c3bc Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Thu, 4 Jan 2018 10:24:13 -0500 Subject: [PATCH 18/33] Transform sb2 looks reporters to new menu blocks --- src/serialization/sb2.js | 27 ++++++++++++++++++++++++--- src/serialization/sb2_specmap.js | 6 +++--- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/serialization/sb2.js b/src/serialization/sb2.js index 85f55910ae..1102c005e4 100644 --- a/src/serialization/sb2.js +++ b/src/serialization/sb2.js @@ -602,17 +602,38 @@ const parseBlock = function (sb2block, addBroadcastMsg, getVariableId, extension } } - // Updated layering blocks - if (oldOpcode === 'comeToFront') { + // Updates for blocks that have new menus (e.g. in Looks) + switch (oldOpcode) { + case 'comeToFront': activeBlock.fields.FRONT_BACK = { name: 'FRONT_BACK', value: 'front' }; - } else if (oldOpcode === 'goBackByLayers:') { + break; + case 'goBackByLayers:': activeBlock.fields.FORWARD_BACKWARD = { name: 'FORWARD_BACKWARD', value: 'backward' }; + break; + case 'backgroundIndex': + activeBlock.fields.NUMBER_NAME = { + name: 'NUMBER_NAME', + value: 'number' + }; + break; + case 'sceneName': + activeBlock.fields.NUMBER_NAME = { + name: 'NUMBER_NAME', + value: 'name' + }; + break; + case 'costumeIndex': + activeBlock.fields.NUMBER_NAME = { + name: 'NUMBER_NAME', + value: 'number' + }; + break; } // Special cases to generate mutations. diff --git a/src/serialization/sb2_specmap.js b/src/serialization/sb2_specmap.js index a8ca435406..bf386a15ec 100644 --- a/src/serialization/sb2_specmap.js +++ b/src/serialization/sb2_specmap.js @@ -360,12 +360,12 @@ const specMap = { ] }, 'costumeIndex': { - opcode: 'looks_costumeorder', + opcode: 'looks_costumenumbername', argMap: [ ] }, 'sceneName': { - opcode: 'looks_backdropname', + opcode: 'looks_backdropnumbername', argMap: [ ] }, @@ -390,7 +390,7 @@ const specMap = { ] }, 'backgroundIndex': { - opcode: 'looks_backdroporder', + opcode: 'looks_backdropnumbername', argMap: [ ] }, From 3afe709cb07447c4a89a26cfa06ac9314ca298dd Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Thu, 4 Jan 2018 10:42:29 -0500 Subject: [PATCH 19/33] Add unit tests for new reporter blocks in looks --- test/unit/blocks_looks.js | 52 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 test/unit/blocks_looks.js diff --git a/test/unit/blocks_looks.js b/test/unit/blocks_looks.js new file mode 100644 index 0000000000..1506d7f843 --- /dev/null +++ b/test/unit/blocks_looks.js @@ -0,0 +1,52 @@ +const test = require('tap').test; +const Looks = require('../../src/blocks/scratch3_looks'); +const util = { + target: { + currentCostume: 0, // Internally, current costume is 0 indexed + sprite: { + costumes: [ + {name: 'first name'}, + {name: 'second name'}, + {name: 'third name'} + ] + } + } +}; + +const fakeRuntime = { + getTargetForStage: () => util.target, // Just return the dummy target above. + on: () => {} // Stub out listener methods used in constructor. +}; +const blocks = new Looks(fakeRuntime); + +test('getCostumeNumberName returns 1-indexed costume number', t => { + util.target.currentCostume = 0; // This is 0-indexed. + const args = {NUMBER_NAME: 'number'}; + const number = blocks.getCostumeNumberName(args, util); + t.strictEqual(number, 1); + t.end(); +}); + +test('getCostumeNumberName can return costume name', t => { + util.target.currentCostume = 0; // This is 0-indexed. + const args = {NUMBER_NAME: 'name'}; + const number = blocks.getCostumeNumberName(args, util); + t.strictEqual(number, 'first name'); + t.end(); +}); + +test('getBackdropNumberName returns 1-indexed costume number', t => { + util.target.currentCostume = 2; // This is 0-indexed. + const args = {NUMBER_NAME: 'number'}; + const number = blocks.getBackdropNumberName(args, util); + t.strictEqual(number, 3); + t.end(); +}); + +test('getBackdropNumberName can return costume name', t => { + util.target.currentCostume = 2; // This is 0-indexed. + const args = {NUMBER_NAME: 'name'}; + const number = blocks.getBackdropNumberName(args, util); + t.strictEqual(number, 'third name'); + t.end(); +}); From 0b6d7adf3e69e296662169fa3b53a9f7a95de4e5 Mon Sep 17 00:00:00 2001 From: DD Liu Date: Mon, 8 Jan 2018 20:22:18 -0500 Subject: [PATCH 20/33] Update monitor on dropdown change --- src/engine/blocks.js | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/engine/blocks.js b/src/engine/blocks.js index 920128dc69..487115d138 100644 --- a/src/engine/blocks.js +++ b/src/engine/blocks.js @@ -3,6 +3,7 @@ const mutationAdapter = require('./mutation-adapter'); const xmlEscape = require('../util/xml-escape'); const MonitorRecord = require('./monitor-record'); const Clone = require('../util/clone'); +const {Map} = require('immutable'); /** * @fileoverview @@ -382,7 +383,20 @@ class Blocks { block.fields[args.name].id = args.value; } } else { + // Changing the value in a dropdown block.fields[args.name].value = args.value; + + if (!optRuntime || !block.parent){ + break; + } + + const parent = this._blocks[block.parent]; + if (parent.isMonitored) { + optRuntime.requestUpdateMonitor(Map({ + id: parent.id, + params: this._getBlockParams(parent) + })); + } } break; case 'mutation': @@ -390,14 +404,17 @@ class Blocks { break; case 'checkbox': block.isMonitored = args.value; - if (optRuntime) { - const isSpriteSpecific = optRuntime.monitorBlockInfo.hasOwnProperty(block.opcode) && - optRuntime.monitorBlockInfo[block.opcode].isSpriteSpecific; - block.targetId = isSpriteSpecific ? optRuntime.getEditingTarget().id : null; + if (!optRuntime) { + break; } - if (optRuntime && wasMonitored && !block.isMonitored) { + + const isSpriteSpecific = optRuntime.monitorBlockInfo.hasOwnProperty(block.opcode) && + optRuntime.monitorBlockInfo[block.opcode].isSpriteSpecific; + block.targetId = isSpriteSpecific ? optRuntime.getEditingTarget().id : null; + + if (wasMonitored && !block.isMonitored) { optRuntime.requestRemoveMonitor(block.id); - } else if (optRuntime && !wasMonitored && block.isMonitored) { + } else if (!wasMonitored && block.isMonitored) { optRuntime.requestAddMonitor(MonitorRecord({ // @todo(vm#564) this will collide if multiple sprites use same block id: block.id, From bbcb6bd5b72c0cc7d9497fb6ae8929874dddf29d Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Tue, 9 Jan 2018 09:39:52 -0500 Subject: [PATCH 21/33] Differentiate clamped from non clamped mouse io data (GUI#179) --- src/blocks/scratch3_motion.js | 10 +++++----- src/blocks/scratch3_sensing.js | 8 ++++---- src/io/mouse.js | 22 +++++++++++++++++++--- test/unit/io_mouse.js | 14 ++++++++++---- 4 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/blocks/scratch3_motion.js b/src/blocks/scratch3_motion.js index 0b206975be..69919c93ae 100644 --- a/src/blocks/scratch3_motion.js +++ b/src/blocks/scratch3_motion.js @@ -64,8 +64,8 @@ class Scratch3MotionBlocks { let targetX = 0; let targetY = 0; if (targetName === '_mouse_') { - targetX = util.ioQuery('mouse', 'getX'); - targetY = util.ioQuery('mouse', 'getY'); + targetX = util.ioQuery('mouse', 'getClampedX'); + targetY = util.ioQuery('mouse', 'getClampedY'); } else if (targetName === '_random_') { const stageWidth = this.runtime.constructor.STAGE_WIDTH; const stageHeight = this.runtime.constructor.STAGE_HEIGHT; @@ -106,8 +106,8 @@ class Scratch3MotionBlocks { let targetX = 0; let targetY = 0; if (args.TOWARDS === '_mouse_') { - targetX = util.ioQuery('mouse', 'getX'); - targetY = util.ioQuery('mouse', 'getY'); + targetX = util.ioQuery('mouse', 'getClampedX'); + targetY = util.ioQuery('mouse', 'getClampedY'); } else { const pointTarget = this.runtime.getSpriteTargetByName(args.TOWARDS); if (!pointTarget) return; @@ -155,7 +155,7 @@ class Scratch3MotionBlocks { util.yield(); } } - + glideTo (args, util) { const targetXY = this.getTargetXY(args.TO, util); if (targetXY) { diff --git a/src/blocks/scratch3_sensing.js b/src/blocks/scratch3_sensing.js index 4f977a6124..d8829f2215 100644 --- a/src/blocks/scratch3_sensing.js +++ b/src/blocks/scratch3_sensing.js @@ -147,8 +147,8 @@ class Scratch3SensingBlocks { let targetX = 0; let targetY = 0; if (args.DISTANCETOMENU === '_mouse_') { - targetX = util.ioQuery('mouse', 'getX'); - targetY = util.ioQuery('mouse', 'getY'); + targetX = util.ioQuery('mouse', 'getClampedX'); + targetY = util.ioQuery('mouse', 'getClampedY'); } else { const distTarget = this.runtime.getSpriteTargetByName( args.DISTANCETOMENU @@ -176,11 +176,11 @@ class Scratch3SensingBlocks { } getMouseX (args, util) { - return util.ioQuery('mouse', 'getX'); + return util.ioQuery('mouse', 'getClampedX'); } getMouseY (args, util) { - return util.ioQuery('mouse', 'getY'); + return util.ioQuery('mouse', 'getClampedY'); } getMouseDown (args, util) { diff --git a/src/io/mouse.js b/src/io/mouse.js index fb4f299ae8..ca16f7cf22 100644 --- a/src/io/mouse.js +++ b/src/io/mouse.js @@ -55,17 +55,33 @@ class Mouse { /** * Get the X position of the mouse. - * @return {number} Clamped X position of the mouse cursor. + * @return {number} Non-clamped X position of the mouse cursor. */ getX () { - return MathUtil.clamp(this._x, -240, 240); + return this._x; } /** * Get the Y position of the mouse. - * @return {number} Clamped Y position of the mouse cursor. + * @return {number} Non-clamped Y position of the mouse cursor. */ getY () { + return -this._y; + } + + /** + * Get the stage-clamped X position of the mouse. + * @return {number} Clamped X position of the mouse cursor. + */ + getClampedX () { + return MathUtil.clamp(this._x, -240, 240); + } + + /** + * Get the stage-clamped Y position of the mouse. + * @return {number} Clamped Y position of the mouse cursor. + */ + getClampedY () { return MathUtil.clamp(-this._y, -180, 180); } diff --git a/test/unit/io_mouse.js b/test/unit/io_mouse.js index 9197866637..fc537e1813 100644 --- a/test/unit/io_mouse.js +++ b/test/unit/io_mouse.js @@ -10,6 +10,8 @@ test('spec', t => { t.type(m.postData, 'function'); t.type(m.getX, 'function'); t.type(m.getY, 'function'); + t.type(m.getClampedX, 'function'); + t.type(m.getClampedY, 'function'); t.type(m.getIsDown, 'function'); t.end(); }); @@ -19,14 +21,16 @@ test('mouseUp', t => { const m = new Mouse(rt); m.postData({ - x: 1, + x: -20, y: 10, isDown: false, canvasWidth: 480, canvasHeight: 360 }); - t.strictEquals(m.getX(), -239); + t.strictEquals(m.getX(), -260); t.strictEquals(m.getY(), 170); + t.strictEquals(m.getClampedX(), -240); + t.strictEquals(m.getClampedY(), 170); t.strictEquals(m.getIsDown(), false); t.end(); }); @@ -37,13 +41,15 @@ test('mouseDown', t => { m.postData({ x: 10, - y: 100, + y: 400, isDown: true, canvasWidth: 480, canvasHeight: 360 }); t.strictEquals(m.getX(), -230); - t.strictEquals(m.getY(), 80); + t.strictEquals(m.getY(), -220); + t.strictEquals(m.getClampedX(), -230); + t.strictEquals(m.getClampedY(), -180); t.strictEquals(m.getIsDown(), true); t.end(); }); From 327d1179d9eafbecba57a7c097a9890c5f5ae1a5 Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Tue, 9 Jan 2018 10:36:03 -0500 Subject: [PATCH 22/33] Update the mouse io to give scratch coordinates and client coordinates Fixes GUI #642 https://github.com/LLK/scratch-gui/issues/642 Fixes GUI #179 https://github.com/LLK/scratch-gui/issues/179 --- src/blocks/scratch3_motion.js | 8 +++---- src/blocks/scratch3_sensing.js | 12 +++++----- src/io/mouse.js | 38 +++++++++++++++++++------------ src/sprites/rendered-target.js | 3 +-- test/unit/io_mouse.js | 41 ++++++++++++++++++++++++---------- 5 files changed, 64 insertions(+), 38 deletions(-) diff --git a/src/blocks/scratch3_motion.js b/src/blocks/scratch3_motion.js index 69919c93ae..524eab24a2 100644 --- a/src/blocks/scratch3_motion.js +++ b/src/blocks/scratch3_motion.js @@ -64,8 +64,8 @@ class Scratch3MotionBlocks { let targetX = 0; let targetY = 0; if (targetName === '_mouse_') { - targetX = util.ioQuery('mouse', 'getClampedX'); - targetY = util.ioQuery('mouse', 'getClampedY'); + targetX = util.ioQuery('mouse', 'getScratchX'); + targetY = util.ioQuery('mouse', 'getScratchY'); } else if (targetName === '_random_') { const stageWidth = this.runtime.constructor.STAGE_WIDTH; const stageHeight = this.runtime.constructor.STAGE_HEIGHT; @@ -106,8 +106,8 @@ class Scratch3MotionBlocks { let targetX = 0; let targetY = 0; if (args.TOWARDS === '_mouse_') { - targetX = util.ioQuery('mouse', 'getClampedX'); - targetY = util.ioQuery('mouse', 'getClampedY'); + targetX = util.ioQuery('mouse', 'getScratchX'); + targetY = util.ioQuery('mouse', 'getScratchY'); } else { const pointTarget = this.runtime.getSpriteTargetByName(args.TOWARDS); if (!pointTarget) return; diff --git a/src/blocks/scratch3_sensing.js b/src/blocks/scratch3_sensing.js index d8829f2215..d1f7ae51a0 100644 --- a/src/blocks/scratch3_sensing.js +++ b/src/blocks/scratch3_sensing.js @@ -120,8 +120,8 @@ class Scratch3SensingBlocks { touchingObject (args, util) { const requestedObject = args.TOUCHINGOBJECTMENU; if (requestedObject === '_mouse_') { - const mouseX = util.ioQuery('mouse', 'getX'); - const mouseY = util.ioQuery('mouse', 'getY'); + const mouseX = util.ioQuery('mouse', 'getClientX'); + const mouseY = util.ioQuery('mouse', 'getClientY'); return util.target.isTouchingPoint(mouseX, mouseY); } else if (requestedObject === '_edge_') { return util.target.isTouchingEdge(); @@ -147,8 +147,8 @@ class Scratch3SensingBlocks { let targetX = 0; let targetY = 0; if (args.DISTANCETOMENU === '_mouse_') { - targetX = util.ioQuery('mouse', 'getClampedX'); - targetY = util.ioQuery('mouse', 'getClampedY'); + targetX = util.ioQuery('mouse', 'getScratchX'); + targetY = util.ioQuery('mouse', 'getScratchY'); } else { const distTarget = this.runtime.getSpriteTargetByName( args.DISTANCETOMENU @@ -176,11 +176,11 @@ class Scratch3SensingBlocks { } getMouseX (args, util) { - return util.ioQuery('mouse', 'getClampedX'); + return util.ioQuery('mouse', 'getScratchX'); } getMouseY (args, util) { - return util.ioQuery('mouse', 'getClampedY'); + return util.ioQuery('mouse', 'getScratchY'); } getMouseDown (args, util) { diff --git a/src/io/mouse.js b/src/io/mouse.js index ca16f7cf22..ecff2db10c 100644 --- a/src/io/mouse.js +++ b/src/io/mouse.js @@ -40,10 +40,20 @@ class Mouse { */ postData (data) { if (data.x) { - this._x = data.x - (data.canvasWidth / 2); + this._clientX = data.x; + this._scratchX = MathUtil.clamp( + 480 * ((data.x / data.canvasWidth) - 0.5), + -240, + 240 + ); } if (data.y) { - this._y = data.y - (data.canvasHeight / 2); + this._clientY = data.y; + this._scratchY = MathUtil.clamp( + -360 * ((data.y / data.canvasHeight) - 0.5), + -180, + 180 + ); } if (typeof data.isDown !== 'undefined') { this._isDown = data.isDown; @@ -54,35 +64,35 @@ class Mouse { } /** - * Get the X position of the mouse. + * Get the X position of the mouse in client coordinates. * @return {number} Non-clamped X position of the mouse cursor. */ - getX () { - return this._x; + getClientX () { + return this._clientX; } /** - * Get the Y position of the mouse. + * Get the Y position of the mouse in client coordinates. * @return {number} Non-clamped Y position of the mouse cursor. */ - getY () { - return -this._y; + getClientY () { + return this._clientY; } /** - * Get the stage-clamped X position of the mouse. + * Get the X position of the mouse in scratch coordinates. * @return {number} Clamped X position of the mouse cursor. */ - getClampedX () { - return MathUtil.clamp(this._x, -240, 240); + getScratchX () { + return this._scratchX; } /** - * Get the stage-clamped Y position of the mouse. + * Get the Y position of the mouse in scratch coordinates. * @return {number} Clamped Y position of the mouse cursor. */ - getClampedY () { - return MathUtil.clamp(-this._y, -180, 180); + getScratchY () { + return this._scratchY; } /** diff --git a/src/sprites/rendered-target.js b/src/sprites/rendered-target.js index 1288240da9..b6edd06ceb 100644 --- a/src/sprites/rendered-target.js +++ b/src/sprites/rendered-target.js @@ -616,8 +616,7 @@ class RenderedTarget extends Target { // Limits test to this Drawable, so this will return true // even if the clone is obscured by another Drawable. const pickResult = this.runtime.renderer.pick( - x + (this.runtime.constructor.STAGE_WIDTH / 2), - -y + (this.runtime.constructor.STAGE_HEIGHT / 2), + x, y, null, null, [this.drawableID] ); diff --git a/test/unit/io_mouse.js b/test/unit/io_mouse.js index fc537e1813..48613fdb29 100644 --- a/test/unit/io_mouse.js +++ b/test/unit/io_mouse.js @@ -8,10 +8,10 @@ test('spec', t => { t.type(m, 'object'); t.type(m.postData, 'function'); - t.type(m.getX, 'function'); - t.type(m.getY, 'function'); - t.type(m.getClampedX, 'function'); - t.type(m.getClampedY, 'function'); + t.type(m.getClientX, 'function'); + t.type(m.getClientY, 'function'); + t.type(m.getScratchX, 'function'); + t.type(m.getScratchY, 'function'); t.type(m.getIsDown, 'function'); t.end(); }); @@ -27,10 +27,10 @@ test('mouseUp', t => { canvasWidth: 480, canvasHeight: 360 }); - t.strictEquals(m.getX(), -260); - t.strictEquals(m.getY(), 170); - t.strictEquals(m.getClampedX(), -240); - t.strictEquals(m.getClampedY(), 170); + t.strictEquals(m.getClientX(), -20); + t.strictEquals(m.getClientY(), 10); + t.strictEquals(m.getScratchX(), -240); + t.strictEquals(m.getScratchY(), 170); t.strictEquals(m.getIsDown(), false); t.end(); }); @@ -46,10 +46,27 @@ test('mouseDown', t => { canvasWidth: 480, canvasHeight: 360 }); - t.strictEquals(m.getX(), -230); - t.strictEquals(m.getY(), -220); - t.strictEquals(m.getClampedX(), -230); - t.strictEquals(m.getClampedY(), -180); + t.strictEquals(m.getClientX(), 10); + t.strictEquals(m.getClientY(), 400); + t.strictEquals(m.getScratchX(), -230); + t.strictEquals(m.getScratchY(), -180); t.strictEquals(m.getIsDown(), true); t.end(); }); + +test('at zoomed scale', t => { + const rt = new Runtime(); + const m = new Mouse(rt); + + m.postData({ + x: 240, + y: 540, + canvasWidth: 960, + canvasHeight: 720 + }); + t.strictEquals(m.getClientX(), 240); + t.strictEquals(m.getClientY(), 540); + t.strictEquals(m.getScratchX(), -120); + t.strictEquals(m.getScratchY(), -90); + t.end(); +}); From db662b8584e2eb2de1ac3793f2f54ffba074f840 Mon Sep 17 00:00:00 2001 From: DD Date: Tue, 9 Jan 2018 14:55:46 -0500 Subject: [PATCH 23/33] fix lint --- src/engine/blocks.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/engine/blocks.js b/src/engine/blocks.js index 487115d138..d5e734ceb4 100644 --- a/src/engine/blocks.js +++ b/src/engine/blocks.js @@ -402,7 +402,7 @@ class Blocks { case 'mutation': block.mutation = mutationAdapter(args.value); break; - case 'checkbox': + case 'checkbox': { block.isMonitored = args.value; if (!optRuntime) { break; @@ -428,6 +428,7 @@ class Blocks { } break; } + } this.resetCache(); } From fce83dc2c3ebb633568c7b9183e16ed87209dc1e Mon Sep 17 00:00:00 2001 From: DD Date: Wed, 10 Jan 2018 17:50:48 -0500 Subject: [PATCH 24/33] make it work for non-droppable inputs --- src/engine/blocks.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/engine/blocks.js b/src/engine/blocks.js index d5e734ceb4..b889f20101 100644 --- a/src/engine/blocks.js +++ b/src/engine/blocks.js @@ -386,15 +386,15 @@ class Blocks { // Changing the value in a dropdown block.fields[args.name].value = args.value; - if (!optRuntime || !block.parent){ + if (!optRuntime){ break; } - const parent = this._blocks[block.parent]; - if (parent.isMonitored) { + const flyoutBlock = block.shadow && block.parent ? this._blocks[block.parent] : block; + if (flyoutBlock.isMonitored) { optRuntime.requestUpdateMonitor(Map({ - id: parent.id, - params: this._getBlockParams(parent) + id: flyoutBlock.id, + params: this._getBlockParams(flyoutBlock) })); } } From 323aafd165cd870d6ec4d3ca0453c389f55a284c Mon Sep 17 00:00:00 2001 From: DD Date: Wed, 10 Jan 2018 17:57:04 -0500 Subject: [PATCH 25/33] Update VM's model of monitored blocks --- src/blocks/scratch3_looks.js | 5 ++--- src/blocks/scratch3_sensing.js | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/blocks/scratch3_looks.js b/src/blocks/scratch3_looks.js index 546f594df3..e173ca04db 100644 --- a/src/blocks/scratch3_looks.js +++ b/src/blocks/scratch3_looks.js @@ -244,9 +244,8 @@ class Scratch3LooksBlocks { getMonitored () { return { looks_size: {isSpriteSpecific: true}, - looks_costumeorder: {isSpriteSpecific: true}, - looks_backdroporder: {}, - looks_backdropname: {} + looks_costumenumbername: {isSpriteSpecific: true}, + looks_backdropnumbername: {} }; } diff --git a/src/blocks/scratch3_sensing.js b/src/blocks/scratch3_sensing.js index 4f977a6124..0cd4dde6d4 100644 --- a/src/blocks/scratch3_sensing.js +++ b/src/blocks/scratch3_sensing.js @@ -56,7 +56,6 @@ class Scratch3SensingBlocks { sensing_answer: {}, sensing_loudness: {}, sensing_timer: {}, - sensing_of: {}, sensing_current: {} }; } From bc64e6ed3e7f950c8b4825bfa7063936252812a7 Mon Sep 17 00:00:00 2001 From: DD Date: Thu, 11 Jan 2018 11:28:21 -0500 Subject: [PATCH 26/33] Unique sprite and backdrop names --- src/virtual-machine.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/virtual-machine.js b/src/virtual-machine.js index 7b997dbdd8..d1d8c2a142 100644 --- a/src/virtual-machine.js +++ b/src/virtual-machine.js @@ -268,6 +268,8 @@ class VirtualMachine extends EventEmitter { targets.forEach(target => { this.runtime.targets.push(target); (/** @type RenderedTarget */ target).updateAllDrawableProperties(); + // Ensure unique sprite name + if (target.isSprite()) this.renameSprite(target.id, target.getName()); }); // Select the first target for editing, e.g., the first sprite. if (wholeProject && (targets.length > 1)) { @@ -448,7 +450,7 @@ class VirtualMachine extends EventEmitter { addBackdrop (md5ext, backdropObject) { return loadCostume(md5ext, backdropObject, this.runtime).then(() => { const stage = this.runtime.getTargetForStage(); - stage.sprite.costumes.push(backdropObject); + stage.addCostume(backdropObject); stage.setCostume(stage.sprite.costumes.length - 1); }); } From 7ed58da1cff4517f8917ee5f3dd1817c59265236 Mon Sep 17 00:00:00 2001 From: DD Date: Thu, 11 Jan 2018 16:43:18 -0500 Subject: [PATCH 27/33] fix set editing target function --- src/virtual-machine.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/virtual-machine.js b/src/virtual-machine.js index d1d8c2a142..61b61e5184 100644 --- a/src/virtual-machine.js +++ b/src/virtual-machine.js @@ -631,7 +631,7 @@ class VirtualMachine extends EventEmitter { */ setEditingTarget (targetId) { // Has the target id changed? If not, exit. - if (targetId === this.editingTarget.id) { + if (this.editingTarget && targetId === this.editingTarget.id) { return; } const target = this.runtime.getTargetById(targetId); From 223575f8505a10050bb5b427ab66953171c4e89c Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Fri, 12 Jan 2018 11:20:17 -0500 Subject: [PATCH 28/33] Remove extra call to initDrawable that was erasing existing drawable. initDrawable is called in createClone previously, where the new costumes are installed. Calling this again here erases those loaded skins and causes an error when trying to calculate the uniforms when a drag starts. --- src/sprites/rendered-target.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sprites/rendered-target.js b/src/sprites/rendered-target.js index 1288240da9..fbd6b4bc81 100644 --- a/src/sprites/rendered-target.js +++ b/src/sprites/rendered-target.js @@ -831,7 +831,6 @@ class RenderedTarget extends Target { newTarget.effects = JSON.parse(JSON.stringify(this.effects)); newTarget.variables = JSON.parse(JSON.stringify(this.variables)); newTarget.lists = JSON.parse(JSON.stringify(this.lists)); - newTarget.initDrawable(); newTarget.updateAllDrawableProperties(); newTarget.goBehindOther(this); return newTarget; From 57f3d43f4f2e4bcf15e0daae5b91ddbcedc59a9d Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Sat, 13 Jan 2018 13:23:33 -0500 Subject: [PATCH 29/33] Update specmap for sensing blocks that are now fixed menus. --- src/serialization/sb2_specmap.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/serialization/sb2_specmap.js b/src/serialization/sb2_specmap.js index bf386a15ec..e9b56b9d0b 100644 --- a/src/serialization/sb2_specmap.js +++ b/src/serialization/sb2_specmap.js @@ -861,9 +861,8 @@ const specMap = { opcode: 'sensing_keypressed', argMap: [ { - type: 'input', - inputOp: 'sensing_keyoptions', - inputName: 'KEY_OPTION' + type: 'field', + fieldName: 'KEY_OPTION' } ] }, @@ -936,9 +935,8 @@ const specMap = { opcode: 'sensing_of', argMap: [ { - type: 'input', - inputOp: 'sensing_of_property_menu', - inputName: 'PROPERTY' + type: 'field', + fieldName: 'PROPERTY' }, { type: 'input', From 2c1412dda42f32c2cd67c39aa91c09d20e09f5bd Mon Sep 17 00:00:00 2001 From: DD Date: Tue, 16 Jan 2018 17:41:18 -0500 Subject: [PATCH 30/33] Use text-encoding library for text encoder --- package.json | 1 + src/virtual-machine.js | 1 + 2 files changed, 2 insertions(+) diff --git a/package.json b/package.json index 02caa814e4..e1a5af558b 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "socket.io-client": "2.0.4", "stats.js": "^0.17.0", "tap": "^10.2.0", + "text-encoding": "0.6.4", "tiny-worker": "^2.1.1", "webpack": "^3.10.0", "webpack-dev-server": "^2.4.1", diff --git a/src/virtual-machine.js b/src/virtual-machine.js index 61b61e5184..5cacafee51 100644 --- a/src/virtual-machine.js +++ b/src/virtual-machine.js @@ -1,3 +1,4 @@ +import {TextEncoder} from 'text-encoding'; const EventEmitter = require('events'); const centralDispatch = require('./dispatch/central-dispatch'); From c0db708986f7f3d511122e8459c531370e75fb33 Mon Sep 17 00:00:00 2001 From: Ray Schamp Date: Wed, 17 Jan 2018 09:47:01 -0500 Subject: [PATCH 31/33] Fix import syntax --- src/virtual-machine.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/virtual-machine.js b/src/virtual-machine.js index 5cacafee51..0612261a9d 100644 --- a/src/virtual-machine.js +++ b/src/virtual-machine.js @@ -1,4 +1,4 @@ -import {TextEncoder} from 'text-encoding'; +const TextEncoder = require('text-encoding'); const EventEmitter = require('events'); const centralDispatch = require('./dispatch/central-dispatch'); From e83141e81068f68b6cc8b3960ffc8ae836c7813e Mon Sep 17 00:00:00 2001 From: Ray Schamp Date: Wed, 17 Jan 2018 10:59:28 -0500 Subject: [PATCH 32/33] Revert "Use text-encoding library for text encoder" --- package.json | 1 - src/virtual-machine.js | 1 - 2 files changed, 2 deletions(-) diff --git a/package.json b/package.json index e1a5af558b..02caa814e4 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,6 @@ "socket.io-client": "2.0.4", "stats.js": "^0.17.0", "tap": "^10.2.0", - "text-encoding": "0.6.4", "tiny-worker": "^2.1.1", "webpack": "^3.10.0", "webpack-dev-server": "^2.4.1", diff --git a/src/virtual-machine.js b/src/virtual-machine.js index 0612261a9d..61b61e5184 100644 --- a/src/virtual-machine.js +++ b/src/virtual-machine.js @@ -1,4 +1,3 @@ -const TextEncoder = require('text-encoding'); const EventEmitter = require('events'); const centralDispatch = require('./dispatch/central-dispatch'); From d649b2555b09c4fae3f1fa2d469b67923848ae0d Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Wed, 17 Jan 2018 11:31:44 -0500 Subject: [PATCH 33/33] Require TextEncoder correctly --- package.json | 1 + src/virtual-machine.js | 1 + 2 files changed, 2 insertions(+) diff --git a/package.json b/package.json index 02caa814e4..e1a5af558b 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "socket.io-client": "2.0.4", "stats.js": "^0.17.0", "tap": "^10.2.0", + "text-encoding": "0.6.4", "tiny-worker": "^2.1.1", "webpack": "^3.10.0", "webpack-dev-server": "^2.4.1", diff --git a/src/virtual-machine.js b/src/virtual-machine.js index 61b61e5184..cab4090857 100644 --- a/src/virtual-machine.js +++ b/src/virtual-machine.js @@ -1,3 +1,4 @@ +const TextEncoder = require('text-encoding').TextEncoder; const EventEmitter = require('events'); const centralDispatch = require('./dispatch/central-dispatch');