diff --git a/demos/blocklyfactory/app_controller.js b/demos/blocklyfactory/app_controller.js index 2a6301cbf..a1d884706 100644 --- a/demos/blocklyfactory/app_controller.js +++ b/demos/blocklyfactory/app_controller.js @@ -230,23 +230,6 @@ AppController.prototype.getBlockTypeFromXml_ = function(xmlText) { } }; -/** - * 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 click handlers to each tab to allow switching between the Block Factory, * Workspace Factory, and Block Exporter tab. @@ -303,14 +286,16 @@ AppController.prototype.onTab = function() { // Warn user if they have unsaved changes when leaving Block Factory. if (this.lastSelectedTab == AppController.BLOCK_FACTORY && this.selectedTab != AppController.BLOCK_FACTORY) { - if (!BlockFactory.isStarterBlock() && !this.savedBlockChanges()) { - if (!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; - } + + 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; } } @@ -398,16 +383,16 @@ AppController.prototype.assignExporterClickHandlers = function() { document.getElementById('dropdownDiv_setBlocks').classList.remove("show"); }); - document.getElementById('dropdown_clearSelected').addEventListener('click', + document.getElementById('dropdown_addAllFromLib').addEventListener('click', function() { - self.exporter.clearSelectedBlocks(); + self.exporter.selectAllBlocks(); self.exporter.updatePreview(); document.getElementById('dropdownDiv_setBlocks').classList.remove("show"); }); - document.getElementById('dropdown_addAllFromLib').addEventListener('click', + document.getElementById('clearSelectedButton').addEventListener('click', function() { - self.exporter.selectAllBlocks(); + self.exporter.clearSelectedBlocks(); self.exporter.updatePreview(); document.getElementById('dropdownDiv_setBlocks').classList.remove("show"); }); @@ -479,50 +464,35 @@ AppController.prototype.ifCheckedDisplay = function(checkbox, elementArray) { } }; -/** - * Returns whether or not a block's changes has been saved to the Block Library. - * - * @return {boolean} True if all changes made to the block have been saved to - * the Block Library. - */ -AppController.prototype.savedBlockChanges = function() { - var blockType = this.blockLibraryController.getCurrentBlockType(); - var currentXml = Blockly.Xml.workspaceToDom(BlockFactory.mainWorkspace); - - if (this.blockLibraryController.has(blockType)) { - // Block is saved in block library. - var savedXml = this.blockLibraryController.getBlockXml(blockType); - return FactoryUtils.sameBlockXml(savedXml, currentXml); - } - return false; -}; - /** * Assign button click handlers for the block library. */ AppController.prototype.assignLibraryClickHandlers = function() { var self = this; - // Assign button click handlers for Block Library. + + // 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(); }); - var dropdown = document.getElementById('blockLibraryDropdown'); - dropdown.addEventListener('change', + // Hide and show the block library dropdown. + document.getElementById('button_blockLib').addEventListener('click', function() { - self.onSelectedBlockChanged(dropdown); + document.getElementById('dropdownDiv_blockLib').classList.toggle("show"); }); }; @@ -559,26 +529,40 @@ AppController.prototype.assignBlockFactoryClickHandlers = function() { document.getElementById('createNewBlockButton') .addEventListener('click', function() { - // If there are unsaved changes to the block in open in Block Factory, - // warn user that proceeding to create a new block will cause them to lose - // their changes if they don't save. - if (!self.savedBlockChanges()) { - if(!confirm('You have unsaved changes. By proceeding without saving ' + - ' your block first, you will lose these changes.')) { - return; - } + // 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(); - BlockLibraryView.selectDefaultOption('blockLibraryDropdown'); - }); + + 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') @@ -636,6 +620,22 @@ AppController.prototype.onresize = function(event) { } }; +/** + * 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. */ @@ -651,7 +651,7 @@ AppController.prototype.init = function() { this.assignBlockFactoryClickHandlers(); this.onresize(); - self = this; + var self = this; window.addEventListener('resize', function() { self.onresize(); }); diff --git a/demos/blocklyfactory/block_library_controller.js b/demos/blocklyfactory/block_library_controller.js index 201a9e705..1c0345e45 100644 --- a/demos/blocklyfactory/block_library_controller.js +++ b/demos/blocklyfactory/block_library_controller.js @@ -51,6 +51,9 @@ 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); + // The BlockLibraryView object handles the proper updating and formatting of + // the block library dropdown. + this.view = new BlockLibraryView(); }; /** @@ -67,7 +70,8 @@ BlockLibraryController.prototype.getCurrentBlockType = function() { }; /** - * Removes current block from Block Library + * Removes current block from Block Library and updates the save and delete + * buttons so that user may save block to library and but not delete. * * @param {string} blockType - Type of block. */ @@ -76,8 +80,7 @@ BlockLibraryController.prototype.removeFromBlockLibrary = function() { this.storage.removeBlock(blockType); this.storage.saveToLocalStorage(); this.populateBlockLibrary(); - // Show default block. - BlockFactory.showStarterBlock(); + this.view.updateButtons(blockType, false, false); }; /** @@ -86,25 +89,24 @@ BlockLibraryController.prototype.removeFromBlockLibrary = function() { * @param {string} blockType - Block to edit on block factory. */ BlockLibraryController.prototype.openBlock = function(blockType) { - if (blockType =='BLOCK_LIBRARY_DEFAULT_BLANK') { - BlockFactory.showStarterBlock(); - } else { + if (blockType) { var xml = this.storage.getBlockXml(blockType); BlockFactory.mainWorkspace.clear(); Blockly.Xml.domToWorkspace(xml, BlockFactory.mainWorkspace); BlockFactory.mainWorkspace.clearUndo(); + } else { + BlockFactory.showStarterBlock(); + this.view.setSelectedBlockType(null); } }; /** * 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); +BlockLibraryController.prototype.getSelectedBlockType = function() { + return this.view.getSelectedBlockType(); }; /** @@ -118,12 +120,12 @@ BlockLibraryController.prototype.clearBlockLibrary = function() { 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'); + this.view.clearOptions(); // Show default block. BlockFactory.showStarterBlock(); + // User may not save the starter block, but will get explicit instructions + // upon clicking the red save button. + this.view.updateButtons(null); } }; @@ -132,15 +134,13 @@ BlockLibraryController.prototype.clearBlockLibrary = function() { */ 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.has(blockType)) { - var replace = confirm('You already have a block called "' + blockType + - '" in your library. Replace this block?'); - if (!replace) { - // Do not save if user doesn't want to replace the saved block. - return; - } + // If user has not changed the name of the starter block. + if (blockType == 'block_type') { + // Do not save block if it has the default type, 'block_type'. + alert('You cannot save a block under the name "block_type". Try changing ' + + 'the name before saving. Then, click on the "Block Library" button ' + + 'to view your saved blocks.'); + return; } // Create block xml. @@ -148,6 +148,11 @@ BlockLibraryController.prototype.saveToBlockLibrary = function() { var block = FactoryUtils.getRootBlock(BlockFactory.mainWorkspace); xmlElement.appendChild(Blockly.Xml.blockToDomWithXY(block)); + // Do not add option again if block type is already in library. + if (!this.has(blockType)) { + this.view.addOption(blockType, true, true); + } + // Save block. this.storage.addBlock(blockType, xmlElement); this.storage.saveToLocalStorage(); @@ -156,12 +161,8 @@ BlockLibraryController.prototype.saveToBlockLibrary = function() { // main workspace. this.openBlock(blockType); - // Do not add another option to dropdown if replacing. - if (replace) { - return; - } - BlockLibraryView.addOption( - blockType, blockType, 'blockLibraryDropdown', true, true); + // Add select handler to the new option. + this.addOptionSelectHandler(blockType); }; /** @@ -179,19 +180,13 @@ BlockLibraryController.prototype.has = function(blockType) { * 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. + this.view.clearOptions(); + // Add an unselected 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); - } + for (var blockType in blockLibrary) { + this.view.addOption(blockType, false); } + this.addOptionSelectHandlers(); }; /** @@ -251,3 +246,92 @@ BlockLibraryController.prototype.hasEmptyBlockLibrary = function() { BlockLibraryController.prototype.getStoredBlockTypes = function() { return this.storage.getBlockTypes(); }; + +/** + * Sets the currently selected block option to none. + */ +BlockLibraryController.prototype.setNoneSelected = function() { + this.view.setSelectedBlockType(null); +}; + +/** + * If there are unsaved changes to the block in open in Block Factory + * and the block is not the starter block, check if user wants to proceed, + * knowing that it will cause them to lose their changes. + * + * @return {boolean} Whether or not to proceed. + */ +BlockLibraryController.prototype.warnIfUnsavedChanges = function() { + if (!FactoryUtils.savedBlockChanges(this)) { + return confirm('You have unsaved changes. By proceeding without saving ' + + ' your block first, you will lose these changes.'); + } + return true; +}; + +/** + * Add select handler for an option of a given block type. The handler will to + * update the view and the selected block accordingly. + * + * @param {!string} blockType - The type of block represented by the option is + * for. + */ +BlockLibraryController.prototype.addOptionSelectHandler = function(blockType) { + var self = this; + + // Click handler for a block option. Sets the block option as the selected + // option and opens the block for edit in Block Factory. + var setSelectedAndOpen_ = function(blockOption) { + var blockType = blockOption.textContent; + self.view.setSelectedBlockType(blockType); + self.openBlock(blockType); + // The block is saved in the block library and all changes have been saved + // when the user opens a block from the block library dropdown. + // Thus, the buttons show up as a disabled update button and an enabled + // delete. + self.view.updateButtons(blockType, true, true); + self.view.hide(); + }; + + // Returns a block option select handler. + var makeOptionSelectHandler_ = function(blockOption) { + return function() { + // If there are unsaved changes warn user, check if they'd like to + // proceed with unsaved changes, and act accordingly. + var proceedWithUnsavedChanges = self.warnIfUnsavedChanges(); + if (!proceedWithUnsavedChanges) { + return; + } + setSelectedAndOpen_(blockOption); + }; + }; + + // Assign a click handler to the block option. + var blockOption = this.view.optionMap[blockType]; + // Use an additional closure to correctly assign the tab callback. + blockOption.addEventListener( + 'click', makeOptionSelectHandler_(blockOption)); +}; + +/** + * Add select handlers to each option to update the view and the selected + * blocks accordingly. + */ +BlockLibraryController.prototype.addOptionSelectHandlers = function() { + // Assign a click handler to each block option. + for (var blockType in this.view.optionMap) { + this.addOptionSelectHandler(blockType); + } +}; + +/** + * Update the save and delete buttons based on the current block type of the + * block the user is currently editing. + * + * @param {boolean} Whether changes to the block have been saved. + */ +BlockLibraryController.prototype.updateButtons = function(savedChanges) { + var blockType = this.getCurrentBlockType(); + var isInLibrary = this.has(blockType); + this.view.updateButtons(blockType, isInLibrary, savedChanges); +}; diff --git a/demos/blocklyfactory/block_library_view.js b/demos/blocklyfactory/block_library_view.js index cc5800646..00c676dfa 100644 --- a/demos/blocklyfactory/block_library_view.js +++ b/demos/blocklyfactory/block_library_view.js @@ -19,8 +19,8 @@ */ /** - * @fileoverview Javascript for Block Library's UI for pulling blocks from the - * Block Library's storage to edit in Block Factory. + * @fileoverview Javascript for BlockLibraryView class. It manages the display + * of the Block Library dropdown, save, and delete buttons. * * @author quachtina96 (Tina Quach) */ @@ -29,89 +29,198 @@ goog.provide('BlockLibraryView'); +goog.require('goog.dom'); +goog.require('goog.dom.classlist'); + +/** + * BlockLibraryView Class + * @constructor + */ +var BlockLibraryView = function() { + // Div element to contain the block types to choose from. + // Id of the div that holds the block library view. + this.blockLibraryViewDivID = 'dropdownDiv_blockLib'; + this.dropdown = goog.dom.getElement('dropdownDiv_blockLib'); + // Map of block type to corresponding 'a' element that is the option in the + // dropdown. Used to quickly and easily get a specific option. + this.optionMap = Object.create(null); + // Save and delete buttons. + this.saveButton = goog.dom.getElement('saveToBlockLibraryButton'); + this.deleteButton = goog.dom.getElement('removeBlockFromLibraryButton'); + // Initially, user should not be able to delete a block. They must save a + // block or select a stored block first. + this.deleteButton.disabled = true; +}; + +/** + * Open the Block Library dropdown. + */ +BlockLibraryView.prototype.show = function() { + this.dropdown.classList.add("show"); +}; + +/** + * Close the Block Library dropdown. + */ +BlockLibraryView.prototype.hide = function() { + this.dropdown.classList.remove("show"); +}; + /** * 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 {!string} blockType - Type of block. * @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); +BlockLibraryView.prototype.addOption = function(blockType, selected) { + // Create option. + var option = goog.dom.createDom('a', { + 'id': 'dropdown_' + blockType, + 'class': 'blockLibOpt' + }, blockType); + + // Add option to dropdown. + this.dropdown.appendChild(option); + this.optionMap[blockType] = option; + + // Select the block. + if (selected) { + this.setSelectedBlockType(blockType); + } }; /** - * Adds a default, blank option to dropdown for when no block from library is - * selected. + * Sets a given block type to selected and all other blocks to deselected. + * If null, deselects all blocks. * - * @param {string} dropdownID - ID of HTML select element + * @param {string} blockTypeToSelect - Type of block to select or null. */ -BlockLibraryView.addDefaultOption = function(dropdownID) { - BlockLibraryView.addOption( - 'BLOCK_LIBRARY_DEFAULT_BLANK', '', dropdownID, true, true); +BlockLibraryView.prototype.setSelectedBlockType = function(blockTypeToSelect) { + // Select given block type and deselect all others. Will deselect all blocks + // if null or invalid block type selected. + for (var blockType in this.optionMap) { + var option = this.optionMap[blockType]; + if (blockType == blockTypeToSelect) { + this.selectOption_(option); + } else { + this.deselectOption_(option); + } + } }; /** - * Selects the default, blank option in dropdown identified by given ID. + * Selects a given option. + * @private * - * @param {string} dropdownID - ID of HTML select element + * @param {!Element} option - HTML 'a' element in the dropdown that represents + * a particular block type. */ -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; +BlockLibraryView.prototype.selectOption_ = function(option) { + goog.dom.classlist.add(option, 'dropdown-content-selected'); +}; + +/** + * Deselects a given option. + * @private + * + * @param {!Element} option - HTML 'a' element in the dropdown that represents + * a particular block type. + */ +BlockLibraryView.prototype.deselectOption_ = function(option) { + goog.dom.classlist.remove(option, 'dropdown-content-selected'); +}; + +/** + * Updates the save and delete buttons to represent how the current block will + * be saved by including the block type in the button text as well as indicating + * whether the block is being saved or updated. + * + * @param {!string} blockType - The type of block being edited. + * @param {boolean} isInLibrary - Whether the block type is in the library. + * @param {boolean} savedChanges - Whether changes to block have been saved. + */ +BlockLibraryView.prototype.updateButtons = + function(blockType, isInLibrary, savedChanges) { + if (blockType) { + // User is editing a block. + + if (!isInLibrary) { + // Block type has not been saved to library yet. Disable the delete button + // and allow user to save. + this.saveButton.textContent = 'Save "' + blockType + '"'; + this.saveButton.disabled = false; + this.deleteButton.disabled = true; + } else { + // Block type has already been saved. Disable the save button unless the + // there are unsaved changes (checked below). + this.saveButton.textContent = 'Update "' + blockType + '"'; + this.saveButton.disabled = true; + this.deleteButton.disabled = false; + } + this.deleteButton.textContent = 'Delete "' + blockType + '"'; + + // If changes to block have been made and are not saved, make button + // green to encourage user to save the block. + if (!savedChanges) { + var buttonFormatClass = 'button_warn'; + + // If block type is the default, 'block_type', make button red to alert + // user. + if (blockType == 'block_type') { + buttonFormatClass = 'button_alert'; + } + goog.dom.classlist.add(this.saveButton, buttonFormatClass); + this.saveButton.disabled = false; + + } else { + // No changes to save. + var classesToRemove = ['button_alert', 'button_warn']; + goog.dom.classlist.removeAll(this.saveButton, classesToRemove); + this.saveButton.disabled = true; + } + + } +}; + +/** + * Removes option currently selected in dropdown from dropdown menu. + */ +BlockLibraryView.prototype.removeSelectedOption = function() { + var selectedOption = this.getSelectedOption(); + this.dropdown.removeNode(selectedOption); }; /** * 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; +BlockLibraryView.prototype.getSelectedBlockType = function() { + var selectedOption = this.getSelectedOption(); + var blockType = selectedOption.textContent; + return blockType; }; /** - * Removes option currently selected in dropdown from dropdown menu. + * Returns selected option. * - * @param {string} dropdownID - ID of HTML select element within which to find - * the selected option. + * @return {!Element} HTML 'a' element that is the option for a block type. */ -BlockLibraryView.removeSelectedOption = function(dropdownID) { - var dropdown = document.getElementById(dropdownID); - if (dropdown) { - dropdown.remove(dropdown.selectedIndex); - } +BlockLibraryView.prototype.getSelectedOption = function() { + return goog.dom.getElementByClass('dropdown-content-selected', this.dropdown); }; /** * 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); +BlockLibraryView.prototype.clearOptions = function() { + var blockOpts = goog.dom.getElementsByClass('blockLibOpt', this.dropdown); + if (blockOpts) { + for (var i = 0, option; option = blockOpts[i]; i++) { + goog.dom.removeNode(option); + } } }; + diff --git a/demos/blocklyfactory/factory.css b/demos/blocklyfactory/factory.css index 704b8de4d..f212b1d49 100644 --- a/demos/blocklyfactory/factory.css +++ b/demos/blocklyfactory/factory.css @@ -302,6 +302,28 @@ button, .buttonStyle { width: 90%; } +/* Block Library */ + +#dropdownDiv_blockLib { + max-height: 65%; + overflow-y: scroll; +} + +#button_blockLib { + border-color: darkgrey; + font-size: large; +} + +.button_alert { + background-color: #fcc; + border-color: #f99; +} + +.button_warn { + background-color: #aea; + border-color: #5d5; +} + /* Tabs */ .tab { @@ -531,7 +553,7 @@ td { /* Dropdown Content (Hidden by Default) */ .dropdown-content { - background-color: #f9f9f9; + background-color: #FFF; box-shadow: 0px 8px 16px 0px rgba(0,0,0,.2); display: none; min-width: 170px; @@ -549,9 +571,14 @@ td { text-decoration: none; } -/* Change color of dropdown links on hover. */ +/* Change color of dropdown links on hover. */ .dropdown-content a:hover, .dropdown-content label:hover { - background-color: #f1f1f1 + background-color: #EEE; +} + +/* Change color of dropdown links on selected. */ +.dropdown-content-selected { + background-color: #DDD; } /* Show the dropdown menu */ diff --git a/demos/blocklyfactory/factory.js b/demos/blocklyfactory/factory.js index 6ec2cfd00..837d04bc7 100644 --- a/demos/blocklyfactory/factory.js +++ b/demos/blocklyfactory/factory.js @@ -209,11 +209,19 @@ BlockFactory.updatePreview = function() { // standard library. var rootBlock = FactoryUtils.getRootBlock(BlockFactory.mainWorkspace); if (StandardCategories.coreBlockTypes.indexOf(blockType) != -1) { - rootBlock.setWarningText('A standard Blockly.Block already exists ' + + rootBlock.setWarningText('A core Blockly block already exists ' + 'under this name.'); + + } else if (blockType == 'block_type') { + // Warn user to let them know they can't save a block under the default + // name 'block_type' + rootBlock.setWarningText('You cannot save a block with the default ' + + 'name, "block_type"'); + } else { rootBlock.setWarningText(null); } + } finally { Blockly.Blocks = backupBlocks; } @@ -248,7 +256,10 @@ BlockFactory.isStarterBlock = function() { var rootBlock = FactoryUtils.getRootBlock(BlockFactory.mainWorkspace); // The starter block does not have blocks nested into the factory_base block. return !(rootBlock.getChildren().length > 0 || + // The starter block's name is the default, 'block_type'. rootBlock.getFieldValue('NAME').trim().toLowerCase() != 'block_type' || + // The starter block has no connections. rootBlock.getFieldValue('CONNECTIONS') != 'NONE' || + // The starter block has automatic inputs. rootBlock.getFieldValue('INLINE') != 'AUTO'); }; diff --git a/demos/blocklyfactory/factory_utils.js b/demos/blocklyfactory/factory_utils.js index 89e421663..08c003a95 100644 --- a/demos/blocklyfactory/factory_utils.js +++ b/demos/blocklyfactory/factory_utils.js @@ -33,8 +33,6 @@ */ goog.provide('FactoryUtils'); -goog.require('goog.dom.classes'); - /** * Get block definition code for the current block. * @@ -889,7 +887,7 @@ FactoryUtils.injectCode = function(code, id) { /** * Returns whether or not two blocks are the same based on their xml. Expects - * xml with a single child node that is a factory_base block. The xml found on + * xml with a single child node that is a factory_base block, the xml found on * Block Factory's main workspace. * * @param {!Element} blockXml1 - An xml element with a single child node that @@ -899,18 +897,31 @@ FactoryUtils.injectCode = function(code, id) { * @return {boolean} Whether or not two blocks are the same based on their xml. */ FactoryUtils.sameBlockXml = function(blockXml1, blockXml2) { - // Each block xml has only one child. - var blockXmlText1 = Blockly.Xml.domToText( - blockXml1.getElementsByTagName('block')[0]); - var blockXmlText2 = Blockly.Xml.domToText( - blockXml2.getElementsByTagName('block')[0]); + // Each xml element should contain a single child element with a 'block' tag + if (goog.string.caseInsensitiveCompare(blockXml1.tagName, 'xml') || + goog.string.caseInsensitiveCompare(blockXml2.tagName, 'xml')) { + throw new Error('Expected two xml elements, recieved elements with tag ' + + 'names: ' + blockXml1.tagName + ' and ' + blockXml2.tagName + '.'); + } - // Strip white space. - blockXmlText1 = blockXmlText1.replace(/\s+/g, ''); - blockXmlText2 = blockXmlText2.replace(/\s+/g, ''); + // Compare the block elements directly. The xml tags may include other meta + // information we want to igrore. + var blockElement1 = blockXml1.getElementsByTagName('block')[0]; + var blockElement2 = blockXml2.getElementsByTagName('block')[0]; - // Return whether or not changes have been saved. - return blockXmlText1 == blockXmlText2; + if (!(blockElement1 && blockElement2)) { + throw new Error('Could not get find block element in xml.'); + } + + var blockXmlText1 = Blockly.Xml.domToText(blockElement1); + var blockXmlText2 = Blockly.Xml.domToText(blockElement2); + + // Strip white space. + blockXmlText1 = blockXmlText1.replace(/\s+/g, ''); + blockXmlText2 = blockXmlText2.replace(/\s+/g, ''); + + // Return whether or not changes have been saved. + return blockXmlText1 == blockXmlText2; }; /* @@ -950,3 +961,29 @@ FactoryUtils.isProcedureBlock = function(block) { block.type == 'procedures_callreturn' || block.type == 'procedures_ifreturn'); }; + +/** + * Returns whether or not a modified block's changes has been saved to the + * Block Library. + * TODO(quachtina96): move into the Block Factory Controller once made. + * + * @param {!BlockLibraryController} blockLibraryController - Block Library + * Controller storing custom blocks. + * @return {boolean} True if all changes made to the block have been saved to + * the given Block Library. + */ +FactoryUtils.savedBlockChanges = function(blockLibraryController) { + if (BlockFactory.isStarterBlock()) { + return true; + } + var blockType = blockLibraryController.getCurrentBlockType(); + var currentXml = Blockly.Xml.workspaceToDom(BlockFactory.mainWorkspace); + + if (blockLibraryController.has(blockType)) { + // Block is saved in block library. + var savedXml = blockLibraryController.getBlockXml(blockType); + return FactoryUtils.sameBlockXml(savedXml, currentXml); + } + return false; +}; + diff --git a/demos/blocklyfactory/index.html b/demos/blocklyfactory/index.html index b002b4061..69d18a09b 100644 --- a/demos/blocklyfactory/index.html +++ b/demos/blocklyfactory/index.html @@ -41,7 +41,7 @@ window.addEventListener('load', init); - +

Blockly > Demos > Blockly Factory

Block Factory
-
Workspace Factory
Block Exporter
+
Workspace Factory

-

First, select blocks from your block library by dragging them into your workspace. Then, use the Export Settings form to download starter code for selected blocks. +

First, select blocks from your block library by clicking on them. Then, use the Export Settings form to download starter code for selected blocks.


Block Selector

+
@@ -157,9 +158,9 @@ @@ -193,6 +194,7 @@ New Category Standard Category Separator + Standard Toolbox
@@ -219,7 +221,7 @@

Configure the options for your Blockly inject call.

- +
Read Only
@@ -279,20 +281,22 @@ -

Block Library:

-
- diff --git a/demos/blocklyfactory/workspacefactory/wfactory_controller.js b/demos/blocklyfactory/workspacefactory/wfactory_controller.js index af2f9b695..faf3c09f6 100644 --- a/demos/blocklyfactory/workspacefactory/wfactory_controller.js +++ b/demos/blocklyfactory/workspacefactory/wfactory_controller.js @@ -84,6 +84,10 @@ WorkspaceFactoryController = function(toolboxName, toolboxDiv, previewDiv) { this.selectedMode = WorkspaceFactoryController.MODE_TOOLBOX; // True if key events are enabled, false otherwise. this.keyEventsEnabled = true; + // True if there are unsaved changes in the toolbox, false otherwise. + this.hasUnsavedToolboxChanges = false; + // True if there are unsaved changes in the preloaded blocks, false otherwise. + this.hasUnsavedPreloadChanges = false; }; // Toolbox editing mode. Changes the user makes to the workspace updates the @@ -98,37 +102,8 @@ WorkspaceFactoryController.MODE_PRELOAD = 'preload'; * before), and then creates a tab and switches to it. */ WorkspaceFactoryController.prototype.addCategory = function() { - this.allowToTransferFlyoutBlocksToCategory(); - // Check if it's the first category added. - var isFirstCategory = !this.model.hasElements(); - // Give the option to save blocks if their workspace is not empty and they - // are creating their first category. - if (isFirstCategory && this.toolboxWorkspace.getAllBlocks().length > 0) { - var confirmCreate = confirm('Do you want to save your work in another ' - + 'category? If you don\'t, the blocks in your workspace will be ' + - 'deleted.'); - - // Create a new category for current blocks. - if (confirmCreate) { - var name = prompt('Enter the name of the category for your ' + - 'current blocks: '); - if (!name) { // Exit if cancelled. - return; - } - - // Create the new category. - this.createCategory(name, true); - // Set the new category as selected. - var id = this.model.getCategoryIdByName(name); - this.model.setSelectedById(id); - this.view.setCategoryTabSelection(id, true); - // Set default options if switching from single flyout to categories. - this.view.setCategoryOptions(this.model.hasElements()); - this.generateNewOptions(); - // Update preview here in case exit early. - this.updatePreview(); - } - } + // Transfers the user's blocks to a flyout if it's the first category created. + this.transferFlyoutBlocksToCategory(); // After possibly creating a category, check again if it's the first category. var isFirstCategory = !this.model.hasElements(); @@ -188,40 +163,28 @@ WorkspaceFactoryController.prototype.addClickToSwitch = function(tab, id) { }; /** - * Allows the user to transfer blocks in their flyout to a new category if + * Transfers the blocks in the user's flyout to a new category if * the user is creating their first category and their workspace is not * empty. Should be called whenever it is possible to switch from single flyout * to categories (not including importing). */ -WorkspaceFactoryController.prototype.allowToTransferFlyoutBlocksToCategory = +WorkspaceFactoryController.prototype.transferFlyoutBlocksToCategory = function() { - // Give the option to save blocks if their workspace is not empty and they - // are creating their first category. + // Saves the user's blocks from the flyout in a category if there is no + // toolbox and the user has dragged in blocks. if (!this.model.hasElements() && this.toolboxWorkspace.getAllBlocks().length > 0) { - var confirmCreate = confirm('Do you want to save your work in another ' - + 'category? If you don\'t, the blocks in your workspace will be ' + - 'deleted.'); - - // Create a new category for current blocks. - if (confirmCreate) { - var name = prompt('Enter the name of the category for your ' + - 'current blocks: '); - if (!name) { // Exit if cancelled. - return; - } - - // Create the new category. - this.createCategory(name, true); - // Set the new category as selected. - var id = this.model.getCategoryIdByName(name); - this.model.setSelectedById(id); - this.view.setCategoryTabSelection(id, true); - // Allow user to use the default options for injecting with categories. - this.allowToSetDefaultOptions(); - // Update preview here in case exit early. - this.updatePreview(); - } + // Create the new category. + this.createCategory('Category 1', true); + // Set the new category as selected. + var id = this.model.getCategoryIdByName('Category 1'); + this.model.setSelectedById(id); + this.view.setCategoryTabSelection(id, true); + // Allow user to use the default options for injecting with categories. + this.view.setCategoryOptions(this.model.hasElements()); + this.generateNewOptions(); + // Update preview here in case exit early. + this.updatePreview(); } }; @@ -359,29 +322,32 @@ WorkspaceFactoryController.prototype.clearAndLoadElement = function(id) { * configuration) */ WorkspaceFactoryController.prototype.exportXmlFile = function(exportMode) { - // Generate XML. - if (exportMode == WorkspaceFactoryController.MODE_TOOLBOX) { - // Export the toolbox XML. - - var configXml = Blockly.Xml.domToPrettyText - (this.generator.generateToolboxXml()); - } else if (exportMode == WorkspaceFactoryController.MODE_PRELOAD) { - // Export the pre-loaded block XML. - - var configXml = Blockly.Xml.domToPrettyText - (this.generator.generateWorkspaceXml()); - } else { - // Unknown mode. Throw error. - throw new Error ("Unknown export mode: " + exportMode); - } - - // Get file name. + // Get file name. var fileName = prompt('File Name for ' + (exportMode == WorkspaceFactoryController.MODE_TOOLBOX ? 'toolbox XML: ' : 'pre-loaded workspace XML: ')); if (!fileName) { // If cancelled return; } + + // Generate XML. + if (exportMode == WorkspaceFactoryController.MODE_TOOLBOX) { + // Export the toolbox XML. + + var configXml = Blockly.Xml.domToPrettyText + (this.generator.generateToolboxXml()); + this.hasUnsavedToolboxChanges = false; + } else if (exportMode == WorkspaceFactoryController.MODE_PRELOAD) { + // Export the pre-loaded block XML. + + var configXml = Blockly.Xml.domToPrettyText + (this.generator.generateWorkspaceXml()); + this.hasUnsavedPreloadChanges = false; + } else { + // Unknown mode. Throw error. + throw new Error ("Unknown export mode: " + exportMode); + } + // Download file. var data = new Blob([configXml], {type: 'text/xml'}); this.view.createAndDownloadFile(fileName, data); @@ -391,15 +357,15 @@ WorkspaceFactoryController.prototype.exportXmlFile = function(exportMode) { * Export the options object to be used for the Blockly inject call. Gets a * file name from the user and downloads the options object to that file. */ -WorkspaceFactoryController.prototype.exportOptionsFile = function() { - var fileName = prompt('File Name for options object for injecting: '); +WorkspaceFactoryController.prototype.exportInjectFile = function() { + var fileName = prompt('File Name for starter Blockly workspace code: '); if (!fileName) { // If cancelled. return; } // Generate new options to remove toolbox XML from options object (if // necessary). this.generateNewOptions(); - var printableOptions = this.generator.generateOptionsString() + var printableOptions = this.generator.generateInjectString() var data = new Blob([printableOptions], {type: 'text/javascript'}); this.view.createAndDownloadFile(fileName, data); }; @@ -474,9 +440,22 @@ WorkspaceFactoryController.prototype.updatePreview = function() { WorkspaceFactoryController.prototype.saveStateFromWorkspace = function() { if (this.selectedMode == WorkspaceFactoryController.MODE_TOOLBOX) { // If currently editing the toolbox. + // Update flags if toolbox has been changed. + if (this.model.getSelectedXml() != + Blockly.Xml.workspaceToDom(this.toolboxWorkspace)) { + this.hasUnsavedToolboxChanges = true; + } + this.model.getSelected().saveFromWorkspace(this.toolboxWorkspace); + } else if (this.selectedMode == WorkspaceFactoryController.MODE_PRELOAD) { // If currently editing the pre-loaded workspace. + // Update flags if preloaded blocks have been changed. + if (this.model.getPreloadXml() != + Blockly.Xml.workspaceToDom(this.toolboxWorkspace)) { + this.hasUnsavedPreloadChanges = true; + } + this.model.savePreloadXml (Blockly.Xml.workspaceToDom(this.toolboxWorkspace)); } @@ -632,8 +611,9 @@ WorkspaceFactoryController.prototype.loadCategoryByName = function(name) { + '. Rename your category and try again.'); return; } - // Allow user to transfer current flyout blocks to a category. - this.allowToTransferFlyoutBlocksToCategory(); + // Transfers current flyout blocks to a category if it's the first category + // created. + this.transferFlyoutBlocksToCategory(); var isFirstCategory = !this.model.hasElements(); // Copy the standard category in the model. @@ -665,6 +645,22 @@ WorkspaceFactoryController.prototype.loadCategoryByName = function(name) { this.updatePreview(); }; +/** + * Loads the standard Blockly toolbox into the editing space. Should only + * be called when the mode is set to toolbox. + */ +WorkspaceFactoryController.prototype.loadStandardToolbox = function() { + this.loadCategoryByName('Logic'); + this.loadCategoryByName('Loops'); + this.loadCategoryByName('Math'); + this.loadCategoryByName('Text'); + this.loadCategoryByName('Lists'); + this.loadCategoryByName('Colour'); + this.addSeparator(); + this.loadCategoryByName('Variables'); + this.loadCategoryByName('Functions'); +} + /** * Given the name of a category, determines if it's the name of a standard * category (case insensitive). @@ -688,9 +684,9 @@ WorkspaceFactoryController.prototype.isStandardCategoryName = function(name) { * the separator, and updates the preview. */ WorkspaceFactoryController.prototype.addSeparator = function() { - // If adding the first element in the toolbox, allow the user to transfer - // their flyout blocks to a category. - this.allowToTransferFlyoutBlocksToCategory(); + // If adding the first element in the toolbox, transfers the user's blocks + // in a flyout to a category. + this.transferFlyoutBlocksToCategory(); // Create the separator in the model. var separator = new ListElement(ListElement.TYPE_SEPARATOR); this.model.addElementToList(separator); @@ -734,12 +730,32 @@ WorkspaceFactoryController.prototype.importFile = function(file, importMode) { try { var tree = Blockly.Xml.textToDom(reader.result); if (importMode == WorkspaceFactoryController.MODE_TOOLBOX) { - // Switch mode and import toolbox XML. + // Switch mode. controller.setMode(WorkspaceFactoryController.MODE_TOOLBOX); + + // Confirm that the user wants to override their current toolbox. + var hasToolboxElements = controller.model.hasElements() || + controller.getAllBlocks().length > 0; + if (hasToolboxElements && + !confirm('Are you sure you want to import? You will lose your ' + + 'current toolbox. ')) { + return; + } + // Import toolbox XML. controller.importToolboxFromTree_(tree); + } else if (importMode == WorkspaceFactoryController.MODE_PRELOAD) { - // Switch mode and import pre-loaded workspace XML. + // Switch mode. controller.setMode(WorkspaceFactoryController.MODE_PRELOAD); + + // Confirm that the user wants to override their current blocks. + if (controller.toolboxWorkspace.getAllBlocks().length > 0 && + !confirm('Are you sure you want to import? You will lose your ' + + 'current workspace blocks. ')) { + return; + } + + // Import pre-loaded workspace XML. controller.importPreloadFromTree_(tree); } else { // Throw error if invalid mode. @@ -883,6 +899,10 @@ WorkspaceFactoryController.prototype.importPreloadFromTree_ = function(tree) { * "Clear" button. */ WorkspaceFactoryController.prototype.clearAll = function() { + if (!confirm('Are you sure you want to clear all of your work in Workspace' + + ' Factory?')) { + return; + } var hasCategories = this.model.hasElements(); this.model.clearToolboxList(); this.view.clearToolboxTabs(); @@ -892,6 +912,8 @@ WorkspaceFactoryController.prototype.clearAll = function() { this.toolboxWorkspace.clear(); this.toolboxWorkspace.clearUndo(); this.saveStateFromWorkspace(); + this.hasUnsavedToolboxChanges = false; + this.hasUnsavedPreloadChanges = false; this.view.setCategoryOptions(this.model.hasElements()); this.generateNewOptions(); this.updatePreview(); @@ -1185,7 +1207,7 @@ WorkspaceFactoryController.prototype.readOptions_ = function() { WorkspaceFactoryController.prototype.importBlocks = function(file, format) { // Generate category name from file name. - var categoryName = file.name + ' blocks'; + var categoryName = file.name; var controller = this; var reader = new FileReader(); @@ -1196,8 +1218,17 @@ WorkspaceFactoryController.prototype.importBlocks = // Define blocks using block types from file. var blockTypes = FactoryUtils.defineAndGetBlockTypes(reader.result, format); - var blocks = controller.generator.getDefinedBlocks(blockTypes); + // If an imported block type is already defined, check if the user wants + // to override the current block definition. + if (controller.model.hasDefinedBlockTypes(blockTypes) && + !confirm('An imported block uses the same name as a block ' + + 'already in your toolbox. Are you sure you want to override the ' + + 'currently defined block?')) { + return; + } + + var blocks = controller.generator.getDefinedBlocks(blockTypes); // Generate category XML and append to toolbox. var categoryXml = FactoryUtils.generateCategoryXml(blocks, categoryName); // Get random color for category between 0 and 360. Gives each imported @@ -1207,9 +1238,10 @@ WorkspaceFactoryController.prototype.importBlocks = controller.toolbox.appendChild(categoryXml); controller.toolboxWorkspace.updateToolbox(controller.toolbox); // Update imported block types. - this.model.addImportedBlocks(blockTypes); + controller.model.addImportedBlockTypes(blockTypes); // Reload current category to possibly reflect any newly defined blocks. - this.clearAndLoadXml_(Blockly.Xml.workspaceToDom(this.toolboxWorkspace)); + controller.clearAndLoadXml_ + (Blockly.Xml.workspaceToDom(controller.toolboxWorkspace)); } catch (e) { alert('Cannot read blocks from file.'); window.console.log(e); @@ -1238,6 +1270,7 @@ WorkspaceFactoryController.prototype.setBlockLibCategory = // Update the toolbox and toolboxWorkspace. this.toolbox.replaceChild(categoryXml, blockLibCategory); + this.toolboxWorkspace.toolbox_.clearSelection(); this.toolboxWorkspace.updateToolbox(this.toolbox); // Update the block library types. @@ -1300,3 +1333,12 @@ WorkspaceFactoryController.prototype.hasVariablesCategory = function() { WorkspaceFactoryController.prototype.hasProceduresCategory = function() { return this.model.hasProcedures(); }; + +/** + * Determines if there are any unsaved changes in workspace factory. + * + * @return {boolean} True if there are unsaved changes, false otherwise. + */ +WorkspaceFactoryController.prototype.hasUnsavedChanges = function() { + return this.hasUnsavedToolboxChanges || this.hasUnsavedPreloadChanges; +}; diff --git a/demos/blocklyfactory/workspacefactory/wfactory_generator.js b/demos/blocklyfactory/workspacefactory/wfactory_generator.js index 8c2fa91d7..c3183ab6d 100644 --- a/demos/blocklyfactory/workspacefactory/wfactory_generator.js +++ b/demos/blocklyfactory/workspacefactory/wfactory_generator.js @@ -130,18 +130,18 @@ WorkspaceFactoryGenerator.prototype.generateWorkspaceXml = function() { // Generate XML and set attributes. var generatedXml = Blockly.Xml.workspaceToDom(this.hiddenWorkspace); - generatedXml.setAttribute('id', 'preload_blocks'); + generatedXml.setAttribute('id', 'workspaceBlocks'); generatedXml.setAttribute('style', 'display:none'); return generatedXml; }; /** * Generates a string representation of the options object for injecting the - * workspace. + * workspace and starter code. * - * @return {!string} String representation of options object. + * @return {!string} String representation of starter code for injecting. */ -WorkspaceFactoryGenerator.prototype.generateOptionsString = function() { +WorkspaceFactoryGenerator.prototype.generateInjectString = function() { var addAttributes = function(obj, tabChar) { if (!obj) { @@ -157,14 +157,32 @@ WorkspaceFactoryGenerator.prototype.generateOptionsString = function() { } else { var temp = tabChar + key + ' : ' + obj[key] + ', \n'; } - str = str.concat(temp); + str += temp; } var lastCommaIndex = str.lastIndexOf(','); str = str.slice(0, lastCommaIndex) + '\n'; return str; }; - return 'var options = { \n' + addAttributes(this.model.options, '\t') + '};'; + var attributes = addAttributes(this.model.options, '\t'); + if (!this.model.options['readOnly']) { + attributes = '\ttoolbox : toolbox, \n' + + attributes; + } + var finalStr = '/* TODO: Change toolbox XML ID if necessary. Can export ' + + 'toolbox XML from Workspace Factory. */\n' + + 'var toolbox = document.getElementById("toolbox");\n\n'; + finalStr += 'var options = { \n' + attributes + '};'; + finalStr += '\n\n/* Inject your workspace */ \nvar workspace = Blockly.' + + 'inject(/* TODO: Add ID of div to inject Blockly into */, options);'; + finalStr += '\n\n/* Load Workspace Blocks from XML to workspace. ' + + 'Remove all code below if no blocks to load */\n\n' + + '/* TODO: Change workspace blocks XML ID if necessary. Can export' + + ' workspace blocks XML from Workspace Factory. */\n' + + 'var workspaceBlocks = document.getElementById("workspaceBlocks"); \n\n' + + '/* Load blocks to workspace. */\n' + + 'Blockly.Xml.domToWorkspace(workspace, workspaceBlocks);'; + return finalStr; } /** diff --git a/demos/blocklyfactory/workspacefactory/wfactory_init.js b/demos/blocklyfactory/workspacefactory/wfactory_init.js index 212c3e04f..78f8b7d41 100644 --- a/demos/blocklyfactory/workspacefactory/wfactory_init.js +++ b/demos/blocklyfactory/workspacefactory/wfactory_init.js @@ -193,6 +193,13 @@ WorkspaceFactoryInit.assignWorkspaceFactoryClickHandlers_ = document.getElementById('dropdownDiv_add').classList.remove("show"); }); + document.getElementById('dropdown_loadStandardToolbox').addEventListener + ('click', + function() { + controller.loadStandardToolbox(); + document.getElementById('dropdownDiv_add').classList.remove("show"); + }); + document.getElementById('button_remove').addEventListener ('click', function() { @@ -216,16 +223,16 @@ WorkspaceFactoryInit.assignWorkspaceFactoryClickHandlers_ = document.getElementById('dropdown_exportOptions').addEventListener ('click', function() { - controller.exportOptionsFile(); + controller.exportInjectFile(); document.getElementById('dropdownDiv_export').classList.remove("show"); }); document.getElementById('dropdown_exportAll').addEventListener ('click', function() { + controller.exportInjectFile(); controller.exportXmlFile(WorkspaceFactoryController.MODE_TOOLBOX); controller.exportXmlFile(WorkspaceFactoryController.MODE_PRELOAD); - controller.exportOptionsFile(); document.getElementById('dropdownDiv_export').classList.remove("show"); }); @@ -521,13 +528,16 @@ WorkspaceFactoryInit.addWorkspaceFactoryEventListeners_ = function(controller) { if (confirm('Your new block has a variables field. To use this block ' + 'fully, you will need a Variables category. Do you want to add ' + 'a Variables category to your custom toolbox?')) { + controller.setMode(WorkspaceFactoryController.MODE_TOOLBOX); controller.loadCategoryByName('variables'); } + } - } else if (procedureCreated && !controller.hasProceduresCategory()) { + if (procedureCreated && !controller.hasProceduresCategory()) { if (confirm('Your new block is a function block. To use this block ' + 'fully, you will need a Functions category. Do you want to add ' + 'a Functions category to your custom toolbox?')) { + controller.setMode(WorkspaceFactoryController.MODE_TOOLBOX); controller.loadCategoryByName('functions'); } } diff --git a/demos/blocklyfactory/workspacefactory/wfactory_model.js b/demos/blocklyfactory/workspacefactory/wfactory_model.js index ec059354c..eabaafbbc 100644 --- a/demos/blocklyfactory/workspacefactory/wfactory_model.js +++ b/demos/blocklyfactory/workspacefactory/wfactory_model.js @@ -94,7 +94,7 @@ WorkspaceFactoryModel.prototype.hasVariables = function() { * false otherwise. */ WorkspaceFactoryModel.prototype.hasProcedures = function() { - return this.hasFunctionCategory; + return this.hasProcedureCategory; }; /** @@ -495,6 +495,22 @@ WorkspaceFactoryModel.prototype.isDefinedBlockType = function(blockType) { var isLibBlock = this.libBlockTypes.indexOf(blockType) != -1; var isImportedBlock = this.importedBlockTypes.indexOf(blockType) != -1; return (isStandardBlock || isLibBlock || isImportedBlock); +}; + +/** + * Checks if any of the block types are already defined. + * + * @param {!Array} blockTypes Array of block types. + * @return {boolean} True if a block type in the array is already defined, + * false if none of the blocks are already defined. + */ +WorkspaceFactoryModel.prototype.hasDefinedBlockTypes = function(blockTypes) { + for (var i = 0, blockType; blockType = blockTypes[i]; i++) { + if (this.isDefinedBlockType(blockType)) { + return true; + } + } + return false; } /**