mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2025-01-09 14:32:07 -05:00
Add a utility for giving blocks new IDs, use it for sharing blocks.
This commit is contained in:
parent
884fd54d2b
commit
31fbcfa4d7
4 changed files with 164 additions and 0 deletions
33
src/util/new-block-ids.js
Normal file
33
src/util/new-block-ids.js
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
const uid = require('./uid');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mutate the given blocks to have new IDs and update all internal ID references.
|
||||||
|
* Does not return anything to make it clear that the blocks are updated in-place.
|
||||||
|
* @param {array} blocks - blocks to be mutated.
|
||||||
|
*/
|
||||||
|
module.exports = blocks => {
|
||||||
|
const oldToNew = {};
|
||||||
|
|
||||||
|
// First update all top-level IDs and create old-to-new mapping
|
||||||
|
for (let i = 0; i < blocks.length; i++) {
|
||||||
|
const newId = uid();
|
||||||
|
const oldId = blocks[i].id;
|
||||||
|
blocks[i].id = oldToNew[oldId] = newId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then go back through and update inputs (block/shadow)
|
||||||
|
// and next/parent properties
|
||||||
|
for (let i = 0; i < blocks.length; i++) {
|
||||||
|
for (const key in blocks[i].inputs) {
|
||||||
|
const input = blocks[i].inputs[key];
|
||||||
|
input.block = oldToNew[input.block];
|
||||||
|
input.shadow = oldToNew[input.shadow];
|
||||||
|
}
|
||||||
|
if (blocks[i].parent) {
|
||||||
|
blocks[i].parent = oldToNew[blocks[i].parent];
|
||||||
|
}
|
||||||
|
if (blocks[i].next) {
|
||||||
|
blocks[i].next = oldToNew[blocks[i].next];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -16,6 +16,7 @@ const formatMessage = require('format-message');
|
||||||
const validate = require('scratch-parser');
|
const validate = require('scratch-parser');
|
||||||
|
|
||||||
const Variable = require('./engine/variable');
|
const Variable = require('./engine/variable');
|
||||||
|
const newBlockIds = require('./util/new-block-ids');
|
||||||
|
|
||||||
const {loadCostume} = require('./import/load-costume.js');
|
const {loadCostume} = require('./import/load-costume.js');
|
||||||
const {loadSound} = require('./import/load-sound.js');
|
const {loadSound} = require('./import/load-sound.js');
|
||||||
|
@ -1178,6 +1179,7 @@ class VirtualMachine extends EventEmitter {
|
||||||
*/
|
*/
|
||||||
shareBlocksToTarget (blocks, targetId, optFromTargetId) {
|
shareBlocksToTarget (blocks, targetId, optFromTargetId) {
|
||||||
const copiedBlocks = JSON.parse(JSON.stringify(blocks));
|
const copiedBlocks = JSON.parse(JSON.stringify(blocks));
|
||||||
|
newBlockIds(copiedBlocks);
|
||||||
const target = this.runtime.getTargetById(targetId);
|
const target = this.runtime.getTargetById(targetId);
|
||||||
|
|
||||||
if (optFromTargetId) {
|
if (optFromTargetId) {
|
||||||
|
|
65
test/fixtures/simple-stack.js
vendored
Normal file
65
test/fixtures/simple-stack.js
vendored
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
module.exports = [
|
||||||
|
{
|
||||||
|
id: '1ZGd(W8DvU?vI1RN)e0E',
|
||||||
|
opcode: 'motion_goto',
|
||||||
|
inputs: {
|
||||||
|
TO: {
|
||||||
|
name: 'TO',
|
||||||
|
block: 'Ht(rOKMe0@sB3n4(b3;=',
|
||||||
|
shadow: '-C=c-_TI_7d(l3ii2[wh'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fields: {
|
||||||
|
|
||||||
|
},
|
||||||
|
next: 'l.JBk`WcXE+A@i9y1tCU',
|
||||||
|
topLevel: true,
|
||||||
|
parent: null,
|
||||||
|
shadow: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Ht(rOKMe0@sB3n4(b3;=',
|
||||||
|
opcode: 'looks_size',
|
||||||
|
inputs: {
|
||||||
|
|
||||||
|
},
|
||||||
|
fields: {
|
||||||
|
|
||||||
|
},
|
||||||
|
next: null,
|
||||||
|
topLevel: false,
|
||||||
|
parent: '1ZGd(W8DvU?vI1RN)e0E',
|
||||||
|
shadow: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '-C=c-_TI_7d(l3ii2[wh',
|
||||||
|
opcode: 'motion_goto_menu',
|
||||||
|
inputs: {
|
||||||
|
|
||||||
|
},
|
||||||
|
fields: {
|
||||||
|
TO: {
|
||||||
|
name: 'TO',
|
||||||
|
value: '_random_'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
next: null,
|
||||||
|
topLevel: false,
|
||||||
|
parent: '1ZGd(W8DvU?vI1RN)e0E',
|
||||||
|
shadow: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'l.JBk`WcXE+A@i9y1tCU',
|
||||||
|
opcode: 'sound_stopallsounds',
|
||||||
|
inputs: {
|
||||||
|
|
||||||
|
},
|
||||||
|
fields: {
|
||||||
|
|
||||||
|
},
|
||||||
|
next: null,
|
||||||
|
topLevel: false,
|
||||||
|
parent: '1ZGd(W8DvU?vI1RN)e0E',
|
||||||
|
shadow: false
|
||||||
|
}
|
||||||
|
];
|
64
test/unit/util_new-block-ids.js
Normal file
64
test/unit/util_new-block-ids.js
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
const newBlockIds = require('../../src/util/new-block-ids');
|
||||||
|
const simpleStack = require('../fixtures/simple-stack');
|
||||||
|
const tap = require('tap');
|
||||||
|
const test = tap.test;
|
||||||
|
|
||||||
|
let originals;
|
||||||
|
let newBlocks;
|
||||||
|
|
||||||
|
tap.beforeEach(done => {
|
||||||
|
originals = simpleStack;
|
||||||
|
// Will be mutated so make a copy first
|
||||||
|
newBlocks = JSON.parse(JSON.stringify(simpleStack));
|
||||||
|
newBlockIds(newBlocks);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The structure of the simple stack is:
|
||||||
|
* moveTo (looks_size) -> stopAllSounds
|
||||||
|
* The list of blocks is
|
||||||
|
* 0: moveTo (TO input block: 1, shadow: 2)
|
||||||
|
* 1: looks_size (parent: 0)
|
||||||
|
* 2: obscured shadow for moveTo input (parent: 0)
|
||||||
|
* 3: stopAllSounds (parent: 0)
|
||||||
|
* Inspect fixtures/blocks for the full object.
|
||||||
|
*/
|
||||||
|
|
||||||
|
test('top-level block IDs have all changed', t => {
|
||||||
|
newBlocks.forEach((block, i) => {
|
||||||
|
t.notEqual(block.id, originals[i].id);
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('input reference is maintained on parent for attached block', t => {
|
||||||
|
t.equal(newBlocks[0].inputs.TO.block, newBlocks[1].id);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('input reference is maintained on parent for obscured shadow', t => {
|
||||||
|
t.equal(newBlocks[0].inputs.TO.shadow, newBlocks[2].id);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('parent reference is maintained for attached input', t => {
|
||||||
|
t.equal(newBlocks[1].parent, newBlocks[0].id);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('parent reference is maintained for obscured shadow', t => {
|
||||||
|
t.equal(newBlocks[2].parent, newBlocks[0].id);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('parent reference is maintained for next block', t => {
|
||||||
|
t.equal(newBlocks[3].parent, newBlocks[0].id);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('next reference is maintained for previous block', t => {
|
||||||
|
t.equal(newBlocks[0].next, newBlocks[3].id);
|
||||||
|
t.end();
|
||||||
|
});
|
Loading…
Reference in a new issue