summaryrefslogtreecommitdiff
path: root/blockly/demos/blocklyfactory/app_controller.js
diff options
context:
space:
mode:
Diffstat (limited to 'blockly/demos/blocklyfactory/app_controller.js')
-rw-r--r--blockly/demos/blocklyfactory/app_controller.js688
1 files changed, 688 insertions, 0 deletions
diff --git a/blockly/demos/blocklyfactory/app_controller.js b/blockly/demos/blocklyfactory/app_controller.js
new file mode 100644
index 0000000..a1d8847
--- /dev/null
+++ b/blockly/demos/blocklyfactory/app_controller.js
@@ -0,0 +1,688 @@
+/**
+ * @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('FactoryUtils');
+goog.require('BlockLibraryController');
+goog.require('BlockExporterController');
+goog.require('goog.dom.classlist');
+goog.require('goog.string');
+goog.require('goog.ui.PopupColorPicker');
+goog.require('goog.ui.ColorPicker');
+
+/**
+ * Controller for the Blockly Factory
+ * @constructor
+ */
+AppController = function() {
+ // Initialize Block Library
+ this.blockLibraryName = 'blockLibrary';
+ this.blockLibraryController =
+ new BlockLibraryController(this.blockLibraryName);
+ this.blockLibraryController.populateBlockLibrary();
+
+ // Construct Workspace Factory Controller.
+ this.workspaceFactoryController = new WorkspaceFactoryController
+ ('workspacefactory_toolbox', 'toolbox_blocks', 'preview_blocks');
+
+ // Initialize Block Exporter
+ this.exporter =
+ new BlockExporterController(this.blockLibraryController.storage);
+
+ // Map of tab type to the div element for the tab.
+ this.tabMap = Object.create(null);
+ this.tabMap[AppController.BLOCK_FACTORY] =
+ goog.dom.getElement('blockFactory_tab');
+ this.tabMap[AppController.WORKSPACE_FACTORY] =
+ goog.dom.getElement('workspaceFactory_tab');
+ this.tabMap[AppController.EXPORTER] =
+ goog.dom.getElement('blocklibraryExporter_tab');
+
+ // Last selected tab.
+ this.lastSelectedTab = null;
+ // Selected tab.
+ this.selectedTab = AppController.BLOCK_FACTORY;
+};
+
+// Constant values representing the three tabs in the controller.
+AppController.BLOCK_FACTORY = 'BLOCK_FACTORY';
+AppController.WORKSPACE_FACTORY = 'WORKSPACE_FACTORY';
+AppController.EXPORTER = 'EXPORTER';
+
+/**
+ * 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.formatBlockLibraryForImport_(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.setBlockLibraryStorage(blockLibStorage);
+ // Update the block library dropdown.
+ self.blockLibraryController.populateBlockLibrary();
+ // Update the exporter's block library storage.
+ self.exporter.setBlockLibraryStorage(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.formatBlockLibraryForExport_(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) {
+ FactoryUtils.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} Xml text containing the block xmls.
+ */
+AppController.prototype.formatBlockLibraryForExport_ = function(blockXmlMap) {
+ // Create DOM for XML.
+ var xmlDom = goog.dom.createDom('xml', {
+ 'xmlns':"http://www.w3.org/1999/xhtml"
+ });
+
+ // Append each block node to xml dom.
+ for (var blockType in blockXmlMap) {
+ var blockXmlDom = Blockly.Xml.textToDom(blockXmlMap[blockType]);
+ var blockNode = blockXmlDom.firstElementChild;
+ xmlDom.appendChild(blockNode);
+ }
+
+ // Return the xml text.
+ return Blockly.Xml.domToText(xmlDom);
+};
+
+/**
+ * Converts imported block library to an object mapping block type to block xml.
+ * @private
+ *
+ * @param {string} xmlText - String representation of an xml with each block as
+ * a child node.
+ * @return {!Object} object mapping block type to xml text.
+ */
+AppController.prototype.formatBlockLibraryForImport_ = function(xmlText) {
+ var xmlDom = Blockly.Xml.textToDom(xmlText);
+
+ // Get array of xmls. Use an asterisk (*) instead of a tag name for the XPath
+ // selector, to match all elements at that level and get all factory_base
+ // blocks.
+ var blockNodes = goog.dom.xml.selectNodes(xmlDom, '*');
+
+ // Create empty map. The line below creates a truly empy object. It doesn't
+ // have built-in attributes/functions such as length or toString.
+ var blockXmlTextMap = Object.create(null);
+
+ // Populate map.
+ for (var i = 0, blockNode; blockNode = blockNodes[i]; i++) {
+
+ // Add outer xml tag to the block for proper injection in to the
+ // main workspace.
+ // Create DOM for XML.
+ var xmlDom = goog.dom.createDom('xml', {
+ 'xmlns':"http://www.w3.org/1999/xhtml"
+ });
+ xmlDom.appendChild(blockNode);
+
+ var xmlText = Blockly.Xml.domToText(xmlDom);
+ // All block types should be lowercase.
+ var blockType = this.getBlockTypeFromXml_(xmlText).toLowerCase();
+
+ blockXmlTextMap[blockType] = xmlText;
+ }
+
+ 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 xmlDom = Blockly.Xml.textToDom(xmlText);
+ // Find factory base block.
+ var factoryBaseBlockXml = xmlDom.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;
+ }
+ }
+};
+
+/**
+ * Add click handlers to each tab to allow switching between the Block Factory,
+ * Workspace Factory, and Block Exporter tab.
+ *
+ * @param {!Object} tabMap - Map of tab name to div element that is the tab.
+ */
+AppController.prototype.addTabHandlers = function(tabMap) {
+ var self = this;
+ for (var tabName in tabMap) {
+ var tab = tabMap[tabName];
+ // Use an additional closure to correctly assign the tab callback.
+ tab.addEventListener('click', self.makeTabClickHandler_(tabName));
+ }
+};
+
+/**
+ * Set the selected tab.
+ * @private
+ *
+ * @param {string} tabName AppController.BLOCK_FACTORY,
+ * AppController.WORKSPACE_FACTORY, or AppController.EXPORTER
+ */
+AppController.prototype.setSelected_ = function(tabName) {
+ this.lastSelectedTab = this.selectedTab;
+ this.selectedTab = tabName;
+};
+
+/**
+ * Creates the tab click handler specific to the tab specified.
+ * @private
+ *
+ * @param {string} tabName AppController.BLOCK_FACTORY,
+ * AppController.WORKSPACE_FACTORY, or AppController.EXPORTER
+ * @return {Function} The tab click handler.
+ */
+AppController.prototype.makeTabClickHandler_ = function(tabName) {
+ var self = this;
+ return function() {
+ self.setSelected_(tabName);
+ self.onTab();
+ };
+};
+
+/**
+ * Called on each tab click. Hides and shows specific content based on which tab
+ * (Block Factory, Workspace Factory, or Exporter) is selected.
+ */
+AppController.prototype.onTab = function() {
+ // Get tab div elements.
+ var blockFactoryTab = this.tabMap[AppController.BLOCK_FACTORY];
+ var exporterTab = this.tabMap[AppController.EXPORTER];
+ var workspaceFactoryTab = this.tabMap[AppController.WORKSPACE_FACTORY];
+
+ // Warn user if they have unsaved changes when leaving Block Factory.
+ if (this.lastSelectedTab == AppController.BLOCK_FACTORY &&
+ this.selectedTab != AppController.BLOCK_FACTORY) {
+
+ var hasUnsavedChanges =
+ !FactoryUtils.savedBlockChanges(this.blockLibraryController);
+ if (hasUnsavedChanges &&
+ !confirm('You have unsaved changes in Block Factory.')) {
+ // If the user doesn't want to switch tabs with unsaved changes,
+ // stay on Block Factory Tab.
+ this.setSelected_(AppController.BLOCK_FACTORY);
+ this.lastSelectedTab = AppController.BLOCK_FACTORY;
+ return;
+ }
+ }
+
+ // Only enable key events in workspace factory if workspace factory tab is
+ // selected.
+ this.workspaceFactoryController.keyEventsEnabled =
+ this.selectedTab == AppController.WORKSPACE_FACTORY;
+
+ // Turn selected tab on and other tabs off.
+ this.styleTabs_();
+
+ if (this.selectedTab == AppController.EXPORTER) {
+ // Hide other tabs.
+ FactoryUtils.hide('workspaceFactoryContent');
+ FactoryUtils.hide('blockFactoryContent');
+ // Show exporter tab.
+ FactoryUtils.show('blockLibraryExporter');
+
+ // Need accurate state in order to know which blocks are used in workspace
+ // factory.
+ this.workspaceFactoryController.saveStateFromWorkspace();
+
+ // Update exporter's list of the types of blocks used in workspace factory.
+ var usedBlockTypes = this.workspaceFactoryController.getAllUsedBlockTypes();
+ this.exporter.setUsedBlockTypes(usedBlockTypes);
+
+ // Update exporter's block selector to reflect current block library.
+ this.exporter.updateSelector();
+
+ // Update the exporter's preview to reflect any changes made to the blocks.
+ this.exporter.updatePreview();
+
+ } else if (this.selectedTab == AppController.BLOCK_FACTORY) {
+ // Hide other tabs.
+ FactoryUtils.hide('blockLibraryExporter');
+ FactoryUtils.hide('workspaceFactoryContent');
+ // Show Block Factory.
+ FactoryUtils.show('blockFactoryContent');
+
+ } else if (this.selectedTab == AppController.WORKSPACE_FACTORY) {
+ // Hide other tabs.
+ FactoryUtils.hide('blockLibraryExporter');
+ FactoryUtils.hide('blockFactoryContent');
+ // Show workspace factory container.
+ FactoryUtils.show('workspaceFactoryContent');
+ // Update block library category.
+ var categoryXml = this.exporter.getBlockLibraryCategory();
+ var blockTypes = this.blockLibraryController.getStoredBlockTypes();
+ this.workspaceFactoryController.setBlockLibCategory(categoryXml,
+ blockTypes);
+ }
+
+ // Resize to render workspaces' toolboxes correctly for all tabs.
+ window.dispatchEvent(new Event('resize'));
+};
+
+/**
+ * Called on each tab click. Styles the tabs to reflect which tab is selected.
+ * @private
+ */
+AppController.prototype.styleTabs_ = function() {
+ for (var tabName in this.tabMap) {
+ if (this.selectedTab == tabName) {
+ goog.dom.classlist.addRemove(this.tabMap[tabName], 'taboff', 'tabon');
+ } else {
+ goog.dom.classlist.addRemove(this.tabMap[tabName], 'tabon', 'taboff');
+ }
+ }
+};
+
+/**
+ * Assign button click handlers for the exporter.
+ */
+AppController.prototype.assignExporterClickHandlers = function() {
+ var self = this;
+ document.getElementById('button_setBlocks').addEventListener('click',
+ function() {
+ document.getElementById('dropdownDiv_setBlocks').classList.toggle("show");
+ });
+
+ document.getElementById('dropdown_addAllUsed').addEventListener('click',
+ function() {
+ self.exporter.selectUsedBlocks();
+ self.exporter.updatePreview();
+ document.getElementById('dropdownDiv_setBlocks').classList.remove("show");
+ });
+
+ document.getElementById('dropdown_addAllFromLib').addEventListener('click',
+ function() {
+ self.exporter.selectAllBlocks();
+ self.exporter.updatePreview();
+ document.getElementById('dropdownDiv_setBlocks').classList.remove("show");
+ });
+
+ document.getElementById('clearSelectedButton').addEventListener('click',
+ function() {
+ self.exporter.clearSelectedBlocks();
+ self.exporter.updatePreview();
+ document.getElementById('dropdownDiv_setBlocks').classList.remove("show");
+ });
+
+ // Export blocks when the user submits the export settings.
+ document.getElementById('exporterSubmitButton').addEventListener('click',
+ function() {
+ self.exporter.export();
+ });
+};
+
+/**
+ * Assign change listeners for the exporter. These allow for the dynamic update
+ * of the exporter preview.
+ */
+AppController.prototype.assignExporterChangeListeners = function() {
+ var self = this;
+
+ var blockDefCheck = document.getElementById('blockDefCheck');
+ var genStubCheck = document.getElementById('genStubCheck');
+
+ var blockDefs = document.getElementById('blockDefs');
+ var blockDefSettings = document.getElementById('blockDefSettings');
+ var blockDefElements = [blockDefs, blockDefSettings];
+
+ var genStubs = document.getElementById('genStubs');
+ var genStubSettings = document.getElementById('genStubSettings');
+ var genStubElements = [genStubs, genStubSettings];
+
+ // Select the block definitions and generator stubs on default.
+ blockDefCheck.checked = true;
+ genStubCheck.checked = true;
+
+ // Checking the block definitions checkbox displays preview of code to export.
+ document.getElementById('blockDefCheck').addEventListener('change',
+ function(e) {
+ self.ifCheckedDisplay(blockDefCheck, blockDefElements);
+ });
+
+ // Preview updates when user selects different block definition format.
+ document.getElementById('exportFormat').addEventListener('change',
+ function(e) {
+ self.exporter.updatePreview();
+ });
+
+ // Checking the generator stub checkbox displays preview of code to export.
+ document.getElementById('genStubCheck').addEventListener('change',
+ function(e) {
+ self.ifCheckedDisplay(genStubCheck, genStubElements);
+ });
+
+ // Preview updates when user selects different generator stub language.
+ document.getElementById('exportLanguage').addEventListener('change',
+ function(e) {
+ self.exporter.updatePreview();
+ });
+};
+
+/**
+ * If given checkbox is checked, display given elements. Otherwise, hide.
+ *
+ * @param {!Element} checkbox - Input element of type checkbox.
+ * @param {!Array.<!Element>} elementArray - Array of elements to show when
+ * block is checked.
+ */
+AppController.prototype.ifCheckedDisplay = function(checkbox, elementArray) {
+ for (var i = 0, element; element = elementArray[i]; i++) {
+ element.style.display = checkbox.checked ? 'block' : 'none';
+ }
+};
+
+/**
+ * Assign button click handlers for the block library.
+ */
+AppController.prototype.assignLibraryClickHandlers = function() {
+ var self = this;
+
+ // Button for saving block to library.
+ document.getElementById('saveToBlockLibraryButton').addEventListener('click',
+ function() {
+ self.blockLibraryController.saveToBlockLibrary();
+ });
+
+ // Button for removing selected block from library.
+ document.getElementById('removeBlockFromLibraryButton').addEventListener(
+ 'click',
+ function() {
+ self.blockLibraryController.removeFromBlockLibrary();
+ });
+
+ // Button for clearing the block library.
+ document.getElementById('clearBlockLibraryButton').addEventListener('click',
+ function() {
+ self.blockLibraryController.clearBlockLibrary();
+ });
+
+ // Hide and show the block library dropdown.
+ document.getElementById('button_blockLib').addEventListener('click',
+ function() {
+ document.getElementById('dropdownDiv_blockLib').classList.toggle("show");
+ });
+};
+
+/**
+ * Assign button click handlers for the block factory.
+ */
+AppController.prototype.assignBlockFactoryClickHandlers = 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('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() {
+ // If there are unsaved changes warn user, check if they'd like to
+ // proceed with unsaved changes, and act accordingly.
+ var proceedWithUnsavedChanges =
+ self.blockLibraryController.warnIfUnsavedChanges();
+ if (!proceedWithUnsavedChanges) {
+ return;
+ }
+
+ BlockFactory.showStarterBlock();
+ self.blockLibraryController.setNoneSelected();
+
+ // Close the Block Library Dropdown.
+ goog.dom.getElement('dropdownDiv_blockLib').classList.remove("show");
+ });
+};
+
+/**
+ * Add event listeners for the block factory.
+ */
+AppController.prototype.addBlockFactoryEventListeners = function() {
+ // Update code on changes to block being edited.
+ BlockFactory.mainWorkspace.addChangeListener(BlockFactory.updateLanguage);
+
+ // Disable blocks not attached to the factory_base block.
+ BlockFactory.mainWorkspace.addChangeListener(Blockly.Events.disableOrphans);
+
+ // Update the buttons on the screen based on whether
+ // changes have been saved.
+ var self = this;
+ BlockFactory.mainWorkspace.addChangeListener(function() {
+ self.blockLibraryController.updateButtons(FactoryUtils.savedBlockChanges(
+ self.blockLibraryController));
+ });
+
+ 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();
+};
+
+/**
+ * Handle resizing of elements.
+ */
+AppController.prototype.onresize = function(event) {
+ if (this.selectedTab == AppController.BLOCK_FACTORY) {
+ // Handle resizing of Block Factory elements.
+ var expandList = [
+ document.getElementById('blocklyPreviewContainer'),
+ 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';
+ }
+ } else if (this.selectedTab == AppController.EXPORTER) {
+ // Handle resize of Exporter block options.
+ this.exporter.view.centerPreviewBlocks();
+ }
+};
+
+/**
+ * Handler for the window's 'onbeforeunload' event. When a user has unsaved
+ * changes and refreshes or leaves the page, confirm that they want to do so
+ * before actually refreshing.
+ */
+AppController.prototype.confirmLeavePage = function() {
+ if ((!BlockFactory.isStarterBlock() &&
+ !FactoryUtils.savedBlockChanges(this.blockLibraryController)) ||
+ this.workspaceFactoryController.hasUnsavedChanges()) {
+ // When a string is assigned to the returnValue Event property, a dialog box
+ // appears, asking the users for confirmation to leave the page.
+ return 'You will lose any unsaved changes. Are you sure you want ' +
+ 'to exit this page?';
+ }
+};
+
+/**
+ * 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.assignBlockFactoryClickHandlers();
+
+ this.onresize();
+ var self = this;
+ window.addEventListener('resize', function() {
+ self.onresize();
+ });
+
+ // Inject Block Factory Main Workspace.
+ var toolbox = document.getElementById('blockfactory_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(this.tabMap);
+
+ // Assign exporter change listeners.
+ this.assignExporterChangeListeners();
+
+ // 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.addBlockFactoryEventListeners();
+
+ // Workspace Factory init.
+ WorkspaceFactoryInit.initWorkspaceFactory(this.workspaceFactoryController);
+};
+
+