Blockly Factory: Generate Category Xml (#552)

* generate category xml from block library and from imported block defs

* simplified algorithms for parsing block definition and cleaned up style

* refactored getCategoryFromBlockDefs, breaking it up and moving it to FactoryUtils

* refactored getCategoryXml, fixed bug in updatingToolBox of exporter

* removed unneeded function, added quick check for empty library

* nit comments
This commit is contained in:
Tina Quach
2016-08-17 15:49:24 -07:00
committed by picklesrus
parent e633539d52
commit 55aa5b804a
4 changed files with 218 additions and 20 deletions

View File

@@ -177,13 +177,27 @@ BlockExporterController.prototype.updateToolbox = function(opt_toolboxXml) {
// Use given xml or xml generated from updated block library.
var updatedToolbox = opt_toolboxXml ||
this.tools.generateToolboxFromLibrary(this.blockLibStorage);
// Update the view's toolbox.
this.view.setToolbox(updatedToolbox);
// Render the toolbox in the selector workspace.
this.view.renderToolbox(updatedToolbox);
// Do not try to disable any selected blocks deleted from the block library.
// Instead, deselect them.
var selectedBlocks = this.view.getSelectedBlocks();
var updatedSelectedBlocks = [];
for (var i = 0, selectedBlock; selectedBlock = selectedBlocks[i]; i++) {
if (this.blockLibStorage[selectedBlock.type]) {
updatedSelectedBlocks.push(selectedBlock);
} else {
this.view.removeBlock(selectedBlock);
}
}
// Disable any selected blocks.
var selectedBlocks = this.getSelectedBlockTypes_();
for (var i = 0, blockType; blockType = selectedBlocks[i]; i++) {
var selectedBlockTypes = this.getSelectedBlockTypes_();
for (var i = 0, blockType; blockType = selectedBlockTypes[i]; i++) {
this.setBlockEnabled(blockType, false);
}
};
@@ -262,8 +276,11 @@ BlockExporterController.prototype.onDeselectBlockForExport_ = function(event) {
// Get type of block created.
var deletedBlockXml = event.oldXml;
var blockType = deletedBlockXml.getAttribute('type');
// Enable the deselected block.
this.setBlockEnabled(blockType, true);
// Do not try to enable any blocks deleted from the block library.
if (this.blockLibStorage[blockType]) {
// Enable the deselected block.
this.setBlockEnabled(blockType, true);
}
// Show currently selected blocks in helper text.
this.view.listSelectedBlocks(this.getSelectedBlockTypes_());
}
@@ -300,3 +317,12 @@ BlockExporterController.prototype.addAllBlocksToWorkspace = function() {
// Clean up workspace.
this.view.cleanUpSelectorWorkspace();
};
/**
* Returns the category xml containing all blocks in the block library.
*
* @return {Element} Xml for a category to be used in toolbox.
*/
BlockExporterController.prototype.getBlockLibCategory = function() {
return this.tools.generateCategoryFromBlockLib(this.blockLibStorage);
};

View File

@@ -188,25 +188,45 @@ BlockExporterTools.prototype.generateToolboxFromLibrary
this.addBlockDefinitions(blockXmlMap);
for (var blockType in blockXmlMap) {
// Create category DOM element.
var categoryElement = goog.dom.createDom('category');
categoryElement.setAttribute('name',blockType);
// Get block.
var block = FactoryUtils.getDefinedBlock(blockType, this.hiddenWorkspace);
// Get preview block XML.
var blockChild = Blockly.Xml.blockToDom(block);
blockChild.removeAttribute('id');
// Add block to category and category to XML.
categoryElement.appendChild(blockChild);
xmlDom.appendChild(categoryElement);
var category = FactoryUtils.generateCategoryXml([block], blockType);
xmlDom.appendChild(category);
}
// If there are no blocks in library, append dummy category.
var categoryElement = goog.dom.createDom('category');
categoryElement.setAttribute('name','Next Saved Block');
xmlDom.appendChild(categoryElement);
// If there are no blocks in library and the map is empty, append dummy
// category.
if (Object.keys(blockXmlMap).length == 0) {
var category = goog.dom.createDom('category');
category.setAttribute('name','Next Saved Block');
xmlDom.appendChild(category);
}
return xmlDom;
};
/**
* Generate xml for the workspace factory's category from imported block
* definitions.
*
* @param {!BlockLibraryStorage} blockLibStorage - Block Library Storage object.
* @return {!Element} Xml representation of a category.
*/
BlockExporterTools.prototype.generateCategoryFromBlockLib =
function(blockLibStorage) {
var allBlockTypes = blockLibStorage.getBlockTypes();
// Object mapping block type to XML.
var blockXmlMap = blockLibStorage.getBlockXmlMap(allBlockTypes);
// Define the custom blocks in order to be able to create instances of
// them in the exporter workspace.
this.addBlockDefinitions(blockXmlMap);
// Get array of defined blocks.
var blocks = [];
for (var blockType in blockXmlMap) {
var block = FactoryUtils.getDefinedBlock(blockType, this.hiddenWorkspace);
blocks.push(block);
}
return FactoryUtils.generateCategoryXml(blocks,'Block Library');
};

View File

@@ -119,6 +119,16 @@ BlockExporterView.prototype.addBlock = function(blockType) {
newBlock.render();
};
/**
* Deletes a block from the selector workspace.
*
* @param {!Blockly.Block} block - Type of block to add to selector workspce.
*/
BlockExporterView.prototype.removeBlock = function(block) {
block.dispose();
};
/**
* Clears selector workspace.
*/

View File

@@ -730,3 +730,145 @@ FactoryUtils.getDefinedBlock = function(blockType, workspace) {
workspace.clear();
return workspace.newBlock(blockType);
};
/**
* Parses a block definition get the type of the block it defines.
*
* @param {!string} blockDef - A single block definition.
* @return {string} Type of block defined by the given definition.
*/
FactoryUtils.getBlockTypeFromJsDef = function(blockDef) {
var indexOfStartBracket = blockDef.indexOf('[\'');
var indexOfEndBracket = blockDef.indexOf('\']');
if (indexOfStartBracket != -1 && indexOfEndBracket != -1) {
return blockDef.substring(indexOfStartBracket + 2, indexOfEndBracket);
} else {
throw new Error ('Could not parse block type out of JavaScript block ' +
'definition. Brackets normally enclosing block type not found.');
}
};
/**
* Generates a category containing blocks of the specified block types.
*
* @param {!Array.<Blockly.Block>} blocks - Blocks to include in the category.
* @param {string} categoryName - Name to use for the generated category.
* @return {Element} - Category xml containing the given block types.
*/
FactoryUtils.generateCategoryXml = function(blocks, categoryName) {
// Create category DOM element.
var categoryElement = goog.dom.createDom('category');
categoryElement.setAttribute('name', categoryName);
// For each block, add block element to category.
for (var i = 0, block; block = blocks[i]; i++) {
// Get preview block XML.
var blockXml = Blockly.Xml.blockToDom(block);
blockXml.removeAttribute('id');
// Add block to category and category to XML.
categoryElement.appendChild(blockXml);
}
return categoryElement;
};
/**
* Parses a string containing JavaScript block definition(s) to create an array
* in which each element is a single block definition.
*
* @param {!string} blockDefsString - JavaScript block definition(s).
* @return {!Array.<string>} - Array of block definitions.
*/
FactoryUtils.parseJsBlockDefs = function(blockDefsString) {
var blockDefArray = [];
var defStart = blockDefsString.indexOf('Blockly.Blocks');
while (blockDefsString.indexOf('Blockly.Blocks', defStart) != -1) {
var nextStart = blockDefsString.indexOf('Blockly.Blocks', defStart + 1);
if (nextStart == -1) {
// This is the last block definition.
nextStart = blockDefsString.length;
}
var blockDef = blockDefsString.substring(defStart, nextStart);
blockDefArray.push(blockDef);
defStart = nextStart;
}
return blockDefArray;
};
/**
* Parses a string containing JSON block definition(s) to create an array
* in which each element is a single block definition. Expected input is
* one or more block definitions in the form of concatenated, stringified
* JSON objects.
*
* @param {!string} blockDefsString - String containing JSON block
* definition(s).
* @return {!Array.<string>} - Array of block definitions.
*/
FactoryUtils.parseJsonBlockDefs = function(blockDefsString) {
var blockDefArray = [];
var unbalancedBracketCount = 0;
var defStart = 0;
// Iterate through the blockDefs string. Keep track of whether brackets
// are balanced.
for (var i = 0; i < blockDefsString.length; i++) {
var currentChar = blockDefsString[i];
if (currentChar == '{') {
unbalancedBracketCount++;
}
else if (currentChar == '}') {
unbalancedBracketCount--;
if (unbalancedBracketCount == 0 && i > 0) {
// The brackets are balanced. We've got a complete block defintion.
var blockDef = blockDefsString.substring(defStart, i + 1);
blockDefArray.push(blockDef);
defStart = i + 1;
}
}
}
return blockDefArray;
};
/**
* Define blocks from imported block definitions.
*
* @param {!string} blockDefsString - Block definition(s).
* @param {!string} format - Block definition format ('JSON' or 'JavaScript').
* @return {!Element} Array of block types defined.
*/
FactoryUtils.defineAndGetBlockTypes = function(blockDefsString, format) {
var blockTypes = [];
// Define blocks and get block types.
if (format == 'JSON') {
var blockDefArray = FactoryUtils.parseJsonBlockDefs(blockDefsString);
// Populate array of blocktypes and define each block.
for (var i = 0, blockDef; blockDef = blockDefArray[i]; i++) {
var json = JSON.parse(blockDef);
blockTypes.push(json.type);
// Define the block.
Blockly.Blocks[json.type] = {
init: function() {
this.jsonInit(json);
}
};
}
} else if (format == 'JavaScript') {
var blockDefArray = FactoryUtils.parseJsBlockDefs(blockDefsString);
// Populate array of block types.
for (var i = 0, blockDef; blockDef = blockDefArray[i]; i++) {
var blockType = FactoryUtils.getBlockTypeFromJsDef(blockDef);
blockTypes.push(blockType);
}
// Define all blocks.
eval(blockDefsString);
}
return blockTypes;
};