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
This commit is contained in:
Tina Quach
2016-08-23 13:39:01 -07:00
committed by picklesrus
parent 8127c69ff2
commit b0432306a1
7 changed files with 458 additions and 291 deletions

View File

@@ -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');

View File

@@ -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.<string>} 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);
};

View File

@@ -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;
};

View File

@@ -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.<string>} 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.<Blockly.Block>} 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.<Blockly.Block>} Array of all blocks in selector workspace.
* @return {!Array.<!string>} 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();
}
};

View File

@@ -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;
};

View File

@@ -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 <div> - 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 */

View File

@@ -19,6 +19,7 @@
<script src="/storage.js"></script>
<script src="../../../closure-library/closure/goog/base.js"></script>
<script src="factory_utils.js"></script>
<script src="block_option.js"></script>
<script src="factory.js"></script>
<script src="block_library_view.js"></script>
<script src="block_library_storage.js"></script>
@@ -38,7 +39,7 @@
blocklyFactory.init();
};
window.addEventListener('load', init);
</script>
</script>
</head>
<body>
<h1><a href="https://developers.google.com/blockly/">Blockly</a> &gt;
@@ -50,7 +51,7 @@
<div id="tabContainer">
<div id="blockFactory_tab" class="tab tabon"> Block Factory</div>
<div id="workspaceFactory_tab" class="tab taboff"> Workspace Factory</div>
<div id="blocklibraryExporter_tab" class="tab taboff"> Exporter</div>
<div id="blocklibraryExporter_tab" class="tab taboff"> Block Exporter</div>
</div>
<!-- Exporter tab -->
@@ -64,15 +65,12 @@
<div class='dropdown'>
<button id="button_setBlocks">Select From Library</button>
<div id="dropdownDiv_setBlocks" class="dropdown-content">
<a id='dropdown_addAllFromLib'>All Stored</a>
<a id='dropdown_addAllUsed'>All Used</a>
<a id='dropdown_clearSelected'>Remove All</a>
<a id='dropdown_addAllFromLib' title="Select all block library blocks.">All Stored</a>
<a id='dropdown_addAllUsed' title="Select all block library blocks used in workspace factory.">All Used</a>
<a id='dropdown_clearSelected' title="Clear selected blocks.">Clear</a>
</div>
</div>
<!-- Inject exportSelectorWorkspace into this div -->
<div id="selectorWorkspace"></div>
<div id="blockSelector"></div>
</div>
<!-- Users may customize export settings through this form -->
@@ -80,6 +78,7 @@
<br>
<h3> Export Settings </h3>
<form id="exportSettingsForm">
<div id="selectedBlocksTextContainer">
<p>Currently Selected:</p>
<p id="selectedBlocksText"></p>
@@ -114,7 +113,7 @@
</div>
<br>
</form>
<button id="exporterSubmitButton"> Export </button>
<button id="exporterSubmitButton" title="Download block starter code as specified in export settings."> Export </button>
</div>
<div id="exportPreview">
<br>
@@ -305,9 +304,6 @@
</h3>
</td>
<td id="buttonContainer">
<button id="linkButton" title="Save and link to blocks.">
<img src="link.png" height="21" width="21">
</button>
<button id="linkButton" title="Save and link to blocks.">
<img src="link.png" height="21" width="21">
</button>