From b0432306a1f9f2b0f953d218e0c916e4a4eecc88 Mon Sep 17 00:00:00 2001 From: Tina Quach Date: Tue, 23 Aug 2016 13:39:01 -0700 Subject: [PATCH] Blockly Factory: New Selector UI (#579) * generate block selector with checkboxes working click handler updated block exporter view and controller to work with new UI select a block by clicking on the option--not just checkbox renamed selectorWorkspace and fixed checkbox selecting bug adding used blocks works added and cleaned up css, removed extra exporter controller function, nit comment simplified code * style * does not clear selected blocks upon tab switch * added tooltips to buttons, reworded some buttons * remove console.log statement and clarify this.selected in blockoption * removed console log & nit comment --- demos/blocklyfactory/app_controller.js | 66 +++-- .../block_exporter_controller.js | 260 ++++++------------ demos/blocklyfactory/block_exporter_tools.js | 44 +++ demos/blocklyfactory/block_exporter_view.js | 117 ++++---- demos/blocklyfactory/block_option.js | 181 ++++++++++++ demos/blocklyfactory/factory.css | 59 +++- demos/blocklyfactory/index.html | 22 +- 7 files changed, 458 insertions(+), 291 deletions(-) create mode 100644 demos/blocklyfactory/block_option.js diff --git a/demos/blocklyfactory/app_controller.js b/demos/blocklyfactory/app_controller.js index c9c8e7c71..caba5ca3e 100644 --- a/demos/blocklyfactory/app_controller.js +++ b/demos/blocklyfactory/app_controller.js @@ -305,8 +305,9 @@ AppController.prototype.onTab = function() { this.selectedTab == AppController.WORKSPACE_FACTORY; if (this.selectedTab == AppController.EXPORTER) { - // Update toolbox to reflect current block library. - this.exporter.updateToolbox(); + // Show container of exporter. + FactoryUtils.show('blockLibraryExporter'); + FactoryUtils.hide('workspaceFactoryContent'); // Need accurate state in order to know which blocks are used in workspace // factory. @@ -316,13 +317,12 @@ AppController.prototype.onTab = function() { var usedBlockTypes = this.workspaceFactoryController.getAllUsedBlockTypes(); this.exporter.setUsedBlockTypes(usedBlockTypes); + // Update exporter's block selector to reflect current block library. + this.exporter.updateSelector(); + // Update the preview to reflect any changes made to the blocks. this.exporter.updatePreview(); - // Show container of exporter. - FactoryUtils.show('blockLibraryExporter'); - FactoryUtils.hide('workspaceFactoryContent'); - } else if (this.selectedTab == AppController.BLOCK_FACTORY) { // Hide container of exporter. FactoryUtils.hide('blockLibraryExporter'); @@ -368,19 +368,22 @@ AppController.prototype.assignExporterClickHandlers = function() { document.getElementById('dropdown_addAllUsed').addEventListener('click', function() { - self.exporter.addUsedBlocksToWorkspace(); + self.exporter.selectUsedBlocks(); + self.exporter.updatePreview(); document.getElementById('dropdownDiv_setBlocks').classList.remove("show"); }); document.getElementById('dropdown_clearSelected').addEventListener('click', function() { self.exporter.clearSelectedBlocks(); + self.exporter.updatePreview(); document.getElementById('dropdownDiv_setBlocks').classList.remove("show"); }); document.getElementById('dropdown_addAllFromLib').addEventListener('click', function() { - self.exporter.addAllBlocksToWorkspace(); + self.exporter.selectAllBlocks(); + self.exporter.updatePreview(); document.getElementById('dropdownDiv_setBlocks').classList.remove("show"); }); @@ -436,8 +439,6 @@ AppController.prototype.assignExporterChangeListeners = function() { function(e) { self.exporter.updatePreview(); }); - - self.exporter.addChangeListenersToSelectorWorkspace(); }; /** @@ -557,6 +558,28 @@ AppController.prototype.initializeBlocklyStorage = function() { BlockFactory.disableEnableLink(); }; +/** + * Handle resizing of elements. + */ +AppController.prototype.onresize = function(event) { + // Handle resizing of Block Factory elements. + var expandList = [ + document.getElementById('blockly'), + document.getElementById('blocklyMask'), + document.getElementById('preview'), + document.getElementById('languagePre'), + document.getElementById('languageTA'), + document.getElementById('generatorPre') + ]; + for (var i = 0, expand; expand = expandList[i]; i++) { + expand.style.width = (expand.parentNode.offsetWidth - 2) + 'px'; + expand.style.height = (expand.parentNode.offsetHeight - 2) + 'px'; + } + + // Handle resize of Exporter block options. + this.exporter.view.centerPreviewBlocks(); +}; + /** * Initialize Blockly and layout. Called on page load. */ @@ -571,24 +594,11 @@ AppController.prototype.init = function() { this.assignLibraryClickHandlers(); this.assignBlockFactoryClickHandlers(); - // Handle resizing of Block Factory elements. - var expandList = [ - document.getElementById('blockly'), - document.getElementById('blocklyMask'), - document.getElementById('preview'), - document.getElementById('languagePre'), - document.getElementById('languageTA'), - document.getElementById('generatorPre') - ]; - - var onresize = function(e) { - for (var i = 0, expand; expand = expandList[i]; i++) { - expand.style.width = (expand.parentNode.offsetWidth - 2) + 'px'; - expand.style.height = (expand.parentNode.offsetHeight - 2) + 'px'; - } - }; - onresize(); - window.addEventListener('resize', onresize); + this.onresize(); + self = this; + window.addEventListener('resize', function() { + self.onresize(); + }); // Inject Block Factory Main Workspace. var toolbox = document.getElementById('blockfactory_toolbox'); diff --git a/demos/blocklyfactory/block_exporter_controller.js b/demos/blocklyfactory/block_exporter_controller.js index 017bef4ca..423114e5f 100644 --- a/demos/blocklyfactory/block_exporter_controller.js +++ b/demos/blocklyfactory/block_exporter_controller.js @@ -43,16 +43,19 @@ goog.require('goog.dom.xml'); * @param {!BlockLibrary.Storage} blockLibStorage - Block Library Storage. */ BlockExporterController = function(blockLibStorage) { - // BlockLibrary.Storage object containing user's saved blocks + // BlockLibrary.Storage object containing user's saved blocks. this.blockLibStorage = blockLibStorage; - // Utils for generating code to export + // Utils for generating code to export. this.tools = new BlockExporterTools(); - // View provides the selector workspace and export settings UI. - this.view = new BlockExporterView( - //Xml representation of the toolbox - this.tools.generateToolboxFromLibrary(this.blockLibStorage)); - // Array to hold the block types used in workspace factory. - this.usedBlockTypes = []; + // The ID of the block selector, a div element that will be populated with the + // block options. + this.selectorID = 'blockSelector'; + // Map of block types stored in block library to their corresponding Block + // Option objects. + this.blockOptions = this.tools.createBlockSelectorFromLib( + this.blockLibStorage, this.selectorID); + // View provides the block selector and export settings UI. + this.view = new BlockExporterView(this.blockOptions); }; /** @@ -72,35 +75,17 @@ BlockExporterController.prototype.setBlockLibStorage = * @return {!BlockLibraryStorage} blockLibStorage - Block Library Storage object * that stores the blocks. */ -BlockExporterController.prototype.getBlockLibStorage = - function(blockLibStorage) { +BlockExporterController.prototype.getBlockLibStorage = function() { return this.blockLibStorage; }; /** - * Get the selected block types. - * @private - * - * @return {!Array.} Types of blocks in workspace. - */ -BlockExporterController.prototype.getSelectedBlockTypes_ = function() { - var selectedBlocks = this.view.getSelectedBlocks(); - var blockTypes = []; - for (var i = 0, block; block = selectedBlocks[i]; i++) { - blockTypes.push(block.type); - } - return blockTypes; -}; - -/** - * Get selected blocks from selector workspace, pulls info from the Export + * Get selected blocks from block selector, pulls info from the Export * Settings form in Block Exporter, and downloads code accordingly. - * - * TODO(quachtina96): allow export as zip. */ BlockExporterController.prototype.export = function() { // Get selected blocks' information. - var blockTypes = this.getSelectedBlockTypes_(); + var blockTypes = this.view.getSelectedBlockTypes(); var blockXmlMap = this.blockLibStorage.getBlockXmlMap(blockTypes); // Pull block definition(s) settings from the Export Settings form. @@ -153,157 +138,48 @@ BlockExporterController.prototype.export = function() { }; /** - * Update the Exporter's toolbox with either the given toolbox xml or toolbox - * xml generated from blocks stored in block library. - * - * @param {Element} opt_toolboxXml - Xml to define toolbox of the selector - * workspace. + * Update the Exporter's block selector with block options generated from blocks + * stored in block library. */ -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); +BlockExporterController.prototype.updateSelector = function() { + // Get previously selected block types. + var oldSelectedTypes = this.view.getSelectedBlockTypes(); - // Update the view's toolbox. - this.view.setToolbox(updatedToolbox); + // Generate options from block library and assign to view. + this.blockOptions = this.tools.createBlockSelectorFromLib( + this.blockLibStorage, this.selectorID); + this.addBlockOptionSelectHandlers(); + this.view.setBlockOptions(this.blockOptions); - // 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); + // Select all previously selected blocks. + for (var i = 0, blockType; blockType = oldSelectedTypes[i]; i++) { + if (this.blockOptions[blockType]) { + this.view.select(blockType); } } - // Disable any selected blocks. - var selectedBlockTypes = this.getSelectedBlockTypes_(); - for (var i = 0, blockType; blockType = selectedBlockTypes[i]; i++) { - this.setBlockEnabled(blockType, false); - } -}; -/** - * Enable or Disable block in selector workspace's toolbox. - * - * @param {!string} blockType - Type of block to disable or enable. - * @param {!boolean} enable - True to enable the block, false to disable block. - */ -BlockExporterController.prototype.setBlockEnabled = - function(blockType, enable) { - // Get toolbox xml, category, and block elements. - var toolboxXml = this.view.toolbox; - var category = goog.dom.xml.selectSingleNode(toolboxXml, - '//category[@name="' + blockType + '"]'); - var block = goog.dom.getFirstElementChild(category); - // Enable block. - goog.dom.xml.setAttributes(block, {disabled: !enable}); -}; - -/** - * Add change listeners to the exporter's selector workspace. - */ -BlockExporterController.prototype.addChangeListenersToSelectorWorkspace - = function() { - // Assign the BlockExporterController to 'self' to be called in the change - // listeners. This keeps it in scope--otherwise, 'this' in the change - // listeners refers to the wrong thing. - var self = this; - var selector = this.view.selectorWorkspace; - selector.addChangeListener( - function(event) { - self.onSelectBlockForExport_(event); - }); - selector.addChangeListener( - function(event) { - self.onDeselectBlockForExport_(event); - }); -}; - -/** - * Callback function for when a user selects a block for export in selector - * workspace. Disables selected block so that the user only exports one - * copy of starter code per block. Attached to the blockly create event in block - * factory expansion's init. - * @private - * - * @param {!Blockly.Events} event - The fired Blockly event. - */ -BlockExporterController.prototype.onSelectBlockForExport_ = function(event) { - // The user created a block in selector workspace. - if (event.type == Blockly.Events.CREATE) { - // Get type of block created. - var block = this.view.selectorWorkspace.getBlockById(event.blockId); - var blockType = block.type; - // Disable the selected block. Users can only export one copy of starter - // code per block. - this.setBlockEnabled(blockType, false); - // Show currently selected blocks in helper text. - this.view.listSelectedBlocks(this.getSelectedBlockTypes_()); - this.updatePreview(); - } -}; - -/** - * Callback function for when a user deselects a block in selector - * workspace by deleting it. Re-enables block so that the user may select it for - * export - * @private - * - * @param {!Blockly.Events} event - The fired Blockly event. - */ -BlockExporterController.prototype.onDeselectBlockForExport_ = function(event) { - // The user deleted a block in selector workspace. - if (event.type == Blockly.Events.DELETE) { - // Get type of block created. - var deletedBlockXml = event.oldXml; - var blockType = deletedBlockXml.getAttribute('type'); - // Do not try to enable any blocks deleted from the block library. - if (this.blockLibStorage.has(blockType)) { - // Enable the deselected block. - this.setBlockEnabled(blockType, true); - } - // Show currently selected blocks in helper text. - this.view.listSelectedBlocks(this.getSelectedBlockTypes_()); - this.updatePreview(); - } + this.view.listSelectedBlocks(); }; /** * Tied to the 'Clear Selected Blocks' button in the Block Exporter. - * Deselects all blocks on the selector workspace by deleting them and updating - * text accordingly. + * Deselects all blocks in the selector and updates text accordingly. */ BlockExporterController.prototype.clearSelectedBlocks = function() { - // Clear selector workspace. - this.view.clearSelectorWorkspace(); + this.view.deselectAllBlocks(); + this.view.listSelectedBlocks(); }; /** - * Tied to the 'Add All Stored Blocks' button in the Block Exporter. - * Adds all blocks stored in block library to the selector workspace. + * Tied to the 'All Stored' button in the Block Exporter 'Select' dropdown. + * Selects all blocks stored in block library for export. */ -BlockExporterController.prototype.addAllBlocksToWorkspace = function() { - // Clear selector workspace. - this.view.clearSelectorWorkspace(); - - // Add and evaluate all blocks' definitions. +BlockExporterController.prototype.selectAllBlocks = function() { var allBlockTypes = this.blockLibStorage.getBlockTypes(); - var blockXmlMap = this.blockLibStorage.getBlockXmlMap(allBlockTypes); - this.tools.addBlockDefinitions(blockXmlMap); - - // For every block, render in selector workspace. for (var i = 0, blockType; blockType = allBlockTypes[i]; i++) { - this.view.addBlock(blockType); + this.view.select(blockType); } - - // Clean up workspace. - this.view.cleanUpSelectorWorkspace(); + this.view.listSelectedBlocks(); }; /** @@ -311,17 +187,52 @@ BlockExporterController.prototype.addAllBlocksToWorkspace = function() { * * @return {Element} Xml for a category to be used in toolbox. */ + BlockExporterController.prototype.getBlockLibCategory = function() { return this.tools.generateCategoryFromBlockLib(this.blockLibStorage); }; /** - * Tied to the 'Add All Stored Blocks' button in the Block Exporter. - * Adds all blocks stored in block library to the selector workspace. + * Add select handlers to each block option to update the view and the selected + * blocks accordingly. */ -BlockExporterController.prototype.addUsedBlocksToWorkspace = function() { - // Clear selector workspace. - this.view.clearSelectorWorkspace(); +BlockExporterController.prototype.addBlockOptionSelectHandlers = function() { + var self = this; + + // Click handler for a block option. Toggles whether or not it's selected and + // updates helper text accordingly. + var updateSelectedBlockTypes_ = function(blockOption) { + // Toggle selected. + blockOption.setSelected(!blockOption.isSelected()); + + // Show currently selected blocks in helper text. + self.view.listSelectedBlocks(); + }; + + // Returns a block option select handler. + var makeBlockOptionSelectHandler_ = function(blockOption) { + return function() { + updateSelectedBlockTypes_(blockOption); + self.updatePreview(); + }; + }; + + // Assign a click handler to each block option. + for (var blockType in this.blockOptions) { + var blockOption = this.blockOptions[blockType]; + // Use an additional closure to correctly assign the tab callback. + blockOption.dom.addEventListener( + 'click', makeBlockOptionSelectHandler_(blockOption)); + } +}; + +/** + * Tied to the 'All Used' button in the Block Exporter's 'Select' button. + * Selects all blocks stored in block library and used in workspace factory. + */ +BlockExporterController.prototype.selectUsedBlocks = function() { + // Deselect all blocks. + this.view.deselectAllBlocks(); // Get list of block types that are in block library and used in workspace // factory. @@ -338,20 +249,14 @@ BlockExporterController.prototype.addUsedBlocksToWorkspace = function() { } } - // Add and evaluate the shared blocks' definitions. - var blockXmlMap = this.blockLibStorage.getBlockXmlMap(sharedBlockTypes); - this.tools.addBlockDefinitions(blockXmlMap); - - // For every block, render in selector workspace. + // Select each shared block type. for (var i = 0, blockType; blockType = sharedBlockTypes[i]; i++) { - this.view.addBlock(blockType); + this.view.select(blockType); } - - // Clean up workspace. - this.view.cleanUpSelectorWorkspace(); + this.view.listSelectedBlocks(); if (unstoredCustomBlockTypes.length > 0){ - // Warn user to import block definitions and generator code for blocks + // Warn user to import block defifnitions and generator code for blocks // not in their Block Library nor Blockly's standard library. var blockTypesText = unstoredCustomBlockTypes.join(', '); var customWarning = 'Custom blocks used in workspace factory but not ' + @@ -393,7 +298,7 @@ BlockExporterController.prototype.updatePreview = function() { * corresponding xml element. */ BlockExporterController.prototype.getSelectedBlockXmlMap = function() { - var blockTypes = this.getSelectedBlockTypes_(); + var blockTypes = this.view.getSelectedBlockTypes(); return this.blockLibStorage.getBlockXmlMap(blockTypes); }; @@ -426,3 +331,4 @@ BlockExporterController.prototype.getGeneratorStubsOfSelected = function() { var language = document.getElementById('exportLanguage').value; return this.tools.getGeneratorCode(blockXmlMap, language); }; + diff --git a/demos/blocklyfactory/block_exporter_tools.js b/demos/blocklyfactory/block_exporter_tools.js index d41dd10a2..1396800a3 100644 --- a/demos/blocklyfactory/block_exporter_tools.js +++ b/demos/blocklyfactory/block_exporter_tools.js @@ -31,6 +31,7 @@ goog.provide('BlockExporterTools'); goog.require('FactoryUtils'); +goog.require('BlockOption'); goog.require('goog.dom'); goog.require('goog.dom.xml'); @@ -230,3 +231,46 @@ BlockExporterTools.prototype.generateCategoryFromBlockLib = return FactoryUtils.generateCategoryXml(blocks,'Block Library'); }; + +/** + * Generate selector dom from block library storage. For each block in the + * library, it has a block option, which consists of a checkbox, a label, + * and a fixed size preview workspace. + * + * @param {!BlockLibraryStorage} blockLibStorage - Block Library Storage object. + * @param {!string} blockSelectorID - ID of the div element that will contain + * the block options. + * @return {!Object} Map of block type to Block Option object. + */ +BlockExporterTools.prototype.createBlockSelectorFromLib = + function(blockLibStorage, blockSelectorID) { + // Object mapping each stored block type to XML. + var allBlockTypes = blockLibStorage.getBlockTypes(); + 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); + + var blockSelector = goog.dom.getElement(blockSelectorID); + // Clear the block selector. + goog.dom.removeChildren(blockSelector); + + // Append each block option's dom to the selector. + var blockOptions = Object.create(null); + for (var blockType in blockXmlMap) { + // Get preview block's xml. + var block = FactoryUtils.getDefinedBlock(blockType, this.hiddenWorkspace); + var previewBlockXml = Blockly.Xml.workspaceToDom(this.hiddenWorkspace); + + // Create block option, inject block into preview workspace, and append + // option to block selector. + var blockOpt = new BlockOption(blockSelector, blockType, previewBlockXml); + blockOpt.createDom(); + goog.dom.appendChild(blockSelector, blockOpt.dom); + blockOpt.showPreviewBlock(); + blockOptions[blockType] = blockOpt; + } + return blockOptions; +}; + diff --git a/demos/blocklyfactory/block_exporter_view.js b/demos/blocklyfactory/block_exporter_view.js index c4297d77e..5b703f388 100644 --- a/demos/blocklyfactory/block_exporter_view.js +++ b/demos/blocklyfactory/block_exporter_view.js @@ -19,9 +19,8 @@ */ /** - * @fileoverview Javascript for the Block Exporter View class. Takes care of - * generating the selector workspace through which users select blocks to - * export. + * @fileoverview Javascript for the Block Exporter View class. Reads from and + * manages a block selector through which users select blocks to export. * * @author quachtina96 (Tina Quach) */ @@ -30,55 +29,29 @@ goog.provide('BlockExporterView'); +goog.require('BlockExporterTools'); +goog.require('BlockOption'); goog.require('goog.dom'); /** * BlockExporter View Class * @constructor * - * @param {Element} toolbox - Xml for the toolbox of the selector workspace. + * @param {!Object} blockOptions - Map of block types to BlockOption objects. */ -BlockExporterView = function(selectorToolbox) { - // Xml representation of the toolbox - if (selectorToolbox.hasChildNodes) { - this.toolbox = selectorToolbox; - } else { - // Toolbox is empty. Append dummy category to toolbox because toolbox - // cannot switch between category and flyout-only mode after injection. - var categoryElement = goog.dom.createDom('category'); - categoryElement.setAttribute('name', 'Next Saved Block'); - selectorToolbox.appendChild(categoryElement); - this.toolbox = selectorToolbox; - } - // Workspace users use to select blocks for export - this.selectorWorkspace = - Blockly.inject('exportSelector', - {collapse: false, - toolbox: this.toolbox, - grid: - {spacing: 20, - length: 3, - colour: '#ccc', - snap: true} - }); +BlockExporterView = function(blockOptions) { + // Map of block types to BlockOption objects to select from. + this.blockOptions = blockOptions; }; /** - * Update the toolbox of this instance of BlockExporterView. + * Set the block options in the selector of this instance of + * BlockExporterView. * - * @param {Element} toolboxXml - Xml for the toolbox of the selector workspace. + * @param {!Object} blockOptions - Map of block types to BlockOption objects. */ -BlockExporterView.prototype.setToolbox = function(toolboxXml) { - // Parse the provided toolbox tree into a consistent DOM format. - this.toolbox = Blockly.Options.parseToolboxTree(toolboxXml); -}; - -/** - * Renders the toolbox in the workspace. Used to update the toolbox upon - * switching between Block Factory tab and Block Exporter Tab. - */ -BlockExporterView.prototype.renderToolbox = function() { - this.selectorWorkspace.updateToolbox(this.toolbox); +BlockExporterView.prototype.setBlockOptions = function(blockOptions) { + this.blockOptions = blockOptions; }; /** @@ -99,56 +72,74 @@ BlockExporterView.prototype.updateHelperText = function(newText, opt_append) { /** * Updates the helper text to show list of currently selected blocks. - * - * @param {!Array.} selectedBlockTypes - Array of blocks selected in workspace. */ -BlockExporterView.prototype.listSelectedBlocks = function(selectedBlockTypes) { - var selectedBlocksText = selectedBlockTypes.join(",\n "); +BlockExporterView.prototype.listSelectedBlocks = function() { + + var selectedBlocksText = this.getSelectedBlockTypes().join(",\n "); goog.dom.getElement('selectedBlocksText').textContent = selectedBlocksText; }; /** - * Renders block of given type on selector workspace assuming block has already - * been defined. + * Selects a given block type in the selector. * - * @param {string} blockType - Type of block to add to selector workspce. + * @param {string} blockType - Type of block to selector. */ -BlockExporterView.prototype.addBlock = function(blockType) { - var newBlock = this.selectorWorkspace.newBlock(blockType); - newBlock.initSvg(); - newBlock.render(); +BlockExporterView.prototype.select = function(blockType) { + this.blockOptions[blockType].setSelected(true); }; /** - * Deletes a block from the selector workspace. + * Deselects a block in the selector. * * @param {!Blockly.Block} block - Type of block to add to selector workspce. */ -BlockExporterView.prototype.removeBlock = function(block) { - block.dispose(); +BlockExporterView.prototype.deselect = function(blockType) { + this.blockOptions[blockType].setSelected(false); }; /** - * Clears selector workspace. + * Deselects all blocks. */ -BlockExporterView.prototype.clearSelectorWorkspace = function() { - this.selectorWorkspace.clear(); +BlockExporterView.prototype.deselectAllBlocks = function() { + for (var blockType in this.blockOptions) { + this.deselect(blockType); + } }; /** - * Neatly layout the blocks in selector workspace. + * Given an array of selected blocks, selects these blocks in the view, marking + * the checkboxes accordingly. + * + * @param {Array.} blockTypes - Array of block types to select. */ -BlockExporterView.prototype.cleanUpSelectorWorkspace = function() { - this.selectorWorkspace.cleanUp(); +BlockExporterView.prototype.setSelectedBlockTypes = function(blockTypes) { + for (var i = 0, blockType; blockType = blockTypes[i]; i++) { + this.select(blockType); + } }; /** * Returns array of selected blocks. * - * @return {Array.} Array of all blocks in selector workspace. + * @return {!Array.} Array of all selected block types. */ -BlockExporterView.prototype.getSelectedBlocks = function() { - return this.selectorWorkspace.getAllBlocks(); +BlockExporterView.prototype.getSelectedBlockTypes = function() { + var selectedTypes = []; + for (var blockType in this.blockOptions) { + var blockOption = this.blockOptions[blockType]; + if (blockOption.isSelected()) { + selectedTypes.push(blockType); + } + } + return selectedTypes; }; +/** + * Centers the preview block of each block option in the exporter selector. + */ +BlockExporterView.prototype.centerPreviewBlocks = function() { + for (var blockType in this.blockOptions) { + this.blockOptions[blockType].centerBlock(); + } +}; diff --git a/demos/blocklyfactory/block_option.js b/demos/blocklyfactory/block_option.js new file mode 100644 index 000000000..265de70cc --- /dev/null +++ b/demos/blocklyfactory/block_option.js @@ -0,0 +1,181 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2016 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Javascript for the BlockOption class, used to represent each of + * the various blocks that you may select. Each block option has a checkbox, + * a label, and a preview workspace through which to view the block. + * + * @author quachtina96 (Tina Quach) + */ +'use strict'; + +goog.provide('BlockOption'); +goog.require('goog.dom'); + + /** + * BlockOption Class + * A block option includes checkbox, label, and div element that shows a preview + * of the block. + * @constructor + * + * @param {!Element} blockSelector - Scrollable div that will contain the + * block options for the selector. + * @param {!string} blockType - Type of block for which to create an option. + * @param {!Element} previewBlockXml - Xml element containing the preview block. + */ +var BlockOption = function(blockSelector, blockType, previewBlockXml) { + // The div to contain the block option. + this.blockSelector = blockSelector; + // The type of block represented by the option. + this.blockType = blockType; + // The checkbox for the option. Set in createDom. + this.checkbox = null; + // The dom for the option. Set in createDom. + this.dom = null; + // Xml element containing the preview block. + this.previewBlockXml = previewBlockXml; + // Workspace containing preview of block. Set upon injection of workspace in + // showPreviewBlock. + this.previewWorkspace = null; + // Whether or not block the option is selected. + this.selected = false; + // Using this.selected rather than this.checkbox.checked allows for proper + // handling of click events on the block option; Without this, clicking + // directly on the checkbox does not toggle selection. +}; + +/** + * Creates the dom for a single block option. Includes checkbox, label, and div + * in which to inject the preview block. + * + * @return {!Element} Root node of the selector dom which consists of a + * checkbox, a label, and a fixed size preview workspace per block. + */ +BlockOption.prototype.createDom = function() { + // Create the div for the block option. + var blockOptContainer = goog.dom.createDom('div', { + 'id': this.blockType, + 'class': 'blockOption' + }, ''); // Empty quotes for empty div. + + // Create and append div in which to inject the workspace for viewing the + // block option. + var blockOptionPreview = goog.dom.createDom('div', { + 'id' : this.blockType + '_workspace', + 'class': 'blockOption_preview' + }, ''); + goog.dom.appendChild(blockOptContainer,blockOptionPreview); + + // Create and append container to hold checkbox and label. + var checkLabelContainer = goog.dom.createDom('div', { + 'class': 'blockOption_checkLabel' + }, ''); + goog.dom.appendChild(blockOptContainer,checkLabelContainer); + + // Create and append container for checkbox. + var checkContainer = goog.dom.createDom('div', { + 'class': 'blockOption_check' + }, ''); + goog.dom.appendChild(checkLabelContainer, checkContainer); + + // Create and append checkbox. + this.checkbox = goog.dom.createDom('input', { + 'type': 'checkbox', + 'id': this.blockType + '_check' + }, ''); + goog.dom.appendChild(checkContainer, this.checkbox); + + // Create and append container for block label. + var labelContainer = goog.dom.createDom('div', { + 'class': 'blockOption_label' + }, ''); + goog.dom.appendChild(checkLabelContainer, labelContainer); + + // Create and append text node for the label. + var labelText = goog.dom.createDom('p', { + 'id': this.blockType + '_text' + }, this.blockType); + goog.dom.appendChild(labelContainer, labelText); + + this.dom = blockOptContainer; + return this.dom; +}; + +/** + * Injects a workspace containing the block into the block option's preview div. + */ +BlockOption.prototype.showPreviewBlock = function() { + // Get ID of preview workspace. + var blockOptPreviewID = this.dom.id + '_workspace'; + + // Inject preview block. + var workspace = Blockly.inject(blockOptPreviewID, {readOnly:true}); + Blockly.Xml.domToWorkspace(this.previewBlockXml, workspace); + this.previewWorkspace = workspace; + + // Center the preview block in the workspace. + this.centerBlock(); +}; + +/** + * Centers the preview block in the workspace. + */ +BlockOption.prototype.centerBlock = function() { + // Get metrics. + var block = this.previewWorkspace.getTopBlocks()[0]; + var blockMetrics = block.getHeightWidth(); + var blockCoordinates = block.getRelativeToSurfaceXY(); + var workspaceMetrics = this.previewWorkspace.getMetrics(); + + // Calculate new coordinates. + var x = workspaceMetrics.viewWidth/2 - blockMetrics['width']/2 - + blockCoordinates.x; + var y = workspaceMetrics.viewHeight/2 - blockMetrics['height']/2 - + blockCoordinates.y; + + // Move block. + block.moveBy(x, y); +}; + +/** + * Selects or deselects the block option. + * + * @param {!boolean} selected - True if selecting option, false if deselecting + * option. + */ +BlockOption.prototype.setSelected = function(selected) { + this.selected = selected; + if (this.checkbox) { + this.checkbox.checked = selected; + } +}; + +/** + * Returns boolean telling whether or not block is selected. + * + * @return {!boolean} True if selecting option, false if deselecting + * option. + */ +BlockOption.prototype.isSelected = function() { + return this.selected; +}; + + diff --git a/demos/blocklyfactory/factory.css b/demos/blocklyfactory/factory.css index 68115f6ad..810b47411 100644 --- a/demos/blocklyfactory/factory.css +++ b/demos/blocklyfactory/factory.css @@ -192,7 +192,7 @@ button, .buttonStyle { #exportSelector { display: inline-block; float: left; - height: 60%; + height: 70%; width: 30%; } @@ -255,6 +255,45 @@ button, .buttonStyle { display: block; } +#blockSelector { + background-color: #eee; + border: 1px solid lightgrey; + width: 80%; + height: 90%; + overflow-y: scroll; + position: relative; +} + +/* Exporter Block Option */ + +.blockOption { + background-color: #eee; + padding: 15px 20px; + width: 95%; +} + +.blockOption_check_label { + position: relative; +} + +.blockOption_check { + float: left; + padding: 4px; +} + +.blockOption_label { + float: left; + max-width: inherit; + overflow-y: scroll; + word-wrap: break-word; +} + +.blockOption_preview { + height: 100px; + padding-top: 10px; + width: 90%; +} + /* Tabs */ .tab { @@ -434,19 +473,19 @@ td { /* The container
- needed to position the dropdown content */ .dropdown { - position: relative; - display: inline-block; + position: relative; + display: inline-block; } /* Dropdown Content (Hidden by Default) */ .dropdown-content { - background-color: #f9f9f9; - box-shadow: 0px 8px 16px 0px rgba(0,0,0,.2); - display: none; - min-width: 170px; - opacity: 1; - position: absolute; - z-index: 1; + background-color: #f9f9f9; + box-shadow: 0px 8px 16px 0px rgba(0,0,0,.2); + display: none; + min-width: 170px; + opacity: 1; + position: absolute; + z-index: 1; } /* Links inside the dropdown */ diff --git a/demos/blocklyfactory/index.html b/demos/blocklyfactory/index.html index 7f7c3129e..1bd3d7d07 100644 --- a/demos/blocklyfactory/index.html +++ b/demos/blocklyfactory/index.html @@ -19,6 +19,7 @@ + @@ -38,7 +39,7 @@ blocklyFactory.init(); }; window.addEventListener('load', init); - +

Blockly > @@ -50,7 +51,7 @@
Block Factory
Workspace Factory
-
Exporter
+
Block Exporter
@@ -64,15 +65,12 @@ - - -
- +

@@ -80,6 +78,7 @@

Export Settings

+

Currently Selected:

@@ -114,7 +113,7 @@

- +

@@ -305,9 +304,6 @@ -