2016-06-08 13:44:09 -04:00
|
|
|
var adapter = require('./adapter');
|
2016-10-03 17:43:24 -04:00
|
|
|
var mutationAdapter = require('./mutation-adapter');
|
2016-09-13 17:51:17 -04:00
|
|
|
var xmlEscape = require('../util/xml-escape');
|
2016-06-08 13:44:09 -04:00
|
|
|
|
2016-06-06 15:29:34 -04:00
|
|
|
/**
|
2016-06-08 13:27:01 -04:00
|
|
|
* @fileoverview
|
2016-06-06 15:29:34 -04:00
|
|
|
* Store and mutate the VM block representation,
|
|
|
|
* and handle updates from Scratch Blocks events.
|
|
|
|
*/
|
|
|
|
|
2016-06-08 13:27:01 -04:00
|
|
|
function Blocks () {
|
|
|
|
/**
|
|
|
|
* All blocks in the workspace.
|
|
|
|
* Keys are block IDs, values are metadata about the block.
|
|
|
|
* @type {Object.<string, Object>}
|
|
|
|
*/
|
|
|
|
this._blocks = {};
|
|
|
|
|
|
|
|
/**
|
2016-08-11 11:11:27 -04:00
|
|
|
* All top-level scripts in the workspace.
|
|
|
|
* A list of block IDs that represent scripts (i.e., first block in script).
|
2016-06-08 13:27:01 -04:00
|
|
|
* @type {Array.<String>}
|
|
|
|
*/
|
2016-08-11 11:11:27 -04:00
|
|
|
this._scripts = [];
|
2016-06-08 13:27:01 -04:00
|
|
|
}
|
2016-06-06 15:29:34 -04:00
|
|
|
|
2016-06-09 13:26:07 -04:00
|
|
|
/**
|
2016-08-10 11:27:21 -04:00
|
|
|
* Blockly inputs that represent statements/branch.
|
2016-06-09 13:26:07 -04:00
|
|
|
* are prefixed with this string.
|
|
|
|
* @const{string}
|
|
|
|
*/
|
2016-08-10 11:27:21 -04:00
|
|
|
Blocks.BRANCH_INPUT_PREFIX = 'SUBSTACK';
|
2016-06-09 13:26:07 -04:00
|
|
|
|
2016-06-06 15:29:34 -04:00
|
|
|
/**
|
|
|
|
* Provide an object with metadata for the requested block ID.
|
|
|
|
* @param {!string} blockId ID of block we have stored.
|
|
|
|
* @return {?Object} Metadata about the block, if it exists.
|
|
|
|
*/
|
2016-06-08 13:27:01 -04:00
|
|
|
Blocks.prototype.getBlock = function (blockId) {
|
|
|
|
return this._blocks[blockId];
|
|
|
|
};
|
2016-06-06 15:29:34 -04:00
|
|
|
|
|
|
|
/**
|
2016-08-11 11:11:27 -04:00
|
|
|
* Get all known top-level blocks that start scripts.
|
2016-06-06 15:29:34 -04:00
|
|
|
* @return {Array.<string>} List of block IDs.
|
|
|
|
*/
|
2016-08-11 11:11:27 -04:00
|
|
|
Blocks.prototype.getScripts = function () {
|
|
|
|
return this._scripts;
|
2016-06-08 13:27:01 -04:00
|
|
|
};
|
2016-06-06 15:29:34 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the next block for a particular block
|
|
|
|
* @param {?string} id ID of block to get the next block for
|
|
|
|
* @return {?string} ID of next block in the sequence
|
|
|
|
*/
|
2016-06-08 13:27:01 -04:00
|
|
|
Blocks.prototype.getNextBlock = function (id) {
|
|
|
|
if (typeof this._blocks[id] === 'undefined') return null;
|
|
|
|
return this._blocks[id].next;
|
|
|
|
};
|
2016-06-06 15:29:34 -04:00
|
|
|
|
2016-06-08 13:27:01 -04:00
|
|
|
/**
|
2016-08-10 11:27:21 -04:00
|
|
|
* Get the branch for a particular C-shaped block.
|
|
|
|
* @param {?string} id ID for block to get the branch for.
|
|
|
|
* @param {?number} branchNum Which branch to select (e.g. for if-else).
|
|
|
|
* @return {?string} ID of block in the branch.
|
2016-06-08 13:27:01 -04:00
|
|
|
*/
|
2016-08-10 11:27:21 -04:00
|
|
|
Blocks.prototype.getBranch = function (id, branchNum) {
|
2016-06-08 13:27:01 -04:00
|
|
|
var block = this._blocks[id];
|
|
|
|
if (typeof block === 'undefined') return null;
|
2016-08-10 11:27:21 -04:00
|
|
|
if (!branchNum) branchNum = 1;
|
2016-06-06 15:31:14 -04:00
|
|
|
|
2016-08-10 11:27:21 -04:00
|
|
|
var inputName = Blocks.BRANCH_INPUT_PREFIX;
|
|
|
|
if (branchNum > 1) {
|
|
|
|
inputName += branchNum;
|
2016-06-08 13:27:01 -04:00
|
|
|
}
|
2016-06-06 15:31:14 -04:00
|
|
|
|
2016-06-08 13:27:01 -04:00
|
|
|
// Empty C-block?
|
|
|
|
if (!(inputName in block.inputs)) return null;
|
|
|
|
return block.inputs[inputName].block;
|
|
|
|
};
|
2016-06-06 15:29:34 -04:00
|
|
|
|
2016-06-08 13:27:01 -04:00
|
|
|
/**
|
|
|
|
* Get the opcode for a particular block
|
|
|
|
* @param {?string} id ID of block to query
|
|
|
|
* @return {?string} the opcode corresponding to that block
|
|
|
|
*/
|
|
|
|
Blocks.prototype.getOpcode = function (id) {
|
|
|
|
if (typeof this._blocks[id] === 'undefined') return null;
|
|
|
|
return this._blocks[id].opcode;
|
|
|
|
};
|
2016-06-06 15:29:34 -04:00
|
|
|
|
2016-06-09 13:26:07 -04:00
|
|
|
/**
|
|
|
|
* Get all fields and their values for a block.
|
|
|
|
* @param {?string} id ID of block to query.
|
|
|
|
* @return {!Object} All fields and their values.
|
|
|
|
*/
|
|
|
|
Blocks.prototype.getFields = function (id) {
|
|
|
|
if (typeof this._blocks[id] === 'undefined') return null;
|
|
|
|
return this._blocks[id].fields;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2016-08-10 11:27:21 -04:00
|
|
|
* Get all non-branch inputs for a block.
|
2016-06-09 13:26:07 -04:00
|
|
|
* @param {?string} id ID of block to query.
|
2016-08-10 11:27:21 -04:00
|
|
|
* @return {!Object} All non-branch inputs and their associated blocks.
|
2016-06-09 13:26:07 -04:00
|
|
|
*/
|
|
|
|
Blocks.prototype.getInputs = function (id) {
|
|
|
|
if (typeof this._blocks[id] === 'undefined') return null;
|
|
|
|
var inputs = {};
|
|
|
|
for (var input in this._blocks[id].inputs) {
|
2016-08-10 11:27:21 -04:00
|
|
|
// Ignore blocks prefixed with branch prefix.
|
|
|
|
if (input.substring(0, Blocks.BRANCH_INPUT_PREFIX.length)
|
|
|
|
!= Blocks.BRANCH_INPUT_PREFIX) {
|
2016-06-09 13:26:07 -04:00
|
|
|
inputs[input] = this._blocks[id].inputs[input];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return inputs;
|
|
|
|
};
|
|
|
|
|
2016-10-03 17:43:24 -04:00
|
|
|
/**
|
|
|
|
* Get mutation data for a block.
|
|
|
|
* @param {?string} id ID of block to query.
|
|
|
|
* @return {!Object} Mutation for the block.
|
|
|
|
*/
|
|
|
|
Blocks.prototype.getMutation = function (id) {
|
|
|
|
if (typeof this._blocks[id] === 'undefined') return null;
|
|
|
|
return this._blocks[id].mutation;
|
|
|
|
};
|
|
|
|
|
2016-09-08 09:40:53 -04:00
|
|
|
/**
|
|
|
|
* Get the top-level script for a given block.
|
|
|
|
* @param {?string} id ID of block to query.
|
|
|
|
* @return {?string} ID of top-level script block.
|
|
|
|
*/
|
|
|
|
Blocks.prototype.getTopLevelScript = function (id) {
|
|
|
|
if (typeof this._blocks[id] === 'undefined') return null;
|
|
|
|
var block = this._blocks[id];
|
|
|
|
while (block.parent !== null) {
|
|
|
|
block = this._blocks[block.parent];
|
|
|
|
}
|
|
|
|
return block.id;
|
|
|
|
};
|
|
|
|
|
2016-10-03 17:43:24 -04:00
|
|
|
/**
|
|
|
|
* Get the procedure definition for a given name.
|
|
|
|
* @param {?string} name Name of procedure to query.
|
|
|
|
* @return {?string} ID of procedure definition.
|
|
|
|
*/
|
|
|
|
Blocks.prototype.getProcedureDefinition = function (name) {
|
|
|
|
for (var id in this._blocks) {
|
|
|
|
var block = this._blocks[id];
|
|
|
|
if ((block.opcode == 'procedures_defnoreturn' ||
|
|
|
|
block.opcode == 'procedures_defreturn') &&
|
2016-10-13 13:11:26 -04:00
|
|
|
block.mutation.proccode == name) {
|
2016-10-03 17:43:24 -04:00
|
|
|
return id;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
};
|
|
|
|
|
2016-10-13 13:11:26 -04:00
|
|
|
/**
|
|
|
|
* Get the procedure definition for a given name.
|
|
|
|
* @param {?string} name Name of procedure to query.
|
|
|
|
* @return {?string} ID of procedure definition.
|
|
|
|
*/
|
|
|
|
Blocks.prototype.getProcedureParamNames = function (name) {
|
|
|
|
for (var id in this._blocks) {
|
|
|
|
var block = this._blocks[id];
|
|
|
|
if ((block.opcode == 'procedures_defnoreturn' ||
|
|
|
|
block.opcode == 'procedures_defreturn') &&
|
|
|
|
block.mutation.proccode == name) {
|
|
|
|
return JSON.parse(block.mutation.argumentnames);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
};
|
|
|
|
|
2016-06-08 13:27:01 -04:00
|
|
|
// ---------------------------------------------------------------------
|
2016-06-06 15:29:34 -04:00
|
|
|
|
2016-06-08 13:44:09 -04:00
|
|
|
/**
|
|
|
|
* Create event listener for blocks. Handles validation and serves as a generic
|
|
|
|
* adapter between the blocks and the runtime interface.
|
2016-08-31 11:34:17 -04:00
|
|
|
* @param {Object} e Blockly "block" event
|
2016-06-08 13:44:09 -04:00
|
|
|
* @param {boolean} isFlyout If true, create a listener for flyout events.
|
|
|
|
* @param {?Runtime} opt_runtime Optional runtime to forward click events to.
|
|
|
|
*/
|
|
|
|
|
2016-08-31 11:34:17 -04:00
|
|
|
Blocks.prototype.blocklyListen = function (e, isFlyout, opt_runtime) {
|
|
|
|
// Validate event
|
|
|
|
if (typeof e !== 'object') return;
|
|
|
|
if (typeof e.blockId !== 'string') return;
|
|
|
|
|
|
|
|
// UI event: clicked scripts toggle in the runtime.
|
|
|
|
if (e.element === 'stackclick') {
|
|
|
|
if (opt_runtime) {
|
|
|
|
opt_runtime.toggleScript(e.blockId);
|
2016-06-08 13:44:09 -04:00
|
|
|
}
|
2016-08-31 11:34:17 -04:00
|
|
|
return;
|
|
|
|
}
|
2016-06-08 13:44:09 -04:00
|
|
|
|
2016-08-31 11:34:17 -04:00
|
|
|
// Block create/update/destroy
|
|
|
|
switch (e.type) {
|
|
|
|
case 'create':
|
|
|
|
var newBlocks = adapter(e);
|
|
|
|
// A create event can create many blocks. Add them all.
|
|
|
|
for (var i = 0; i < newBlocks.length; i++) {
|
|
|
|
this.createBlock(newBlocks[i], isFlyout);
|
2016-06-08 13:44:09 -04:00
|
|
|
}
|
2016-08-31 11:34:17 -04:00
|
|
|
break;
|
|
|
|
case 'change':
|
|
|
|
this.changeBlock({
|
|
|
|
id: e.blockId,
|
|
|
|
element: e.element,
|
|
|
|
name: e.name,
|
|
|
|
value: e.newValue
|
|
|
|
});
|
|
|
|
break;
|
|
|
|
case 'move':
|
|
|
|
this.moveBlock({
|
|
|
|
id: e.blockId,
|
|
|
|
oldParent: e.oldParentId,
|
|
|
|
oldInput: e.oldInputName,
|
|
|
|
newParent: e.newParentId,
|
|
|
|
newInput: e.newInputName,
|
|
|
|
newCoordinate: e.newCoordinate
|
|
|
|
});
|
|
|
|
break;
|
|
|
|
case 'delete':
|
2016-09-06 10:55:52 -04:00
|
|
|
// Don't accept delete events for shadow blocks being obscured.
|
|
|
|
if (this._blocks[e.blockId].shadow) {
|
|
|
|
return;
|
|
|
|
}
|
2016-09-15 13:51:40 -04:00
|
|
|
// Inform any runtime to forget about glows on this script.
|
|
|
|
if (opt_runtime && this._blocks[e.blockId].topLevel) {
|
|
|
|
opt_runtime.quietGlow(e.blockId);
|
|
|
|
}
|
2016-08-31 11:34:17 -04:00
|
|
|
this.deleteBlock({
|
|
|
|
id: e.blockId
|
|
|
|
});
|
|
|
|
break;
|
|
|
|
}
|
2016-06-08 13:44:09 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------
|
|
|
|
|
2016-06-08 13:27:01 -04:00
|
|
|
/**
|
2016-08-11 11:11:27 -04:00
|
|
|
* Block management: create blocks and scripts from a `create` event
|
2016-06-08 13:27:01 -04:00
|
|
|
* @param {!Object} block Blockly create event to be processed
|
2016-06-08 16:57:08 -04:00
|
|
|
* @param {boolean} opt_isFlyoutBlock Whether the block is in the flyout.
|
2016-06-08 13:27:01 -04:00
|
|
|
*/
|
|
|
|
Blocks.prototype.createBlock = function (block, opt_isFlyoutBlock) {
|
2016-09-06 10:55:52 -04:00
|
|
|
// Does the block already exist?
|
|
|
|
// Could happen, e.g., for an unobscured shadow.
|
|
|
|
if (this._blocks.hasOwnProperty(block.id)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Create new block.
|
2016-06-08 13:27:01 -04:00
|
|
|
this._blocks[block.id] = block;
|
2016-08-11 11:11:27 -04:00
|
|
|
// Push block id to scripts array.
|
2016-06-08 13:27:01 -04:00
|
|
|
// Blocks are added as a top-level stack if they are marked as a top-block
|
|
|
|
// (if they were top-level XML in the event) and if they are not
|
|
|
|
// flyout blocks.
|
|
|
|
if (!opt_isFlyoutBlock && block.topLevel) {
|
2016-08-11 11:11:27 -04:00
|
|
|
this._addScript(block.id);
|
2016-06-08 13:27:01 -04:00
|
|
|
}
|
|
|
|
};
|
2016-06-06 15:29:34 -04:00
|
|
|
|
2016-06-08 13:27:01 -04:00
|
|
|
/**
|
|
|
|
* Block management: change block field values
|
|
|
|
* @param {!Object} args Blockly change event to be processed
|
|
|
|
*/
|
|
|
|
Blocks.prototype.changeBlock = function (args) {
|
|
|
|
// Validate
|
2016-10-03 17:43:24 -04:00
|
|
|
if (args.element !== 'field' && args.element !== 'mutation') return;
|
2016-06-08 13:27:01 -04:00
|
|
|
if (typeof this._blocks[args.id] === 'undefined') return;
|
2016-06-06 15:29:34 -04:00
|
|
|
|
2016-10-03 17:43:24 -04:00
|
|
|
if (args.element == 'field') {
|
|
|
|
// Update block value
|
|
|
|
if (!this._blocks[args.id].fields[args.name]) return;
|
|
|
|
this._blocks[args.id].fields[args.name].value = args.value;
|
|
|
|
} else if (args.element == 'mutation') {
|
|
|
|
this._blocks[args.id].mutation = mutationAdapter(args.value);
|
|
|
|
}
|
2016-06-08 13:27:01 -04:00
|
|
|
};
|
2016-06-06 15:29:34 -04:00
|
|
|
|
2016-06-08 13:27:01 -04:00
|
|
|
/**
|
|
|
|
* Block management: move blocks from parent to parent
|
|
|
|
* @param {!Object} e Blockly move event to be processed
|
|
|
|
*/
|
|
|
|
Blocks.prototype.moveBlock = function (e) {
|
2016-08-31 11:34:29 -04:00
|
|
|
// Move coordinate changes.
|
|
|
|
if (e.newCoordinate) {
|
|
|
|
this._blocks[e.id].x = e.newCoordinate.x;
|
|
|
|
this._blocks[e.id].y = e.newCoordinate.y;
|
|
|
|
}
|
|
|
|
|
2016-06-08 13:27:01 -04:00
|
|
|
// Remove from any old parent.
|
|
|
|
if (e.oldParent !== undefined) {
|
|
|
|
var oldParent = this._blocks[e.oldParent];
|
|
|
|
if (e.oldInput !== undefined &&
|
|
|
|
oldParent.inputs[e.oldInput].block === e.id) {
|
|
|
|
// This block was connected to the old parent's input.
|
|
|
|
oldParent.inputs[e.oldInput].block = null;
|
|
|
|
} else if (oldParent.next === e.id) {
|
|
|
|
// This block was connected to the old parent's next connection.
|
|
|
|
oldParent.next = null;
|
|
|
|
}
|
2016-09-08 09:40:53 -04:00
|
|
|
this._blocks[e.id].parent = null;
|
2016-06-08 13:27:01 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Has the block become a top-level block?
|
|
|
|
if (e.newParent === undefined) {
|
2016-08-11 11:11:27 -04:00
|
|
|
this._addScript(e.id);
|
2016-06-08 13:27:01 -04:00
|
|
|
} else {
|
2016-08-11 11:11:27 -04:00
|
|
|
// Remove script, if one exists.
|
|
|
|
this._deleteScript(e.id);
|
2016-06-08 13:27:01 -04:00
|
|
|
// Otherwise, try to connect it in its new place.
|
|
|
|
if (e.newInput !== undefined) {
|
2016-09-06 10:55:52 -04:00
|
|
|
// Moved to the new parent's input.
|
|
|
|
// Don't obscure the shadow block.
|
2016-09-08 09:40:01 -04:00
|
|
|
var oldShadow = null;
|
|
|
|
if (this._blocks[e.newParent].inputs.hasOwnProperty(e.newInput)) {
|
|
|
|
oldShadow = this._blocks[e.newParent].inputs[e.newInput].shadow;
|
|
|
|
}
|
|
|
|
this._blocks[e.newParent].inputs[e.newInput] = {
|
|
|
|
name: e.newInput,
|
|
|
|
block: e.id,
|
|
|
|
shadow: oldShadow
|
|
|
|
};
|
2016-06-08 13:27:01 -04:00
|
|
|
} else {
|
|
|
|
// Moved to the new parent's next connection.
|
|
|
|
this._blocks[e.newParent].next = e.id;
|
|
|
|
}
|
2016-09-08 09:40:53 -04:00
|
|
|
this._blocks[e.id].parent = e.newParent;
|
2016-06-08 13:27:01 -04:00
|
|
|
}
|
|
|
|
};
|
2016-06-06 15:29:34 -04:00
|
|
|
|
2016-06-08 13:27:01 -04:00
|
|
|
/**
|
2016-08-11 11:11:27 -04:00
|
|
|
* Block management: delete blocks and their associated scripts.
|
|
|
|
* @param {!Object} e Blockly delete event to be processed.
|
2016-06-08 13:27:01 -04:00
|
|
|
*/
|
|
|
|
Blocks.prototype.deleteBlock = function (e) {
|
2016-08-11 11:11:27 -04:00
|
|
|
// @todo In runtime, stop threads running on this script.
|
2016-06-06 15:29:34 -04:00
|
|
|
|
2016-06-08 13:27:01 -04:00
|
|
|
// Get block
|
|
|
|
var block = this._blocks[e.id];
|
2016-06-06 15:29:34 -04:00
|
|
|
|
2016-06-08 13:27:01 -04:00
|
|
|
// Delete children
|
|
|
|
if (block.next !== null) {
|
|
|
|
this.deleteBlock({id: block.next});
|
|
|
|
}
|
2016-06-06 15:29:34 -04:00
|
|
|
|
2016-08-10 11:27:21 -04:00
|
|
|
// Delete inputs (including branches)
|
2016-06-08 13:27:01 -04:00
|
|
|
for (var input in block.inputs) {
|
|
|
|
// If it's null, the block in this input moved away.
|
|
|
|
if (block.inputs[input].block !== null) {
|
|
|
|
this.deleteBlock({id: block.inputs[input].block});
|
|
|
|
}
|
2016-09-06 10:55:52 -04:00
|
|
|
// Delete obscured shadow blocks.
|
|
|
|
if (block.inputs[input].shadow !== null &&
|
|
|
|
block.inputs[input].shadow !== block.inputs[input].block) {
|
|
|
|
this.deleteBlock({id: block.inputs[input].shadow});
|
|
|
|
}
|
2016-06-08 13:27:01 -04:00
|
|
|
}
|
2016-06-06 15:29:34 -04:00
|
|
|
|
2016-08-11 11:11:27 -04:00
|
|
|
// Delete any script starting with this block.
|
|
|
|
this._deleteScript(e.id);
|
2016-06-06 15:29:34 -04:00
|
|
|
|
2016-08-11 11:11:27 -04:00
|
|
|
// Delete block itself.
|
2016-06-08 13:27:01 -04:00
|
|
|
delete this._blocks[e.id];
|
|
|
|
};
|
2016-06-06 15:29:34 -04:00
|
|
|
|
2016-06-08 13:27:01 -04:00
|
|
|
// ---------------------------------------------------------------------
|
2016-06-06 15:29:34 -04:00
|
|
|
|
2016-08-31 11:38:45 -04:00
|
|
|
/**
|
|
|
|
* Encode all of `this._blocks` as an XML string usable
|
|
|
|
* by a Blockly/scratch-blocks workspace.
|
|
|
|
* @return {string} String of XML representing this object's blocks.
|
|
|
|
*/
|
|
|
|
Blocks.prototype.toXML = function () {
|
|
|
|
var xmlString = '<xml xmlns="http://www.w3.org/1999/xhtml">';
|
|
|
|
for (var i = 0; i < this._scripts.length; i++) {
|
|
|
|
xmlString += this.blockToXML(this._scripts[i]);
|
|
|
|
}
|
|
|
|
return xmlString + '</xml>';
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Recursively encode an individual block and its children
|
|
|
|
* into a Blockly/scratch-blocks XML string.
|
|
|
|
* @param {!string} blockId ID of block to encode.
|
|
|
|
* @return {string} String of XML representing this block and any children.
|
|
|
|
*/
|
|
|
|
Blocks.prototype.blockToXML = function (blockId) {
|
|
|
|
var block = this._blocks[blockId];
|
|
|
|
// Encode properties of this block.
|
|
|
|
var tagName = (block.shadow) ? 'shadow' : 'block';
|
|
|
|
var xy = (block.topLevel) ?
|
|
|
|
' x="' + block.x +'"' + ' y="' + block.y +'"' :
|
|
|
|
'';
|
|
|
|
var xmlString = '';
|
|
|
|
xmlString += '<' + tagName +
|
|
|
|
' id="' + block.id + '"' +
|
|
|
|
' type="' + block.opcode + '"' +
|
|
|
|
xy +
|
|
|
|
'>';
|
2016-10-03 17:43:24 -04:00
|
|
|
// Add any mutation. Must come before inputs.
|
|
|
|
if (block.mutation) {
|
|
|
|
xmlString += this.mutationToXML(block.mutation);
|
|
|
|
}
|
2016-08-31 11:38:45 -04:00
|
|
|
// Add any inputs on this block.
|
|
|
|
for (var input in block.inputs) {
|
|
|
|
var blockInput = block.inputs[input];
|
|
|
|
// Only encode a value tag if the value input is occupied.
|
2016-09-06 10:55:52 -04:00
|
|
|
if (blockInput.block || blockInput.shadow) {
|
|
|
|
xmlString += '<value name="' + blockInput.name + '">';
|
|
|
|
if (blockInput.block) {
|
|
|
|
xmlString += this.blockToXML(blockInput.block);
|
|
|
|
}
|
|
|
|
if (blockInput.shadow && blockInput.shadow != blockInput.block) {
|
|
|
|
// Obscured shadow.
|
|
|
|
xmlString += this.blockToXML(blockInput.shadow);
|
|
|
|
}
|
|
|
|
xmlString += '</value>';
|
2016-08-31 11:38:45 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// Add any fields on this block.
|
|
|
|
for (var field in block.fields) {
|
|
|
|
var blockField = block.fields[field];
|
2016-09-13 17:51:17 -04:00
|
|
|
var value = blockField.value;
|
|
|
|
if (typeof value === 'string') {
|
|
|
|
value = xmlEscape(blockField.value);
|
|
|
|
}
|
2016-08-31 11:38:45 -04:00
|
|
|
xmlString += '<field name="' + blockField.name + '">' +
|
2016-09-13 17:51:17 -04:00
|
|
|
value + '</field>';
|
2016-08-31 11:38:45 -04:00
|
|
|
}
|
|
|
|
// Add blocks connected to the next connection.
|
|
|
|
if (block.next) {
|
|
|
|
xmlString += '<next>' + this.blockToXML(block.next) + '</next>';
|
|
|
|
}
|
|
|
|
xmlString += '</' + tagName + '>';
|
|
|
|
return xmlString;
|
|
|
|
};
|
|
|
|
|
2016-10-03 17:43:24 -04:00
|
|
|
/**
|
|
|
|
* Recursively encode a mutation object to XML.
|
|
|
|
* @param {!Object} mutation Object representing a mutation.
|
|
|
|
* @return {string} XML string representing a mutation.
|
|
|
|
*/
|
|
|
|
Blocks.prototype.mutationToXML = function (mutation) {
|
|
|
|
var mutationString = '<' + mutation.tagName;
|
|
|
|
for (var prop in mutation) {
|
|
|
|
if (prop == 'children' || prop == 'tagName') continue;
|
2016-10-13 13:11:26 -04:00
|
|
|
var mutationValue = (typeof mutation[prop] === 'string') ?
|
|
|
|
xmlEscape(mutation[prop]) : mutation[prop];
|
|
|
|
mutationString += ' ' + prop + '="' + mutationValue + '"';
|
2016-10-03 17:43:24 -04:00
|
|
|
}
|
|
|
|
mutationString += '>';
|
|
|
|
for (var i = 0; i < mutation.children.length; i++) {
|
|
|
|
mutationString += this.mutationToXML(mutation.children[i]);
|
|
|
|
}
|
|
|
|
mutationString += '</' + mutation.tagName + '>';
|
|
|
|
return mutationString;
|
|
|
|
};
|
|
|
|
|
2016-08-31 11:38:45 -04:00
|
|
|
// ---------------------------------------------------------------------
|
|
|
|
|
2016-06-08 13:27:01 -04:00
|
|
|
/**
|
2016-08-11 11:11:27 -04:00
|
|
|
* Helper to add a stack to `this._scripts`.
|
|
|
|
* @param {?string} topBlockId ID of block that starts the script.
|
2016-06-08 13:27:01 -04:00
|
|
|
*/
|
2016-08-11 11:11:27 -04:00
|
|
|
Blocks.prototype._addScript = function (topBlockId) {
|
|
|
|
var i = this._scripts.indexOf(topBlockId);
|
|
|
|
if (i > -1) return; // Already in scripts.
|
|
|
|
this._scripts.push(topBlockId);
|
2016-06-08 13:27:01 -04:00
|
|
|
// Update `topLevel` property on the top block.
|
2016-08-11 11:11:27 -04:00
|
|
|
this._blocks[topBlockId].topLevel = true;
|
2016-06-08 13:27:01 -04:00
|
|
|
};
|
2016-06-06 15:29:34 -04:00
|
|
|
|
2016-06-08 13:27:01 -04:00
|
|
|
/**
|
2016-08-11 11:11:27 -04:00
|
|
|
* Helper to remove a script from `this._scripts`.
|
|
|
|
* @param {?string} topBlockId ID of block that starts the script.
|
2016-06-08 13:27:01 -04:00
|
|
|
*/
|
2016-08-11 11:11:27 -04:00
|
|
|
Blocks.prototype._deleteScript = function (topBlockId) {
|
|
|
|
var i = this._scripts.indexOf(topBlockId);
|
|
|
|
if (i > -1) this._scripts.splice(i, 1);
|
2016-06-08 13:27:01 -04:00
|
|
|
// Update `topLevel` property on the top block.
|
2016-08-11 11:11:27 -04:00
|
|
|
if (this._blocks[topBlockId]) this._blocks[topBlockId].topLevel = false;
|
2016-06-08 13:27:01 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
module.exports = Blocks;
|