Merge pull request #620 from google/develop

Merge develop
This commit is contained in:
Rachel Fenichel
2016-09-07 13:02:12 -07:00
committed by GitHub
11 changed files with 645 additions and 287 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -41,7 +41,7 @@
window.addEventListener('load', init);
</script>
</head>
<body>
<body onbeforeunload="return blocklyFactory.confirmLeavePage()">
<h1><a href="https://developers.google.com/blockly/">Blockly</a> &gt;
<a href="../index.html">Demos</a> &gt; Blockly Factory
<button id="helpButton" title="View documentation in new window.">
@@ -50,26 +50,27 @@
</h1>
<div id="tabContainer">
<div id="blockFactory_tab" class="tab tabon"> Block Factory</div>
<div id="workspaceFactory_tab" class="tab taboff"> Workspace Factory</div>
<div id="blocklibraryExporter_tab" class="tab taboff"> Block Exporter</div>
<div id="workspaceFactory_tab" class="tab taboff"> Workspace Factory</div>
</div>
<!-- Exporter tab -->
<div id="blockLibraryExporter">
<br>
<p id="helperText"> 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.
<p id="helperText"> First, select blocks from your block library by clicking on them. Then, use the Export Settings form to download starter code for selected blocks.
</p>
<div id="exportSelector">
<br>
<h3> Block Selector </h3>
<div class='dropdown'>
<button id="button_setBlocks">Select From Library</button>
<button id="button_setBlocks">Select</button>
<div id="dropdownDiv_setBlocks" class="dropdown-content">
<a id='dropdown_addAllFromLib' title="Select all block library blocks.">All Stored</a>
<a id='dropdown_addAllUsed' title="Select all block library blocks used in workspace factory.">All Used</a>
<a id='dropdown_clearSelected' title="Clear selected blocks.">Clear</a>
<a id='dropdown_addAllFromLib' title="Select all block library blocks.">All Stored in Block Library</a>
<a id='dropdown_addAllUsed' title="Select all block library blocks used in workspace factory.">All Used in Workspace Factory</a>
</div>
<button id='clearSelectedButton' title="Clear selected blocks.">Clear Selected</a>
</div>
<div id="blockSelector"></div>
</div>
@@ -157,9 +158,9 @@
<div class="dropdown">
<button id="button_export">Export</button>
<div id="dropdownDiv_export" class="dropdown-content">
<a id='dropdown_exportOptions'>Starter Code</a>
<a id='dropdown_exportToolbox'>Toolbox</a>
<a id='dropdown_exportPreload'>Workspace Blocks</a>
<a id='dropdown_exportOptions'>Inject Options</a>
<a id='dropdown_exportAll'>All</a>
</div>
</div>
@@ -193,6 +194,7 @@
<a id='dropdown_newCategory'>New Category</a>
<a id='dropdown_loadCategory'>Standard Category</a>
<a id='dropdown_separator'>Separator</a>
<a id='dropdown_loadStandardToolbox'>Standard Toolbox</a>
</div>
</div>
@@ -219,7 +221,7 @@
<div id="preloadHelp">
<p>Configure the options for your Blockly inject call.</p>
<button id="button_optionsHelp">Help</button>
<button class="small" id="button_standardOptions">Restore</button>
<button class="small" id="button_standardOptions">Reset to Default</button>
</div>
<div id="workspace_options">
<input type="checkbox" id="option_readOnly_checkbox" class="optionsInput">Read Only<br>
@@ -279,20 +281,22 @@
<tr id="blockLibrary">
<td id="blockLibraryContainer">
<span>
<h3>Block Library:</h3>
<select id="blockLibraryDropdown">
<div class='dropdown'>
<button id="button_blockLib">Block Library</button>
<div id="dropdownDiv_blockLib" class="dropdown-content">
<a id='createNewBlockButton'>Create New Block</a>
</div>
</div>
<select id="blockLibraryDropdown" style="display:none">
</select>
</span>
</td>
<td id="blockLibraryControls">
<button id="saveToBlockLibraryButton" title="Save block to Block Library.">
<span>Save Block</span>
Save "block_type"
</button>
<button id="removeBlockFromLibraryButton" title="Remove block from Block Library.">
<span>Delete Block</span>
</button>
<button id="createNewBlockButton" title="Create a new block.">
<span> Create Block</span>
Delete "block_type"
</button>
</td>
</tr>

View File

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

View File

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

View File

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

View File

@@ -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<!string>} 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;
}
/**