From 55aa5b804a3441339f8cdf1472a95f04db20ce56 Mon Sep 17 00:00:00 2001 From: Tina Quach Date: Wed, 17 Aug 2016 15:49:24 -0700 Subject: [PATCH] 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 --- .../block_exporter_controller.js | 34 ++++- demos/blocklyfactory/block_exporter_tools.js | 52 +++++-- demos/blocklyfactory/block_exporter_view.js | 10 ++ demos/blocklyfactory/factory_utils.js | 142 ++++++++++++++++++ 4 files changed, 218 insertions(+), 20 deletions(-) diff --git a/demos/blocklyfactory/block_exporter_controller.js b/demos/blocklyfactory/block_exporter_controller.js index 527a53915..5d0c83063 100644 --- a/demos/blocklyfactory/block_exporter_controller.js +++ b/demos/blocklyfactory/block_exporter_controller.js @@ -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); +}; diff --git a/demos/blocklyfactory/block_exporter_tools.js b/demos/blocklyfactory/block_exporter_tools.js index 7c9bd02ea..4c4381715 100644 --- a/demos/blocklyfactory/block_exporter_tools.js +++ b/demos/blocklyfactory/block_exporter_tools.js @@ -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'); +}; diff --git a/demos/blocklyfactory/block_exporter_view.js b/demos/blocklyfactory/block_exporter_view.js index 7c3366d10..ba6799036 100644 --- a/demos/blocklyfactory/block_exporter_view.js +++ b/demos/blocklyfactory/block_exporter_view.js @@ -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. */ diff --git a/demos/blocklyfactory/factory_utils.js b/demos/blocklyfactory/factory_utils.js index e726aacc7..76d12a464 100644 --- a/demos/blocklyfactory/factory_utils.js +++ b/demos/blocklyfactory/factory_utils.js @@ -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.} 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.} - 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.} - 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; +}; \ No newline at end of file