diff --git a/src/engine/blocks.js b/src/engine/blocks.js index 5a0bbd965..b70c564c0 100644 --- a/src/engine/blocks.js +++ b/src/engine/blocks.js @@ -964,6 +964,14 @@ class Blocks { } } + // Delete comments attached to the block. + if (block.comment) { + const editingTarget = this.runtime.getEditingTarget(); + if (editingTarget && editingTarget.comments.hasOwnProperty(block.comment)) { + delete editingTarget.comments[block.comment]; + } + } + // Delete any script starting with this block. this._deleteScript(blockId); diff --git a/src/engine/target.js b/src/engine/target.js index 166e44369..01cce1d47 100644 --- a/src/engine/target.js +++ b/src/engine/target.js @@ -20,9 +20,10 @@ class Target extends EventEmitter { /** * @param {Runtime} runtime Reference to the runtime. * @param {?Blocks} blocks Blocks instance for the blocks owned by this target. + * @param {?Object.<string, Comment>} comments Array of comments owned by this target. * @constructor */ - constructor (runtime, blocks) { + constructor (runtime, blocks, comments) { super(); if (!blocks) { @@ -55,7 +56,7 @@ class Target extends EventEmitter { * Key is the comment id. * @type {Object.<string,*>} */ - this.comments = {}; + this.comments = comments || {}; /** * Dictionary of custom state for this target. * This can be used to store target-specific custom state for blocks which need it. diff --git a/src/sprites/rendered-target.js b/src/sprites/rendered-target.js index e4a12e244..d3230cce1 100644 --- a/src/sprites/rendered-target.js +++ b/src/sprites/rendered-target.js @@ -15,7 +15,7 @@ class RenderedTarget extends Target { * @constructor */ constructor (sprite, runtime) { - super(runtime, sprite.blocks); + super(runtime, sprite.blocks, sprite.comments); /** * Reference to the sprite that this is a render of. diff --git a/src/sprites/sprite.js b/src/sprites/sprite.js index a38fa129c..bb6d920f3 100644 --- a/src/sprites/sprite.js +++ b/src/sprites/sprite.js @@ -1,10 +1,12 @@ const RenderedTarget = require('./rendered-target'); const Blocks = require('../engine/blocks'); +const Comment = require('../engine/comment'); const {loadSoundFromAsset} = require('../import/load-sound'); const {loadCostumeFromAsset} = require('../import/load-costume'); const newBlockIds = require('../util/new-block-ids'); const StringUtil = require('../util/string-util'); const StageLayering = require('../engine/stage-layering'); +const log = require('../util/log'); class Sprite { /** @@ -54,6 +56,13 @@ class Sprite { if (this.runtime && this.runtime.audioEngine) { this.soundBank = this.runtime.audioEngine.createBank(); } + + /** + * Dictionary of comments for this target. + * Key is the comment id. + * @type {Object.<string, Comment>} + */ + this.comments = {}; } /** @@ -140,11 +149,48 @@ class Sprite { const blocksContainer = this.blocks._blocks; const originalBlocks = Object.keys(blocksContainer).map(key => blocksContainer[key]); const copiedBlocks = JSON.parse(JSON.stringify(originalBlocks)); - newBlockIds(copiedBlocks); + const oldToNew = newBlockIds(copiedBlocks); copiedBlocks.forEach(block => { newSprite.blocks.createBlock(block); }); + Object.keys(this.comments).forEach(commentId => { + const comment = this.comments[commentId]; + const newComment = new Comment( + null, // generate new comment id + comment.text, + comment.x, + comment.y, + comment.width, + comment.height, + comment.minimized + ); + // If the comment is attached to a block, it has a blockId property for the block it's attached to. + // Because we generate new block IDs for all the duplicated blocks, change all the comments' attached-block + // IDs from the old block IDs to the new ones. + if (comment.blockId) { + const newBlockId = oldToNew[comment.blockId]; + if (newBlockId) { + newComment.blockId = newBlockId; + const newBlock = newSprite.blocks.getBlock(newBlockId); + if (newBlock) { + newBlock.comment = newComment.id; + } else { + log.warn(`Could not find block with id ${newBlockId} associated with comment ${newComment.id}`); + } + } else { + // Comments did not get deleted when the block got deleted + // Such comments have blockId, but because the block is deleted + // oldToNew mapping does not include that blockId + // Handle it as workspace comment + // TODO do not load such comments when deserializing + log.warn(`Could not find block with id ${comment.blockId + } associated with comment ${comment.id} in the block ID mapping`); + } + } + newSprite.comments[newComment.id] = newComment; + }); + const allNames = this.runtime.targets.map(t => t.sprite.name); newSprite.name = StringUtil.unusedName(this.name, allNames); diff --git a/src/util/new-block-ids.js b/src/util/new-block-ids.js index 1fc5f6967..a167e7f48 100644 --- a/src/util/new-block-ids.js +++ b/src/util/new-block-ids.js @@ -4,6 +4,7 @@ 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. + * @returns {object.<string, string>} - mapping of old block ID to new block ID */ module.exports = blocks => { const oldToNew = {}; @@ -30,4 +31,7 @@ module.exports = blocks => { blocks[i].next = oldToNew[blocks[i].next]; } } + + // There are other things that need this mapping e.g. comments + return oldToNew; }; diff --git a/test/unit/engine_blocks.js b/test/unit/engine_blocks.js index f4451aa39..80b99b06d 100644 --- a/test/unit/engine_blocks.js +++ b/test/unit/engine_blocks.js @@ -600,6 +600,25 @@ test('delete inputs', t => { t.end(); }); +test('delete block with comment', t => { + const b = new Blocks(new Runtime()); + const fakeTarget = { + comments: { + bar: { + blockId: 'foo' + } + } + }; + b.runtime.getEditingTarget = () => fakeTarget; + b.createBlock({ + id: 'foo', + comment: 'bar' + }); + b.deleteBlock('foo'); + t.notOk(fakeTarget.comments.hasOwnProperty('bar')); + t.end(); +}); + test('updateAssetName function updates name in sound field', t => { const b = new Blocks(new Runtime()); b.createBlock({ diff --git a/test/unit/sprites_rendered-target.js b/test/unit/sprites_rendered-target.js index a9823424a..ff5e1fa69 100644 --- a/test/unit/sprites_rendered-target.js +++ b/test/unit/sprites_rendered-target.js @@ -45,6 +45,21 @@ test('blocks get new id on duplicate', t => { }); }); +test('comments are duplicated when duplicating target', t => { + const r = new Runtime(); + const s = new Sprite(null, r); + const rt = new RenderedTarget(s, r); + rt.createComment('commentid', null, 'testcomment', 0, 0, 100, 100, false); + t.ok(s.comments.hasOwnProperty('commentid')); + return rt.duplicate().then(duplicate => { + // Not ok because comment id should be re-generated + t.notOk(duplicate.comments.hasOwnProperty('commentid')); + t.ok(Object.keys(duplicate.comments).length === 1); + t.end(); + }); +}); + + test('direction', t => { const r = new Runtime(); const s = new Sprite(null, r);