Blockly Factory: Block Library & Block Exporter (#530)

Created Blockly Factory, an expansion of the Block Factory demo that adds the Block Library and Block Exporter to the Block Factory. 

The Block Library provides the interfaces for the user to save their blocks to local storage so that upon opening and closing the page, their blocks will still persist. In Blockly Factory, Users can re-open saved blocks for edit through a dropdown interface, delete blocks from their library, clear their block library, and import and export their block library. Importing and exporting their block library may be useful for creating specific sets of in-progress blocks for editing.

The Block Exporter allows users to export block definitions and generator stubs of their saved blocks easily by using a visual interface powered by Blockly. It contains a selector workspace in which users add and remove blocks to the workspace to select and deselect them for export.
The exporter also contains an export settings form through which people may export block definitions and generator stubs.
This commit is contained in:
Tina Quach
2016-08-10 13:49:19 -04:00
committed by rachel-fenichel
parent 93af9c59b3
commit 9819d677a9
11 changed files with 3047 additions and 0 deletions

View File

@@ -0,0 +1,453 @@
/**
* @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 The AppController Class brings together the Block
* Factory, Block Library, and Block Exporter functionality into a single web
* app.
*
* @author quachtina96 (Tina Quach)
*/
goog.provide('AppController');
goog.require('BlockFactory');
goog.require('BlockLibraryController');
goog.require('BlockExporterController');
goog.require('goog.dom.classlist');
goog.require('goog.string');
/**
* Controller for the Blockly Factory
* @constructor
*/
AppController = function() {
// Initialize Block Library
this.blockLibraryName = 'blockLibrary';
this.blockLibraryController =
new BlockLibraryController(this.blockLibraryName);
this.blockLibraryController.populateBlockLibrary();
// Initialize Block Exporter
this.exporter =
new BlockExporterController(this.blockLibraryController.storage);
};
/**
* Tied to the 'Import Block Library' button. Imports block library from file to
* Block Factory. Expects user to upload a single file of JSON mapping each
* block type to its xml text representation.
*/
AppController.prototype.importBlockLibraryFromFile = function() {
var self = this;
var files = document.getElementById('files');
// If the file list is empty, the user likely canceled in the dialog.
if (files.files.length > 0) {
// The input tag doesn't have the "multiple" attribute
// so the user can only choose 1 file.
var file = files.files[0];
var fileReader = new FileReader();
// Create a map of block type to xml text from the file when it has been
// read.
fileReader.addEventListener('load', function(event) {
var fileContents = event.target.result;
// Create empty object to hold the read block library information.
var blockXmlTextMap = Object.create(null);
try {
// Parse the file to get map of block type to xml text.
blockXmlTextMap = self.formatBlockLibForImport_(fileContents);
} catch (e) {
var message = 'Could not load your block library file.\n'
window.alert(message + '\nFile Name: ' + file.name);
return;
}
// Create a new block library storage object with inputted block library.
var blockLibStorage = new BlockLibraryStorage(
self.blockLibraryName, blockXmlTextMap);
// Update block library controller with the new block library
// storage.
self.blockLibraryController.setBlockLibStorage(blockLibStorage);
// Update the block library dropdown.
self.blockLibraryController.populateBlockLibrary();
// Update the exporter's block library storage.
self.exporter.setBlockLibStorage(blockLibStorage);
});
// Read the file.
fileReader.readAsText(file);
}
};
/**
* Tied to the 'Export Block Library' button. Exports block library to file that
* contains JSON mapping each block type to its xml text representation.
*/
AppController.prototype.exportBlockLibraryToFile = function() {
// Get map of block type to xml.
var blockLib = this.blockLibraryController.getBlockLibrary();
// Concatenate the xmls, each separated by a blank line.
var blockLibText = this.formatBlockLibForExport_(blockLib);
// Get file name.
var filename = prompt('Enter the file name under which to save your block' +
'library.');
// Download file if all necessary parameters are provided.
if (filename) {
BlockFactory.createAndDownloadFile_(blockLibText, filename, 'xml');
} else {
alert('Could not export Block Library without file name under which to ' +
'save library.');
}
};
/**
* Converts an object mapping block type to xml to text file for output.
* @private
*
* @param {!Object} blockXmlMap - object mapping block type to xml
* @return {string} String of each block's xml separated by a new line.
*/
AppController.prototype.formatBlockLibForExport_ = function(blockXmlMap) {
var blockXmls = [];
for (var blockType in blockXmlMap) {
blockXmls.push(blockXmlMap[blockType]);
}
return blockXmls.join("\n\n");
};
/**
* Converts imported block library to an object mapping block type to block xml.
* @private
*
* @param {string} xmlText - String containing each block's xml optionally
* separated by whitespace.
* @return {!Object} object mapping block type to xml text.
*/
AppController.prototype.formatBlockLibForImport_ = function(xmlText) {
// Get array of xmls.
var xmlText = goog.string.collapseWhitespace(xmlText);
var blockXmls = goog.string.splitLimit(xmlText, '</xml>', 500);
// Create and populate map.
var blockXmlTextMap = Object.create(null);
// The line above is equivalent of {} except that this object is TRULY
// empty. It doesn't have built-in attributes/functions such as length or
// toString.
for (var i = 0, xml; xml = blockXmls[i]; i++) {
var blockType = this.getBlockTypeFromXml_(xml);
blockXmlTextMap[blockType] = xml;
}
return blockXmlTextMap;
};
/**
* Extracts out block type from xml text, the kind that is saved in block
* library storage.
* @private
*
* @param {!string} xmlText - A block's xml text.
* @return {string} The block type that corresponds to the provided xml text.
*/
AppController.prototype.getBlockTypeFromXml_ = function(xmlText) {
var xmlText = Blockly.Options.parseToolboxTree(xmlText);
// Find factory base block.
var factoryBaseBlockXml = xmlText.getElementsByTagName('block')[0];
// Get field elements from factory base.
var fields = factoryBaseBlockXml.getElementsByTagName('field');
for (var i = 0; i < fields.length; i++) {
// The field whose name is 'NAME' holds the block type as its value.
if (fields[i].getAttribute('name') == 'NAME') {
return fields[i].childNodes[0].nodeValue;
}
}
};
/**
* Updates the Block Factory tab to show selected block when user selects a
* different block in the block library dropdown. Tied to block library dropdown
* in index.html.
*
* @param {!Element} blockLibraryDropdown - HTML select element from which the
* user selects a block to work on.
*/
AppController.prototype.onSelectedBlockChanged = function(blockLibraryDropdown) {
// Get selected block type.
var blockType = this.blockLibraryController.getSelectedBlockType(
blockLibraryDropdown);
// Update Block Factory page by showing the selected block.
this.blockLibraryController.openBlock(blockType);
};
/**
* Add tab handlers to allow switching between the Block Factory
* tab and the Block Exporter tab.
*
* @param {string} blockFactoryTabID - ID of element containing Block Factory
* tab
* @param {string} blockExporterTabID - ID of element containing Block
* Exporter tab
*/
AppController.prototype.addTabHandlers =
function(blockFactoryTabID, blockExporterTabID) {
// Assign this instance of Block Factory Expansion to self in order to
// keep the reference to this object upon tab click.
var self = this;
// Get div elements representing tabs
var blockFactoryTab = goog.dom.getElement(blockFactoryTabID);
var blockExporterTab = goog.dom.getElement(blockExporterTabID);
// Add event listeners.
blockFactoryTab.addEventListener('click',
function() {
self.onFactoryTab(blockFactoryTab, blockExporterTab);
});
blockExporterTab.addEventListener('click',
function() {
self.onExporterTab(blockFactoryTab, blockExporterTab);
});
};
/**
* Tied to 'Block Factory' Tab. Shows Block Factory and Block Library.
*
* @param {string} blockFactoryTab - div element that is the Block Factory tab
* @param {string} blockExporterTab - div element that is the Block Exporter tab
*/
AppController.prototype.onFactoryTab =
function(blockFactoryTab, blockExporterTab) {
// Turn factory tab on and exporter tab off.
goog.dom.classlist.addRemove(blockFactoryTab, 'taboff', 'tabon');
goog.dom.classlist.addRemove(blockExporterTab, 'tabon', 'taboff');
// Hide container of exporter.
BlockFactory.hide('blockLibraryExporter');
// Resize to render workspaces' toolboxes correctly.
window.dispatchEvent(new Event('resize'));
};
/**
* Tied to 'Block Exporter' Tab. Shows Block Exporter.
*
* @param {string} blockFactoryTab - div element that is the Block Factory tab
* @param {string} blockExporterTab - div element that is the Block Exporter tab
*/
AppController.prototype.onExporterTab =
function(blockFactoryTab, blockExporterTab) {
// Turn exporter tab on and factory tab off.
goog.dom.classlist.addRemove(blockFactoryTab, 'tabon', 'taboff');
goog.dom.classlist.addRemove(blockExporterTab, 'taboff', 'tabon');
// Update toolbox to reflect current block library.
this.exporter.updateToolbox();
// Show container of exporter.
BlockFactory.show('blockLibraryExporter');
// Resize to render workspaces' toolboxes correctly.
window.dispatchEvent(new Event('resize'));
};
/**
* Assign button click handlers for the exporter.
*/
AppController.prototype.assignExporterClickHandlers = function() {
var self = this;
// Export blocks when the user submits the export settings.
document.getElementById('exporterSubmitButton').addEventListener('click',
function() {
self.exporter.exportBlocks();
});
document.getElementById('clearSelectedButton').addEventListener('click',
function() {
self.exporter.clearSelectedBlocks();
});
document.getElementById('addAllButton').addEventListener('click',
function() {
self.exporter.addAllBlocksToWorkspace();
});
};
/**
* Assign button click handlers for the block library.
*/
AppController.prototype.assignLibraryClickHandlers = function() {
var self = this;
// Assign button click handlers for Block Library.
document.getElementById('saveToBlockLibraryButton').addEventListener('click',
function() {
self.blockLibraryController.saveToBlockLibrary();
});
document.getElementById('removeBlockFromLibraryButton').addEventListener(
'click',
function() {
self.blockLibraryController.removeFromBlockLibrary();
});
document.getElementById('clearBlockLibraryButton').addEventListener('click',
function() {
self.blockLibraryController.clearBlockLibrary();
});
var dropdown = document.getElementById('blockLibraryDropdown');
dropdown.addEventListener('change',
function() {
self.onSelectedBlockChanged(dropdown);
});
};
/**
* Assign button click handlers for the block factory.
*/
AppController.prototype.assignFactoryClickHandlers = function() {
var self = this;
// Assign button event handlers for Block Factory.
document.getElementById('localSaveButton')
.addEventListener('click', function() {
self.exportBlockLibraryToFile();
});
document.getElementById('helpButton').addEventListener('click',
function() {
open('https://developers.google.com/blockly/custom-blocks/block-factory',
'BlockFactoryHelp');
});
document.getElementById('downloadBlocks').addEventListener('click',
function() {
BlockFactory.downloadTextArea('blocks', 'languagePre');
});
document.getElementById('downloadGenerator').addEventListener('click',
function() {
BlockFactory.downloadTextArea('generator', 'generatorPre');
});
document.getElementById('files').addEventListener('change',
function() {
// Warn user.
var replace = confirm('This imported block library will ' +
'replace your current block library.');
if (replace) {
self.importBlockLibraryFromFile();
// Clear this so that the change event still fires even if the
// same file is chosen again. If the user re-imports a file, we
// want to reload the workspace with its contents.
this.value = null;
}
});
document.getElementById('createNewBlockButton')
.addEventListener('click', function() {
BlockFactory.mainWorkspace.clear();
BlockFactory.showStarterBlock();
BlockLibraryView.selectDefaultOption('blockLibraryDropdown');
});
};
/**
* Add event listeners for the block factory.
*/
AppController.prototype.addFactoryEventListeners = function() {
BlockFactory.mainWorkspace.addChangeListener(BlockFactory.updateLanguage);
document.getElementById('direction')
.addEventListener('change', BlockFactory.updatePreview);
document.getElementById('languageTA')
.addEventListener('change', BlockFactory.updatePreview);
document.getElementById('languageTA')
.addEventListener('keyup', BlockFactory.updatePreview);
document.getElementById('format')
.addEventListener('change', BlockFactory.formatChange);
document.getElementById('language')
.addEventListener('change', BlockFactory.updatePreview);
};
/**
* Handle Blockly Storage with App Engine.
*/
AppController.prototype.initializeBlocklyStorage = function() {
BlocklyStorage.HTTPREQUEST_ERROR =
'There was a problem with the request.\n';
BlocklyStorage.LINK_ALERT =
'Share your blocks with this link:\n\n%1';
BlocklyStorage.HASH_ERROR =
'Sorry, "%1" doesn\'t correspond with any saved Blockly file.';
BlocklyStorage.XML_ERROR = 'Could not load your saved file.\n' +
'Perhaps it was created with a different version of Blockly?';
var linkButton = document.getElementById('linkButton');
linkButton.style.display = 'inline-block';
linkButton.addEventListener('click',
function() {
BlocklyStorage.link(BlockFactory.mainWorkspace);});
BlockFactory.disableEnableLink();
};
/**
* Initialize Blockly and layout. Called on page load.
*/
AppController.prototype.init = function() {
// Handle Blockly Storage with App Engine
if ('BlocklyStorage' in window) {
this.initializeBlocklyStorage();
}
// Assign click handlers.
this.assignExporterClickHandlers();
this.assignLibraryClickHandlers();
this.assignFactoryClickHandlers();
// 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);
// Inject Block Factory Main Workspace.
var toolbox = document.getElementById('toolbox');
BlockFactory.mainWorkspace = Blockly.inject('blockly',
{collapse: false,
toolbox: toolbox,
media: '../../media/'});
// Add tab handlers for switching between Block Factory and Block Exporter.
this.addTabHandlers("blockfactory_tab", "blocklibraryExporter_tab");
this.exporter.addChangeListenersToSelectorWorkspace();
// Create the root block on Block Factory main workspace.
if ('BlocklyStorage' in window && window.location.hash.length > 1) {
BlocklyStorage.retrieveXml(window.location.hash.substring(1),
BlockFactory.mainWorkspace);
} else {
BlockFactory.showStarterBlock();
}
BlockFactory.mainWorkspace.clearUndo();
// Add Block Factory event listeners.
this.addFactoryEventListeners();
};

View File

@@ -0,0 +1,272 @@
/**
* @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 Block Exporter Controller class. Allows
* users to export block definitions and generator stubs of their saved blocks
* easily using a visual interface. Depends on Block Exporter View and Block
* Exporter Tools classes. Interacts with Export Settings in the index.html.
*
* @author quachtina96 (Tina Quach)
*/
'use strict';
goog.provide('BlockExporterController');
goog.require('BlockExporterView');
goog.require('BlockExporterTools');
goog.require('goog.dom.xml');
/**
* BlockExporter Controller Class
* @constructor
*
* @param {!BlockLibrary.Storage} blockLibStorage - Block Library Storage.
*/
BlockExporterController = function(blockLibStorage) {
// BlockLibrary.Storage object containing user's saved blocks
this.blockLibStorage = blockLibStorage;
// 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));
};
/**
* Set the block library storage object from which exporter exports.
*
* @param {!BlockLibraryStorage} blockLibStorage - Block Library Storage object
* that stores the blocks.
*/
BlockExporterController.prototype.setBlockLibStorage =
function(blockLibStorage) {
this.blockLibStorage = blockLibStorage;
};
/**
* Get the block library storage object from which exporter exports.
*
* @return {!BlockLibraryStorage} blockLibStorage - Block Library Storage object
* that stores the blocks.
*/
BlockExporterController.prototype.getBlockLibStorage =
function(blockLibStorage) {
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
* Settings form in Block Exporter, and downloads block code accordingly.
*/
BlockExporterController.prototype.exportBlocks = function() {
var blockTypes = this.getSelectedBlockTypes_();
var blockXmlMap = this.blockLibStorage.getBlockXmlMap(blockTypes);
// Pull inputs from the Export Settings form.
var definitionFormat = document.getElementById('exportFormat').value;
var language = document.getElementById('exportLanguage').value;
var blockDef_filename = document.getElementById('blockDef_filename').value;
var generatorStub_filename = document.getElementById(
'generatorStub_filename').value;
var wantBlockDef = document.getElementById('blockDefCheck').checked;
var wantGenStub = document.getElementById('genStubCheck').checked;
if (wantBlockDef) {
// User wants to export selected blocks' definitions.
if (!blockDef_filename) {
// User needs to enter filename.
alert('Please enter a filename for your block definition(s) download.');
} else {
// Get block definition code in the selected format for the blocks.
var blockDefs = this.tools.getBlockDefs(blockXmlMap,
definitionFormat);
// Download the file.
BlockFactory.createAndDownloadFile_(
blockDefs, blockDef_filename, definitionFormat);
}
}
if (wantGenStub) {
// User wants to export selected blocks' generator stubs.
if (!generatorStub_filename) {
// User needs to enter filename.
alert('Please enter a filename for your generator stub(s) download.');
} else {
// Get generator stub code in the selected language for the blocks.
var genStubs = this.tools.getGeneratorCode(blockXmlMap,
language);
// Download the file.
BlockFactory.createAndDownloadFile_(
genStubs, generatorStub_filename, language);
}
}
};
/**
* 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.
*/
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);
// Disable any selected blocks.
var selectedBlocks = this.getSelectedBlockTypes_();
for (var i = 0, blockType; blockType = selectedBlocks[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_());
}
};
/**
* 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');
// Enable the deselected block.
this.setBlockEnabled(blockType, true);
// Show currently selected blocks in helper text.
this.view.listSelectedBlocks(this.getSelectedBlockTypes_());
}
};
/**
* 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.
*/
BlockExporterController.prototype.clearSelectedBlocks = function() {
// Clear selector workspace.
this.view.clearSelectorWorkspace();
};
/**
* Tied to the 'Add All Stored Blocks' button in the Block Exporter.
* Adds all blocks stored in block library to the selector workspace.
*/
BlockExporterController.prototype.addAllBlocksToWorkspace = function() {
// Clear selector workspace.
this.view.clearSelectorWorkspace();
// Add and evaluate all blocks' definitions.
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);
}
// Clean up workspace.
this.view.cleanUpSelectorWorkspace();
};

View File

@@ -0,0 +1,222 @@
/**
* @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 BlockExporter Tools class, which generates
* block definitions and generator stubs for given block types. Also generates
* toolbox xml for the exporter's workspace. Depends on the BlockFactory for
* its code generation functions.
*
* @author quachtina96 (Tina Quach)
*/
'use strict';
goog.provide('BlockExporterTools');
goog.require('BlockFactory');
goog.require('goog.dom');
goog.require('goog.dom.xml');
/**
* Block Exporter Tools Class
* @constructor
*/
BlockExporterTools = function() {
// Create container for hidden workspace.
this.container = goog.dom.createDom('div', {
'id': 'blockExporterTools_hiddenWorkspace'
}, ''); // Empty quotes for empty div.
// Hide hidden workspace.
this.container.style.display = 'none';
goog.dom.appendChild(document.body, this.container);
/**
* Hidden workspace for the Block Exporter that holds pieces that make
* up the block
* @type {Blockly.Workspace}
*/
this.hiddenWorkspace = Blockly.inject(this.container.id,
{collapse: false,
media: '../../media/'});
};
/**
* Get Blockly Block object from xml that encodes the blocks used to design
* the block.
* @private
*
* @param {!Element} xml - Xml element that encodes the blocks used to design
* the block. For example, the block xmls saved in block library.
* @return {!Blockly.Block} - Root block (factory_base block) which contains
* all information needed to generate block definition or null.
*/
BlockExporterTools.prototype.getRootBlockFromXml_ = function(xml) {
// Render xml in hidden workspace.
this.hiddenWorkspace.clear();
Blockly.Xml.domToWorkspace(xml, this.hiddenWorkspace);
// Get root block.
var rootBlock = this.hiddenWorkspace.getTopBlocks()[0] || null;
return rootBlock;
};
/**
* Get Blockly Block by rendering pre-defined block in workspace.
* @private
*
* @param {!Element} blockType - Type of block.
* @return {!Blockly.Block} the Blockly.Block of desired type.
*/
BlockExporterTools.prototype.getDefinedBlock_ = function(blockType) {
this.hiddenWorkspace.clear();
return this.hiddenWorkspace.newBlock(blockType);
};
/**
* Return the given language code of each block type in an array.
*
* @param {!Object} blockXmlMap - Map of block type to xml.
* @param {string} definitionFormat - 'JSON' or 'JavaScript'
* @return {string} The concatenation of each block's language code in the
* desired format.
*/
BlockExporterTools.prototype.getBlockDefs =
function(blockXmlMap, definitionFormat) {
var blockCode = [];
for (var blockType in blockXmlMap) {
var xml = blockXmlMap[blockType];
if (xml) {
// Render and get block from hidden workspace.
var rootBlock = this.getRootBlockFromXml_(xml);
if (rootBlock) {
// Generate the block's definition.
var code = BlockFactory.getBlockDefinition(blockType, rootBlock,
definitionFormat, this.hiddenWorkspace);
// Add block's definition to the definitions to return.
} else {
// Append warning comment and write to console.
var code = '// No block definition generated for ' + blockType +
'. Could not find root block in xml stored for this block.';
console.log('No block definition generated for ' + blockType +
'. Could not find root block in xml stored for this block.');
}
} else {
// Append warning comment and write to console.
var code = '// No block definition generated for ' + blockType +
'. Block was not found in Block Library Storage.';
console.log('No block definition generated for ' + blockType +
'. Block was not found in Block Library Storage.');
}
blockCode.push(code);
}
return blockCode.join("\n\n");
};
/**
* Return the generator code of each block type in an array in a given language.
*
* @param {!Object} blockXmlMap - Map of block type to xml.
* @param {string} generatorLanguage - e.g.'JavaScript', 'Python', 'PHP', 'Lua',
* 'Dart'
* @return {string} The concatenation of each block's generator code in the
* desired format.
*/
BlockExporterTools.prototype.getGeneratorCode =
function(blockXmlMap, generatorLanguage) {
var multiblockCode = [];
// Define the custom blocks in order to be able to create instances of
// them in the exporter workspace.
this.addBlockDefinitions(blockXmlMap);
for (var blockType in blockXmlMap) {
var xml = blockXmlMap[blockType];
if (xml) {
// Render the preview block in the hidden workspace.
var tempBlock = this.getDefinedBlock_(blockType);
// Get generator stub for the given block and add to generator code.
var blockGenCode =
BlockFactory.getGeneratorStub(tempBlock, generatorLanguage);
} else {
// Append warning comment and write to console.
var blockGenCode = '// No generator stub generated for ' + blockType +
'. Block was not found in Block Library Storage.';
console.log('No block generator stub generated for ' + blockType +
'. Block was not found in Block Library Storage.');
}
multiblockCode.push(blockGenCode);
}
return multiblockCode.join("\n\n");
};
/**
* Evaluates block definition code of each block in given object mapping
* block type to xml. Called in order to be able to create instances of the
* blocks in the exporter workspace.
*
* @param {!Object} blockXmlMap - Map of block type to xml.
*/
BlockExporterTools.prototype.addBlockDefinitions = function(blockXmlMap) {
var blockDefs = this.getBlockDefs(blockXmlMap, 'JavaScript');
eval(blockDefs);
};
/**
* Pulls information about all blocks in the block library to generate xml
* for the selector workpace's toolbox.
*
* @return {!Element} Xml representation of the toolbox.
*/
BlockExporterTools.prototype.generateToolboxFromLibrary
= function(blockLibStorage) {
// Create DOM for XML.
var xmlDom = goog.dom.createDom('xml', {
'id' : 'blockExporterTools_toolbox',
'style' : 'display:none'
});
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);
for (var blockType in blockXmlMap) {
// Create category DOM element.
var categoryElement = goog.dom.createDom('category');
categoryElement.setAttribute('name',blockType);
// Get block.
var block = this.getDefinedBlock_(blockType);
// 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);
}
// 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);
return xmlDom;
};

View File

@@ -0,0 +1,145 @@
/**
* @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 Block Exporter View class. Takes care of
* generating the selector workspace through which users select blocks to
* export.
*
* @author quachtina96 (Tina Quach)
*/
'use strict';
goog.provide('BlockExporterView');
goog.require('goog.dom');
/**
* BlockExporter View Class
* @constructor
*
* @param {Element} toolbox - Xml for the toolbox of the selector workspace.
*/
BlockExporterView = function(toolbox) {
// Xml representation of the toolbox
if (toolbox.hasChildNodes) {
this.toolbox = toolbox;
} 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');
toolbox.appendChild(categoryElement);
this.toolbox = toolbox;
}
// 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}
});
};
/**
* Update the toolbox of this instance of BlockExporterView.
*
* @param {Element} toolboxXml - Xml for the toolbox of the selector workspace.
*/
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);
};
/**
* Updates the helper text.
*
* @param {string} newText - New helper text.
* @param {boolean} opt_append - True if appending to helper Text, false if
* replacing.
*/
BlockExporterView.prototype.updateHelperText = function(newText, opt_append) {
if (opt_append) {
goog.dom.getElement('helperText').textContent =
goog.dom.getElement('helperText').textContent + newText;
} else {
goog.dom.getElement('helperText').textContent = newText;
}
};
/**
* 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(', ');
this.updateHelperText('Currently Selected: ' + selectedBlocksText);
};
/**
* Renders block of given type on selector workspace assuming block has already
* been defined.
*
* @param {string} blockType - Type of block to add to selector workspce.
*/
BlockExporterView.prototype.addBlock = function(blockType) {
var newBlock = this.selectorWorkspace.newBlock(blockType);
newBlock.initSvg();
newBlock.render();
};
/**
* Clears selector workspace.
*/
BlockExporterView.prototype.clearSelectorWorkspace = function() {
this.selectorWorkspace.clear();
};
/**
* Neatly layout the blocks in selector workspace.
*/
BlockExporterView.prototype.cleanUpSelectorWorkspace = function() {
this.selectorWorkspace.cleanUp_();
};
/**
* Returns array of selected blocks.
*
* @return {Array.<Blockly.Block>} Array of all blocks in selector workspace.
*/
BlockExporterView.prototype.getSelectedBlocks = function() {
return this.selectorWorkspace.getAllBlocks();
};

View File

@@ -0,0 +1,219 @@
/**
* @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 Contains the code for Block Library Controller, which
* depends on Block Library Storage and Block Library UI. Provides the
* interfaces for the user to
* - save their blocks to the browser
* - re-open and edit saved blocks
* - delete blocks
* - clear their block library
* Depends on BlockFactory functions defined in factory.js.
*
* @author quachtina96 (Tina Quach)
*/
'use strict';
goog.provide('BlockLibraryController');
goog.require('BlockLibraryStorage');
goog.require('BlockLibraryView');
goog.require('BlockFactory');
/**
* Block Library Controller Class
* @constructor
*
* @param {string} blockLibraryName - Desired name of Block Library, also used
* to create the key for where it's stored in local storage.
* @param {!BlockLibraryStorage} opt_blockLibraryStorage - optional storage
* object that allows user to import a block library.
*/
BlockLibraryController = function(blockLibraryName, opt_blockLibraryStorage) {
this.name = blockLibraryName;
// Create a new, empty Block Library Storage object, or load existing one.
this.storage = opt_blockLibraryStorage || new BlockLibraryStorage(this.name);
};
/**
* Returns the block type of the block the user is building.
* @private
*
* @return {string} The current block's type.
*/
BlockLibraryController.prototype.getCurrentBlockType_ = function() {
var rootBlock = BlockFactory.getRootBlock(BlockFactory.mainWorkspace);
var blockType = rootBlock.getFieldValue('NAME').trim().toLowerCase();
// Replace white space with underscores
return blockType.replace(/\W/g, '_').replace(/^(\d)/, '_\\1');
};
/**
* Removes current block from Block Library
*
* @param {string} blockType - Type of block.
*/
BlockLibraryController.prototype.removeFromBlockLibrary = function() {
var blockType = this.getCurrentBlockType_();
this.storage.removeBlock(blockType);
this.storage.saveToLocalStorage();
this.populateBlockLibrary();
};
/**
* Updates the workspace to show the block user selected from library
*
* @param {string} blockType - Block to edit on block factory.
*/
BlockLibraryController.prototype.openBlock = function(blockType) {
var xml = this.storage.getBlockXml(blockType);
BlockFactory.mainWorkspace.clear();
Blockly.Xml.domToWorkspace(xml, BlockFactory.mainWorkspace);
};
/**
* Returns type of block selected from library.
*
* @param {Element} blockLibraryDropdown - The block library dropdown.
* @return {string} Type of block selected.
*/
BlockLibraryController.prototype.getSelectedBlockType =
function(blockLibraryDropdown) {
return BlockLibraryView.getSelected(blockLibraryDropdown);
};
/**
* Confirms with user before clearing the block library in local storage and
* updating the dropdown.
*/
BlockLibraryController.prototype.clearBlockLibrary = function() {
var check = confirm(
'Click OK to clear your block library.');
if (check) {
// Clear Block Library Storage.
this.storage.clear();
this.storage.saveToLocalStorage();
// Update dropdown.
BlockLibraryView.clearOptions('blockLibraryDropdown');
// Add a default, blank option to dropdown for when no block from library is
// selected.
BlockLibraryView.addDefaultOption('blockLibraryDropdown');
}
};
/**
* Saves current block to local storage and updates dropdown.
*/
BlockLibraryController.prototype.saveToBlockLibrary = function() {
var blockType = this.getCurrentBlockType_();
// If block under that name already exists, confirm that user wants to replace
// saved block.
if (this.isInBlockLibrary(blockType)) {
var replace = confirm('You already have a block called ' + blockType +
' in your library. Click OK to replace.');
if (!replace) {
// Do not save if user doesn't want to replace the saved block.
return;
}
}
// Save block.
var xmlElement = Blockly.Xml.workspaceToDom(BlockFactory.mainWorkspace);
this.storage.addBlock(blockType, xmlElement);
this.storage.saveToLocalStorage();
// Do not add another option to dropdown if replacing.
if (replace) {
return;
}
BlockLibraryView.addOption(
blockType, blockType, 'blockLibraryDropdown', true, true);
};
/**
* Checks to see if the given blockType is already in Block Library
*
* @param {string} blockType - Type of block.
* @return {boolean} Boolean indicating whether or not block is in the library.
*/
BlockLibraryController.prototype.isInBlockLibrary = function(blockType) {
var blockLibrary = this.storage.blocks;
return (blockType in blockLibrary && blockLibrary[blockType] != null);
};
/**
* Populates the dropdown menu.
*/
BlockLibraryController.prototype.populateBlockLibrary = function() {
BlockLibraryView.clearOptions('blockLibraryDropdown');
// Add a default, blank option to dropdown for when no block from library is
// selected.
BlockLibraryView.addDefaultOption('blockLibraryDropdown');
// Add option for each saved block.
var blockLibrary = this.storage.blocks;
for (var block in blockLibrary) {
// Make sure the block wasn't deleted.
if (blockLibrary[block] != null) {
BlockLibraryView.addOption(
block, block, 'blockLibraryDropdown', false, true);
}
}
};
/**
* Return block library mapping block type to xml.
*
* @return {Object} Object mapping block type to xml text.
*/
BlockLibraryController.prototype.getBlockLibrary = function() {
return this.storage.getBlockXmlTextMap();
};
/**
* Set the block library storage object from which exporter exports.
*
* @param {!BlockLibraryStorage} blockLibStorage - Block Library Storage
* object.
*/
BlockLibraryController.prototype.setBlockLibStorage
= function(blockLibStorage) {
this.storage = blockLibStorage;
};
/**
* Get the block library storage object from which exporter exports.
*
* @return {!BlockLibraryStorage} blockLibStorage - Block Library Storage object
* that stores the blocks.
*/
BlockLibraryController.prototype.getBlockLibStorage =
function(blockLibStorage) {
return this.blockLibStorage;
};
/**
* Get the block library storage object from which exporter exports.
*
* @return {boolean} True if the Block Library is empty, false otherwise.
*/
BlockLibraryController.prototype.hasEmptyBlockLib = function() {
return this.storage.isEmpty();
};

View File

@@ -0,0 +1,167 @@
/**
* @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 Block Library's Storage Class.
* Depends on Block Library for its namespace.
*
* @author quachtina96 (Tina Quach)
*/
'use strict';
goog.provide('BlockLibraryStorage');
/**
* Represents a block library's storage.
* @constructor
*
* @param {string} blockLibraryName - Desired name of Block Library, also used
* to create the key for where it's stored in local storage.
* @param {Object} opt_blocks - Object mapping block type to xml.
*/
BlockLibraryStorage = function(blockLibraryName, opt_blocks) {
// Add prefix to this.name to avoid collisions in local storage.
this.name = 'BlockLibraryStorage.' + blockLibraryName;
if (!opt_blocks) {
// Initialize this.blocks by loading from local storage.
this.loadFromLocalStorage();
if (this.blocks == null) {
this.blocks = Object.create(null);
// The line above is equivalent of {} except that this object is TRULY
// empty. It doesn't have built-in attributes/functions such as length or
// toString.
this.saveToLocalStorage();
}
} else {
this.blocks = opt_blocks;
this.saveToLocalStorage();
}
};
/**
* Reads the named block library from local storage and saves it in this.blocks.
*/
BlockLibraryStorage.prototype.loadFromLocalStorage = function() {
// goog.global is synonymous to window, and allows for flexibility
// between browsers.
var object = goog.global.localStorage[this.name];
this.blocks = object ? JSON.parse(object) : null;
};
/**
* Writes the current block library (this.blocks) to local storage.
*/
BlockLibraryStorage.prototype.saveToLocalStorage = function() {
goog.global.localStorage[this.name] = JSON.stringify(this.blocks);
};
/**
* Clears the current block library.
*/
BlockLibraryStorage.prototype.clear = function() {
this.blocks = Object.create(null);
// The line above is equivalent of {} except that this object is TRULY
// empty. It doesn't have built-in attributes/functions such as length or
// toString.
};
/**
* Saves block to block library.
*
* @param {string} blockType - Type of block.
* @param {Element} blockXML - The block's XML pulled from workspace.
*/
BlockLibraryStorage.prototype.addBlock = function(blockType, blockXML) {
var prettyXml = Blockly.Xml.domToPrettyText(blockXML);
this.blocks[blockType] = prettyXml;
};
/**
* Removes block from current block library (this.blocks).
*
* @param {string} blockType - Type of block.
*/
BlockLibraryStorage.prototype.removeBlock = function(blockType) {
delete this.blocks[blockType];
};
/**
* Returns the xml of given block type stored in current block library
* (this.blocks).
*
* @param {string} blockType - Type of block.
* @return {Element} The xml that represents the block type or null.
*/
BlockLibraryStorage.prototype.getBlockXml = function(blockType) {
var xml = this.blocks[blockType] || null;
if (xml) {
var xml = Blockly.Xml.textToDom(xml);
}
return xml;
};
/**
* Returns map of each block type to its corresponding xml stored in current
* block library (this.blocks).
*
* @param {Array.<!string>} blockTypes - Types of blocks.
* @return {!Object} Map of block type to corresponding xml.
*/
BlockLibraryStorage.prototype.getBlockXmlMap = function(blockTypes) {
var blockXmlMap = {};
for (var i = 0; i < blockTypes.length; i++) {
var blockType = blockTypes[i];
var xml = this.getBlockXml(blockType);
blockXmlMap[blockType] = xml;
}
return blockXmlMap;
};
/**
* Returns array of all block types stored in current block library.
*
* @return {!Array.<string>} Array of block types stored in library.
*/
BlockLibraryStorage.prototype.getBlockTypes = function() {
return Object.keys(this.blocks);
};
/**
* Checks to see if block library is empty.
*
* @return {boolean} True if empty, false otherwise.
*/
BlockLibraryStorage.prototype.isEmpty = function() {
for (var blockType in this.blocks) {
return false;
}
return true;
};
/**
* Returns array of all block types stored in current block library.
*
* @return {!Array.<string>} Map of block type to corresponding xml text.
*/
BlockLibraryStorage.prototype.getBlockXmlTextMap = function() {
return this.blocks;
};

View File

@@ -0,0 +1,117 @@
/**
* @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 Block Library's UI for pulling blocks from the
* Block Library's storage to edit in Block Factory.
*
* @author quachtina96 (Tina Quach)
*/
'use strict';
goog.provide('BlockLibraryView');
/**
* Creates a node of a given element type and appends to the node with given id.
*
* @param {string} optionIdentifier - String used to identify option.
* @param {string} optionText - Text to display in the dropdown for the option.
* @param {string} dropdownID - ID for HTML select element.
* @param {boolean} selected - Whether or not the option should be selected on
* the dropdown.
* @param {boolean} enabled - Whether or not the option should be enabled.
*/
BlockLibraryView.addOption
= function(optionIdentifier, optionText, dropdownID, selected, enabled) {
var dropdown = document.getElementById(dropdownID);
var option = document.createElement('option');
// The value attribute of a dropdown's option is not visible in the UI, but is
// useful for identifying different options that may have the same text.
option.value = optionIdentifier;
// The text attribute is what the user sees in the dropdown for the option.
option.text = optionText;
option.selected = selected;
option.disabled = !enabled;
dropdown.add(option);
};
/**
* Adds a default, blank option to dropdown for when no block from library is
* selected.
*
* @param {string} dropdownID - ID of HTML select element
*/
BlockLibraryView.addDefaultOption = function(dropdownID) {
BlockLibraryView.addOption(
'BLOCK_LIBRARY_DEFAULT_BLANK', '', dropdownID, true, false);
};
/**
* Selects the default, blank option in dropdown identified by given ID.
*
* @param {string} dropdownID - ID of HTML select element
*/
BlockLibraryView.selectDefaultOption = function(dropdownID) {
var dropdown = document.getElementById(dropdownID);
// Deselect currently selected option.
var index = dropdown.selectedIndex;
dropdown.options[index].selected = false;
// Select default option, always the first in the dropdown.
var defaultOption = dropdown.options[0];
defaultOption.selected = true;
};
/**
* Returns block type of selected block.
*
* @param {Element} dropdown - HTML select element.
* @return {string} Type of block selected.
*/
BlockLibraryView.getSelected = function(dropdown) {
var index = dropdown.selectedIndex;
return dropdown.options[index].value;
};
/**
* Removes option currently selected in dropdown from dropdown menu.
*
* @param {string} dropdownID - ID of HTML select element within which to find
* the selected option.
*/
BlockLibraryView.removeSelectedOption = function(dropdownID) {
var dropdown = document.getElementById(dropdownID);
if (dropdown) {
dropdown.remove(dropdown.selectedIndex);
}
};
/**
* Removes all options from dropdown.
*
* @param {string} dropdownID - ID of HTML select element to clear options of.
*/
BlockLibraryView.clearOptions = function(dropdownID) {
var dropdown = document.getElementById(dropdownID);
while (dropdown.length > 0) {
dropdown.remove(dropdown.length - 1);
}
};

View File

@@ -0,0 +1,199 @@
/**
* @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.
*/
html, body {
height: 100%;
}
body {
background-color: #fff;
font-family: sans-serif;
margin: 0 5px;
overflow: hidden
}
h1 {
font-weight: normal;
font-size: 140%;
}
h3 {
margin-top: 5px;
margin-bottom: 0;
}
table {
height: 100%;
width: 100%;
}
td {
vertical-align: top;
padding: 0;
}
p {
display: block;
-webkit-margin-before: 0em;
-webkit-margin-after: 0em;
-webkit-margin-start: 0px;
-webkit-margin-end: 0px;
padding: 5px 0px;
}
#blockly {
position: fixed;
}
#blocklyMask {
background-color: #000;
cursor: not-allowed;
display: none;
position: fixed;
opacity: 0.2;
z-index: 9;
}
#preview {
position: absolute;
}
pre,
#languageTA {
border: #ddd 1px solid;
margin-top: 0;
position: absolute;
overflow: scroll;
}
#languageTA {
display: none;
font: 10pt monospace;
}
.downloadButton {
padding: 5px;
}
button:disabled, .buttonStyle:disabled {
opacity: 0.6;
}
button>*, .buttonStyle>* {
opacity: 1;
vertical-align: text-bottom;
}
button, .buttonStyle {
border-radius: 4px;
border: 1px solid #ddd;
background-color: #eee;
color: #000;
padding: 10px;
margin: 10px 5px;
font-size: small;
}
.buttonStyle:hover:not(:disabled), button:hover:not(:disabled) {
box-shadow: 2px 2px 5px #888;
}
.buttonStyle:hover:not(:disabled)>*, button:hover:not(:disabled)>* {
opacity: 1;
}
#linkButton {
display: none;
}
#blockFactoryContent {
height: 87%;
}
#blockLibraryContainer {
vertical-align: bottom;
}
#blockLibraryControls {
text-align: right;
vertical-align: middle;
}
#previewContainer {
vertical-align: bottom;
}
#buttonContainer {
text-align: right;
vertical-align: middle;
}
#files {
position: absolute;
visibility: hidden;
}
#toolbox {
display: none;
}
#blocklyWorkspaceContainer {
height: 95%;
padding: 2px;
width: 50%;
}
#blockLibraryExporter {
clear: both;
display: none;
height: 100%;
}
#exportSelector {
float: left;
height: 75%;
width: 60%;
}
#exportSettings {
margin: auto;
padding: 16px;
overflow: hidden;
}
#exporterHiddenWorkspace {
display: none;
}
/* Tabs */
.tab {
float: left;
padding: 5px 19px;
}
.tab.tabon {
background-color: #ddd;
}
.tab.taboff {
cursor: pointer;
}

View File

@@ -0,0 +1,989 @@
/**
* @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 Blockly's Block Factory application through
* which users can build blocks using a visual interface and dynamically
* generate a preview block and starter code for the block (block definition and
* generator stub. Uses the Block Factory namespace.
*
* @author fraser@google.com (Neil Fraser), quachtina96 (Tina Quach)
*/
'use strict';
/**
* Namespace for Block Factory.
*/
goog.provide('BlockFactory');
goog.require('goog.dom.classes');
/**
* Workspace for user to build block.
* @type {Blockly.Workspace}
*/
BlockFactory.mainWorkspace = null;
/**
* Workspace for preview of block.
* @type {Blockly.Workspace}
*/
BlockFactory.previewWorkspace = null;
/**
* Name of block if not named.
*/
BlockFactory.UNNAMED = 'unnamed';
/**
* Existing direction ('ltr' vs 'rtl') of preview.
*/
BlockFactory.oldDir = null;
// UI
/**
* Inject code into a pre tag, with syntax highlighting.
* Safe from HTML/script injection.
* @param {string} code Lines of code.
* @param {string} id ID of <pre> element to inject into.
*/
BlockFactory.injectCode = function(code, id) {
var pre = document.getElementById(id);
pre.textContent = code;
code = pre.innerHTML;
code = prettyPrintOne(code, 'js');
pre.innerHTML = code;
};
// Utils
/**
* Escape a string.
* @param {string} string String to escape.
* @return {string} Escaped string surrouned by quotes.
*/
BlockFactory.escapeString = function(string) {
return JSON.stringify(string);
};
/**
* Return the uneditable container block that everything else attaches to in
* given workspace
*
* @param {!Blockly.Workspace} workspace - where the root block lives
* @return {Blockly.Block} root block
*/
BlockFactory.getRootBlock = function(workspace) {
var blocks = workspace.getTopBlocks(false);
for (var i = 0, block; block = blocks[i]; i++) {
if (block.type == 'factory_base') {
return block;
}
}
return null;
};
// Language Code: Block Definitions
/**
* Change the language code format.
*/
BlockFactory.formatChange = function() {
var mask = document.getElementById('blocklyMask');
var languagePre = document.getElementById('languagePre');
var languageTA = document.getElementById('languageTA');
if (document.getElementById('format').value == 'Manual') {
Blockly.hideChaff();
mask.style.display = 'block';
languagePre.style.display = 'none';
languageTA.style.display = 'block';
var code = languagePre.textContent.trim();
languageTA.value = code;
languageTA.focus();
BlockFactory.updatePreview();
} else {
mask.style.display = 'none';
languageTA.style.display = 'none';
languagePre.style.display = 'block';
BlockFactory.updateLanguage();
}
BlockFactory.disableEnableLink();
};
/**
* Get block definition code for the current block.
*
* @param {string} blockType - Type of block.
* @param {!Blockly.Block} rootBlock - RootBlock from main workspace in which
* user uses Block Factory Blocks to create a custom block.
* @param {string} format - 'JSON' or 'JavaScript'.
* @param {!Blockly.Workspace} workspace - Where the root block lives.
* @return {string} Block definition.
*/
BlockFactory.getBlockDefinition = function(blockType, rootBlock, format, workspace) {
blockType = blockType.replace(/\W/g, '_').replace(/^(\d)/, '_\\1');
switch (format) {
case 'JSON':
var code = BlockFactory.formatJson_(blockType, rootBlock);
break;
case 'JavaScript':
var code = BlockFactory.formatJavaScript_(blockType, rootBlock, workspace);
break;
}
return code;
};
/**
* Update the language code based on constructs made in Blockly.
*/
BlockFactory.updateLanguage = function() {
var rootBlock = BlockFactory.getRootBlock(BlockFactory.mainWorkspace);
if (!rootBlock) {
return;
}
var blockType = rootBlock.getFieldValue('NAME').trim().toLowerCase();
if (!blockType) {
blockType = BlockFactory.UNNAMED;
}
var format = document.getElementById('format').value;
var code = BlockFactory.getBlockDefinition(blockType, rootBlock, format,
BlockFactory.mainWorkspace);
BlockFactory.injectCode(code, 'languagePre');
BlockFactory.updatePreview();
};
/**
* Update the language code as JSON.
* @param {string} blockType Name of block.
* @param {!Blockly.Block} rootBlock Factory_base block.
* @return {string} Generanted language code.
* @private
*/
BlockFactory.formatJson_ = function(blockType, rootBlock) {
var JS = {};
// Type is not used by Blockly, but may be used by a loader.
JS.type = blockType;
// Generate inputs.
var message = [];
var args = [];
var contentsBlock = rootBlock.getInputTargetBlock('INPUTS');
var lastInput = null;
while (contentsBlock) {
if (!contentsBlock.disabled && !contentsBlock.getInheritedDisabled()) {
var fields = BlockFactory.getFieldsJson_(
contentsBlock.getInputTargetBlock('FIELDS'));
for (var i = 0; i < fields.length; i++) {
if (typeof fields[i] == 'string') {
message.push(fields[i].replace(/%/g, '%%'));
} else {
args.push(fields[i]);
message.push('%' + args.length);
}
}
var input = {type: contentsBlock.type};
// Dummy inputs don't have names. Other inputs do.
if (contentsBlock.type != 'input_dummy') {
input.name = contentsBlock.getFieldValue('INPUTNAME');
}
var check = JSON.parse(
BlockFactory.getOptTypesFrom(contentsBlock, 'TYPE') || 'null');
if (check) {
input.check = check;
}
var align = contentsBlock.getFieldValue('ALIGN');
if (align != 'LEFT') {
input.align = align;
}
args.push(input);
message.push('%' + args.length);
lastInput = contentsBlock;
}
contentsBlock = contentsBlock.nextConnection &&
contentsBlock.nextConnection.targetBlock();
}
// Remove last input if dummy and not empty.
if (lastInput && lastInput.type == 'input_dummy') {
var fields = lastInput.getInputTargetBlock('FIELDS');
if (fields && BlockFactory.getFieldsJson_(fields).join('').trim() != '') {
var align = lastInput.getFieldValue('ALIGN');
if (align != 'LEFT') {
JS.lastDummyAlign0 = align;
}
args.pop();
message.pop();
}
}
JS.message0 = message.join(' ');
if (args.length) {
JS.args0 = args;
}
// Generate inline/external switch.
if (rootBlock.getFieldValue('INLINE') == 'EXT') {
JS.inputsInline = false;
} else if (rootBlock.getFieldValue('INLINE') == 'INT') {
JS.inputsInline = true;
}
// Generate output, or next/previous connections.
switch (rootBlock.getFieldValue('CONNECTIONS')) {
case 'LEFT':
JS.output =
JSON.parse(
BlockFactory.getOptTypesFrom(rootBlock, 'OUTPUTTYPE') || 'null');
break;
case 'BOTH':
JS.previousStatement =
JSON.parse(
BlockFactory.getOptTypesFrom(rootBlock, 'TOPTYPE') || 'null');
JS.nextStatement =
JSON.parse(
BlockFactory.getOptTypesFrom(rootBlock, 'BOTTOMTYPE') || 'null');
break;
case 'TOP':
JS.previousStatement =
JSON.parse(
BlockFactory.getOptTypesFrom(rootBlock, 'TOPTYPE') || 'null');
break;
case 'BOTTOM':
JS.nextStatement =
JSON.parse(
BlockFactory.getOptTypesFrom(rootBlock, 'BOTTOMTYPE') || 'null');
break;
}
// Generate colour.
var colourBlock = rootBlock.getInputTargetBlock('COLOUR');
if (colourBlock && !colourBlock.disabled) {
var hue = parseInt(colourBlock.getFieldValue('HUE'), 10);
JS.colour = hue;
}
JS.tooltip = '';
JS.helpUrl = 'http://www.example.com/';
return JSON.stringify(JS, null, ' ');
};
/**
* Update the language code as JavaScript.
* @param {string} blockType Name of block.
* @param {!Blockly.Block} rootBlock Factory_base block.
* @param {!Blockly.Workspace} workspace - Where the root block lives.
* @return {string} Generated language code.
* @private
*/
BlockFactory.formatJavaScript_ = function(blockType, rootBlock, workspace) {
var code = [];
code.push("Blockly.Blocks['" + blockType + "'] = {");
code.push(" init: function() {");
// Generate inputs.
var TYPES = {'input_value': 'appendValueInput',
'input_statement': 'appendStatementInput',
'input_dummy': 'appendDummyInput'};
var contentsBlock = rootBlock.getInputTargetBlock('INPUTS');
while (contentsBlock) {
if (!contentsBlock.disabled && !contentsBlock.getInheritedDisabled()) {
var name = '';
// Dummy inputs don't have names. Other inputs do.
if (contentsBlock.type != 'input_dummy') {
name =
BlockFactory.escapeString(contentsBlock.getFieldValue('INPUTNAME'));
}
code.push(' this.' + TYPES[contentsBlock.type] + '(' + name + ')');
var check = BlockFactory.getOptTypesFrom(contentsBlock, 'TYPE');
if (check) {
code.push(' .setCheck(' + check + ')');
}
var align = contentsBlock.getFieldValue('ALIGN');
if (align != 'LEFT') {
code.push(' .setAlign(Blockly.ALIGN_' + align + ')');
}
var fields = BlockFactory.getFieldsJs_(
contentsBlock.getInputTargetBlock('FIELDS'));
for (var i = 0; i < fields.length; i++) {
code.push(' .appendField(' + fields[i] + ')');
}
// Add semicolon to last line to finish the statement.
code[code.length - 1] += ';';
}
contentsBlock = contentsBlock.nextConnection &&
contentsBlock.nextConnection.targetBlock();
}
// Generate inline/external switch.
if (rootBlock.getFieldValue('INLINE') == 'EXT') {
code.push(' this.setInputsInline(false);');
} else if (rootBlock.getFieldValue('INLINE') == 'INT') {
code.push(' this.setInputsInline(true);');
}
// Generate output, or next/previous connections.
switch (rootBlock.getFieldValue('CONNECTIONS')) {
case 'LEFT':
code.push(BlockFactory.connectionLineJs_('setOutput', 'OUTPUTTYPE', workspace));
break;
case 'BOTH':
code.push(
BlockFactory.connectionLineJs_('setPreviousStatement', 'TOPTYPE', workspace));
code.push(
BlockFactory.connectionLineJs_('setNextStatement', 'BOTTOMTYPE', workspace));
break;
case 'TOP':
code.push(
BlockFactory.connectionLineJs_('setPreviousStatement', 'TOPTYPE', workspace));
break;
case 'BOTTOM':
code.push(
BlockFactory.connectionLineJs_('setNextStatement', 'BOTTOMTYPE', workspace));
break;
}
// Generate colour.
var colourBlock = rootBlock.getInputTargetBlock('COLOUR');
if (colourBlock && !colourBlock.disabled) {
var hue = parseInt(colourBlock.getFieldValue('HUE'), 10);
if (!isNaN(hue)) {
code.push(' this.setColour(' + hue + ');');
}
}
code.push(" this.setTooltip('');");
code.push(" this.setHelpUrl('http://www.example.com/');");
code.push(' }');
code.push('};');
return code.join('\n');
};
/**
* Create JS code required to create a top, bottom, or value connection.
* @param {string} functionName JavaScript function name.
* @param {string} typeName Name of type input.
* @param {!Blockly.Workspace} workspace - Where the root block lives.
* @return {string} Line of JavaScript code to create connection.
* @private
*/
BlockFactory.connectionLineJs_ = function(functionName, typeName, workspace) {
var type = BlockFactory.getOptTypesFrom(
BlockFactory.getRootBlock(workspace), typeName);
if (type) {
type = ', ' + type;
} else {
type = '';
}
return ' this.' + functionName + '(true' + type + ');';
};
/**
* Returns field strings and any config.
* @param {!Blockly.Block} block Input block.
* @return {!Array.<string>} Field strings.
* @private
*/
BlockFactory.getFieldsJs_ = function(block) {
var fields = [];
while (block) {
if (!block.disabled && !block.getInheritedDisabled()) {
switch (block.type) {
case 'field_static':
// Result: 'hello'
fields.push(BlockFactory.escapeString(block.getFieldValue('TEXT')));
break;
case 'field_input':
// Result: new Blockly.FieldTextInput('Hello'), 'GREET'
fields.push('new Blockly.FieldTextInput(' +
BlockFactory.escapeString(block.getFieldValue('TEXT')) + '), ' +
BlockFactory.escapeString(block.getFieldValue('FIELDNAME')));
break;
case 'field_number':
// Result: new Blockly.FieldNumber(10, 0, 100, 1), 'NUMBER'
var args = [
Number(block.getFieldValue('VALUE')),
Number(block.getFieldValue('MIN')),
Number(block.getFieldValue('MAX')),
Number(block.getFieldValue('PRECISION'))
];
// Remove any trailing arguments that aren't needed.
if (args[3] == 0) {
args.pop();
if (args[2] == Infinity) {
args.pop();
if (args[1] == -Infinity) {
args.pop();
}
}
}
fields.push('new Blockly.FieldNumber(' + args.join(', ') + '), ' +
BlockFactory.escapeString(block.getFieldValue('FIELDNAME')));
break;
case 'field_angle':
// Result: new Blockly.FieldAngle(90), 'ANGLE'
fields.push('new Blockly.FieldAngle(' +
parseFloat(block.getFieldValue('ANGLE')) + '), ' +
BlockFactory.escapeString(block.getFieldValue('FIELDNAME')));
break;
case 'field_checkbox':
// Result: new Blockly.FieldCheckbox('TRUE'), 'CHECK'
fields.push('new Blockly.FieldCheckbox(' +
BlockFactory.escapeString(block.getFieldValue('CHECKED')) +
'), ' +
BlockFactory.escapeString(block.getFieldValue('FIELDNAME')));
break;
case 'field_colour':
// Result: new Blockly.FieldColour('#ff0000'), 'COLOUR'
fields.push('new Blockly.FieldColour(' +
BlockFactory.escapeString(block.getFieldValue('COLOUR')) +
'), ' +
BlockFactory.escapeString(block.getFieldValue('FIELDNAME')));
break;
case 'field_date':
// Result: new Blockly.FieldDate('2015-02-04'), 'DATE'
fields.push('new Blockly.FieldDate(' +
BlockFactory.escapeString(block.getFieldValue('DATE')) + '), ' +
BlockFactory.escapeString(block.getFieldValue('FIELDNAME')));
break;
case 'field_variable':
// Result: new Blockly.FieldVariable('item'), 'VAR'
var varname
= BlockFactory.escapeString(block.getFieldValue('TEXT') || null);
fields.push('new Blockly.FieldVariable(' + varname + '), ' +
BlockFactory.escapeString(block.getFieldValue('FIELDNAME')));
break;
case 'field_dropdown':
// Result:
// new Blockly.FieldDropdown([['yes', '1'], ['no', '0']]), 'TOGGLE'
var options = [];
for (var i = 0; i < block.optionCount_; i++) {
options[i] = '[' +
BlockFactory.escapeString(block.getFieldValue('USER' + i)) +
', ' +
BlockFactory.escapeString(block.getFieldValue('CPU' + i)) + ']';
}
if (options.length) {
fields.push('new Blockly.FieldDropdown([' +
options.join(', ') + ']), ' +
BlockFactory.escapeString(block.getFieldValue('FIELDNAME')));
}
break;
case 'field_image':
// Result: new Blockly.FieldImage('http://...', 80, 60)
var src = BlockFactory.escapeString(block.getFieldValue('SRC'));
var width = Number(block.getFieldValue('WIDTH'));
var height = Number(block.getFieldValue('HEIGHT'));
var alt = BlockFactory.escapeString(block.getFieldValue('ALT'));
fields.push('new Blockly.FieldImage(' +
src + ', ' + width + ', ' + height + ', ' + alt + ')');
break;
}
}
block = block.nextConnection && block.nextConnection.targetBlock();
}
return fields;
};
/**
* Returns field strings and any config.
* @param {!Blockly.Block} block Input block.
* @return {!Array.<string|!Object>} Array of static text and field configs.
* @private
*/
BlockFactory.getFieldsJson_ = function(block) {
var fields = [];
while (block) {
if (!block.disabled && !block.getInheritedDisabled()) {
switch (block.type) {
case 'field_static':
// Result: 'hello'
fields.push(block.getFieldValue('TEXT'));
break;
case 'field_input':
fields.push({
type: block.type,
name: block.getFieldValue('FIELDNAME'),
text: block.getFieldValue('TEXT')
});
break;
case 'field_number':
var obj = {
type: block.type,
name: block.getFieldValue('FIELDNAME'),
value: parseFloat(block.getFieldValue('VALUE'))
};
var min = parseFloat(block.getFieldValue('MIN'));
if (min > -Infinity) {
obj.min = min;
}
var max = parseFloat(block.getFieldValue('MAX'));
if (max < Infinity) {
obj.max = max;
}
var precision = parseFloat(block.getFieldValue('PRECISION'));
if (precision) {
obj.precision = precision;
}
fields.push(obj);
break;
case 'field_angle':
fields.push({
type: block.type,
name: block.getFieldValue('FIELDNAME'),
angle: Number(block.getFieldValue('ANGLE'))
});
break;
case 'field_checkbox':
fields.push({
type: block.type,
name: block.getFieldValue('FIELDNAME'),
checked: block.getFieldValue('CHECKED') == 'TRUE'
});
break;
case 'field_colour':
fields.push({
type: block.type,
name: block.getFieldValue('FIELDNAME'),
colour: block.getFieldValue('COLOUR')
});
break;
case 'field_date':
fields.push({
type: block.type,
name: block.getFieldValue('FIELDNAME'),
date: block.getFieldValue('DATE')
});
break;
case 'field_variable':
fields.push({
type: block.type,
name: block.getFieldValue('FIELDNAME'),
variable: block.getFieldValue('TEXT') || null
});
break;
case 'field_dropdown':
var options = [];
for (var i = 0; i < block.optionCount_; i++) {
options[i] = [block.getFieldValue('USER' + i),
block.getFieldValue('CPU' + i)];
}
if (options.length) {
fields.push({
type: block.type,
name: block.getFieldValue('FIELDNAME'),
options: options
});
}
break;
case 'field_image':
fields.push({
type: block.type,
src: block.getFieldValue('SRC'),
width: Number(block.getFieldValue('WIDTH')),
height: Number(block.getFieldValue('HEIGHT')),
alt: block.getFieldValue('ALT')
});
break;
}
}
block = block.nextConnection && block.nextConnection.targetBlock();
}
return fields;
};
/**
* Fetch the type(s) defined in the given input.
* Format as a string for appending to the generated code.
* @param {!Blockly.Block} block Block with input.
* @param {string} name Name of the input.
* @return {?string} String defining the types.
*/
BlockFactory.getOptTypesFrom = function(block, name) {
var types = BlockFactory.getTypesFrom_(block, name);
if (types.length == 0) {
return undefined;
} else if (types.indexOf('null') != -1) {
return 'null';
} else if (types.length == 1) {
return types[0];
} else {
return '[' + types.join(', ') + ']';
}
};
/**
* Fetch the type(s) defined in the given input.
* @param {!Blockly.Block} block Block with input.
* @param {string} name Name of the input.
* @return {!Array.<string>} List of types.
* @private
*/
BlockFactory.getTypesFrom_ = function(block, name) {
var typeBlock = block.getInputTargetBlock(name);
var types;
if (!typeBlock || typeBlock.disabled) {
types = [];
} else if (typeBlock.type == 'type_other') {
types = [BlockFactory.escapeString(typeBlock.getFieldValue('TYPE'))];
} else if (typeBlock.type == 'type_group') {
types = [];
for (var n = 0; n < typeBlock.typeCount_; n++) {
types = types.concat(BlockFactory.getTypesFrom_(typeBlock, 'TYPE' + n));
}
// Remove duplicates.
var hash = Object.create(null);
for (var n = types.length - 1; n >= 0; n--) {
if (hash[types[n]]) {
types.splice(n, 1);
}
hash[types[n]] = true;
}
} else {
types = [BlockFactory.escapeString(typeBlock.valueType)];
}
return types;
};
// Generator Code
/**
* Get the generator code for a given block.
*
* @param {!Blockly.Block} block - Rendered block in preview workspace.
* @param {string} generatorLanguage - 'JavaScript', 'Python', 'PHP', 'Lua',
* 'Dart'.
* @return {string} Generator code for multiple blocks.
*/
BlockFactory.getGeneratorStub = function(block, generatorLanguage) {
function makeVar(root, name) {
name = name.toLowerCase().replace(/\W/g, '_');
return ' var ' + root + '_' + name;
}
// The makevar function lives in the original update generator.
var language = generatorLanguage;
var code = [];
code.push("Blockly." + language + "['" + block.type +
"'] = function(block) {");
// Generate getters for any fields or inputs.
for (var i = 0, input; input = block.inputList[i]; i++) {
for (var j = 0, field; field = input.fieldRow[j]; j++) {
var name = field.name;
if (!name) {
continue;
}
if (field instanceof Blockly.FieldVariable) {
// Subclass of Blockly.FieldDropdown, must test first.
code.push(makeVar('variable', name) +
" = Blockly." + language +
".variableDB_.getName(block.getFieldValue('" + name +
"'), Blockly.Variables.NAME_TYPE);");
} else if (field instanceof Blockly.FieldAngle) {
// Subclass of Blockly.FieldTextInput, must test first.
code.push(makeVar('angle', name) +
" = block.getFieldValue('" + name + "');");
} else if (Blockly.FieldDate && field instanceof Blockly.FieldDate) {
// Blockly.FieldDate may not be compiled into Blockly.
code.push(makeVar('date', name) +
" = block.getFieldValue('" + name + "');");
} else if (field instanceof Blockly.FieldColour) {
code.push(makeVar('colour', name) +
" = block.getFieldValue('" + name + "');");
} else if (field instanceof Blockly.FieldCheckbox) {
code.push(makeVar('checkbox', name) +
" = block.getFieldValue('" + name + "') == 'TRUE';");
} else if (field instanceof Blockly.FieldDropdown) {
code.push(makeVar('dropdown', name) +
" = block.getFieldValue('" + name + "');");
} else if (field instanceof Blockly.FieldNumber) {
code.push(makeVar('number', name) +
" = block.getFieldValue('" + name + "');");
} else if (field instanceof Blockly.FieldTextInput) {
code.push(makeVar('text', name) +
" = block.getFieldValue('" + name + "');");
}
}
var name = input.name;
if (name) {
if (input.type == Blockly.INPUT_VALUE) {
code.push(makeVar('value', name) +
" = Blockly." + language + ".valueToCode(block, '" + name +
"', Blockly." + language + ".ORDER_ATOMIC);");
} else if (input.type == Blockly.NEXT_STATEMENT) {
code.push(makeVar('statements', name) +
" = Blockly." + language + ".statementToCode(block, '" +
name + "');");
}
}
}
// Most languages end lines with a semicolon. Python does not.
var lineEnd = {
'JavaScript': ';',
'Python': '',
'PHP': ';',
'Dart': ';'
};
code.push(" // TODO: Assemble " + language + " into code variable.");
if (block.outputConnection) {
code.push(" var code = '...';");
code.push(" // TODO: Change ORDER_NONE to the correct strength.");
code.push(" return [code, Blockly." + language + ".ORDER_NONE];");
} else {
code.push(" var code = '..." + (lineEnd[language] || '') + "\\n';");
code.push(" return code;");
}
code.push("};");
return code.join('\n');
};
/**
* Update the generator code.
* @param {!Blockly.Block} block Rendered block in preview workspace.
*/
BlockFactory.updateGenerator = function(block) {
var language = document.getElementById('language').value;
var generatorStub = BlockFactory.getGeneratorStub(block, language);
BlockFactory.injectCode(generatorStub, 'generatorPre');
};
// Preview Block
/**
* Update the preview display.
*/
BlockFactory.updatePreview = function() {
// Toggle between LTR/RTL if needed (also used in first display).
var newDir = document.getElementById('direction').value;
if (BlockFactory.oldDir != newDir) {
if (BlockFactory.previewWorkspace) {
BlockFactory.previewWorkspace.dispose();
}
var rtl = newDir == 'rtl';
BlockFactory.previewWorkspace = Blockly.inject('preview',
{rtl: rtl,
media: '../../media/',
scrollbars: true});
BlockFactory.oldDir = newDir;
}
BlockFactory.previewWorkspace.clear();
// Fetch the code and determine its format (JSON or JavaScript).
var format = document.getElementById('format').value;
if (format == 'Manual') {
var code = document.getElementById('languageTA').value;
// If the code is JSON, it will parse, otherwise treat as JS.
try {
JSON.parse(code);
format = 'JSON';
} catch (e) {
format = 'JavaScript';
}
} else {
var code = document.getElementById('languagePre').textContent;
}
if (!code.trim()) {
// Nothing to render. Happens while cloud storage is loading.
return;
}
// Backup Blockly.Blocks object so that main workspace and preview don't
// collide if user creates a 'factory_base' block, for instance.
var backupBlocks = Blockly.Blocks;
console.log(backupBlocks);
try {
// Make a shallow copy.
Blockly.Blocks = Object.create(null);
for (var prop in backupBlocks) {
Blockly.Blocks[prop] = backupBlocks[prop];
}
if (format == 'JSON') {
var json = JSON.parse(code);
Blockly.Blocks[json.type || BlockFactory.UNNAMED] = {
init: function() {
this.jsonInit(json);
}
};
} else if (format == 'JavaScript') {
eval(code);
} else {
throw 'Unknown format: ' + format;
}
// Look for a block on Blockly.Blocks that does not match the backup.
var blockType = null;
console.log('Blockly Blocks types');
for (var type in Blockly.Blocks) {
console.log(type);
if (typeof Blockly.Blocks[type].init == 'function' &&
Blockly.Blocks[type] != backupBlocks[type]) {
blockType = type;
console.log('found non matching type');
console.log(blockType);
break;
}
}
if (!blockType) {
console.log('non matching type NOT FOUND');
return;
}
// Create the preview block.
var previewBlock = BlockFactory.previewWorkspace.newBlock(blockType);
previewBlock.initSvg();
previewBlock.render();
previewBlock.setMovable(false);
previewBlock.setDeletable(false);
previewBlock.moveBy(15, 10);
BlockFactory.previewWorkspace.clearUndo();
BlockFactory.updateGenerator(previewBlock);
} finally {
Blockly.Blocks = backupBlocks;
}
};
// File Import, Creation, Download
/**
* Generate a file from the contents of a given text area and
* download that file.
* @param {string} filename The name of the file to create.
* @param {string} id The text area to download.
*/
BlockFactory.downloadTextArea = function(filename, id) {
var code = document.getElementById(id).textContent;
BlockFactory.createAndDownloadFile_(code, filename, 'plain');
};
/**
* Create a file with the given attributes and download it.
* @param {string} contents - The contents of the file.
* @param {string} filename - The name of the file to save to.
* @param {string} fileType - The type of the file to save.
* @private
*/
BlockFactory.createAndDownloadFile_ = function(contents, filename, fileType) {
var data = new Blob([contents], {type: 'text/' + fileType});
var clickEvent = new MouseEvent("click", {
"view": window,
"bubbles": true,
"cancelable": false
});
var a = document.createElement('a');
a.href = window.URL.createObjectURL(data);
a.download = filename;
a.textContent = 'Download file!';
a.dispatchEvent(clickEvent);
};
/**
* Save the workspace's xml representation to a file.
* @private
*/
BlockFactory.saveWorkspaceToFile = function() {
var xmlElement = Blockly.Xml.workspaceToDom(BlockFactory.mainWorkspace);
var prettyXml = Blockly.Xml.domToPrettyText(xmlElement);
BlockFactory.createAndDownloadFile_(prettyXml, 'blockXml', 'xml');
};
/**
* Imports xml file for a block to the workspace.
*/
BlockFactory.importBlockFromFile = function() {
var files = document.getElementById('files');
// If the file list is empty, they user likely canceled in the dialog.
if (files.files.length > 0) {
// The input tag doesn't have the "mulitple" attribute
// so the user can only choose 1 file.
var file = files.files[0];
var fileReader = new FileReader();
fileReader.addEventListener('load', function(event) {
var fileContents = event.target.result;
var xml = '';
try {
xml = Blockly.Xml.textToDom(fileContents);
} catch (e) {
var message = 'Could not load your saved file.\n'+
'Perhaps it was created with a different version of Blockly?';
window.alert(message + '\nXML: ' + fileContents);
return;
}
BlockFactory.mainWorkspace.clear();
Blockly.Xml.domToWorkspace(xml, BlockFactory.mainWorkspace);
});
fileReader.readAsText(file);
}
};
/**
* Disable link and save buttons if the format is 'Manual', enable otherwise.
*/
BlockFactory.disableEnableLink = function() {
var linkButton = document.getElementById('linkButton');
var saveBlockButton = document.getElementById('localSaveButton');
var saveToLibButton = document.getElementById('saveToBlockLibraryButton');
var disabled = document.getElementById('format').value == 'Manual';
linkButton.disabled = disabled;
saveBlockButton.disabled = disabled;
saveToLibButton.disabled = disabled;
};
// Block Factory Expansion View Utils
/**
* Render starter block (factory_base).
*/
BlockFactory.showStarterBlock = function() {
var xml = '<xml><block type="factory_base" deletable="false" ' +
'movable="false"></block></xml>';
Blockly.Xml.domToWorkspace(
Blockly.Xml.textToDom(xml), BlockFactory.mainWorkspace);
};
/**
* Hides element so that it's invisible and doesn't take up space.
*
* @param {string} elementID - ID of element to hide.
*/
BlockFactory.hide = function(elementID) {
document.getElementById(elementID).style.display = 'none';
};
/**
* Un-hides an element.
*
* @param {string} elementID - ID of element to hide.
*/
BlockFactory.show = function(elementID) {
document.getElementById(elementID).style.display = 'block';
};
/**
* Hides element so that it's invisible but still takes up space.
*
* @param {string} elementID - ID of element to hide.
*/
BlockFactory.makeInvisible = function(elementID) {
document.getElementById(elementID).visibility = 'hidden';
};
/**
* Makes element visible.
*
* @param {string} elementID - ID of element to hide.
*/
BlockFactory.makeVisible = function(elementID) {
document.getElementById(elementID).visibility = 'visible';
};

View File

@@ -0,0 +1,264 @@
<!-- TODO(quacht): move the CSS out to a separate file -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="target-densitydpi=device-dpi, height=660, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Blockly Demo: Blockly Factory</title>
<script src="/storage.js"></script>
<script src="../../blockly_uncompressed.js"></script>
<script src="../../generators/javascript.js"></script>
<script src="../../../closure-library/closure/goog/base.js"></script>
<script src="factory.js"></script>
<script src="block_library_view.js"></script>
<script src="block_library_storage.js"></script>
<script src="block_library_controller.js"></script>
<script src="block_exporter_tools.js"></script>
<script src="block_exporter_view.js"></script>
<script src="block_exporter_controller.js"></script>
<script src="../blockfactory/blocks.js"></script>
<script src="app_controller.js"></script>
<link rel="stylesheet" href="factory.css">
<link rel="stylesheet" href="../prettify.css">
<script src="../prettify.js"></script>
<script>
var blocklyFactory;
var init = function() {
blocklyFactory = new AppController();
blocklyFactory.init();
if (blocklyFactory.blockLibraryController.hasEmptyBlockLib()) {
alert('Your block library is empty! Click "Save to Block Library" so ' +
'you can reopen it the next time you visit Block Factory!');
}
};
window.addEventListener('load', init);
</script>
</head>
<body>
<h1><a href="https://developers.google.com/blockly/">Blockly</a> &gt;
<a href="../index.html">Demos</a> &gt; Blockly Factory</h1>
<div id="tabContainer">
<div id="blockfactory_tab" class="tab tabon"> Block Factory</div>
<div class="tabGap"></div>
<div id="blocklibraryExporter_tab" class="tab taboff"> Block Library Exporter</div>
</div>
<div id="blockLibraryExporter">
<div id="exporterButtons">
<button id="clearSelectedButton"> Clear Blocks </button>
<button id="addAllButton"> Add All Stored Blocks </button>
</div>
<div id="helperTextDiv">
<p id="helperText"> Drag blocks into your workspace to select them for download.</p>
</div>
<!-- Inject exportSelectorWorkspace into this div -->
<div id="exportSelector"></div>
<!-- Users may customize export settings through this form -->
<div id="exportSettings">
<h3> Block Export Settings </h3>
<br>
<form id="exportSettingsForm">
Download Block Definition:
<input type="checkbox" id="blockDefCheck"><br>
Language code:
<select id="exportFormat">
<option value="JSON">JSON</option>
<option value="JavaScript">JavaScript</option>
</select><br>
Block Definition(s) File Name:<br>
<input type="text" id="blockDef_filename"><br>
<br>
Download Generator Stubs:
<input type="checkbox" id="genStubCheck"><br>
<select id="exportLanguage">
<option value="JavaScript">JavaScript</option>
<option value="Python">Python</option>
<option value="PHP">PHP</option>
<option value="Lua">Lua</option>
<option value="Dart">Dart</option>
</select><br>
Block Generator Stub(s) File Name: <br>
<input type="text" id="generatorStub_filename"><br>
<br>
</form>
<button id="exporterSubmitButton"> Export </button>
</div>
</div>
<table id="blockFactoryContent">
<tr>
<td width="50%" height="5%">
<table>
<tr id="blockLibrary">
<td id="blockLibraryContainer">
<span>
<h3>Block Library:</h3>
<select id="blockLibraryDropdown">
</select>
</span>
</td>
<td id="blockLibraryControls">
<button id="saveToBlockLibraryButton" title="Save block to Block Library.">
<span>Save Block</span>
</button>
<button id="removeBlockFromLibraryButton" title="Remove block from Block Library.">
<span>Delete Block</span>
</button>
<button id="clearBlockLibraryButton" title="Clear Block Library.">
<span>Clear Library</span>
</button>
</td>
</tr>
</table>
</td>
<td>
<table>
<tr>
<td id="previewContainer">
<h3>Preview:
<select id="direction">
<option value="ltr">LTR</option>
<option value="rtl">RTL</option>
</select>
</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>
<button id="createNewBlockButton" title="Create a new block.">
<span> Create New Block</span>
</button>
<label for="files" class="buttonStyle">
<span class=>Import Block Library</span>
</label>
<input id="files" type="file" name="files"
accept="application/xml">
<button id="localSaveButton" title="Save block library xml to a local file.">
<span>Download Block Library</span>
</button>
<button id="helpButton" title="View documentation in new window.">
<span>Help</span>
</button>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td id="blocklyWorkspaceContainer">
<div id="blockly"></div>
<div id="blocklyMask"></div>
</td>
<td width="50%" height="95%">
<table>
<tr>
<td height="30%">
<div id="preview"></div>
</td>
</tr>
<tr>
<td height="5%">
<h3>Language code:
<select id="format">
<option value="JSON">JSON</option>
<option value="JavaScript">JavaScript</option>
<option value="Manual">Manual edit&hellip;</option>
</select>
<button class ="downloadButton" id="downloadBlocks"
title="Download block definition to a file.">
<span>Download</span>
</button>
</h3>
</td>
</tr>
<tr>
<td height="30%">
<pre id="languagePre"></pre>
<textarea id="languageTA"></textarea>
</td>
</tr>
<tr>
<td height="5%">
<h3>Generator stub:
<select id="language">
<option value="JavaScript">JavaScript</option>
<option value="Python">Python</option>
<option value="PHP">PHP</option>
<option value="Lua">Lua</option>
<option value="Dart">Dart</option>
</select>
<button id="downloadGenerator" class="downloadButton"
title="Downloadgenerator stub to a file.">
<span>Download</span>
</button>
</h3>
</td>
</tr>
<tr>
<td height="30%">
<pre id="generatorPre"></pre>
</td>
</tr>
</table>
</td>
</table>
<xml id="toolbox">
<category name="Input">
<block type="input_value">
<value name="TYPE">
<shadow type="type_null"></shadow>
</value>
</block>
<block type="input_statement">
<value name="TYPE">
<shadow type="type_null"></shadow>
</value>
</block>
<block type="input_dummy"></block>
</category>
<category name="Field">
<block type="field_static"></block>
<block type="field_input"></block>
<block type="field_number"></block>
<block type="field_angle"></block>
<block type="field_dropdown"></block>
<block type="field_checkbox"></block>
<block type="field_colour"></block>
<!--
Date picker commented out since it increases footprint by 60%.
Add it only if you need it. See also goog.require in blockly.js.
<block type="field_date"></block>
-->
<block type="field_variable"></block>
<block type="field_image"></block>
</category>
<category name="Type">
<block type="type_group"></block>
<block type="type_null"></block>
<block type="type_boolean"></block>
<block type="type_number"></block>
<block type="type_string"></block>
<block type="type_list"></block>
<block type="type_other"></block>
</category>
<category name="Colour" id="colourCategory">
<block type="colour_hue"><mutation colour="20"></mutation><field name="HUE">20</field></block>
<block type="colour_hue"><mutation colour="65"></mutation><field name="HUE">65</field></block>
<block type="colour_hue"><mutation colour="120"></mutation><field name="HUE">120</field></block>
<block type="colour_hue"><mutation colour="160"></mutation><field name="HUE">160</field></block>
<block type="colour_hue"><mutation colour="210"></mutation><field name="HUE">210</field></block>
<block type="colour_hue"><mutation colour="230"></mutation><field name="HUE">230</field></block>
<block type="colour_hue"><mutation colour="260"></mutation><field name="HUE">260</field></block>
<block type="colour_hue"><mutation colour="290"></mutation><field name="HUE">290</field></block>
<block type="colour_hue"><mutation colour="330"></mutation><field name="HUE">330</field></block>
</category>
</xml>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B