Skip to content

Commit 695a8f7

Browse files
author
Christopher Willis-Ford
committed
in custom deserialize path, support dynamic blockInfo mutation
1 parent 91f1593 commit 695a8f7

File tree

3 files changed

+64
-21
lines changed

3 files changed

+64
-21
lines changed

src/blocks/data.js

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -790,17 +790,40 @@ class Scratch3DataBlocks {
790790
}
791791

792792
deserializeVariable (primitiveArray) {
793-
if (primitiveArray[0] !== VAR_PRIMITIVE) {
793+
const [primitiveTag, variableName, variableId] = primitiveArray;
794+
if (primitiveTag !== VAR_PRIMITIVE) {
794795
throw new Error('bad primitive ID on variable!');
795796
}
796-
const result = {};
797-
result.opcode = 'variable';
798-
result.fields = {
799-
VARIABLE: {
800-
name: 'VARIABLE',
801-
value: primitiveArray[1],
802-
id: primitiveArray[2],
803-
variableType: Variable.SCALAR_TYPE
797+
// @todo reduce duplication here.
798+
// This effectively duplicates some of the content in `getInfo()` as well as some of the logic in
799+
// `defineDynamicBlock`. Maybe it should return only the mutation blockInfo, or maybe it should return
800+
// something that looks like the block's entry in `getInfo()`. If we do either of those things we'll need to
801+
// decide how we want to handle the block's (x,y) position and `topLevel` flag.
802+
const result = {
803+
opcode: 'variable',
804+
fields: {
805+
VARIABLE: {
806+
name: 'VARIABLE',
807+
id: variableId,
808+
variableType: Variable.SCALAR_TYPE
809+
}
810+
},
811+
inputs: {},
812+
next: null,
813+
parent: null,
814+
shadow: false,
815+
mutation: {
816+
blockInfo: {
817+
blockType: BlockType.REPORTER,
818+
isDynamic: true,
819+
opcode: 'variable',
820+
text: variableName,
821+
paletteKey: variableId,
822+
customContextMenu: [{
823+
text: 'Rename Variable',
824+
builtInCallback: 'RENAME_A_VARIABLE'
825+
}]
826+
}
804827
}
805828
};
806829
if (primitiveArray.length > 3) {

src/engine/mutation-adapter.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const mutatorTagToObject = function (dom) {
3434
* @param {(object|string)} mutation Mutation XML string or DOM.
3535
* @return {object} Object representing the mutation.
3636
*/
37-
const mutationAdpater = function (mutation) {
37+
const mutationAdapter = function (mutation) {
3838
let mutationParsed;
3939
// Check if the mutation is already parsed; if not, parse it.
4040
if (typeof mutation === 'object') {
@@ -45,4 +45,4 @@ const mutationAdpater = function (mutation) {
4545
return mutatorTagToObject(mutationParsed);
4646
};
4747

48-
module.exports = mutationAdpater;
48+
module.exports = mutationAdapter;

src/serialization/sb3.js

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ const uid = require('../util/uid');
1616
const MathUtil = require('../util/math-util');
1717
const StringUtil = require('../util/string-util');
1818
const VariableUtil = require('../util/variable-util');
19+
const mutationAdapter = require('../engine/mutation-adapter');
20+
const xmlEscape = require('../util/xml-escape');
1921

2022
const {loadCostume} = require('../import/load-costume.js');
2123
const {loadSound} = require('../import/load-sound.js');
@@ -852,6 +854,16 @@ const deserializeFields = function (fields) {
852854
return obj;
853855
};
854856

857+
/**
858+
* Convert dynamic blockInfo JSON from a custom deserialization function into an XML element ready for runtime use.
859+
* @param {object} dynamicBlockInfo - a BlockInfo-like object with properties like `opcode` and `isDynamic`.
860+
* @returns {Element} - an XML Element with tag name 'mutation' and an attribute called 'blockInfo'
861+
*/
862+
const makeMutation = function (dynamicBlockInfo) {
863+
const xmlString = `<mutation blockInfo="${xmlEscape(JSON.stringify(dynamicBlockInfo))}"/>`;
864+
return mutationAdapter(xmlString);
865+
};
866+
855867
/**
856868
* Convert serialized INPUT and FIELD primitives back to hydrated block templates.
857869
* Should be able to deserialize a format that has already been deserialized. The only
@@ -867,26 +879,34 @@ const deserializeBlocks = function (runtime, blocks) {
867879
if (!blocks.hasOwnProperty(blockId)) {
868880
continue;
869881
}
870-
const block = blocks[blockId];
882+
let block = blocks[blockId];
871883
const {extensionId, extendedOpcode} = getExtensionAndOpcode(block);
872884
const extensionInfo = runtime.getExtensionInfo(extensionId);
873885
const customDeserialize = getSerializationInfo(extensionInfo, extendedOpcode).deserialize;
874886
if (customDeserialize) {
875-
const newBlock = customDeserialize(block);
876-
newBlock.id = uid();
877-
delete blocks[blockId];
878-
blocks[newBlock.id] = newBlock;
879-
// fall through to input/field handling
880-
}
881-
if (Array.isArray(block)) {
887+
block = customDeserialize(block);
888+
blocks[blockId] = block; // customDeserialize might have made a new object; make sure to keep it!
889+
// the deserialize function should return opcode 'foo' and we'll fix it to 'ext_foo'
890+
const newBlockOpcode = `${extensionId}_${block.opcode}`;
891+
if (newBlockOpcode !== extendedOpcode) {
892+
log.warn(`Block deserialization changed opcode from ${extendedOpcode} to ${block.opcode}`);
893+
}
894+
block.opcode = newBlockOpcode;
895+
if (block.mutation && block.mutation.blockInfo) {
896+
block.mutation = makeMutation(block.mutation.blockInfo);
897+
}
898+
// fall through to input & field handling below
899+
} else if (Array.isArray(block)) {
882900
// this is a compressed primitive expressed as an array instead of as an object
883901
// delete the old entry in object.blocks and replace it w/the deserialized object
884902
delete blocks[blockId];
885903
deserializeInputDesc(block, null, false, blocks);
904+
// deserializeInputDesc did any id/input/field handling needed, so just skip to the next block
886905
continue;
887-
} else {
888-
block.id = blockId; // add id back to block since it wasn't serialized
889906
}
907+
// we want this stuff to run for custom-deserialized blocks as well as for default-deserialized blocks
908+
// except for primitives, which are handled specially inside `deserializeInputDesc` above.
909+
block.id = blockId; // add id back to block since it wasn't serialized
890910
block.inputs = deserializeInputs(block.inputs, blockId, blocks);
891911
block.fields = deserializeFields(block.fields);
892912
}

0 commit comments

Comments
 (0)